Skip to content

Commit 44e89d8

Browse files
test(coordination): carry a recorded decision rejection in the production-scale fixture
The shared production-scale coordination fixture only generated approvals, so no provider conformance arm ever had to prove that an explicitly refused decision stays recorded without becoming active authority. Add a bounded rejection band to the checked-in envelope and the shared generator, expose the derived expectations, assert the dimension on every provider arm, and add one independent mutation/negative case. - envelope: rejected_standing_decision_count. The refusal is scoped under a second decision kind so it keeps its own decision identity and cannot merge into the approved scope. - generator: expected_inactive_standing_decision_count, plus a retained-standing count that now includes refusals; expected_user_archive_count is derived from the retained count instead of a fixed offset. - conformance: per-arm inactive_count and per-entry active === (outcome === "approve"), so no arm can present a refusal as an approval. - fixture test: a rejection is a receipt, only an explicit approval activates a scope, and dropping the typed decision_scope leaves no entry at all. Refs GH-C102 (#4541) Signed-off-by: superwesleyhys-ux <251160695+superwesleyhys-ux@users.noreply.github.com>
1 parent 1fd0a26 commit 44e89d8

4 files changed

Lines changed: 128 additions & 3 deletions

File tree

‎tests/control_plane_ts/authority_store_conformance.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,16 @@ export function registerAuthorityStoreConformance(
959959
const todos = loaded.head.todos as Record<string, unknown>[];
960960
const leases = loaded.head.leases as Record<string, unknown>[];
961961
const standing = projectStandingDecisions(todos)!;
962-
assert.equal(standing.active_count, 1); // Four receipts, one scope/owner.
962+
assert.equal(standing.active_count, 1); // Four approvals, one scope/owner.
963+
assert.equal(
964+
standing.inactive_count,
965+
fixture.expected_inactive_standing_decision_count,
966+
"a recorded rejection stays a standing receipt without becoming authority",
967+
);
968+
for (const entry of standing.entries as JsonObject[]) {
969+
assert.equal(entry.active, entry.outcome === "approve",
970+
"only an explicit approval may activate a standing decision");
971+
}
963972
assert.equal(standing.conflict_count, undefined);
964973
assert.equal(todos.length, fixture.expected_initial_todo_count);
965974
assert.equal(leases.length, fixture.expected_current_lease_count);

‎tests/control_plane_ts/production_scale_coordination_fixture.ts‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const envelope = JSON.parse(readFileSync(new URL(
2323
current_lease_count: number;
2424
retired_lease_count: number;
2525
standing_user_decision_count: number;
26+
rejected_standing_decision_count: number;
2627
scoped_without_outcome_count: number;
2728
linked_decision_count: number;
2829
completion_target_index: number;
@@ -104,6 +105,13 @@ export interface ProductionScaleCoordinationFixture {
104105
readonly expected_agent_archive_count_after_terminals: number;
105106
readonly expected_user_archive_count: number;
106107
readonly expected_standing_user_decision_count: number;
108+
/**
109+
* Standing receipts whose recorded outcome is not `approve`.
110+
*
111+
* Authority collapses per decision identity, so every rejection sharing one
112+
* scope produces a single inactive entry rather than one per Todo.
113+
*/
114+
readonly expected_inactive_standing_decision_count: number;
107115
readonly semantic_cases: Readonly<Record<string, Record<string, unknown>>>;
108116
readonly presentation_cases: Readonly<Record<string, Record<string, unknown>>>;
109117
readonly update_cases: Readonly<Record<string, Record<string, unknown>>>;
@@ -210,6 +218,19 @@ function todoRecords(
210218
record.unblocks_todo_id = todoId("agent", envelope.completion_target_index);
211219
}
212220
}
221+
// An explicit rejection is a recorded decision, not absent authority: the
222+
// same broad goal is refused under a second decision kind. It stays a
223+
// standing receipt while its outcome keeps it inactive, so a provider
224+
// cannot present "no active approval" as "no decision was made".
225+
const rejectedStart = partialEnd + envelope.linked_decision_count;
226+
if (role === "user" && index >= rejectedStart &&
227+
index < rejectedStart + envelope.rejected_standing_decision_count) {
228+
record.task_class = "user_gate";
229+
record.decision_scope = {kind: "write_scope", granularity: "goal", scope_key: goalId};
230+
record.decision_outcome = "reject";
231+
record.global_gate = true;
232+
record.goal_bound = true;
233+
}
213234
return record;
214235
});
215236
}
@@ -278,6 +299,14 @@ export function productionScaleCoordinationFixture(
278299
todo.task_class === "user_gate" && todo.decision_outcome === "approve" &&
279300
todo.global_gate === true && todo.goal_bound === true,
280301
).length;
302+
const rejectedStanding = users.filter(todo =>
303+
todo.task_class === "user_gate" && todo.decision_outcome === "reject" &&
304+
todo.global_gate === true && todo.goal_bound === true,
305+
);
306+
const expectedInactiveStanding = new Set(rejectedStanding.map(todo => {
307+
const scope = todo.decision_scope as {kind: string; granularity: string; scope_key: string};
308+
return JSON.stringify([scope.kind, scope.granularity, scope.scope_key, "global"]);
309+
})).size;
281310
return {
282311
projection: schema === "legacy"
283312
? legacyProjection
@@ -292,8 +321,14 @@ export function productionScaleCoordinationFixture(
292321
expected_initial_todo_count: todos.length,
293322
expected_current_lease_count: leases.length,
294323
expected_agent_archive_count_after_terminals: expectedAgentDone + 2 - 5,
295-
expected_user_archive_count: expectedUserDone - 5,
296-
expected_standing_user_decision_count: expectedStanding,
324+
// Archive keeps every standing receipt, approved or rejected, so each
325+
// rejection leaves one fewer movable completed row behind.
326+
expected_user_archive_count: Math.min(
327+
expectedUserDone - expectedStanding - rejectedStanding.length,
328+
expectedUserDone - 5,
329+
),
330+
expected_standing_user_decision_count: expectedStanding + rejectedStanding.length,
331+
expected_inactive_standing_decision_count: expectedInactiveStanding,
297332
semantic_cases: envelope.semantic_cases,
298333
presentation_cases: envelope.presentation_cases,
299334
update_cases: envelope.update_cases,
@@ -307,6 +342,7 @@ export function productionScaleCoordinationFixture(
307342
}
308343

309344
export const PRODUCTION_SCALE_RETIRED_LEASE_COUNT = envelope.retired_lease_count;
345+
export const PRODUCTION_SCALE_REJECTED_DECISION_COUNT = envelope.rejected_standing_decision_count;
310346

311347
function requireSafeCount(value: number, label: string): number {
312348
if (!Number.isSafeInteger(value) || value < 1) {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
4+
import type {JsonObject} from "../../loopx/control_plane/effect_program.ts";
5+
import {
6+
isStandingDecisionReceipt,
7+
projectStandingDecisions,
8+
} from "../../loopx/control_plane/todos/standing_decision.ts";
9+
import {
10+
PRODUCTION_SCALE_REJECTED_DECISION_COUNT,
11+
productionScaleCoordinationFixture,
12+
} from "./production_scale_coordination_fixture.ts";
13+
14+
function standingEntries(todos: readonly JsonObject[]): JsonObject[] {
15+
const standing = projectStandingDecisions(todos);
16+
return (standing?.entries ?? []) as JsonObject[];
17+
}
18+
19+
function rejectedTodos(todos: readonly JsonObject[]): JsonObject[] {
20+
return todos.filter(todo => todo.decision_outcome === "reject");
21+
}
22+
23+
test("production-scale rejections are standing receipts that stay inactive", () => {
24+
const fixture = productionScaleCoordinationFixture("fixture-goal");
25+
const todos = fixture.projection.todos as JsonObject[];
26+
const rejected = rejectedTodos(todos);
27+
assert.equal(rejected.length, PRODUCTION_SCALE_REJECTED_DECISION_COUNT);
28+
assert.equal(rejected.length > 0, true, "the fixture must carry a recorded rejection");
29+
for (const todo of rejected) {
30+
assert.equal(isStandingDecisionReceipt(todo), true,
31+
"an explicit rejection is a decision, not an absent one");
32+
assert.equal(todo.status, "done");
33+
assert.equal(todo.global_gate, true);
34+
}
35+
const entries = standingEntries(todos);
36+
const rejections = entries.filter(entry => entry.outcome === "reject");
37+
assert.equal(rejections.length, fixture.expected_inactive_standing_decision_count);
38+
for (const entry of rejections) {
39+
assert.equal(entry.active, false, "a rejection must never read as an approval");
40+
}
41+
});
42+
43+
test("only an explicit approval activates a production-scale decision scope", () => {
44+
const fixture = productionScaleCoordinationFixture("fixture-goal");
45+
const todos = fixture.projection.todos as JsonObject[];
46+
const before = standingEntries(todos);
47+
const approved = before.filter(entry => entry.active === true).length;
48+
const approvedTodo = todos
49+
.filter(todo => todo.decision_outcome === "approve" && isStandingDecisionReceipt(todo))
50+
.sort((left, right) => String(left.completed_at).localeCompare(String(right.completed_at)))
51+
.at(-1)!;
52+
// Mutation: the newest rejection decides the scope once its outcome changes.
53+
const newest = rejectedTodos(todos)
54+
.sort((left, right) => String(left.completed_at).localeCompare(String(right.completed_at)))
55+
.at(-1)!;
56+
const mutated = todos.map(todo => todo === newest
57+
? {...todo, decision_outcome: "approve"} : todo);
58+
const after = standingEntries(mutated);
59+
assert.equal(after.filter(entry => entry.active === true).length, approved + 1);
60+
assert.equal(after.some(entry => entry.outcome === "reject" && entry.active === true), false);
61+
// The untouched approval is still the same receipt.
62+
assert.equal(after.some(entry => entry.source_todo_id === approvedTodo.todo_id), true);
63+
});
64+
65+
test("a rejection without its typed decision scope leaves no standing entry", () => {
66+
const fixture = productionScaleCoordinationFixture("fixture-goal");
67+
const todos = fixture.projection.todos as JsonObject[];
68+
const stripped = todos.map(todo => todo.decision_outcome === "reject"
69+
? {...todo, decision_scope: undefined} : todo);
70+
for (const todo of stripped) {
71+
if (todo.decision_outcome === "reject") {
72+
assert.equal(isStandingDecisionReceipt(todo), false,
73+
"decision identity comes from the typed scope, never from prose");
74+
}
75+
}
76+
assert.equal(standingEntries(stripped).some(entry => entry.outcome === "reject"), false);
77+
assert.equal(standingEntries(stripped).length,
78+
standingEntries(todos).length - fixture.expected_inactive_standing_decision_count);
79+
});

‎tests/fixtures/control_plane/coordination_production_scale_v0.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"current_lease_count": 64,
1717
"retired_lease_count": 160,
1818
"standing_user_decision_count": 4,
19+
"rejected_standing_decision_count": 2,
1920
"scoped_without_outcome_count": 12,
2021
"linked_decision_count": 12,
2122
"completion_target_index": 160,

0 commit comments

Comments
 (0)