Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion tests/control_plane_ts/authority_store_conformance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,16 @@ export function registerAuthorityStoreConformance(
const todos = loaded.head.todos as Record<string, unknown>[];
const leases = loaded.head.leases as Record<string, unknown>[];
const standing = projectStandingDecisions(todos)!;
assert.equal(standing.active_count, 1); // Four receipts, one scope/owner.
assert.equal(standing.active_count, 1); // Four approvals, one scope/owner.
assert.equal(
standing.inactive_count,
fixture.expected_inactive_standing_decision_count,
"a recorded rejection stays a standing receipt without becoming authority",
);
for (const entry of standing.entries as JsonObject[]) {
assert.equal(entry.active, entry.outcome === "approve",
"only an explicit approval may activate a standing decision");
}
assert.equal(standing.conflict_count, undefined);
assert.equal(todos.length, fixture.expected_initial_todo_count);
assert.equal(leases.length, fixture.expected_current_lease_count);
Expand Down
40 changes: 38 additions & 2 deletions tests/control_plane_ts/production_scale_coordination_fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const envelope = JSON.parse(readFileSync(new URL(
current_lease_count: number;
retired_lease_count: number;
standing_user_decision_count: number;
rejected_standing_decision_count: number;
scoped_without_outcome_count: number;
linked_decision_count: number;
completion_target_index: number;
Expand Down Expand Up @@ -104,6 +105,13 @@ export interface ProductionScaleCoordinationFixture {
readonly expected_agent_archive_count_after_terminals: number;
readonly expected_user_archive_count: number;
readonly expected_standing_user_decision_count: number;
/**
* Standing receipts whose recorded outcome is not `approve`.
*
* Authority collapses per decision identity, so every rejection sharing one
* scope produces a single inactive entry rather than one per Todo.
*/
readonly expected_inactive_standing_decision_count: number;
readonly semantic_cases: Readonly<Record<string, Record<string, unknown>>>;
readonly presentation_cases: Readonly<Record<string, Record<string, unknown>>>;
readonly update_cases: Readonly<Record<string, Record<string, unknown>>>;
Expand Down Expand Up @@ -210,6 +218,19 @@ function todoRecords(
record.unblocks_todo_id = todoId("agent", envelope.completion_target_index);
}
}
// An explicit rejection is a recorded decision, not absent authority: the
// same broad goal is refused under a second decision kind. It stays a
// standing receipt while its outcome keeps it inactive, so a provider
// cannot present "no active approval" as "no decision was made".
const rejectedStart = partialEnd + envelope.linked_decision_count;
if (role === "user" && index >= rejectedStart &&
index < rejectedStart + envelope.rejected_standing_decision_count) {
record.task_class = "user_gate";
record.decision_scope = {kind: "write_scope", granularity: "goal", scope_key: goalId};
record.decision_outcome = "reject";
record.global_gate = true;
record.goal_bound = true;
}
return record;
});
}
Expand Down Expand Up @@ -278,6 +299,14 @@ export function productionScaleCoordinationFixture(
todo.task_class === "user_gate" && todo.decision_outcome === "approve" &&
todo.global_gate === true && todo.goal_bound === true,
).length;
const rejectedStanding = users.filter(todo =>
todo.task_class === "user_gate" && todo.decision_outcome === "reject" &&
todo.global_gate === true && todo.goal_bound === true,
);
const expectedInactiveStanding = new Set(rejectedStanding.map(todo => {
const scope = todo.decision_scope as {kind: string; granularity: string; scope_key: string};
return JSON.stringify([scope.kind, scope.granularity, scope.scope_key, "global"]);
})).size;
return {
projection: schema === "legacy"
? legacyProjection
Expand All @@ -292,8 +321,14 @@ export function productionScaleCoordinationFixture(
expected_initial_todo_count: todos.length,
expected_current_lease_count: leases.length,
expected_agent_archive_count_after_terminals: expectedAgentDone + 2 - 5,
expected_user_archive_count: expectedUserDone - 5,
expected_standing_user_decision_count: expectedStanding,
// Archive keeps every standing receipt, approved or rejected, so each
// rejection leaves one fewer movable completed row behind.
expected_user_archive_count: Math.min(
expectedUserDone - expectedStanding - rejectedStanding.length,
expectedUserDone - 5,
),
expected_standing_user_decision_count: expectedStanding + rejectedStanding.length,
expected_inactive_standing_decision_count: expectedInactiveStanding,
semantic_cases: envelope.semantic_cases,
presentation_cases: envelope.presentation_cases,
update_cases: envelope.update_cases,
Expand All @@ -307,6 +342,7 @@ export function productionScaleCoordinationFixture(
}

export const PRODUCTION_SCALE_RETIRED_LEASE_COUNT = envelope.retired_lease_count;
export const PRODUCTION_SCALE_REJECTED_DECISION_COUNT = envelope.rejected_standing_decision_count;

function requireSafeCount(value: number, label: string): number {
if (!Number.isSafeInteger(value) || value < 1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import assert from "node:assert/strict";
import test from "node:test";

import type {JsonObject} from "../../loopx/control_plane/effect_program.ts";
import {
isStandingDecisionReceipt,
projectStandingDecisions,
} from "../../loopx/control_plane/todos/standing_decision.ts";
import {
PRODUCTION_SCALE_REJECTED_DECISION_COUNT,
productionScaleCoordinationFixture,
} from "./production_scale_coordination_fixture.ts";

function standingEntries(todos: readonly JsonObject[]): JsonObject[] {
const standing = projectStandingDecisions(todos);
return (standing?.entries ?? []) as JsonObject[];
}

function rejectedTodos(todos: readonly JsonObject[]): JsonObject[] {
return todos.filter(todo => todo.decision_outcome === "reject");
}

test("production-scale rejections are standing receipts that stay inactive", () => {
const fixture = productionScaleCoordinationFixture("fixture-goal");
const todos = fixture.projection.todos as JsonObject[];
const rejected = rejectedTodos(todos);
assert.equal(rejected.length, PRODUCTION_SCALE_REJECTED_DECISION_COUNT);
assert.equal(rejected.length > 0, true, "the fixture must carry a recorded rejection");
for (const todo of rejected) {
assert.equal(isStandingDecisionReceipt(todo), true,
"an explicit rejection is a decision, not an absent one");
assert.equal(todo.status, "done");
assert.equal(todo.global_gate, true);
}
const entries = standingEntries(todos);
const rejections = entries.filter(entry => entry.outcome === "reject");
assert.equal(rejections.length, fixture.expected_inactive_standing_decision_count);
for (const entry of rejections) {
assert.equal(entry.active, false, "a rejection must never read as an approval");
}
});

test("only an explicit approval activates a production-scale decision scope", () => {
const fixture = productionScaleCoordinationFixture("fixture-goal");
const todos = fixture.projection.todos as JsonObject[];
const before = standingEntries(todos);
const approved = before.filter(entry => entry.active === true).length;
const approvedTodo = todos
.filter(todo => todo.decision_outcome === "approve" && isStandingDecisionReceipt(todo))
.sort((left, right) => String(left.completed_at).localeCompare(String(right.completed_at)))
.at(-1)!;
// Mutation: the newest rejection decides the scope once its outcome changes.
const newest = rejectedTodos(todos)
.sort((left, right) => String(left.completed_at).localeCompare(String(right.completed_at)))
.at(-1)!;
const mutated = todos.map(todo => todo === newest
? {...todo, decision_outcome: "approve"} : todo);
const after = standingEntries(mutated);
assert.equal(after.filter(entry => entry.active === true).length, approved + 1);
assert.equal(after.some(entry => entry.outcome === "reject" && entry.active === true), false);
// The untouched approval is still the same receipt.
assert.equal(after.some(entry => entry.source_todo_id === approvedTodo.todo_id), true);
});

test("a rejection without its typed decision scope leaves no standing entry", () => {
const fixture = productionScaleCoordinationFixture("fixture-goal");
const todos = fixture.projection.todos as JsonObject[];
const stripped = todos.map(todo => todo.decision_outcome === "reject"
? {...todo, decision_scope: undefined} : todo);
for (const todo of stripped) {
if (todo.decision_outcome === "reject") {
assert.equal(isStandingDecisionReceipt(todo), false,
"decision identity comes from the typed scope, never from prose");
}
}
assert.equal(standingEntries(stripped).some(entry => entry.outcome === "reject"), false);
assert.equal(standingEntries(stripped).length,
standingEntries(todos).length - fixture.expected_inactive_standing_decision_count);
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"current_lease_count": 64,
"retired_lease_count": 160,
"standing_user_decision_count": 4,
"rejected_standing_decision_count": 2,
"scoped_without_outcome_count": 12,
"linked_decision_count": 12,
"completion_target_index": 160,
Expand Down