From ed21d681d0f28e229e108931e418fb4a5cb74451 Mon Sep 17 00:00:00 2001 From: "lusendong.6789" Date: Sat, 7 Nov 2026 16:27:30 +0800 Subject: [PATCH 1/2] fix(control-plane): close original Turn after automatic Todo archival Signed-off-by: lusendong.6789 Co-authored-by: TRAE CLI --- .../rfcs/monorepo-distribution-split-v0.md | 33 +++++ .../monorepo-distribution-split-v0.zh-CN.md | 28 ++++ .../coordination/todo_terminal_lifecycle.ts | 114 +++++++++------- .../test_quota_settlement_cli.py | 124 ++++++++++++++++++ 4 files changed, 252 insertions(+), 47 deletions(-) diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index 1f253100e4..364c3447c4 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -1357,6 +1357,39 @@ comparison before adopting a performance change. This checkpoint creates no new owner, budget, preset or default. R0's complete census, R1/minimal, D2/D5/D6, I3/D3, hard-lease/context/model and split-install acceptance remain open; independent exact-head review and required validation govern adoption. +### 2026-11-07 — Original-Turn closeout survives automatic Todo archival + +The complete CLI comparison in #8257 exposed a lifecycle failure with 101 +synthetic Todos. Fresh source-console reproductions on `4a33eb4b5` retain +`todo_archived` on both File and default SQLite after ordinary completion, +durable writeback and one quota debit. The frozen comparison remains unchanged; +these failed episodes are not accepted latency samples. + +The proposed repair stays in `todo_terminal_lifecycle.ts`. Its existing +same-Turn fence and exact ordinary-completion receipt verification also qualify +an archived Todo's closeout before the active-only rejection. One local helper +owns both active and archived parent checks. Original claim/lease, task digest, +validation declaration/receipt and provider CAS checks remain in force; archive +selection and retention thresholds are unchanged. This permits only the +original completion's terminal phase, without reopening archived work. + +Real File/SQLite console fixtures now complete the large-state sequence and +replay completion, writeback, quota spend and terminal closeout with one debit +and an unchanged independently verified JSON result. The durable CLI regression +uses a real validator and ordinary hard-lease claim: premature settlement, +unrelated archived work, another actor, claim or Turn, and changed completion +intent are refused. Terminal closeout retains the original passed validation +receipt, first completion time and archive state without rerunning the validator. +Existing provider conformance retains source/digest, validation, lease and replay +checks; an isolated real PostgreSQL suite qualifies its shared owner separately. + +This is a proposed lifecycle repair enabling the remaining R1/V4 journey and +large-state comparison. Independent exact-head review, reviewed-repair wheel +readback and remaining guard/parser characterization still govern the next +step. It does not upgrade an installed host or qualify interruption/restart, +split installation, full R0/V10/R1, M1/#8011, D2/D5/D6/D3, context/model, +adoption or complete-cost benefit. I3 remains at least one minor release. + ## Appendix B: Decision log | Date | Decision | Owner / approval | Alternatives | Normative sections changed | diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 6a3f6a75f4..f28be25240 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -1016,6 +1016,34 @@ owner;采纳性能变更前必须另做匹配的真实路径对照。本 check 预算、preset 或默认行为。R0 完整清点、R1/minimal、D2/D5/D6、I3/D3、 hard-lease/context/model 和拆包安装验收仍开放;采纳仍受独立 exact-head 评审及必需验证约束。 +### 2026-11-07 — 原 Turn 终结在 Todo 自动归档后仍可完成 + +#8257 的完整 CLI 比较在 101 个合成 Todo 下暴露了生命周期故障。基于 +`4a33eb4b5` 的新源码 console 复现在 File 和默认 SQLite 上均保留同一 +`todo_archived`:普通完成、持久写回和一次额度扣账已提交。冻结比较保持原样, +这些失败 episode 不计入验收通过的延迟样本。 + +修复提案仍由 `todo_terminal_lifecycle.ts` 拥有。在仅限 active 的拒绝之前, +复用已有同 Turn fence 与精确普通 completion receipt 校验,验证已归档 Todo +的终结资格。一个文件内 helper 统一 active 和已归档路径的父 receipt 检查。 +原 claim/lease、任务 digest、validation 声明与 receipt、provider CAS 校验 +继续生效;归档选择与保留阈值保持原样。允许的只是原 completion 的终结阶段, +不会重新开放已归档工作。 + +真实 File/SQLite console fixture 现在可以完成大状态整轮,并回放 completion、 +写回、扣账和 terminal closeout:只有一次扣账,独立验证的 JSON 结果保持原样。 +持久 CLI 回归使用真实 validator 和普通 hard-lease claim,拒绝过早结算、 +无关归档工作、其他 actor、claim 或 Turn,以及变更后的 completion intent。 +终结保留原已通过的 validation receipt、首次完成时间和归档状态,不重复执行 +validator。既有 provider conformance 继续检查 source/digest、validation、 +lease 和 replay;隔离真实 PostgreSQL suite 单独验证其共享 owner。 + +这是支持后续 R1/V4 journey 和大状态比较的生命周期修复提案。下一步仍需 +独立 exact-head review、审阅修复后的 wheel 回读,以及剩余 guard/parser +刻画。它不会升级 installed host,也不验收中断/重启、split installation、 +完整 R0/V10/R1、M1/#8011、D2/D5/D6/D3、context/model、adoption 或完整成本 +收益。I3 继续保留至少一个 minor release。 + ## 附录 B:决策日志 | 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 | diff --git a/loopx/control_plane/coordination/todo_terminal_lifecycle.ts b/loopx/control_plane/coordination/todo_terminal_lifecycle.ts index c7c9fb11da..a6ce5ab171 100644 --- a/loopx/control_plane/coordination/todo_terminal_lifecycle.ts +++ b/loopx/control_plane/coordination/todo_terminal_lifecycle.ts @@ -1194,6 +1194,59 @@ function semanticDuplicate( return undefined; } +async function readSameTurnCompletionReceipt( + store: AuthorityStore, + input: ResolvedCoordinationTodoTerminalLifecycleInput, + normalized: CoordinationTodoTerminalLifecycleInput, + todo: JsonObject, +): Promise { + if (input.command !== "complete" || normalized.operation_identity.kind !== "completion_turn" || + !input.requested_no_followup) return null; + const validationRequired = todo.completion_validation_required === true; + const validationSha256 = todo.completion_validation_sha256; + let fence; + try { + fence = evaluateTodoCompletionFence({schema_version: TODO_COMPLETION_FENCE_REQUEST_SCHEMA, + projection_source: "materialized", goal_id: input.goal_id, todo_id: input.todo_id, todo, + requested_no_followup: input.requested_no_followup, + requested_completion_turn_key: input.requested_completion_turn_key, + requested_completion_identity_source: input.requested_completion_identity_source}); + } catch (error) { + return terminalFailure("invalid_todo_completion_transaction", + error instanceof Error ? error.message : "Invalid original completion identity"); + } + if (fence.outcome === "continue" && fence.reason === "same_turn_terminal_upgrade") { + const originalInput = {...input, + ...(normalized.claim_operation_id === undefined ? {} : {claim_operation_id: normalized.claim_operation_id, + lease_idempotency_key: normalized.lease_idempotency_key, lease_expected_version: normalized.lease_expected_version}), + requested_no_followup: false, + operation_id: completionTurnOperationId(input, false)}; + const original = await terminalReceipt(originalInput, terminalRequestSha(originalInput)).read(store); + if (original === null) return terminalFailure("terminal_completion_receipt_required", + "Same-Turn closeout requires the original accepted ordinary completion receipt.", {}, "decision_rejection"); + if (original.status !== "replayed") return original; + const originalValidation = original.validation_receipt as JsonObject | null; + const receiptDigest = originalValidation?.validation_declaration_sha256; + // Revision-zero receipts may omit the digest. The exact recovered + // request already binds the canonical declaration, actor and lease; + // later declaration revisions still require the receipt's own digest. + const requiresDigest = Number.isSafeInteger(todo.completion_validation_revision) && + Number(todo.completion_validation_revision) > 0; + if (original.completion_identity_key !== input.requested_completion_turn_key || + (validationRequired && (originalValidation === null || + typeof originalValidation !== "object" || originalValidation.passed !== true || + (receiptDigest != null && receiptDigest !== validationSha256) || + (requiresDigest && receiptDigest !== validationSha256))) || + (input.validation_receipt !== null && + canonicalAuthoritySha256(input.validation_receipt) !== canonicalAuthoritySha256(original.validation_receipt))) { + return terminalFailure("terminal_completion_receipt_mismatch", + "Original completion validation and identity must still match the completed Todo.", {}, "decision_rejection"); + } + return original; + } + return null; +} + /** * Execute complete/supersede as one provider-neutral CAS transaction. * @@ -1360,13 +1413,17 @@ export async function executeCoordinationTodoTerminalLifecycle( "Todo requirements changed after Turn admission; reread the task and run a fresh Turn", {todo_id: input.todo_id}, "decision_rejection"); } + let terminalUpgradeReceipt: CoordinationTodoTerminalLifecycleResult | null = null; if (todo.archive_state !== "active") { - return terminalFailure( - "todo_archived", - "Todo terminal lifecycle requires an active Todo", - {}, - "decision_rejection", - ); + // Ordinary completion may archive this Todo before its original Turn + // settles. Only its exact accepted parent permits this second phase; + // archive alone grants no new execution or completion authority. + terminalUpgradeReceipt = await readSameTurnCompletionReceipt(store, input, normalized, todo); + if (terminalUpgradeReceipt === null) { + return terminalFailure("todo_archived", "Todo terminal lifecycle requires an active Todo", + {}, "decision_rejection"); + } + if (terminalUpgradeReceipt.status !== "replayed") return terminalUpgradeReceipt; } const authorityTodo = todo; let edit: ReturnType | null = null; @@ -1607,50 +1664,13 @@ export async function executeCoordinationTodoTerminalLifecycle( } } let completion: ReturnType | null = null; - let terminalUpgradeReceipt: JsonObject | null = null; if (input.command === "complete") { const validationRequired = todo.completion_validation_required === true; const validationSha256 = todo.completion_validation_sha256; - if (normalized.operation_identity.kind === "completion_turn" && input.requested_no_followup) { - let fence; - try { - fence = evaluateTodoCompletionFence({schema_version: TODO_COMPLETION_FENCE_REQUEST_SCHEMA, - projection_source: "materialized", goal_id: input.goal_id, todo_id: input.todo_id, todo, - requested_no_followup: input.requested_no_followup, - requested_completion_turn_key: input.requested_completion_turn_key, - requested_completion_identity_source: input.requested_completion_identity_source}); - } catch (error) { - return terminalFailure("invalid_todo_completion_transaction", - error instanceof Error ? error.message : "Invalid original completion identity"); - } - if (fence.outcome === "continue" && fence.reason === "same_turn_terminal_upgrade") { - const originalInput = {...input, - ...(normalized.claim_operation_id === undefined ? {} : {claim_operation_id: normalized.claim_operation_id, - lease_idempotency_key: normalized.lease_idempotency_key, lease_expected_version: normalized.lease_expected_version}), - requested_no_followup: false, - operation_id: completionTurnOperationId(input, false)}; - const original = await terminalReceipt(originalInput, terminalRequestSha(originalInput)).read(store); - if (original === null) return terminalFailure("terminal_completion_receipt_required", - "Same-Turn closeout requires the original accepted ordinary completion receipt.", {}, "decision_rejection"); - if (original.status !== "replayed") return original; - const originalValidation = original.validation_receipt as JsonObject | null; - const receiptDigest = originalValidation?.validation_declaration_sha256; - // Revision-zero receipts may omit the digest. The exact recovered - // request already binds the canonical declaration, actor and lease; - // later declaration revisions still require the receipt's own digest. - const requiresDigest = Number.isSafeInteger(todo.completion_validation_revision) && - Number(todo.completion_validation_revision) > 0; - if (original.completion_identity_key !== input.requested_completion_turn_key || - (validationRequired && (originalValidation === null || - typeof originalValidation !== "object" || originalValidation.passed !== true || - (receiptDigest != null && receiptDigest !== validationSha256) || - (requiresDigest && receiptDigest !== validationSha256))) || - (input.validation_receipt !== null && - canonicalAuthoritySha256(input.validation_receipt) !== canonicalAuthoritySha256(original.validation_receipt))) { - return terminalFailure("terminal_completion_receipt_mismatch", - "Original completion validation and identity must still match the completed Todo.", {}, "decision_rejection"); - } - terminalUpgradeReceipt = original; + if (terminalUpgradeReceipt === null) { + terminalUpgradeReceipt = await readSameTurnCompletionReceipt(store, input, normalized, todo); + if (terminalUpgradeReceipt !== null && terminalUpgradeReceipt.status !== "replayed") { + return terminalUpgradeReceipt; } } if (validationRequired && !implicitMonitorNoChange && diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py index 3be80d5da8..938791def7 100644 --- a/tests/control_plane/test_quota_settlement_cli.py +++ b/tests/control_plane/test_quota_settlement_cli.py @@ -8056,6 +8056,130 @@ def test_completed_todo_reentry_preserves_original_settlement_commands( assert settled["interaction_contract"]["agent_channel"]["must_attempt"] is False +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_original_turn_closes_automatically_archived_todo_once( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, +) -> None: + from canonical_authority_fixture import isolate_sqlite_runtime + + # A real validator checks a synthetic result; its counter detects any + # repeated validation during closeout or receipt recovery. + project, runtime, registry_path = _write_fixture(tmp_path, claimed=False) + state_path = _configure_read_only_todo(project) + artifact = project / "result.json" + artifact.write_text('{"sum":45}\n', encoding="utf-8") + counter = project / "validator-count.txt" + validator = ( + "import json; from pathlib import Path; " + f"assert json.loads(Path({str(artifact)!r}).read_text()) == {{'sum':45}}; " + f"p=Path({str(counter)!r}); " + "p.write_text(str(int(p.read_text())+1 if p.exists() else 1))" + ) + argv = quote(json.dumps([sys.executable, "-c", validator]), safe="") + state_path.write_text( + state_path.read_text(encoding="utf-8").replace( + "action_kind=validate ", + f"action_kind=validate validation_command_argv={argv} ", 1, + ) + "".join( + f"\n- [x] [P2] Completed synthetic history {index}.\n" + f" \n" + for index in range(100) + ), + encoding="utf-8", + ) + if provider == "sqlite": + isolate_sqlite_runtime(tmp_path, monkeypatch) + _initialize_fixture_authority(project, runtime, provider=provider, + handoff_mode="hard_lease", leases=[]) + claim_id = "original-archive-closeout-claim" + rc, claim = _run_cli(registry_path, runtime, "todo", "claim", + "--goal-id", GOAL_ID, "--todo-id", TODO_ID, + "--agent-id", AGENT_ID, "--claimed-by", AGENT_ID, + "--claim-operation-id", claim_id) + assert rc == 0, claim + binding = ("--goal-id", GOAL_ID, "--agent-id", AGENT_ID, + "--todo-id", TODO_ID, "--turn-instance-id", TURN_ID) + rc, guard = _run_cli(registry_path, runtime, "quota", "should-run", + "--codex-app", *binding) + assert rc == 0 and guard["normal_delivery_allowed"], guard + identity = guard["heartbeat_receipt"]["settlement_identity"] + ordinary_args = ("todo", "complete", *binding, "--claim-operation-id", claim_id, + "--evidence", "artifact:verified-sum") + rc, ordinary = _run_cli(registry_path, runtime, *ordinary_args) + assert rc == 0 and ordinary["validation_receipt"]["passed"], ordinary + rc, archived = _run_cli(registry_path, runtime, "todo", "list", + "--goal-id", GOAL_ID, "--todo-id", TODO_ID, + "--include-archived") + assert rc == 0, archived + assert archived["todo"]["archive_state"] == "archive" + assert archived["todo"]["status"] == "done" + completed_at = archived["todo"]["completed_at"] + terminal_args = (*ordinary_args, "--no-follow-up") + rc, premature = _run_cli(registry_path, runtime, *terminal_args) + assert rc == 1, premature + assert "requires matching writeback and quota spend receipts" in premature["error"] + assert _spend_run_count(runtime) == 0 + + rc, written = _run_cli( + registry_path, runtime, "refresh-state", *binding, + "--classification", "archived_result_verified", + "--delivery-batch-scale", "single_surface", + "--delivery-outcome", "outcome_progress", + "--vision-state", "vision_closed", + "--vision-acceptance", "The independently verified sum is durably recorded.", + "--no-global-sync", "--suppress-external-sinks", + ) + assert rc == 0, written + spend_args = ("quota", "spend-slot", *binding, "--slots", "1", + "--source", "heartbeat", "--execute") + for _ in range(2): + rc, spent = _run_cli(registry_path, runtime, *spend_args) + assert rc == 0, spent + validation_runs = counter.read_text() + assert int(validation_runs) >= 1 + + # An unrelated archived task, another actor, and a different claim or Turn + # cannot borrow the completed task's original receipt or settlement. + attempts = [ + ("todo", "complete", "--goal-id", GOAL_ID, "--agent-id", AGENT_ID, + "--todo-id", "todo_history_0000"), + tuple("unregistered-actor" if arg == AGENT_ID else arg for arg in terminal_args), + tuple("unrelated-claim" if arg == claim_id else arg for arg in terminal_args), + tuple("different-turn" if arg == TURN_ID else arg for arg in terminal_args), + (*terminal_args, "--clear-claim"), + ] + for attempt in attempts: + rc, refused = _run_cli(registry_path, runtime, *attempt) + assert rc == 1, refused + assert _spend_run_count(runtime) == 1 + assert counter.read_text() == validation_runs + assert refused["error_code"] == "coordination_operation_identity_mismatch", refused + + rc, terminal = _run_cli(registry_path, runtime, *terminal_args) + assert rc == 0, terminal + assert terminal["completion_continuation"] == "no_followup" + assert terminal["completion_recovery"] == "same_turn_terminal_closeout" + assert terminal["completion_identity_key"] == ordinary["completion_identity_key"] + assert terminal["validation_receipt"] == ordinary["validation_receipt"] + assert [row["step_kind"] for row in terminal["settlement_result"]["receipts"]] == [ + "validation", "durable_writeback", "quota_spend", "terminal_closeout", + ] + for args in (terminal_args, ordinary_args): + rc, replay = _run_cli(registry_path, runtime, *args) + assert rc == 0 and replay["idempotent_replay"], replay + rc, final = _run_cli(registry_path, runtime, "todo", "list", + "--goal-id", GOAL_ID, "--todo-id", TODO_ID, + "--include-archived") + assert rc == 0, final + assert final["todo"]["archive_state"] == "archive" + assert final["todo"]["completed_at"] == completed_at + assert final["todo"]["completion_turn_key"] == identity["effect_id"] + assert counter.read_text() == validation_runs + assert _spend_run_count(runtime) == 1 + assert _classification_count(runtime, "archived_result_verified") == 1 + + @pytest.mark.parametrize("provider", ["file", "sqlite"]) def test_read_only_settlement_omits_non_causal_delivery_workspace( tmp_path: Path, From 12627eff8005b900d8c1b99589de02a8a481a8aa Mon Sep 17 00:00:00 2001 From: "lusendong.6789" Date: Sat, 7 Nov 2026 16:51:47 +0800 Subject: [PATCH 2/2] test(control-plane): retain Goal checks through archived Todo closeout Signed-off-by: lusendong.6789 Co-authored-by: TRAE CLI --- .../rfcs/monorepo-distribution-split-v0.md | 8 ++++ .../monorepo-distribution-split-v0.zh-CN.md | 6 +++ .../test_quota_settlement_cli.py | 38 ++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index 364c3447c4..6b4dca259b 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -1383,6 +1383,14 @@ receipt, first completion time and archive state without rerunning the validator Existing provider conformance retains source/digest, validation, lease and replay checks; an isolated real PostgreSQL suite qualifies its shared owner separately. +The author proposal also integrates main `abf144202` (#8161), whose explicit +Goal-scoped checks do not gate local Todo completion. The archive regression +covers a failed Goal commitment alongside the local validator: ordinary and +terminal completion still preserve the original receipt and one debit, never +rerun the Goal checker, and leave the overall check failed. This is author-side +integration coverage; it does not replace independent review or reviewed-repair +wheel qualification. + This is a proposed lifecycle repair enabling the remaining R1/V4 journey and large-state comparison. Independent exact-head review, reviewed-repair wheel readback and remaining guard/parser characterization still govern the next diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index f28be25240..6aca7f491a 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -1038,6 +1038,12 @@ hard-lease/context/model 和拆包安装验收仍开放;采纳仍受独立 validator。既有 provider conformance 继续检查 source/digest、validation、 lease 和 replay;隔离真实 PostgreSQL suite 单独验证其共享 owner。 +作者提案还集成了 main `abf144202`(#8161):显式 Goal 范围的检查不约束局部 +Todo 完成。归档回归在局部 validator 之外加入未通过的整体 Goal 承诺检查: +普通完成和终结仍保留原 receipt 与一次扣账,不重复执行 Goal checker,整体 +检查继续失败。这是作者阶段的集成覆盖,不能替代独立审阅或审阅修复后的 +wheel 资格验证。 + 这是支持后续 R1/V4 journey 和大状态比较的生命周期修复提案。下一步仍需 独立 exact-head review、审阅修复后的 wheel 回读,以及剩余 guard/parser 刻画。它不会升级 installed host,也不验收中断/重启、split installation、 diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py index 938791def7..f2a36d8ea7 100644 --- a/tests/control_plane/test_quota_settlement_cli.py +++ b/tests/control_plane/test_quota_settlement_cli.py @@ -8056,9 +8056,10 @@ def test_completed_todo_reentry_preserves_original_settlement_commands( assert settled["interaction_contract"]["agent_channel"]["must_attempt"] is False +@pytest.mark.parametrize("goal_commitment", [False, True]) @pytest.mark.parametrize("provider", ["file", "sqlite"]) def test_original_turn_closes_automatically_archived_todo_once( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, goal_commitment: bool, ) -> None: from canonical_authority_fixture import isolate_sqlite_runtime @@ -8092,6 +8093,35 @@ def test_original_turn_closes_automatically_archived_todo_once( isolate_sqlite_runtime(tmp_path, monkeypatch) _initialize_fixture_authority(project, runtime, provider=provider, handoff_mode="hard_lease", leases=[]) + goal_counter = project / "goal-validator-count.txt" + if goal_commitment: + # A failed cross-task Goal check cannot replace or block the local + # validator, nor become accepted just because this Todo closes. + checks = tmp_path / "goal-checks.json" + checks.write_text(json.dumps({ + "scope": {"kind": "goal"}, "objective": "Fulfil the overall commitment", + "non_goals": [], "bindings": [], "criteria": [{ + "id": "overall-commitment", "description": "Overall work remains incomplete", + "validation_argv": [sys.executable, "-c", + "from pathlib import Path; " + f"p=Path({str(goal_counter)!r}); " + "p.write_text(str(int(p.read_text())+1 if p.exists() else 1)); " + "raise SystemExit(1)"], + }], + }), encoding="utf-8") + rc, inspected = _run_cli(registry_path, runtime, "goal-acceptance", "inspect", + "--goal-id", GOAL_ID) + assert rc == 0, inspected + rc, configured = _run_cli( + registry_path, runtime, "goal-acceptance", "configure", "--goal-id", GOAL_ID, + "--document", str(checks), "--operation-id", "overall-check", + "--expected-provider-revision", inspected["provider_revision"], "--execute", + ) + assert rc == 0, configured + rc, failed = _run_cli(registry_path, runtime, "goal-acceptance", "verify", + "--goal-id", GOAL_ID, "--agent-id", AGENT_ID, "--execute") + assert rc == 1 and failed["checks_passed"] is False, failed + assert goal_counter.read_text() == "1" claim_id = "original-archive-closeout-claim" rc, claim = _run_cli(registry_path, runtime, "todo", "claim", "--goal-id", GOAL_ID, "--todo-id", TODO_ID, @@ -8178,6 +8208,12 @@ def test_original_turn_closes_automatically_archived_todo_once( assert counter.read_text() == validation_runs assert _spend_run_count(runtime) == 1 assert _classification_count(runtime, "archived_result_verified") == 1 + if goal_commitment: + rc, overall = _run_cli(registry_path, runtime, "goal-acceptance", "inspect", + "--goal-id", GOAL_ID) + assert rc == 0, overall + assert overall["goal_acceptance_contract"]["status"] == "failed" + assert goal_counter.read_text() == "1" @pytest.mark.parametrize("provider", ["file", "sqlite"])