From 501c88099a68f9f75d018360c394c657556c3ebd Mon Sep 17 00:00:00 2001 From: aihop <4408344+hhyykk@users.noreply.github.com> Date: Wed, 21 Oct 2026 05:38:49 +0000 Subject: [PATCH 1/2] perf(control-plane): reuse acceptance reads within one Todo snapshot Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com> --- .../typescript-control-plane-migration-v0.md | 9 + ...script-control-plane-migration-v0.zh-CN.md | 7 + .../coordination/canonical_snapshot_page.ts | 7 +- .../coordination/local_authority_read.ts | 20 +- .../goals/acceptance_contract.ts | 195 ++++++++++++------ .../canonical_snapshot_page.test.ts | 30 +++ .../goal_acceptance_runtime.test.ts | 27 ++- 7 files changed, 218 insertions(+), 77 deletions(-) diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md index 44368ff77e..3f3353d506 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md @@ -1772,6 +1772,15 @@ input/output independence unchanged. This is a bounded shared-codec cost repair, not a provider-format migration or qualification of sustained operation, CI capacity or release admission. +Canonical Todo collection reads also share one `GoalAcceptanceRead` within the +loaded provider head. Contract, oracle basis and work guards reuse the validated +authority, Todo index and task decisions. Guard projection stays lazy so source +identity and continuation checks retain their original precedence. Public +standalone helpers start fresh, and returned containers remain independent. +Every new provider head or continuation read creates a new scope. This adds no +Python bridge, RPC, persisted cache or write authority; native-read measurements +do not qualify full CLI latency, installed adoption or PostgreSQL operation. + `as unknown as T` is permitted only as a named migration seam: its exact call site, upstream validator, negative boundary coverage, and removal owner must be visible in the cutover PR. A migrated domain cannot pass its promotion gate diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md index 3dee7c57d9..8ae4b7b53f 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md @@ -1254,6 +1254,13 @@ TypeScript 类型在运行时会被擦除。因此 network/RPC payload、解析 检查与 schema validation、semantic invariant 分层负责。未经检查的 `JSON.parse(...) as T` 不能建立控制面 authority。 +Canonical Todo 集合读取在同一个已加载 provider head 内共享 `GoalAcceptanceRead`。 +合同、oracle basis 和工作 guard 复用已验证的 authority、Todo 索引和任务判断。 +guard 保持惰性投影,保留来源 identity 与连续页检查的原拒绝顺序。公开独立 helper +每次重新读取,返回的容器相互隔离。每个新 provider head 或连续页读取都建立新 +scope。不新增 Python bridge、RPC、持久化缓存或写权限;原生读取测量不证明 +完整 CLI 延迟、安装采用或 PostgreSQL 运行资格。 + `as unknown as T` 只允许作为具名迁移缝:cutover PR 必须明确其调用点、上游 validator、负向边界覆盖和移除 owner。只要 public、持久化、RPC 或 extension 输入仍通过未经验证的断言进入已迁 domain 的 semantic core,该 domain 就不能 diff --git a/loopx/control_plane/coordination/canonical_snapshot_page.ts b/loopx/control_plane/coordination/canonical_snapshot_page.ts index a85500b469..003a782b04 100644 --- a/loopx/control_plane/coordination/canonical_snapshot_page.ts +++ b/loopx/control_plane/coordination/canonical_snapshot_page.ts @@ -4,7 +4,6 @@ import {configuredGoalChecks} from "../goals/acceptance_contract.ts"; import type {JsonObject} from "../effect_program.ts"; import {requireJsonObject} from "../runtime_decode.ts"; import {MAX_LOCAL_SNAPSHOT_BYTES} from "../effect_runtime_snapshot.ts"; -import {projectGoalAcceptanceWorkGuards} from "../goals/acceptance_contract.ts"; import {decodeProjectionReadback, confirmProjectionReadback} from "../todos/projection_delivery.ts"; import {authorityStoreSourceAuthority, type AuthorityStore} from "./authority_store.ts"; import {canonicalAuthoritySha256, hasExactAuthorityKeys, requireAuthorityStoreId} from "./authority_store_codec.ts"; @@ -95,7 +94,7 @@ export async function readCanonicalSnapshotFromStore(value: unknown, store: Auth after.snapshot.provider_revision === head.provider_revision && after.snapshot.cursor === head.cursor && after.snapshot.query_sha256 === query, "canonical_snapshot_changed", "canonical snapshot changed; restart the complete read, never append a newer page"); - const {projection, todoReadModel: readModel, leaseIndex: leases, acceptance, oracleObservationBasis} = + const {projection, todoReadModel: readModel, leaseIndex: leases, acceptance, oracleObservationBasis, projectWorkGuards} = canonicalTodoCollection(head.head, goal, input.include_leases); const snapshot: Snapshot = {goal_id: goal, store_identity: identity.store_identity, provider_revision: head.provider_revision, cursor: head.cursor, query_sha256: query, @@ -128,7 +127,7 @@ export async function readCanonicalSnapshotFromStore(value: unknown, store: Auth let completeGuards: JsonObject | null = null; if (after === null && completeBytes > 0) { completeGuards = configuredGoalChecks(acceptance) - ? projectGoalAcceptanceWorkGuards(head.head, goal, projection.todo_ids) : {}; + ? projectWorkGuards(projection.todo_ids) : {}; const complete = result(projection.todo_ids, leases?.lease_todo_ids ?? [], completeGuards); if (bytes(complete) <= Math.min(completeBytes, CANONICAL_SNAPSHOT_LOCAL_BYTES)) return complete; } @@ -137,7 +136,7 @@ export async function readCanonicalSnapshotFromStore(value: unknown, store: Auth const todoIds = projection.todo_ids.slice(todoStart, todoStart + CANONICAL_SNAPSHOT_PAGE_ITEMS); const leaseIds = leases?.lease_todo_ids.slice(leaseStart, leaseStart + CANONICAL_SNAPSHOT_PAGE_ITEMS - todoIds.length) ?? []; const guards = completeGuards ?? (configuredGoalChecks(acceptance) - ? projectGoalAcceptanceWorkGuards(head.head, goal, todoIds) : {}); + ? projectWorkGuards(todoIds) : {}); const page = (size: number) => result(todoIds.slice(0, size), leaseIds.slice(0, Math.max(0, size - todoIds.length)), guards); const available = todoIds.length + leaseIds.length; diff --git a/loopx/control_plane/coordination/local_authority_read.ts b/loopx/control_plane/coordination/local_authority_read.ts index 7e7374f5a6..cf9b78e985 100644 --- a/loopx/control_plane/coordination/local_authority_read.ts +++ b/loopx/control_plane/coordination/local_authority_read.ts @@ -2,8 +2,7 @@ import {configuredGoalChecks} from "../goals/acceptance_contract.ts"; /** Canonical Todo reads and projection confirmation share one provider snapshot. */ import type {JsonObject} from "../effect_program.ts"; import {requireJsonObject} from "../runtime_decode.ts"; -import {acceptanceWorkGuard, projectGoalAcceptance, - projectGoalAcceptanceWorkGuards, goalAcceptanceOracleObservationBasis} from "../goals/acceptance_contract.ts"; +import {acceptanceWorkGuard, GoalAcceptanceRead} from "../goals/acceptance_contract.ts"; import {authorityStoreSourceAuthority as sourceAuthorityFor} from "./authority_store.ts"; import {canonicalAuthoritySha256, hasExactAuthorityKeys, parseAuthorityCursor, requireAuthorityStoreId} from "./authority_store_codec.ts"; @@ -21,14 +20,16 @@ import {completionTurnOperationId, * this same owner. */ export function canonicalTodoCollection(head: JsonObject, goalId: string, includeLeases: boolean) { const read = new CoordinationProjectionRead(head, goalId); - const acceptance = projectGoalAcceptance(head, goalId); + const acceptanceRead = new GoalAcceptanceRead(head, goalId, read); + const acceptance = acceptanceRead.contract(); return { projection: read.todoIndex, - todoReadModel: read.validateTodoReadModel(), + todoReadModel: acceptanceRead.validateTodoReadModel(), leaseIndex: includeLeases ? read.coordinationIndex : null, acceptance, oracleObservationBasis: acceptance.outcome_requirements - ? goalAcceptanceOracleObservationBasis(head, goalId) : null, + ? acceptanceRead.oracleBasis() : null, + projectWorkGuards: (todoIds: readonly string[]) => acceptanceRead.workGuards(todoIds), }; } @@ -149,7 +150,7 @@ export async function readLocalCoordinationTodoSource( if (after.status !== "available" || after.store_identity !== identity.store_identity) { return failure("todo_source_lineage_mismatch"); } - const {projection, todoReadModel, acceptance} = canonicalTodoCollection( + const {projection, todoReadModel, acceptance, projectWorkGuards} = canonicalTodoCollection( transaction.projection, goalId, false, ); let completion: JsonObject | null = null; @@ -187,8 +188,7 @@ export async function readLocalCoordinationTodoSource( todo_ids: projection.todo_ids, todo_read_model: todoReadModel, ...(!configuredGoalChecks(acceptance) && !acceptance.outcome_requirements ? {} : { goal_acceptance_contract: acceptance, - goal_acceptance_work_guards: projectGoalAcceptanceWorkGuards( - transaction.projection, goalId, projection.todo_ids), + goal_acceptance_work_guards: projectWorkGuards(projection.todo_ids), }), source_authority: sourceAuthority, decision_read_from_provider: true, legacy_fallback_used: false, @@ -314,7 +314,7 @@ export async function listLocalCoordinationTodos( legacy_fallback_used: false, }; } - const {projection, todoReadModel, leaseIndex, acceptance, oracleObservationBasis} = canonicalTodoCollection( + const {projection, todoReadModel, leaseIndex, acceptance, oracleObservationBasis, projectWorkGuards} = canonicalTodoCollection( head.head, goalId, input.include_leases === true, ); return { @@ -325,7 +325,7 @@ export async function listLocalCoordinationTodos( todo_ids: projection.todo_ids, todo_read_model: todoReadModel, ...(!configuredGoalChecks(acceptance) && !acceptance.outcome_requirements ? {} : {goal_acceptance_contract: acceptance, - goal_acceptance_work_guards: projectGoalAcceptanceWorkGuards(head.head, goalId, projection.todo_ids)}), + goal_acceptance_work_guards: projectWorkGuards(projection.todo_ids)}), ...(oracleObservationBasis === null ? {} : {goal_acceptance_oracle_observation_basis: oracleObservationBasis}), ...(leaseIndex === null ? {} : { leases: leaseIndex.lease_todo_ids.map((id) => leaseIndex.leases.get(id)!), diff --git a/loopx/control_plane/goals/acceptance_contract.ts b/loopx/control_plane/goals/acceptance_contract.ts index 67d79eb325..f45707d9e9 100644 --- a/loopx/control_plane/goals/acceptance_contract.ts +++ b/loopx/control_plane/goals/acceptance_contract.ts @@ -6,7 +6,7 @@ import {goalOutcomeCheckBasis, projectGoalOutcomes} from "./outcome_requirements import type {JsonObject} from "../effect_program.ts"; import {AuthorityStoreProtocolError, authorityUnicodeCompare, canonicalAuthorityBytes, canonicalAuthorityObject, canonicalAuthoritySha256} from "../coordination/authority_store_codec.ts"; -import {indexCoordinationProjectionTodos, validateCoordinationTodoReadModel} from "../coordination/coordination_projection.ts"; +import {CoordinationProjectionRead, indexCoordinationProjectionTodos} from "../coordination/coordination_projection.ts"; import {COMPLETION_VALIDATION_BINDING_RECEIPT_SCHEMA, completionValidationRevisionHistory} from "../todos/completion_validation_revision.ts"; import { BARE_SHA256_PATTERN } from "../content_digest.ts"; @@ -328,13 +328,17 @@ export function acceptanceApplies(todo: JsonObject): boolean { (todo.status === "open" || todo.status === "blocked") && todo.done === false; } export function acceptanceTodos(head: JsonObject, goalId: string): ReadonlyMap { - validateCoordinationTodoReadModel(head, goalId); - return indexCoordinationProjectionTodos(head, goalId).todos; + const read = new CoordinationProjectionRead(head, goalId); + read.validateTodoReadModel(); + return read.todoIndex.todos; } export function goalAcceptanceWorkDigest(head: JsonObject, goalId: string, scope?: AcceptanceScope): string { // This semantic fingerprint is independent of provider row representation. // Projection and mutation callers separately validate the canonical read model. const todos = indexCoordinationProjectionTodos(head, goalId).todos; + return acceptanceWorkDigest(todos, scope); +} +function acceptanceWorkDigest(todos: ReadonlyMap, scope?: AcceptanceScope): string { if (scope?.kind === "selected_work") return canonicalAuthoritySha256(scope.todo_ids.map(todo_id => ({ todo_id, digest: todos.has(todo_id) ? goalAcceptanceTodoDigest(todos.get(todo_id)!) : null, }))); @@ -377,10 +381,11 @@ export function readGoalAcceptanceAuthority(head: JsonObject, goalId: string): G } const document = normalizeGoalAcceptanceDocument(state.document); acceptanceRequire(state.digest === canonicalAuthoritySha256(document), "acceptance contract digest mismatch"); + const declaredBindings = new Map(document.bindings.map(binding => [binding.todo_id, binding])); const bindings = list(state.bindings, "canonical bindings", 4096).map(value => { const binding = canonicalAuthorityObject(value, "canonical binding"); acceptanceKeys(binding, ["todo_id", "todo_semantic_digest", "revision", "criterion_ids", "confirmed_by"]); - const declared = document.bindings.find(item => item.todo_id === binding.todo_id); + const declared = typeof binding.todo_id === "string" ? declaredBindings.get(binding.todo_id) : undefined; acceptanceRequire(declared && binding.confirmed_by === "owner" && binding.revision === state.revision && typeof binding.todo_semantic_digest === "string" && BARE_SHA256_PATTERN.test(binding.todo_semantic_digest) && canonicalAuthoritySha256(binding.criterion_ids) === canonicalAuthoritySha256(declared.criterion_ids), "invalid owner-confirmed acceptance binding"); @@ -428,12 +433,7 @@ export function readGoalAcceptance(head: JsonObject, goalId: string): Acceptance * They contain only already pinned files and oracle refs, never commands or * a new result. Public summaries keep using the acceptance projection. */ export function goalAcceptanceOracleObservationBasis(head: JsonObject, goalId: string): JsonObject | null { - const authority = readGoalAcceptanceAuthority(head, goalId); - const state = authority.kind === "legacy" ? authority.state - : authority.owner_matches && authority.lifecycle.state === "active" ? authority.state : null; - const criteria = state?.document.criteria.filter(criterion => criterion.oracle !== undefined) - .map(criterion => ({id: criterion.id, validation_files: criterion.validation_files, oracle: criterion.oracle!})) ?? []; - return state && criteria.length ? {revision: state.revision, digest: state.digest, criteria} : null; + return new GoalAcceptanceRead(head, goalId).oracleBasis(); } export function bindGoalAcceptanceStateOwner( @@ -454,6 +454,10 @@ export function bindGoalAcceptanceStateOwner( export function acceptanceTask(todoId: string, todo: JsonObject | undefined, state: AcceptanceState): AcceptanceTask { const binding = state.bindings.find(item => item.todo_id === todoId); + return acceptanceTaskForBinding(todoId, todo, state, binding); +} +function acceptanceTaskForBinding(todoId: string, todo: JsonObject | undefined, state: AcceptanceState, + binding: AcceptanceBinding | undefined): AcceptanceTask { const reason_code = !binding ? "goal_acceptance_unbound" : !todo || !acceptanceBindingMatches(todo, binding.todo_semantic_digest) ? "goal_acceptance_stale" : "goal_acceptance_ready"; return {todo_id: todoId, state: !binding ? "unbound" : reason_code === "goal_acceptance_stale" ? "stale" : "ready", @@ -489,55 +493,136 @@ export function normalizeAcceptanceResults(value: unknown, expectedIds?: readonl return results; } -function projectAcceptanceChecks(head: JsonObject, goalId: string): JsonObject { - const authority = readGoalAcceptanceAuthority(head, goalId); - const exactProjection = authority.kind === "exact" - ? {lifecycle_state: authority.lifecycle.state, goal_ref: authority.lifecycle.goal_ref} - : {}; - const state = authority.kind === "legacy" ? authority.state - : authority.owner_matches ? authority.state : null; - if (!state || authority.kind === "exact" && authority.lifecycle.state !== "active") { - return {...exactProjection}; +/** Reuse only within one loaded provider head; every public read starts fresh. */ +export class GoalAcceptanceRead { + private readonly head: JsonObject; + private readonly goalId: string; + private readonly read: CoordinationProjectionRead; + private authority: GoalAcceptanceAuthority | undefined; + private todoReadModel: JsonObject | undefined; + private bindings: ReadonlyMap | undefined; + private readonly tasks = new Map(); + + constructor(head: JsonObject, goalId: string, read = new CoordinationProjectionRead(head, goalId)) { + this.head = head; + this.goalId = goalId; + this.read = read; } - const todos = acceptanceTodos(head, goalId); - const taskIds = new Set(state.document.scope?.kind === "selected_work" ? state.document.scope.todo_ids - : [...todos.values()].filter(advancement).map(todo => String(todo.todo_id))); - for (const binding of state.bindings) taskIds.add(binding.todo_id); - const tasks = [...taskIds].sort(authorityUnicodeCompare).map(key => acceptanceTask(key, todos.get(key), state)); - const held = tasks.filter(task => task.applicable && task.state !== "ready"); - const receipt = state.verification; - let status = "unverified"; - if (receipt) { - if (receipt.contract_revision !== state.revision || receipt.contract_digest !== state.digest || - receipt.work_digest !== goalAcceptanceWorkDigest(head, goalId, state.document.scope)) status = "stale"; - else if (receipt.results.some(item => !item.passed)) status = "failed"; - else if (receipt.todo_id !== null) status = "partial"; - else status = "accepted"; + + private readAuthority(): GoalAcceptanceAuthority { + return this.authority ??= readGoalAcceptanceAuthority(this.head, this.goalId); + } + + validateTodoReadModel(): JsonObject { + this.todoReadModel ??= this.read.validateTodoReadModel(); + return structuredClone(this.todoReadModel); + } + + private bindingIndex(state: AcceptanceState): ReadonlyMap { + if (this.bindings === undefined) { + const bindings = new Map(); + for (const binding of state.bindings) { + if (!bindings.has(binding.todo_id)) bindings.set(binding.todo_id, binding); + } + this.bindings = bindings; + } + return this.bindings; + } + + private task(todoId: string, todo: JsonObject | undefined, state: AcceptanceState): AcceptanceTask { + let task = this.tasks.get(todoId); + if (task === undefined) { + task = acceptanceTaskForBinding(todoId, todo, state, this.bindingIndex(state).get(todoId)); + this.tasks.set(todoId, task); + } + return task; + } + + oracleBasis(): JsonObject | null { + const authority = this.readAuthority(); + const state = authority.kind === "legacy" ? authority.state + : authority.owner_matches && authority.lifecycle.state === "active" ? authority.state : null; + const criteria = state?.document.criteria.filter(criterion => criterion.oracle !== undefined) + .map(criterion => ({id: criterion.id, validation_files: criterion.validation_files, oracle: criterion.oracle!})) ?? []; + return state && criteria.length ? structuredClone({revision: state.revision, digest: state.digest, criteria}) : null; + } + + private checks(): JsonObject { + const authority = this.readAuthority(); + const exactProjection = authority.kind === "exact" + ? {lifecycle_state: authority.lifecycle.state, goal_ref: authority.lifecycle.goal_ref} + : {}; + const state = authority.kind === "legacy" ? authority.state + : authority.owner_matches ? authority.state : null; + if (!state || authority.kind === "exact" && authority.lifecycle.state !== "active") { + return {...exactProjection}; + } + this.validateTodoReadModel(); + const todos = this.read.todoIndex.todos; + const taskIds = new Set(state.document.scope?.kind === "selected_work" ? state.document.scope.todo_ids + : [...todos.values()].filter(advancement).map(todo => String(todo.todo_id))); + for (const binding of state.bindings) taskIds.add(binding.todo_id); + const tasks = [...taskIds].sort(authorityUnicodeCompare).map(key => this.task(key, todos.get(key), state)); + const held = tasks.filter(task => task.applicable && task.state !== "ready"); + const receipt = state.verification; + let status = "unverified"; + if (receipt) { + if (receipt.contract_revision !== state.revision || receipt.contract_digest !== state.digest || + receipt.work_digest !== acceptanceWorkDigest(todos, state.document.scope)) status = "stale"; + else if (receipt.results.some(item => !item.passed)) status = "failed"; + else if (receipt.todo_id !== null) status = "partial"; + else status = "accepted"; + } + if (held.length) status = "held"; + return {...exactProjection, ...(state.document.scope === undefined ? {} : {scope: state.document.scope}), + ...(state.document.outcome_id === undefined ? {} : {outcome_id: state.document.outcome_id}), + revision: state.revision, digest: state.digest, objective: state.document.objective, + non_goals: state.document.non_goals, criteria: state.document.criteria.map(criterion => ({id: criterion.id, description: criterion.description, + ...(criterion.oracle === undefined ? {} : {oracle: projectAcceptanceOracle(criterion, receipt?.results.find(row => row.criterion_id === criterion.id), status === "stale" || status === "held")})})), + tasks, held_todo_ids: held.map(task => task.todo_id), status, + verification: receipt ? {operation_id: receipt.operation_id, contract_revision: receipt.contract_revision, + contract_digest: receipt.contract_digest, todo_id: receipt.todo_id, results: receipt.results} : null}; + } + + contract(): JsonObject { + const checks = structuredClone(this.checks()); + const outcomes = projectGoalOutcomes(this.head, this.goalId, checks); + return outcomes === null ? checks : {...checks, outcome_requirements: outcomes}; + } + + workGuards(todoIds: readonly string[]): Record { + const authority = this.readAuthority(); + const state = authority.kind === "legacy" ? authority.state + : authority.owner_matches ? authority.state : null; + if (!state) return {}; + this.validateTodoReadModel(); + const todos = this.read.todoIndex.todos, bindings = this.bindingIndex(state); + const retiring = authority.kind === "exact" && authority.lifecycle.state === "retiring"; + return Object.fromEntries(todoIds.flatMap(todoId => { + const todo = todos.get(todoId); + if (!acceptanceRequired(state, todoId, todo, bindings.get(todoId) ?? null)) return []; + const task = this.task(todoId, todo, state); + return [[todoId, {allowed: !retiring && task.state === "ready", ...task, + criterion_ids: [...task.criterion_ids], + ...(retiring ? {reason_code: "goal_acceptance_goal_retiring", + reason: "The Goal instance is retiring; acceptance cannot authorize more work."} : {}), + revision: state.revision, digest: state.digest}]]; + })); } - if (held.length) status = "held"; - return {...exactProjection, ...(state.document.scope === undefined ? {} : {scope: state.document.scope}), - ...(state.document.outcome_id === undefined ? {} : {outcome_id: state.document.outcome_id}), - revision: state.revision, digest: state.digest, objective: state.document.objective, - non_goals: state.document.non_goals, criteria: state.document.criteria.map(criterion => ({id: criterion.id, description: criterion.description, - ...(criterion.oracle === undefined ? {} : {oracle: projectAcceptanceOracle(criterion, receipt?.results.find(row => row.criterion_id === criterion.id), status === "stale" || status === "held")})})), - tasks, held_todo_ids: held.map(task => task.todo_id), status, - verification: receipt ? {operation_id: receipt.operation_id, contract_revision: receipt.contract_revision, - contract_digest: receipt.contract_digest, todo_id: receipt.todo_id, results: receipt.results} : null}; } /** Outcomes extend the existing acceptance read, even before checks are configured. * No declaration preserves the original projection byte-for-byte. */ export function projectGoalAcceptance(head: JsonObject, goalId: string): JsonObject { - const checks = projectAcceptanceChecks(head, goalId); - const outcomes = projectGoalOutcomes(head, goalId, checks); - return outcomes === null ? checks : {...checks, outcome_requirements: outcomes}; + return new GoalAcceptanceRead(head, goalId).contract(); } /** Explicit selection and confirmed bindings survive mutable work fields. * The guarded Agent cannot escape acceptance by changing role/class/status. */ -function acceptanceRequired(state: AcceptanceState, todoId: string, todo: JsonObject | undefined): boolean { +function acceptanceRequired(state: AcceptanceState, todoId: string, todo: JsonObject | undefined, + binding: AcceptanceBinding | null = state.bindings.find(binding => binding.todo_id === todoId) ?? null): boolean { return acceptanceIncludes(state.document.scope, todoId) && - (state.document.scope?.kind === "selected_work" || state.bindings.some(binding => binding.todo_id === todoId) || + (state.document.scope?.kind === "selected_work" || binding !== null || todo === undefined || acceptanceApplies(todo)); } @@ -553,21 +638,7 @@ export function projectGoalAcceptanceWorkGuards( goalId: string, todoIds: readonly string[], ): Record { - const authority = readGoalAcceptanceAuthority(head, goalId); - const state = authority.kind === "legacy" ? authority.state - : authority.owner_matches ? authority.state : null; - if (!state) return {}; - const todos = acceptanceTodos(head, goalId); - const retiring = authority.kind === "exact" && authority.lifecycle.state === "retiring"; - return Object.fromEntries(todoIds.flatMap(todoId => { - const todo = todos.get(todoId); - if (!acceptanceRequired(state, todoId, todo)) return []; - const task = acceptanceTask(todoId, todo, state); - return [[todoId, {allowed: !retiring && task.state === "ready", ...task, - ...(retiring ? {reason_code: "goal_acceptance_goal_retiring", - reason: "The Goal instance is retiring; acceptance cannot authorize more work."} : {}), - revision: state.revision, digest: state.digest}]]; - })); + return new GoalAcceptanceRead(head, goalId).workGuards(todoIds); } /** Trusted execution adapter only. Run these commands at the inspected provider diff --git a/tests/control_plane_ts/canonical_snapshot_page.test.ts b/tests/control_plane_ts/canonical_snapshot_page.test.ts index e7bcdd9354..492addc77d 100644 --- a/tests/control_plane_ts/canonical_snapshot_page.test.ts +++ b/tests/control_plane_ts/canonical_snapshot_page.test.ts @@ -11,6 +11,9 @@ import {coordinationTodoReadModel} from "../../loopx/control_plane/coordination/ import {readCanonicalSnapshotFromStore, readCanonicalSnapshotPage, readCanonicalSnapshot, CANONICAL_SNAPSHOT_PAGE_BYTES, CANONICAL_SNAPSHOT_LOCAL_BYTES} from "../../loopx/control_plane/coordination/canonical_snapshot_page.ts"; import {productionScaleCoordinationFixture} from "./production_scale_coordination_fixture.ts"; +import {authorityProjectionFixture} from "./authority_projection_fixture.ts"; +import {canonicalAuthoritySha256} from "../../loopx/control_plane/coordination/authority_store_codec.ts"; +import {goalAcceptanceTodoDigest, normalizeGoalAcceptanceDocument} from "../../loopx/control_plane/goals/acceptance_contract.ts"; import {collectSnapshot, registerCanonicalSnapshotConformance, seedSnapshot, snapshotRequest, snapshotFixture} from "./canonical_snapshot_conformance.ts"; async function fixture(t: test.TestContext, kind: "file" | "sqlite" = "file") { @@ -32,6 +35,33 @@ for (const kind of (qualifiedSqlite ? ["file", "sqlite"] : ["file"]) as ("file" assert.equal(result.todos.length, source.expected_initial_todo_count); assert.equal(result.leases.length, source.expected_current_lease_count); }); + test(`${kind}: acceptance read reuse ends at the loaded provider head`, async t => { + const {store} = await fixture(t, kind); + const todo = {todo_id: "todo_work", role: "agent", status: "open", done: false, + text: "Deliver the reviewed requirement", task_class: "advancement_task", archive_state: "active"}; + const document = normalizeGoalAcceptanceDocument({objective: "Verify the reviewed requirement", non_goals: [], + criteria: [{id: "criterion-a", description: "The check passes", validation_argv: ["true"]}], + bindings: [{todo_id: todo.todo_id, criterion_ids: ["criterion-a"]}]}); + const acceptance = {schema_version: "loopx_goal_acceptance_v0", enabled: true, revision: 1, + digest: canonicalAuthoritySha256(document), document, verification: null, + bindings: [{todo_id: todo.todo_id, todo_semantic_digest: goalAcceptanceTodoDigest(todo), + revision: 1, criterion_ids: ["criterion-a"], confirmed_by: "owner"}]}; + await seedSnapshot(store, authorityProjectionFixture("goal-a", [todo], [], "native", {goal_acceptance: acceptance})); + const before = await readCanonicalSnapshotFromStore(snapshotRequest(), store); + assert.equal(((before.goal_acceptance_work_guards as JsonObject).todo_work as JsonObject).allowed, true); + const current = await store.loadAuthority(); + assert.equal(current.status, "loaded"); + if (current.status !== "loaded") throw new Error("synthetic provider head missing"); + const changed = authorityProjectionFixture("goal-a", [{...todo, text: "A different requirement"}], [], "native", {goal_acceptance: acceptance}); + const commit = await store.commitAuthority({operation_id: "change-requirement", + expected_provider_revision: current.provider_revision, next_projection: changed, events: [], receipts: []}); + assert.equal(commit.status, "applied"); + const after = await readCanonicalSnapshotFromStore(snapshotRequest(), store); + const guard = (after.goal_acceptance_work_guards as JsonObject).todo_work as JsonObject; + assert.equal(guard.allowed, false); + assert.equal(guard.reason_code, "goal_acceptance_stale"); + assert.notDeepEqual(after.snapshot, before.snapshot); + }); test(`${kind}: UTF-8 byte pages retain records beyond the old 2 MiB RPC ceiling`, async t => { const {store} = await fixture(t, kind); const projection = snapshotFixture().projection; diff --git a/tests/control_plane_ts/goal_acceptance_runtime.test.ts b/tests/control_plane_ts/goal_acceptance_runtime.test.ts index a7d937dff0..0c827f0f70 100644 --- a/tests/control_plane_ts/goal_acceptance_runtime.test.ts +++ b/tests/control_plane_ts/goal_acceptance_runtime.test.ts @@ -20,7 +20,7 @@ import {executeCoordinationTodoClaim} from "../../loopx/control_plane/coordinati import {executeCoordinationTodoUpdate} from "../../loopx/control_plane/coordination/todo_update.ts"; import {executeCanonicalTaskLeaseAcquire} from "../../loopx/control_plane/coordination/task_lease_acquire.ts"; import {executeCoordinationTodoTerminalLifecycle, type CoordinationTodoTerminalLifecycleInput} from "../../loopx/control_plane/coordination/todo_terminal_lifecycle.ts"; -import {listLocalCoordinationTodos, readLocalCoordinationTodo} from "../../loopx/control_plane/coordination/local_authority_read.ts"; +import {canonicalTodoCollection, listLocalCoordinationTodos, readLocalCoordinationTodo} from "../../loopx/control_plane/coordination/local_authority_read.ts"; const now = new Date("2026-09-17T12:00:00Z"); const todo = (extra: JsonObject = {}): JsonObject => ({schema_version: "todo_domain_record_v0", @@ -323,6 +323,31 @@ test("collection guard projection reuses one acceptance and Todo snapshot", () = assert.ok(elapsedMs < 1_000, `150 Todo guards should remain a bounded collection read, got ${elapsedMs}ms`); }); +test("collection projections do not expose shared mutable acceptance verdicts", () => { + const records = [todo()]; + const head = projection(records, acceptance(records)); + const before = structuredClone(head); + const collection = canonicalTodoCollection(head, "goal-a", false); + const expected = projectGoalAcceptanceWorkGuards(head, "goal-a", ["todo_work"]); + const guards = collection.projectWorkGuards(["todo_work"]); + (guards.todo_work.criterion_ids as string[]).push("foreign-criterion"); + ((collection.acceptance.tasks as JsonObject[])[0].criterion_ids as string[]).push("foreign-contract"); + (collection.acceptance.non_goals as string[]).push("foreign-non-goal"); + assert.deepEqual(collection.projectWorkGuards(["todo_work"]), expected); + assert.deepEqual(projectGoalAcceptance(head, "goal-a"), projectGoalAcceptance(before, "goal-a")); + assert.deepEqual(head, before); +}); + +test("collection validation keeps acceptance errors ahead of malformed Todo data", () => { + const records = [todo()]; + const head = projection(records, {...acceptance(records), digest: "invalid"}); + head.todos = [{todo_id: "invalid"}]; + assert.throws(() => canonicalTodoCollection(head, "goal-a", false), /acceptance contract digest mismatch/); + delete head.goal_acceptance; + assert.throws(() => canonicalTodoCollection(head, "goal-a", false)); + assert.deepEqual(projectGoalAcceptanceWorkGuards(head, "goal-a", []), {}); +}); + test("completion plans both validators and rejects a combined budget exceeding 29 seconds", async t => { const declaration = {validation_command: null, validation_command_argv: ["true"], validation_label: "caller-check", validation_timeout_seconds: null}; From d79b54f4b773080943d29b75cb689269470f8653 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 22 Oct 2026 01:47:57 +0800 Subject: [PATCH 2/2] test(control-plane): read objective from snapshot metadata Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- tests/control_plane_ts/canonical_snapshot_page.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/control_plane_ts/canonical_snapshot_page.test.ts b/tests/control_plane_ts/canonical_snapshot_page.test.ts index 2470e4bbe9..ca4b831441 100644 --- a/tests/control_plane_ts/canonical_snapshot_page.test.ts +++ b/tests/control_plane_ts/canonical_snapshot_page.test.ts @@ -70,10 +70,10 @@ for (const kind of (qualifiedSqlite ? ["file", "sqlite"] : ["file"]) as ("file" head.goal_objective = reviseGoalObjective(head, null, "Bootstrap the service", ["agent-a"], direction); await seedSnapshot(store, head); const result = await readCanonicalSnapshotFromStore(snapshotRequest(), store); - const contract = result.goal_acceptance_contract as JsonObject; + const contract = (result.metadata as JsonObject).goal_acceptance_contract as JsonObject; assert.equal((contract.goal_objective as JsonObject).objective, direction.objective); assert.equal(contract.criteria, undefined, "the Goal direction does not enable optional acceptance checks"); - assert.deepEqual(result.goal_acceptance_work_guards, {}); + assert.equal(result.goal_acceptance_work_guards, undefined); }); test(`${kind}: UTF-8 byte pages retain records beyond the old 2 MiB RPC ceiling`, async t => { const {store} = await fixture(t, kind);