From 40f717a177ff435f4a55b7ed86184cf6f2b2b9cc Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:09:03 +0800 Subject: [PATCH 01/38] test: qualify archive capacity validation and concurrent canonical writes Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> (cherry picked from commit 3a5897e09401b5208eba5f01728e7835cb78992f) --- ...pletion-validation-accountability-smoke.py | 10 +- .../todo-archive-completed-smoke.py | 30 ++++- .../todo-concurrent-write-lock-smoke.py | 112 +++++++++++------- .../todo-deferred-capacity-cli-smoke.py | 6 +- 4 files changed, 100 insertions(+), 58 deletions(-) diff --git a/examples/control_plane/completion-validation-accountability-smoke.py b/examples/control_plane/completion-validation-accountability-smoke.py index e8a0efd022..8b6e926fcb 100755 --- a/examples/control_plane/completion-validation-accountability-smoke.py +++ b/examples/control_plane/completion-validation-accountability-smoke.py @@ -13,14 +13,15 @@ if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) -from loopx.control_plane.quota.slot_accounting import ( +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.control_plane.quota.slot_accounting import ( # noqa: E402 build_quota_slot_preview_for_decision, ) -from loopx.control_plane.todos.active_state_todo_parser import ( +from loopx.control_plane.todos.active_state_todo_parser import ( # noqa: E402 parse_active_state_todos, ) -from loopx.state_refresh import refresh_state_run -from loopx.todos import add_goal_todo, complete_goal_todo +from loopx.state_refresh import refresh_state_run # noqa: E402 +from loopx.todos import add_goal_todo, complete_goal_todo # noqa: E402 GOAL_ID = "completion-validation-accountability" AGENT_ID = "codex-author" @@ -71,6 +72,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: ), encoding="utf-8", ) + import_fixture_authority(registry, GOAL_ID) return registry, runtime, state diff --git a/examples/control_plane/todo-archive-completed-smoke.py b/examples/control_plane/todo-archive-completed-smoke.py index 70d8923454..9b676cb372 100644 --- a/examples/control_plane/todo-archive-completed-smoke.py +++ b/examples/control_plane/todo-archive-completed-smoke.py @@ -17,6 +17,7 @@ completed_todo_archive_warning, completed_todo_count, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.status import parse_active_state_todos # noqa: E402 GOAL_ID = "todo-archive-completed-goal" @@ -47,6 +48,7 @@ def state_text() -> str: + "- [ ] [P2] Keep monitor work after completed items visible.\n\n" "## Completed Work Archive\n\n" "- [x] [P1] Previously archived completed work.\n" + " \n" ) @@ -80,7 +82,7 @@ def user_role_state_text() -> str: ) -def write_fixture(root: Path, *, text: str | None = None) -> tuple[Path, Path, Path]: +def write_fixture(root: Path, *, text: str | None = None, import_authority: bool = True) -> tuple[Path, Path, Path]: project = root / "project" runtime = root / "runtime" state_file = ".codex/goals/todo-archive-completed-goal/ACTIVE_GOAL_STATE.md" @@ -119,6 +121,8 @@ def write_fixture(root: Path, *, text: str | None = None) -> tuple[Path, Path, P + "\n", encoding="utf-8", ) + if import_authority: + import_fixture_authority(registry_path, GOAL_ID) return registry_path, runtime, state_path @@ -312,7 +316,7 @@ def main() -> int: assert updated.count("[P1] Completed implementation lane 1.") == 1 assert updated.count("[P1] Completed implementation lane 2.") == 1 assert "excluded_agents=codex-reviewer" in updated - assert "updated_at: 2026-01-01T00:00:00+00:00" not in updated + assert "updated_at: 2026-01-01T00:00:00+00:00" in updated status_after = run_cli(registry_path, runtime, "status") assert status_after["ok"] is True, status_after @@ -438,8 +442,9 @@ def main() -> int: assert lineage_status["ok"] is True, lineage_status assert lineage_status["contract_errors"] == [], lineage_status - state_path.write_text( - archived_lineage + before_authority = run_cli(registry_path, runtime, "todo", "list", "--goal-id", GOAL_ID) + invalid_rows = ( + "" + "- [ ] [P1] Contradictory archived work item.\n" + " \n" @@ -448,11 +453,24 @@ def main() -> int: + "task_class=advancement_task excluded_agents=codex-reviewer -->\n" + "- [x] [P1] Empty-status archived work item.\n" + " \n", - encoding="utf-8", + + "task_class=advancement_task excluded_agents=codex-reviewer -->\n" ) + state_path.write_text(archived_lineage + invalid_rows, encoding="utf-8") invalid_status = run_cli(registry_path, runtime, "status", check=False) + assert invalid_status["ok"] is True, invalid_status + after_authority = run_cli(registry_path, runtime, "todo", "list", "--goal-id", GOAL_ID) + assert after_authority["todos"] == before_authority["todos"] + assert after_authority["authority_read"]["provider_revision"] == before_authority["authority_read"]["provider_revision"] + # These diagnostics still belong to explicit legacy source inspection, + # before import. Display text outside canonical regions grants no work. + source_registry, source_runtime, source_state = write_fixture( + Path(tmp) / "invalid-source", text=state_text() + invalid_rows, + import_authority=False, + ) + before_source = source_state.read_bytes() + invalid_status = run_cli(source_registry, source_runtime, "status", check=False) assert invalid_status["ok"] is False, invalid_status + assert source_state.read_bytes() == before_source assert any( "todo_archived_open" in item and "executor exclusions" in item for item in invalid_status["contract_errors"] diff --git a/examples/control_plane/todo-concurrent-write-lock-smoke.py b/examples/control_plane/todo-concurrent-write-lock-smoke.py index ee88eb10eb..62938adca3 100644 --- a/examples/control_plane/todo-concurrent-write-lock-smoke.py +++ b/examples/control_plane/todo-concurrent-write-lock-smoke.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Smoke-test active-state todo writers under a contended file lock.""" +"""Smoke-test concurrent canonical writes while readable projection is locked.""" from __future__ import annotations @@ -16,14 +16,16 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.file_lock import exclusive_file_lock # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 from loopx.status import parse_active_state_todos # noqa: E402 GOAL_ID = "todo-concurrent-write-lock-goal" -CHILD_ADD_TODO = "Child add should preserve the parent todo written under lock." -PARENT_ADD_TODO = "Parent todo written while child add waits on the lock." +CHILD_ADD_TODO = "Child add should preserve the concurrently committed parent todo." +PARENT_ADD_TODO = "Parent todo committed while child add awaits readable projection." UPDATE_TARGET_TODO = "Update target todo should keep unrelated concurrent additions." -PARENT_UPDATE_TODO = "Parent todo written while child update waits on the lock." +PARENT_UPDATE_TODO = "Parent todo committed while child update awaits readable projection." def write_fixture(root: Path) -> tuple[Path, Path]: @@ -73,6 +75,7 @@ def write_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + import_fixture_authority(registry_path, GOAL_ID) return registry_path, state_file @@ -118,14 +121,33 @@ def wait_json(process: subprocess.Popen[str]) -> dict[str, Any]: def assert_waiting_on_lock(process: subprocess.Popen[str], label: str) -> None: time.sleep(0.35) - assert process.poll() is None, ( - f"{label} process finished before the parent released the lock" + if process.poll() is not None: + stdout, stderr = process.communicate() + raise AssertionError(f"{label} finished before display delivery: {stdout} {stderr}") + + +def authority(registry_path: Path) -> dict[str, Any]: + registry = json.loads(registry_path.read_text(encoding="utf-8")) + result = read_canonical_todos_if_promoted( + runtime_root=Path(registry["common_runtime_root"]), goal_id=GOAL_ID, ) + assert result is not None and result["legacy_fallback_used"] is False, result + return result -def append_agent_todo(state_file: Path, text: str) -> None: - with state_file.open("a", encoding="utf-8") as file: - file.write(f"- [ ] {text}\n") +def wait_for_committed_texts( + registry_path: Path, texts: set[str], *, blocked_todo_id: str | None = None, +) -> None: + # The five-second display-lock deadline is unchanged. Authority commits + # must be observable independently while both CLI callers await projection. + deadline = time.monotonic() + 3 + while time.monotonic() < deadline: + rows = authority(registry_path)["todos"] + if texts <= {row["text"] for row in rows} and (blocked_todo_id is None or + any(row["todo_id"] == blocked_todo_id and row["status"] == "blocked" for row in rows)): + return + time.sleep(0.05) + raise AssertionError("canonical writes did not commit before display delivery") def agent_items(state_file: Path) -> list[dict[str, Any]]: @@ -141,24 +163,24 @@ def main() -> int: with tempfile.TemporaryDirectory(prefix="loopx-todo-lock-smoke-") as tmp: registry_path, state_file = write_fixture(Path(tmp)) + child_add = ("todo", "add", "--goal-id", GOAL_ID, "--role", "agent", + "--text", CHILD_ADD_TODO, "--operation-id", "child-add") with exclusive_file_lock(state_file): - add_process = start_cli( - registry_path, - "todo", - "add", - "--goal-id", - GOAL_ID, - "--role", - "agent", - "--text", - CHILD_ADD_TODO, - ) + add_process = start_cli(registry_path, *child_add) + parent_add = start_cli(registry_path, "todo", "add", "--goal-id", GOAL_ID, + "--role", "agent", "--text", PARENT_ADD_TODO, + "--operation-id", "parent-add") assert_waiting_on_lock(add_process, "todo add") - append_agent_todo(state_file, PARENT_ADD_TODO) - add_result = wait_json(add_process) - assert add_result["added"] is True, add_result - assert find_item(state_file, CHILD_ADD_TODO), state_file.read_text(encoding="utf-8") - assert find_item(state_file, PARENT_ADD_TODO), state_file.read_text(encoding="utf-8") + wait_for_committed_texts(registry_path, {CHILD_ADD_TODO, PARENT_ADD_TODO}) + assert wait_json(add_process)["added"] is True + assert wait_json(parent_add)["added"] is True + assert find_item(state_file, CHILD_ADD_TODO) + assert find_item(state_file, PARENT_ADD_TODO) + before_replay = authority(registry_path) + run_cli(registry_path, *child_add) + after_replay = authority(registry_path) + assert after_replay["provider_revision"] == before_replay["provider_revision"] + assert after_replay["todos"] == before_replay["todos"] target = run_cli( registry_path, @@ -173,34 +195,34 @@ def main() -> int: ) target_id = target["todo_id"] + child_update = ("todo", "update", "--goal-id", GOAL_ID, "--todo-id", target_id, + "--agent-id", "codex-side-bypass", "--status", "blocked", + "--reason", "waiting-on-concurrent-state", "--claimed-by", + "codex-side-bypass", "--update-operation-id", "child-update") with exclusive_file_lock(state_file): - update_process = start_cli( - registry_path, - "todo", - "update", - "--goal-id", - GOAL_ID, - "--todo-id", - target_id, - "--agent-id", - "codex-side-bypass", - "--status", - "blocked", - "--reason", - "waiting-on-concurrent-state", - "--claimed-by", - "codex-side-bypass", - ) + update_process = start_cli(registry_path, *child_update) + parent_update = start_cli(registry_path, "todo", "add", "--goal-id", GOAL_ID, + "--role", "agent", "--text", PARENT_UPDATE_TODO, + "--operation-id", "parent-during-update") assert_waiting_on_lock(update_process, "todo update") - append_agent_todo(state_file, PARENT_UPDATE_TODO) - update_result = wait_json(update_process) - assert update_result["changed"] is True, update_result + wait_for_committed_texts(registry_path, {PARENT_UPDATE_TODO}, blocked_todo_id=target_id) + assert next(row for row in authority(registry_path)["todos"] + if row["todo_id"] == target_id)["status"] == "blocked" + assert wait_json(update_process)["changed"] is True + assert wait_json(parent_update)["added"] is True updated_target = find_item(state_file, UPDATE_TARGET_TODO) assert updated_target["status"] == "blocked", updated_target assert updated_target["reason"] == "waiting-on-concurrent-state", updated_target assert updated_target["claimed_by"] == "codex-side-bypass", updated_target assert find_item(state_file, PARENT_UPDATE_TODO), state_file.read_text(encoding="utf-8") + before_replay = authority(registry_path) + run_cli(registry_path, *child_update) + after_replay = authority(registry_path) + assert after_replay["provider_revision"] == before_replay["provider_revision"] + assert after_replay["todos"] == before_replay["todos"] + assert len([row for row in after_replay["todos"] if row["text"] == PARENT_UPDATE_TODO]) == 1 + print("todo-concurrent-write-lock-smoke ok") return 0 diff --git a/examples/control_plane/todo-deferred-capacity-cli-smoke.py b/examples/control_plane/todo-deferred-capacity-cli-smoke.py index 5a54de1d64..874e929e17 100644 --- a/examples/control_plane/todo-deferred-capacity-cli-smoke.py +++ b/examples/control_plane/todo-deferred-capacity-cli-smoke.py @@ -87,7 +87,7 @@ def assert_deferred_add_and_capacity_resume() -> None: "--resume-when", CAPACITY_RESUME, ) - assert added["status"] == "deferred", added + assert added["todo"]["status"] == "deferred", added todo_id = added["todo_id"] item = next(item for item in parsed_items(state_file) if item["todo_id"] == todo_id) assert item["status"] == "deferred" and item["done"] is True, item @@ -187,7 +187,7 @@ def assert_deferred_add_and_capacity_resume() -> None: "capacity condition was satisfied", ) assert ( - reopened["status"] == "open" and reopened["resume_when"] is None + reopened["status"] == "applied" and reopened["changed"] is True ), reopened item = next(item for item in parsed_items(state_file) if item["todo_id"] == todo_id) assert item["status"] == "open" and item.get("resume_when") is None, item @@ -260,7 +260,7 @@ def assert_open_to_deferred_update() -> None: "--reason", "short worker capacity is not currently available", ) - assert updated["status"] == "deferred" and updated["status_changed"] is True, updated + assert updated["status"] == "applied" and updated["changed"] is True, updated item = next(item for item in parsed_items(state_file) if item["todo_id"] == todo_id) assert item["status"] == "deferred" and item["resume_when"] == CAPACITY_RESUME, item From d359b59810eba8394ffff933c4198ea4403fe5d1 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:54:20 +0800 Subject: [PATCH 02/38] fix: admit readonly completion facts for imported metadata repair Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../coordination/todo_terminal_lifecycle.ts | 25 +++++++++- .../test_native_todo_planning_update.py | 50 +++++++++++++++++++ .../terminal_reentry_conformance.ts | 14 +++++- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/loopx/control_plane/coordination/todo_terminal_lifecycle.ts b/loopx/control_plane/coordination/todo_terminal_lifecycle.ts index d6840ea74d..3ee8a057b6 100644 --- a/loopx/control_plane/coordination/todo_terminal_lifecycle.ts +++ b/loopx/control_plane/coordination/todo_terminal_lifecycle.ts @@ -60,6 +60,7 @@ import { selectTodoCompletionState, TODO_COMPLETION_STATE_REQUEST_SCHEMA, } from "../todos/completion_state.ts"; +import {resolveTodoCompletionPolicy} from "../todos/completion_policy.ts"; import { TASK_LEASE_SCHEMA_VERSION, leaseEpoch, @@ -1031,6 +1032,24 @@ interface UntypedCompletionRepairPlan { readonly successorTodoIds: readonly string[]; } +function completionPolicyFactsOnly( + todo: JsonObject, input: ResolvedCoordinationTodoTerminalLifecycleInput, +): boolean { + const request = input.completion_policy_request; + if (request === null) return true; + // The ordinary CLI always supplies this existing source-fact envelope. + // Decode through its owner, but never apply a policy during imported repair. + const policy = resolveTodoCompletionPolicy(request); + return request.goal_id === input.goal_id && request.next_agent_todo == null && + request.next_action_kind == null && request.next_continuation_policy == null && + request.evidence == null && !policy.self_merged && + policy.effective_next_claimed_by === null && policy.effective_next_excluded_agents.length === 0 && + Array.isArray(request.linked_successors) && request.linked_successors.length === 0 && + (policy.effective_claimed_by === null || policy.effective_claimed_by === todo.claimed_by) && + canonicalAuthoritySha256([...policy.registered_agents].sort()) === + canonicalAuthoritySha256([...input.registered_agents].sort()); +} + function planUntypedCompletionRepair( todo: JsonObject, input: ResolvedCoordinationTodoTerminalLifecycleInput, @@ -1057,9 +1076,9 @@ function planUntypedCompletionRepair( } if (input.user_update !== undefined || input.note !== null || input.evidence !== null || input.reason !== null || input.decision_outcome !== null || input.clear_claim || - input.allow_user_gate_auto_acquire || input.linked_successor_todo_ids.length > 0 || + input.linked_successor_todo_ids.length > 0 || input.successor_intents.length > 0 || input.completion_result != null || - input.completion_policy_request !== null || input.validation_declaration !== null || + !completionPolicyFactsOnly(todo, input) || input.validation_declaration !== null || input.validation_receipt !== null || input.validation_source_provider_revision != null || input.validation_declaration_sha256 != null || input.goal_acceptance_source_binding != null || input.goal_acceptance_validation_receipts != null) { @@ -1337,6 +1356,8 @@ export async function executeCoordinationTodoTerminalLifecycle( } if (untypedCompletionRepair !== null) { const target = untypedCompletionRepair.todo; + // A public auto-acquire option is not an acquisition: this branch commits + // only the existing Todo's completion metadata and never mutates leases. return commitTerminalResult(store, input, requestSha, head, { todo_id: input.todo_id, command: input.command, diff --git a/tests/control_plane/test_native_todo_planning_update.py b/tests/control_plane/test_native_todo_planning_update.py index b9857da4a5..a7ab8ab0fa 100644 --- a/tests/control_plane/test_native_todo_planning_update.py +++ b/tests/control_plane/test_native_todo_planning_update.py @@ -67,6 +67,56 @@ def records(registry: Path) -> dict[str, dict]: return {item["todo_id"]: item for item in list_goal_todos(registry_path=registry, goal_id="goal-a")["todos"]} +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +@pytest.mark.parametrize("claim_flag", [False, True]) +def test_cli_imported_completion_repairs_only_metadata(tmp_path, monkeypatch, provider, claim_flag): + from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted + from loopx.control_plane.testing.canary_harness import run_json_cli, run_json_cli_result + + isolate_sqlite_runtime(tmp_path, monkeypatch) + registry, state = source_fixture(tmp_path) + rows = list_goal_todos(registry_path=registry, goal_id="goal-a")["todos"] + imported = next(row for row in rows if row["todo_id"] == "todo_target") + imported.update(status="done", done=True, evidence="Original evidence", reason="Original reason", + completed_at="2026-01-02T00:00:00Z", updated_at="2026-01-02T01:00:00Z", + last_actor_agent_id="agent-b", completion_receipt_id="tcw_original") + projection = build_todo_runtime_shadow_projection(goal_id="goal-a", todos=rows, handoff_mode="soft_claim") + runtime = tmp_path / "runtime" + initialize_canonical_authority(runtime, "goal-a", projection, state_path=state, provider=provider) + before = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="goal-a") + source_before = state.read_bytes() + prefix = ("todo", "complete", "--goal-id", "goal-a", "--todo-id", "todo_target") + for actor, extra, code in [ + ("agent-b", (), "claim_owner_mismatch"), + ("agent-a", ("--note", "A fresh annotation"), "invalid_todo_completion_transaction"), + ("agent-a", ("--evidence", "A fresh completion result"), "invalid_todo_completion_transaction"), + ("agent-a", ("--next-agent-todo", "Fresh successor work"), "invalid_todo_completion_transaction"), + ]: + returncode, rejected = run_json_cli_result(*prefix, "--agent-id", actor, *extra, registry_path=registry) + assert returncode == 1 and rejected["error_code"] == code, rejected + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="goal-a") == before + assert state.read_bytes() == source_before + repair = (*prefix, "--agent-id", "agent-a", *(("--claimed-by", "agent-a") if claim_flag else ())) + preview = run_json_cli(*repair, "--dry-run", registry_path=registry) + assert preview["dry_run"] is True, preview + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="goal-a") == before + assert state.read_bytes() == source_before + applied = run_json_cli(*repair, registry_path=registry) + assert applied["changed"] is True and applied["completion_policy"] is None, applied + original = next(row for row in before["todos"] if row["todo_id"] == "todo_target") + current = records(registry)["todo_target"] + assert {k: v for k, v in current.items() if k != "content_revision"} == { + **{k: v for k, v in original.items() if k != "content_revision"}, "completion_continuation": "active_goal", + } + run_json_cli("todo", "add", "--goal-id", "goal-a", "--role", "agent", + "--text", "A later independent operation", registry_path=registry) + later = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="goal-a") + replay = run_json_cli(*repair, registry_path=registry) + assert replay["changed"] is False, replay + after = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="goal-a") + assert after["todos"] == later["todos"] and after["provider_revision"] == later["provider_revision"] + + @pytest.mark.parametrize("provider", ["file", "sqlite"]) @pytest.mark.parametrize("command", ["complete", "supersede"]) def test_cli_same_title_terminal_successor_preserves_peers_and_replay(tmp_path, monkeypatch, provider, command): diff --git a/tests/control_plane_ts/terminal_reentry_conformance.ts b/tests/control_plane_ts/terminal_reentry_conformance.ts index 3b598dbe5e..4bfac3a6ea 100644 --- a/tests/control_plane_ts/terminal_reentry_conformance.ts +++ b/tests/control_plane_ts/terminal_reentry_conformance.ts @@ -35,11 +35,15 @@ export function registerTerminalReentryConformance(provider: string, factory: Au operation_identity: {kind: "completion_turn"}, actor_agent_id: "agent-a", registered_agents: ["agent-a", "agent-b"], lifecycle_grants: [], authority_reason: null, decision_outcome: null, lease_idempotency_key: null, lease_expected_version: null, - allow_user_gate_auto_acquire: false, requested_no_followup: false, + allow_user_gate_auto_acquire: true, requested_no_followup: false, requested_completion_turn_key: "original-turn", requested_completion_identity_source: "turn_settlement", linked_successor_todo_ids: [], successor_intents: [], note: null, evidence: null, reason: null, clear_claim: false, validation_declaration: null, validation_receipt: null, - completion_policy_request: null, dry_run: false, now: new Date("2026-09-07T07:00:00Z"), + completion_policy_request: {schema_version: "loopx_todo_completion_policy_request_v0", + goal_id: "goal-a", agent_model: "peer_v1", claimed_by: "agent-a", + registered_agents: ["agent-a", "agent-b"], next_claimed_by: null, next_agent_todo: null, + next_continuation_policy: null, next_excluded_agents: [], self_merged: false, + evidence: null, linked_successors: []}, dry_run: false, now: new Date("2026-09-07T07:00:00Z"), }; const before = await store.loadAuthority(), importedReceipt = await store.readReceipt("import-untyped"); const rejectedChanges = [{requested_no_followup: true}, {actor_agent_id: "agent-b"}, @@ -47,6 +51,11 @@ export function registerTerminalReentryConformance(provider: string, factory: Au {note: "Replace original note"}, {evidence: "Replace original evidence"}, {reason: "Replace original reason"}, {clear_claim: true}, {decision_outcome: "approve"}, {completion_policy_request: {claimed_by: "agent-a"}}, + {completion_policy_request: {...request.completion_policy_request, claimed_by: "agent-b"}}, + {completion_policy_request: {...request.completion_policy_request, goal_id: "other-goal"}}, + {completion_policy_request: {...request.completion_policy_request, registered_agents: ["agent-a"]}}, + {completion_policy_request: {...request.completion_policy_request, evidence: "Fresh policy evidence"}}, + {completion_policy_request: {...request.completion_policy_request, next_agent_todo: "Fresh policy successor"}}, {successor_intents: [{role: "agent", text: "Cannot add work during repair"}]}] satisfies readonly Partial[]; for (const change of rejectedChanges) { @@ -67,6 +76,7 @@ export function registerTerminalReentryConformance(provider: string, factory: Au assert.equal(before.status, "loaded"); if (before.status !== "loaded") throw new Error("imported head missing"); const repairedRows = repaired.head.todos, importedRows = before.head.todos; + assert.deepEqual(repaired.head.leases, before.head.leases); if (!Array.isArray(repairedRows) || repairedRows.length !== 1 || !Array.isArray(importedRows) || importedRows.length !== 1) { throw new Error("imported repair Todo missing"); From 422b0173a7b5c494b0d2dcc1f0994978bf2306f2 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:54:20 +0800 Subject: [PATCH 03/38] docs: explain original-identity completion metadata repair Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../canonical-todo-completion-update.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/reference/canonical-todo-completion-update.md b/docs/reference/canonical-todo-completion-update.md index 8adff406fd..d3aaa1a021 100644 --- a/docs/reference/canonical-todo-completion-update.md +++ b/docs/reference/canonical-todo-completion-update.md @@ -530,6 +530,34 @@ Markdown 投影、备份读取及活跃 Host IO 保留。真实 File/SQLite CLI wheel 路径验证完成与结算恢复;Markdown writer 和 shadow capture producer 已 退役。代码回滚不需要转换数据或重写回执。 +### Repairing imported completion metadata / 修复导入的完成元数据 + +An imported completed Todo may lack `completion_continuation`. Use its original +Actor and completion identity to preview and apply the ordinary completion +command without new note, evidence, successor, policy or validation intent. +For a record without a stored Turn identity: + +```sh +loopx todo complete --goal-id example --todo-id todo_imported --agent-id agent-a --dry-run +loopx todo complete --goal-id example --todo-id todo_imported --agent-id agent-a +loopx todo list --goal-id example --todo-id todo_imported +``` + +A stored identity requires the original Turn tuple and any required execution +proof; do not replace it with a new identity. The TypeScript owner accepts the +ordinary transport's read-only policy facts, but applies no policy and acquires +no lease during this repair. It preserves identity, completion time, content, +claim and original receipt. Preview/refusal has no effects; retrying the same +operation preserves newer writes. Fresh edits require their own authorized +operation after repair. + +导入的已完成 Todo 若缺少 `completion_continuation`,使用原 Actor、完成身份和所需 +执行证明,通过普通完成命令预览并修复;不夹带新的 note、evidence、successor、 +policy 或验证意图。上例适用于未存储 Turn 身份的记录;已有身份必须提供原 tuple, +不能替换身份。TS owner 接受普通入口携带的只读 policy 事实,但修复不应用 policy、 +不领取租约,并保留原 ID、完成时间、内容、claim 与回执。预览和拒绝无副作用; +原操作重试保留后写。后续编辑仍须单独通过原授权入口。 + ## 2.0 canonical mutation boundary Create, update, complete, supersede and archive share From 9e6d860399a327e8bc0f02d13194b9d7b1862153 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:22:13 +0800 Subject: [PATCH 04/38] test: migrate user gate and monitor smokes to canonical authority Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../monitor-poll-writeback-smoke.py | 21 + .../todo-user-gate-scope-smoke.py | 395 +++++++++--------- 2 files changed, 207 insertions(+), 209 deletions(-) diff --git a/examples/control_plane/monitor-poll-writeback-smoke.py b/examples/control_plane/monitor-poll-writeback-smoke.py index a3ebdb1b14..f3134cb430 100644 --- a/examples/control_plane/monitor-poll-writeback-smoke.py +++ b/examples/control_plane/monitor-poll-writeback-smoke.py @@ -29,6 +29,7 @@ write_monitor_poll_todo_state, ) from loopx.control_plane.testing.canary_harness import ( # noqa: E402 + import_fixture_authority, read_run_index, run_json_cli, run_json_cli_result, @@ -238,6 +239,7 @@ def write_fixture( registered_agents=[AGENT_ID], quota_allowed_slots=None, ) + import_fixture_authority(registry_path, GOAL_ID) return registry_path, state_file @@ -1167,6 +1169,8 @@ def assert_writeback_helper_preview_contract() -> None: assert resolved["todo_id"] == TODO_ID, resolved assert resolved["target_key"] == TARGET_KEY, resolved generated_at = "2026-01-01T00:05:00+00:00" + before = run_cli(registry_path, "todo", "list", "--goal-id", GOAL_ID, "--todo-id", TODO_ID) + source = _state_file.read_bytes() preview = write_monitor_poll_todo_state( registry_path=registry_path, runtime_root=runtime_root_from_registry(registry_path), @@ -1175,6 +1179,7 @@ def assert_writeback_helper_preview_contract() -> None: execute=False, todo_id=TODO_ID, result_hash="old", + agent_id=AGENT_ID, ) assert preview["schema_version"] == "monitor_poll_todo_writeback_v0", preview assert preview["dry_run"] is True, preview @@ -1187,6 +1192,22 @@ def assert_writeback_helper_preview_contract() -> None: assert preview["last_checked_at"] == generated_at, preview assert preview["next_due_at"] != "2026-01-01T00:00:00+00:00", preview assert preview["cadence"] == "15m", preview + for actor in (None, "codex-unregistered"): + try: + write_monitor_poll_todo_state( + registry_path=registry_path, + runtime_root=runtime_root_from_registry(registry_path), + goal_id=GOAL_ID, generated_at=generated_at, + execute=False, todo_id=TODO_ID, result_hash="old", agent_id=actor, + ) + except ValueError as exc: + assert "registered actor" in str(exc), str(exc) + else: + raise AssertionError("Monitor preview admitted an undeclared actor") + after = run_cli(registry_path, "todo", "list", "--goal-id", GOAL_ID, "--todo-id", TODO_ID) + assert after["todo"] == before["todo"], after + assert after["authority_read"]["provider_revision"] == before["authority_read"]["provider_revision"], after + assert _state_file.read_bytes() == source def assert_cli_monitor_poll_uses_should_run_lookback() -> None: diff --git a/examples/control_plane/todo-user-gate-scope-smoke.py b/examples/control_plane/todo-user-gate-scope-smoke.py index c3e022c8b0..f2cf7ff69b 100644 --- a/examples/control_plane/todo-user-gate-scope-smoke.py +++ b/examples/control_plane/todo-user-gate-scope-smoke.py @@ -15,10 +15,13 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.contract import check_contract # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.control_plane.todos.todo_summary import active_state_todo_attention_item # noqa: E402 from loopx.quota import build_quota_should_run # noqa: E402 from loopx.status import collect_status, parse_active_state_todos # noqa: E402 -from loopx.todos import add_goal_todo, complete_goal_todo, update_goal_todo # noqa: E402 +from loopx.todos import ( # noqa: E402 + add_goal_todo, complete_goal_todo, list_goal_todos, update_goal_todo, +) GOAL_ID = "user-gate-scope-smoke" @@ -26,7 +29,7 @@ SIDE_AGENT = "codex-product-capability" -def write_fixture(root: Path) -> tuple[Path, Path, Path]: +def write_fixture(root: Path, *, import_authority: bool = True) -> tuple[Path, Path, Path]: repo = root / "repo" repo.mkdir() state = repo / "ACTIVE_GOAL_STATE.md" @@ -38,6 +41,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: "updated_at: 2026-06-26T00:00:00+00:00", "---", "", + "## User Todo", + "", "## Agent Todo", "", ] @@ -68,6 +73,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: ), encoding="utf-8", ) + if import_authority: + import_fixture_authority(registry, GOAL_ID) return repo, state, registry @@ -164,6 +171,128 @@ def assert_user_action_does_not_drive_active_state_attention() -> None: assert gate_item["waiting_on"] == "controller", gate_item + +def read_todo(registry: Path, todo_id: str) -> dict: + view = list_goal_todos(registry_path=registry, goal_id=GOAL_ID, todo_id=todo_id) + assert view["authority_read"]["source_authority"] == "file_v0", view + assert view["authority_read"]["legacy_fallback_used"] is False, view + return view["todo"] + + +def assert_legacy_scope_diagnostics() -> None: + # Historical source validation remains readable before import. It cannot + # serve as a normal writer or alter an already canonical authority. + with tempfile.TemporaryDirectory(prefix="loopx-legacy-gate-diagnostics-") as tmp: + root = Path(tmp) + repo, state, registry = write_fixture(root, import_authority=False) + insert_before_agent_section( + state, + "- [ ] Approve an intentionally unscoped gate.\n" + " \n" + "- [ ] Track an unbound non-blocking user action.\n" + " \n" + "- [ ] Resolve a mismatched gate binding.\n" + f" \n" + "- [ ] Track an intentionally untyped user todo.\n" + " \n" + "- [ ] Approve a gate with an unregistered blocked agent.\n" + " \n" + "- [ ] Approve a gate with conflicting scope.\n" + f" ", + ) + state.write_text( + state.read_text(encoding="utf-8") + + "\n- [ ] Repair a removed continuation value.\n" + + " \n" + + "- [ ] Repair removed agent gate routing.\n" + + f" \n" + + "- [ ] Repair an unknown executor exclusion.\n" + + " \n" + + "- [ ] Repair malformed executor exclusion metadata.\n" + + " \n", + encoding="utf-8", + ) + original = state.read_bytes() + check = check_contract( + registry_path=registry, runtime_root_override=str(root / "runtime"), + scan_roots=[repo], limit=1, + ) + assert check["ok"] is False, check + for todo_id, diagnostic in ( + ("todo_unscoped_gate", "requires bound_agent"), + ("todo_unbound_action", "requires bound_agent"), + ("todo_mismatched_gate_binding", "must bind to blocks_agent"), + ("todo_untyped_user", "requires task_class"), + ("todo_bad_agent", "not registered"), + ("todo_both_scopes", "cannot set both"), + ("todo_removed_continuation", "removed continuation_policy"), + ("todo_removed_agent_gate", "removed blocks_agent routing"), + ("todo_unknown_exclusion", "unregistered agents"), + ("todo_malformed_exclusion", "malformed excluded_agents"), + ): + assert any(todo_id in error and diagnostic in error for error in check["errors"]), check + assert state.read_bytes() == original + + +def assert_imported_scope_repairs() -> None: + with tempfile.TemporaryDirectory(prefix="loopx-imported-gate-repair-") as tmp: + root = Path(tmp) + repo, state, registry = write_fixture(root, import_authority=False) + insert_before_agent_section( + state, + "- [ ] Approve an intentionally unscoped gate.\n" + " \n" + "- [ ] Track an unbound non-blocking user action.\n" + " \n" + "- [ ] Resolve a mismatched gate binding.\n" + f" \n" + "- [ ] Track an intentionally untyped user todo.\n" + " ", + ) + state.write_text( + state.read_text(encoding="utf-8") + + "\n- [ ] Repair removed agent gate routing.\n" + + f" \n", + encoding="utf-8", + ) + import_fixture_authority(registry, GOAL_ID) + for todo_id, role, changes, expected in ( + ("todo_unscoped_gate", "user", {"status": "done"}, {"status": "done"}), + ("todo_unbound_action", "user", {"bound_agent": SIDE_AGENT}, {"bound_agent": SIDE_AGENT}), + ("todo_mismatched_gate_binding", "user", {"bound_agent": SIDE_AGENT}, {"bound_agent": SIDE_AGENT}), + # The caller now declares the missing type/binding explicitly; + # closing old source data does not infer an approval or audience. + ("todo_untyped_user", "user", {"status": "done", "task_class": "user_action", "goal_bound": True}, + {"status": "done", "task_class": "user_action", "goal_bound": True}), + ("todo_removed_agent_gate", "agent", {"clear_blocks_agent": True}, {"blocks_agent": None}), + ): + original = read_todo(registry, todo_id) + source = state.read_bytes() + preview = update_goal_todo( + registry_path=registry, goal_id=GOAL_ID, todo_id=todo_id, + role=role, agent_id=PRIMARY_AGENT, dry_run=True, **changes, + ) + assert preview["changed"] is True, preview + assert read_todo(registry, todo_id) == original + assert state.read_bytes() == source + changed = update_goal_todo( + registry_path=registry, goal_id=GOAL_ID, todo_id=todo_id, + role=role, agent_id=PRIMARY_AGENT, **changes, + ) + assert changed["changed"] is True, changed + current = read_todo(registry, todo_id) + assert current["text"] == original["text"], current + for key, value in expected.items(): + assert current.get(key) == value, current + repaired_check = check_contract( + registry_path=registry, runtime_root_override=str(root / "runtime"), + scan_roots=[repo], limit=1, + ) + assert repaired_check["ok"] is True, repaired_check + + def main() -> int: assert_user_action_does_not_drive_active_state_attention() @@ -214,9 +343,10 @@ def main() -> int: task_class="user_action", agent_id=SIDE_AGENT, ) + user_action = {**user_action, **read_todo(registry, user_action["todo_id"])} assert user_action["task_class"] == "user_action", user_action assert user_action["bound_agent"] == SIDE_AGENT, user_action - assert user_action["claimed_by"] is None, user_action + assert user_action.get("claimed_by") is None, user_action goal_bound_action = add_goal_todo( registry_path=registry, @@ -227,7 +357,9 @@ def main() -> int: goal_bound=True, dry_run=True, ) - assert goal_bound_action["goal_bound"] is True, goal_bound_action + assert goal_bound_action["status"] == "planned", goal_bound_action + assert goal_bound_action["changed"] is True, goal_bound_action + assert len(list_goal_todos(registry_path=registry, goal_id=GOAL_ID)["todos"]) == 1 other_lane_only_status = collect_status( registry_path=registry, @@ -281,6 +413,7 @@ def main() -> int: task_class="advancement_task", claimed_by=SIDE_AGENT, ) + side_agent_todo = {**side_agent_todo, **read_todo(registry, side_agent_todo["todo_id"])} assert side_agent_todo["claimed_by"] == SIDE_AGENT, side_agent_todo blocked_agent_todo = add_goal_todo( @@ -301,6 +434,9 @@ def main() -> int: bound_agent=SIDE_AGENT, unblocks_todo_id=blocked_agent_todo["todo_id"], ) + approval_before = read_todo(registry, approval["todo_id"]) + target_before = read_todo(registry, blocked_agent_todo["todo_id"]) + source_before = state.read_bytes() assert_raises_message( lambda: complete_goal_todo( registry_path=registry, @@ -311,8 +447,11 @@ def main() -> int: evidence="wrong-lane response attempt", dry_run=True, ), - "response continuation is bound", + "bound_agent_mismatch", ) + assert read_todo(registry, approval["todo_id"]) == approval_before + assert read_todo(registry, blocked_agent_todo["todo_id"]) == target_before + assert state.read_bytes() == source_before approval_completed = complete_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -441,6 +580,7 @@ def main() -> int: task_class="user_gate", agent_id=SIDE_AGENT, ) + scoped = {**scoped, **read_todo(registry, scoped["todo_id"])} assert scoped["blocks_agent"] == SIDE_AGENT, scoped assert scoped["bound_agent"] == SIDE_AGENT, scoped assert f"blocks_agent={SIDE_AGENT}" in state.read_text(encoding="utf-8") @@ -454,6 +594,7 @@ def main() -> int: task_class="user_gate", global_gate=True, ) + global_gate = {**global_gate, **read_todo(registry, global_gate["todo_id"])} assert global_gate["global_gate"] is True, global_gate assert global_gate["goal_bound"] is True, global_gate assert "global_gate=true" in state.read_text(encoding="utf-8") @@ -467,22 +608,6 @@ def main() -> int: task_class="advancement_task", claimed_by=PRIMARY_AGENT, ) - complete_goal_todo( - registry_path=registry, - goal_id=GOAL_ID, - todo_id=str(agent_todo["todo_id"]), - role="agent", - claimed_by=PRIMARY_AGENT, - agent_id=PRIMARY_AGENT, - evidence="fixture validation", - next_user_todo="Approve publishing the release notes.", - next_user_task_class="user_gate", - ) - state_text = state.read_text(encoding="utf-8") - assert "Approve publishing the release notes." in state_text, state_text - assert f"blocks_agent={PRIMARY_AGENT}" in state_text, state_text - assert f"bound_agent={PRIMARY_AGENT}" in state_text, state_text - assert_raises_message( lambda: update_goal_todo( registry_path=registry, @@ -496,210 +621,62 @@ def main() -> int: "global_gate is only valid for user_gate todos", ) - clean_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert clean_check["ok"] is True, clean_check - - insert_before_agent_section( - state, - "- [ ] Approve an intentionally unscoped gate.\n" - " ", - ) - bad_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert bad_check["ok"] is False, bad_check - assert any("todo_unscoped_gate" in item for item in bad_check["errors"]), bad_check - - closed = update_goal_todo( - registry_path=registry, - goal_id=GOAL_ID, - todo_id="todo_unscoped_gate", - role="user", - agent_id=PRIMARY_AGENT, - status="done", - ) - assert closed["changed"] is True, closed - repaired_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert repaired_check["ok"] is True, repaired_check - - insert_before_agent_section( - state, - "- [ ] Track an unbound non-blocking user action.\n" - " ", - ) - unbound_action_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert unbound_action_check["ok"] is False, unbound_action_check - assert any( - "todo_unbound_action" in item and "requires bound_agent" in item - for item in unbound_action_check["errors"] - ), unbound_action_check - rebound_action = update_goal_todo( + complete_goal_todo( registry_path=registry, goal_id=GOAL_ID, - todo_id="todo_unbound_action", - role="user", + todo_id=str(agent_todo["todo_id"]), + role="agent", + claimed_by=PRIMARY_AGENT, agent_id=PRIMARY_AGENT, - bound_agent=SIDE_AGENT, - ) - assert rebound_action["bound_agent"] == SIDE_AGENT, rebound_action - rebound_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, + evidence="fixture validation", + next_user_todo="Approve publishing the release notes.", + next_user_task_class="user_gate", ) - assert rebound_check["ok"] is True, rebound_check + state_text = state.read_text(encoding="utf-8") + assert "Approve publishing the release notes." in state_text, state_text + assert f"blocks_agent={PRIMARY_AGENT}" in state_text, state_text + assert f"bound_agent={PRIMARY_AGENT}" in state_text, state_text - insert_before_agent_section( - state, - "- [ ] Resolve a mismatched gate binding.\n" - f" ", - ) - mismatched_binding_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert mismatched_binding_check["ok"] is False, mismatched_binding_check - assert any( - "todo_mismatched_gate_binding" in item - and "must bind to blocks_agent" in item - for item in mismatched_binding_check["errors"] - ), mismatched_binding_check - repaired_gate_binding = update_goal_todo( - registry_path=registry, - goal_id=GOAL_ID, - todo_id="todo_mismatched_gate_binding", - role="user", - agent_id=PRIMARY_AGENT, - bound_agent=SIDE_AGENT, + goal_bound_action = add_goal_todo( + registry_path=registry, goal_id=GOAL_ID, role="user", + text="Track a goal-wide owner note.", task_class="user_action", + goal_bound=True, ) - assert repaired_gate_binding["bound_agent"] == SIDE_AGENT, repaired_gate_binding - repaired_binding_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, + goal_bound_readback = read_todo(registry, goal_bound_action["todo_id"]) + assert goal_bound_readback["goal_bound"] is True, goal_bound_readback + assert goal_bound_readback.get("bound_agent") is None, goal_bound_readback + assert goal_bound_readback.get("global_gate") is not True, goal_bound_readback + complete_goal_todo( + registry_path=registry, goal_id=GOAL_ID, + todo_id=goal_bound_action["todo_id"], role="user", agent_id=PRIMARY_AGENT, + evidence="fixture owner note acknowledged", ) - assert repaired_binding_check["ok"] is True, repaired_binding_check + assert read_todo(registry, goal_bound_action["todo_id"])["status"] == "done" - insert_before_agent_section( - state, - "- [ ] Track an intentionally untyped user todo.\n" - " ", - ) - untyped_check = check_contract( + clean_check = check_contract( registry_path=registry, runtime_root_override=str(root / "runtime"), scan_roots=[repo], limit=1, ) - assert untyped_check["ok"] is False, untyped_check - assert any("todo_untyped_user" in item and "requires task_class" in item for item in untyped_check["errors"]), untyped_check - - closed_untyped = update_goal_todo( - registry_path=registry, - goal_id=GOAL_ID, - todo_id="todo_untyped_user", - role="user", - agent_id=PRIMARY_AGENT, - status="done", - ) - assert closed_untyped["changed"] is True, closed_untyped + assert clean_check["ok"] is True, clean_check + original = state.read_bytes() + before = list_goal_todos(registry_path=registry, goal_id=GOAL_ID) insert_before_agent_section( state, - "- [ ] Approve a gate with an unregistered blocked agent.\n" - " \n" - "- [ ] Approve a gate with conflicting scope.\n" - f" ", - ) - invalid_scope_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert invalid_scope_check["ok"] is False, invalid_scope_check - assert any("todo_bad_agent" in item and "not registered" in item for item in invalid_scope_check["errors"]), invalid_scope_check - assert any("todo_both_scopes" in item and "cannot set both" in item for item in invalid_scope_check["errors"]), invalid_scope_check - - state.write_text( - state.read_text(encoding="utf-8") - + "\n- [ ] Repair a removed continuation value.\n" - + " \n" - + "- [ ] Repair removed agent gate routing.\n" - + f" \n" - + "- [ ] Repair an unknown executor exclusion.\n" - + " \n" - + "- [ ] Repair malformed executor exclusion metadata.\n" - + " \n", - encoding="utf-8", - ) - hard_cut_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert hard_cut_check["ok"] is False, hard_cut_check - assert any( - "todo_removed_continuation" in item and "removed continuation_policy" in item - for item in hard_cut_check["errors"] - ), hard_cut_check - assert any( - "todo_removed_agent_gate" in item and "removed blocks_agent routing" in item - for item in hard_cut_check["errors"] - ), hard_cut_check - assert any( - "todo_unknown_exclusion" in item and "unregistered agents" in item - for item in hard_cut_check["errors"] - ), hard_cut_check - assert any( - "todo_malformed_exclusion" in item and "malformed excluded_agents" in item - for item in hard_cut_check["errors"] - ), hard_cut_check - - repaired_agent_gate = update_goal_todo( - registry_path=registry, - goal_id=GOAL_ID, - todo_id="todo_removed_agent_gate", - role="agent", - agent_id=PRIMARY_AGENT, - clear_blocks_agent=True, - ) - assert repaired_agent_gate["blocks_agent"] is None, repaired_agent_gate - repaired_hard_cut_check = check_contract( - registry_path=registry, - runtime_root_override=str(root / "runtime"), - scan_roots=[repo], - limit=1, - ) - assert not any( - "todo_removed_agent_gate" in item - for item in repaired_hard_cut_check["errors"] - ), repaired_hard_cut_check + "- [ ] An injected projection cannot add a gate.\n" + f" ", + ) + after = list_goal_todos(registry_path=registry, goal_id=GOAL_ID) + assert after["todos"] == before["todos"], after + assert after["authority_read"]["provider_revision"] == before["authority_read"]["provider_revision"], after + assert state.read_bytes() != original + state.write_bytes(original) + + assert_legacy_scope_diagnostics() + assert_imported_scope_repairs() print("todo-user-gate-scope-smoke ok") return 0 From 6a600eef979167e733de764e3affabf7a8514d4b Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:56:53 +0800 Subject: [PATCH 05/38] docs: clarify authorized imported completion repair claims Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- docs/reference/canonical-todo-completion-update.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/reference/canonical-todo-completion-update.md b/docs/reference/canonical-todo-completion-update.md index d3aaa1a021..334a5b985d 100644 --- a/docs/reference/canonical-todo-completion-update.md +++ b/docs/reference/canonical-todo-completion-update.md @@ -532,8 +532,8 @@ wheel 路径验证完成与结算恢复;Markdown writer 和 shadow capture pro ### Repairing imported completion metadata / 修复导入的完成元数据 -An imported completed Todo may lack `completion_continuation`. Use its original -Actor and completion identity to preview and apply the ordinary completion +An imported completed Todo may lack `completion_continuation`. Use an authorized +Actor and its original completion identity to preview and apply the ordinary completion command without new note, evidence, successor, policy or validation intent. For a record without a stored Turn identity: @@ -545,16 +545,18 @@ loopx todo list --goal-id example --todo-id todo_imported A stored identity requires the original Turn tuple and any required execution proof; do not replace it with a new identity. The TypeScript owner accepts the -ordinary transport's read-only policy facts, but applies no policy and acquires -no lease during this repair. It preserves identity, completion time, content, +ordinary transport's read-only policy facts, including an explicit `--claimed-by` +that matches the stored claim, but applies no policy and acquires no lease during +this repair. A different claim is rejected. It preserves identity, completion time, content, claim and original receipt. Preview/refusal has no effects; retrying the same operation preserves newer writes. Fresh edits require their own authorized operation after repair. -导入的已完成 Todo 若缺少 `completion_continuation`,使用原 Actor、完成身份和所需 +导入的已完成 Todo 若缺少 `completion_continuation`,使用已授权 Actor、原完成身份和所需 执行证明,通过普通完成命令预览并修复;不夹带新的 note、evidence、successor、 policy 或验证意图。上例适用于未存储 Turn 身份的记录;已有身份必须提供原 tuple, -不能替换身份。TS owner 接受普通入口携带的只读 policy 事实,但修复不应用 policy、 +不能替换身份。TS owner 接受普通入口携带的只读 policy 事实,包括与已存 claim 一致的 +显式 `--claimed-by`;不同 claim 会被拒绝。修复不应用 policy、 不领取租约,并保留原 ID、完成时间、内容、claim 与回执。预览和拒绝无副作用; 原操作重试保留后写。后续编辑仍须单独通过原授权入口。 From 89966f7ebd0d4dda930f11e59e75f29cc4cc9856 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:20:31 +0800 Subject: [PATCH 06/38] test: migrate status and quota smokes to canonical authority Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../quota-no-git-status-smoke.py | 15 +++++++- .../status-projection-cache-smoke.py | 7 +++- .../status-quota-perf-budget-smoke.py | 38 ++++++++++++++++--- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/examples/control_plane/quota-no-git-status-smoke.py b/examples/control_plane/quota-no-git-status-smoke.py index 6cf51ef55d..ab47e98154 100644 --- a/examples/control_plane/quota-no-git-status-smoke.py +++ b/examples/control_plane/quota-no-git-status-smoke.py @@ -12,6 +12,11 @@ REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 + GOAL_ID = "no-git-quota-fixture" AGENT_ID = "codex-benchmark-agent" @@ -30,7 +35,8 @@ def write_fixture(root: Path) -> Path: "## Objective\n\n" "Solve this no-git fixture.\n\n" "## Next Action\n\n" - "- Pick the seeded todo.\n", + "- Pick the seeded todo.\n\n" + "## Agent Todo\n\n", encoding="utf-8", ) registry_path.parent.mkdir(parents=True) @@ -60,6 +66,7 @@ def write_fixture(root: Path) -> Path: + "\n", encoding="utf-8", ) + import_fixture_authority(registry_path, GOAL_ID) return registry_path @@ -100,6 +107,7 @@ def main() -> int: "Solve the no-git quota fixture.", ) assert todo["ok"] is True, todo + before = run_cli(registry_path, "todo", "list", "--goal-id", GOAL_ID) no_git_bin = root / "no-git-bin" no_git_bin.mkdir() @@ -119,6 +127,11 @@ def main() -> int: assert quota.get("status") != "quota_collection_failed", quota assert quota["should_run"] is True, quota assert quota["recommended_action"] == "Solve the no-git quota fixture.", quota + assert quota["selected_todo"]["todo_id"] == todo["todo_id"], quota + after = run_cli(registry_path, "todo", "list", "--goal-id", GOAL_ID, env=env) + assert after["todos"] == before["todos"], after + assert after["authority_read"]["provider_revision"] == before["authority_read"]["provider_revision"], after + assert after["authority_read"]["legacy_fallback_used"] is False, after return 0 diff --git a/examples/control_plane/status-projection-cache-smoke.py b/examples/control_plane/status-projection-cache-smoke.py index 1b81901b4e..dc3eedc8ff 100644 --- a/examples/control_plane/status-projection-cache-smoke.py +++ b/examples/control_plane/status-projection-cache-smoke.py @@ -17,6 +17,7 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.control_plane.runtime.status_projection_cache import write_status_projection_cache # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 GOAL_ID = "status-projection-cache-fixture" AGENT_ID = "codex-main-control" @@ -41,7 +42,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: "## Objective\n\n" "Validate opt-in status projection cache behavior.\n\n" "## Next Action\n\n" - "- Exercise the status/quota projection cache.\n", + "- Exercise the status/quota projection cache.\n\n" + "## Agent Todo\n\n", encoding="utf-8", ) public_doc.write_text("Public smoke fixture for status projection cache.\n", encoding="utf-8") @@ -81,6 +83,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: + "\n", encoding="utf-8", ) + import_fixture_authority(registry_path, GOAL_ID) return registry_path, runtime, public_doc @@ -258,6 +261,8 @@ def main() -> int: "agent", "--todo-id", initial_todo_id, + "--agent-id", + AGENT_ID, "--claimed-by", AGENT_ID, "--evidence", diff --git a/examples/control_plane/status-quota-perf-budget-smoke.py b/examples/control_plane/status-quota-perf-budget-smoke.py index 0e07ba7eb8..e5d7c7af2e 100644 --- a/examples/control_plane/status-quota-perf-budget-smoke.py +++ b/examples/control_plane/status-quota-perf-budget-smoke.py @@ -14,6 +14,11 @@ REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 + GOAL_ID = "status-quota-perf-budget-fixture" AGENT_ID = "codex-main-control" IGNORED_FILE_COUNT = 1_200 @@ -22,7 +27,7 @@ PRIVATE_DOC_MARKER = "https://" + "la" + "rk" + "office.example/doc" -def write_fixture(root: Path) -> tuple[Path, Path, Path]: +def write_fixture(root: Path, *, import_authority: bool = True) -> tuple[Path, Path, Path]: project = root / "project" runtime = root / "runtime" state_rel = f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" @@ -40,7 +45,12 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: "## Objective\n\n" "Keep status and quota hot paths fast without skipping public scans.\n\n" "## Next Action\n\n" - "- Validate the selected hot-path budget todo.\n", + "- Validate the selected hot-path budget todo.\n\n" + "## Agent Todo\n\n" + + ("" if import_authority else + "- [ ] Inspect the historical status budget fixture.\n" + f" \n"), encoding="utf-8", ) public_doc.write_text("Public smoke fixture for status/quota perf budgets.\n", encoding="utf-8") @@ -90,6 +100,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: + "\n", encoding="utf-8", ) + if import_authority: + import_fixture_authority(registry_path, GOAL_ID) return registry_path, project, public_doc @@ -230,10 +242,23 @@ def main() -> int: ) state_text = state_file.read_text(encoding="utf-8") assert " status=open " in state_text, state_text - state_file.write_text( - state_text.replace(" status=open ", " status=stuck ", 1), - encoding="utf-8", - ) + before, _ = run_cli(registry_path, "todo", "list", "--goal-id", GOAL_ID) + tampered = state_text.replace(" status=open ", " status=stuck ", 1) + state_file.write_text(tampered, encoding="utf-8") + after, _ = run_cli(registry_path, "todo", "list", "--goal-id", GOAL_ID) + assert after["todos"] == before["todos"], after + assert after["authority_read"]["provider_revision"] == before["authority_read"]["provider_revision"], after + assert after["authority_read"]["legacy_fallback_used"] is False, after + assert state_file.read_text(encoding="utf-8") == tampered + + # Malformed historical source still has a read-only diagnostic/budget + # contract. It cannot act as the authority of the imported fixture. + registry_path, project, public_doc = write_fixture(root / "legacy-inspection", import_authority=False) + state_file = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" + state_text = state_file.read_text(encoding="utf-8") + assert " status=open " in state_text, state_text + state_file.write_text(state_text.replace(" status=open ", " status=stuck ", 1), encoding="utf-8") + legacy_source = state_file.read_bytes() ( malformed_status_payload, @@ -308,6 +333,7 @@ def main() -> int: QUOTA_BUDGET_SECONDS, "malformed_state", ) + assert state_file.read_bytes() == legacy_source print( "status_quota_perf_budget: " From 0cadb66e9e6eeff2f0e6bf56617b1e620e9c4a23 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:04:22 +0800 Subject: [PATCH 07/38] test: migrate peer workspace and lease lifecycle smokes to canonical Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- ...-agent-continuation-state-machine-smoke.py | 9 +- .../peer-agent-workspace-guard-smoke.py | 68 ++++++--- .../control_plane/task-lease-runtime-smoke.py | 142 +++++++++++------- 3 files changed, 138 insertions(+), 81 deletions(-) diff --git a/examples/control_plane/peer-agent-continuation-state-machine-smoke.py b/examples/control_plane/peer-agent-continuation-state-machine-smoke.py index 034f1f0b36..db5d0ab413 100644 --- a/examples/control_plane/peer-agent-continuation-state-machine-smoke.py +++ b/examples/control_plane/peer-agent-continuation-state-machine-smoke.py @@ -13,6 +13,7 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.control_plane.testing.canary_harness import ( # noqa: E402 + import_fixture_authority, run_json_cli, write_fixture_registry, ) @@ -80,6 +81,7 @@ def write_fixture(root: Path, *, include_review: bool = False) -> tuple[Path, Pa quota_allowed_slots=5, peer_independent_worktree_required=False, ) + import_fixture_authority(registry_path, GOAL_ID) return project, runtime, registry_path @@ -93,6 +95,7 @@ def should_run(project: Path, runtime: Path, registry_path: Path, agent_id: str) agent_id, "--scan-path", str(project), + "--host-surface", "generic_cli", "--scheduler-owner", "none", "--execution-mode", "interactive", registry_path=registry_path, runtime=runtime, cwd=project, @@ -137,7 +140,7 @@ def assert_same_peer_continuation() -> None: runtime=runtime, cwd=project, ) - assert completed["self_merged"] is True, completed + assert completed["completion_policy"]["self_merged"] is True, completed successor = completed["next_todos"][0] assert successor["claimed_by"] == PEER_ALPHA, successor second = should_run(project, runtime, registry_path, PEER_ALPHA) @@ -168,8 +171,8 @@ def assert_review_is_action_semantics_over_generic_handoff() -> None: runtime=runtime, cwd=project, ) - assert completed["self_merged"] is False, completed - assert completed["linked_successor_id"] == REVIEW_TODO_ID, completed + assert completed["completion_policy"]["self_merged"] is False, completed + assert completed["completion_policy"]["linked_successor_id"] == REVIEW_TODO_ID, completed beta = should_run(project, runtime, registry_path, PEER_BETA) assert beta["decision"] == "run", beta diff --git a/examples/control_plane/peer-agent-workspace-guard-smoke.py b/examples/control_plane/peer-agent-workspace-guard-smoke.py index 4488459a4f..765108a3fd 100644 --- a/examples/control_plane/peer-agent-workspace-guard-smoke.py +++ b/examples/control_plane/peer-agent-workspace-guard-smoke.py @@ -12,6 +12,10 @@ REPO_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 + GOAL_ID = "workspace-guard-canary" PEER_ALPHA = "codex-alpha" PEER_BETA = "codex-beta" @@ -47,11 +51,12 @@ def run_cli(cwd: Path, *args: str, registry_path: Path, runtime: Path) -> dict: *args, ], cwd=cwd, - check=True, + check=False, capture_output=True, text=True, env=env, ) + assert result.returncode == 0, result.stdout + result.stderr return json.loads(result.stdout) @@ -155,6 +160,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path, Path, Path, Path]: + "\n", encoding="utf-8", ) + import_fixture_authority(registry_path, GOAL_ID) return project, independent, task_project, task_independent, runtime, registry_path @@ -176,7 +182,13 @@ def set_task_repository(project: Path, runtime: Path, registry_path: Path) -> No registry_path=registry_path, runtime=runtime, ) - assert updated["task_repository"] == TASK_REPOSITORY, updated + assert updated["ok"] is True, updated + current = run_cli( + project, "todo", "list", "--goal-id", GOAL_ID, + "--todo-id", "todo_workspace_guard", registry_path=registry_path, runtime=runtime, + ) + assert current["todo"]["task_repository"] == TASK_REPOSITORY, current + assert current["authority_read"]["legacy_fallback_used"] is False, current def should_run( @@ -195,6 +207,7 @@ def should_run( PEER_ALPHA, "--scan-path", str(project), + "--host-surface", "generic_cli", "--scheduler-owner", "none", "--execution-mode", "interactive", registry_path=registry_path, runtime=runtime, ) @@ -271,6 +284,18 @@ def main() -> None: assert goal_worktree_guarded["workspace_guard"]["task_repository"] == ( TASK_REPOSITORY ), goal_worktree_guarded + state_path = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" + original_display = state_path.read_text(encoding="utf-8") + before = run_cli(project, "todo", "list", "--goal-id", GOAL_ID, + registry_path=registry_path, runtime=runtime) + state_path.write_text(original_display.replace(TASK_REPOSITORY, PROJECT_REPOSITORY), encoding="utf-8") + spoofed = should_run(independent, project, runtime, registry_path) + assert spoofed["workspace_guard"]["task_repository"] == TASK_REPOSITORY, spoofed + after = run_cli(project, "todo", "list", "--goal-id", GOAL_ID, + registry_path=registry_path, runtime=runtime) + assert after["authority_read"]["provider_revision"] == before["authority_read"]["provider_revision"] + assert after["todos"] == before["todos"] + state_path.write_text(original_display, encoding="utf-8") registry = json.loads(registry_path.read_text(encoding="utf-8")) registry["goals"][0].pop("workspace_guard_policy", None) @@ -278,25 +303,24 @@ def main() -> None: json.dumps(registry, indent=2, sort_keys=True) + "\n", encoding="utf-8", ) - state_path = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" - state_path.write_text( - "---\nstatus: active\nowner_mode: goal\n" - 'objective: "Exercise selected-todo workspace routing."\n' - "updated_at: 2026-01-01T00:00:00+00:00\n---\n\n" - "# Selected Todo Workspace Guard Canary\n\n## Agent Todo\n\n" - "- [ ] [P0] Monitor one due repository lifecycle without repository writes.\n" - f" \n" - "- [ ] [P1] Repair a separate repository-writing control-plane task.\n" - f" \n", - encoding="utf-8", + monitor = run_cli( + project, "todo", "add", "--goal-id", GOAL_ID, "--role", "agent", + "--operation-id", "due-monitor", + "--text", "[P0] Monitor one due repository lifecycle without repository writes.", + "--task-class", "continuous_monitor", "--action-kind", "monitor_repository_lifecycle", + "--task-repository", PROJECT_REPOSITORY, "--claimed-by", PEER_ALPHA, + "--target-key", "repository-lifecycle", "--cadence", "30m", + "--next-due-at", "2020-01-01T00:00:00+00:00", "--expires-at", "2099-01-01T00:00:00+00:00", + registry_path=registry_path, runtime=runtime, + ) + run_cli( + project, "todo", "add", "--goal-id", GOAL_ID, "--role", "agent", + "--operation-id", "unrelated-repair", + "--text", "[P1] Repair a separate repository-writing control-plane task.", + "--task-class", "advancement_task", "--action-kind", "repair_workspace_route", + "--task-repository", TASK_REPOSITORY, "--required-write-scope", "loopx/**", + "--claimed-by", PEER_ALPHA, + registry_path=registry_path, runtime=runtime, ) monitor_selected = should_run(project, project, runtime, registry_path) @@ -304,7 +328,7 @@ def main() -> None: assert "workspace_guard" not in monitor_selected, monitor_selected assert "boundary_projection_gap" not in monitor_selected, monitor_selected assert monitor_selected["selected_todo"]["todo_id"] == ( - "todo_due_monitor" + monitor["todo_id"] ), monitor_selected assert monitor_selected["selected_todo"]["task_repository"] == ( PROJECT_REPOSITORY diff --git a/examples/control_plane/task-lease-runtime-smoke.py b/examples/control_plane/task-lease-runtime-smoke.py index 1b0ac95ac7..d15d2aac86 100644 --- a/examples/control_plane/task-lease-runtime-smoke.py +++ b/examples/control_plane/task-lease-runtime-smoke.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Smoke-test the task_lease_v0 runtime and CLI contract.""" +"""Exercise canonical lease, claim handover and completion through the real CLI.""" from __future__ import annotations import json -import re +import os import subprocess import sys import tempfile @@ -14,6 +14,10 @@ REPO_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(REPO_ROOT)) +sys.path.insert(0, str(REPO_ROOT / "tests" / "control_plane")) + +from canonical_authority_fixture import initialize_canonical_authority # noqa: E402 +from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection # noqa: E402 GOAL_ID = "task-lease-runtime-goal" TODO_A = "todo_taskleasea" @@ -21,7 +25,7 @@ TODO_C = "todo_taskleasec" -def write_fixture(root: Path) -> tuple[Path, Path]: +def write_fixture(root: Path, provider: str) -> tuple[Path, Path]: project = root / "project" runtime = root / "runtime" registry_path = project / ".loopx" / "registry.json" @@ -32,14 +36,7 @@ def write_fixture(root: Path) -> tuple[Path, Path]: "status: active\n" "updated_at: 2026-01-01T00:00:00+00:00\n" "---\n\n" - "# Active Goal State\n\n" - "## Agent Todo\n\n" - f"- [ ] First independently claimable todo.\n" - f" \n" - f"- [ ] Second independently claimable todo.\n" - f" \n" - f"- [ ] Conflicting write-scope todo.\n" - f" \n", + "# Active Goal State\n\n## Agent Todo\n\n", encoding="utf-8", ) registry_path.parent.mkdir(parents=True) @@ -76,28 +73,43 @@ def write_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + worker = project.parent / "worker" + for args in [ + ("init", "--initial-branch", "main"), + ("remote", "add", "origin", "https://example.invalid/loopx/lease-fixture.git"), + ("-c", "user.name=LoopX Fixture", "-c", "user.email=fixture@example.invalid", + "commit", "--allow-empty", "-m", "fixture"), + ("worktree", "add", "-b", "worker", str(worker)), + ]: + subprocess.run(["git", "-C", str(project), *args], check=True, capture_output=True, text=True) + projection = build_todo_runtime_shadow_projection( + goal_id=GOAL_ID, handoff_mode="hard_lease", leases=[], todos=[ + { + "schema_version": "todo_item_v0", "todo_id": todo_id, "role": "agent", + "status": "open", "done": False, "text": text, "archive_state": "active", + "source_section": "Agent Todo", "index": index, "task_class": "advancement_task", + "action_kind": "validate", "task_domain": "validation", "claimed_by": owner, + "task_repository": "git:example.invalid/loopx/lease-fixture", + "required_write_scopes": [scope], + } + for index, (todo_id, text, owner, scope) in enumerate([ + (TODO_A, "First independently claimable todo.", "codex-main-control", "loopx/**"), + (TODO_B, "Second independently claimable todo.", None, "docs/**"), + (TODO_C, "Conflicting write-scope todo.", None, "loopx/cli_commands/todo.py"), + ], 1) + ], + ) + initialize_canonical_authority(runtime, GOAL_ID, projection, state_path=state_file, provider=provider) return registry_path, state_file -def set_claimed_by(state_file: Path, *, todo_id: str, owner: str | None) -> None: - lines = state_file.read_text(encoding="utf-8").splitlines() - for index, line in enumerate(lines): - if f"todo_id={todo_id}" not in line: - continue - line = re.sub(r"\s+claimed_by=[^\s<>]+", "", line) - if owner: - line = line.replace(" -->", f" claimed_by={owner} -->") - lines[index] = line - state_file.write_text("\n".join(lines) + "\n", encoding="utf-8") - return - raise AssertionError(f"todo metadata not found: {todo_id}") - - def command( registry_path: Path, *args: str, check: bool = True, ) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env["PYTHONPATH"] = str(REPO_ROOT) return subprocess.run( [ sys.executable, @@ -109,7 +121,8 @@ def command( "json", *args, ], - cwd=REPO_ROOT, + cwd=registry_path.parents[2] / "worker", + env=env, check=check, text=True, capture_output=True, @@ -119,6 +132,8 @@ def command( def cli( registry_path: Path, *args: str, check: bool = True ) -> subprocess.CompletedProcess[str]: + if args[0] == "acquire": + args = (*args, "--write-worktree", str(registry_path.parents[2] / "worker")) return command(registry_path, "task-lease", *args, check=check) @@ -158,15 +173,9 @@ def inspected_lease(registry_path: Path, todo_id: str) -> dict[str, Any]: ) -def main() -> int: +def exercise_provider(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-task-lease-smoke-") as tmp: - registry_path, state_file = write_fixture(Path(tmp)) - - set_claimed_by( - state_file, - todo_id=TODO_A, - owner="codex-main-control", - ) + registry_path, _state_file = write_fixture(Path(tmp), provider) first = payload( cli( registry_path, @@ -190,6 +199,10 @@ def main() -> int: assert first["lease"]["version"] == 1, first assert first["lease"]["lease_epoch"] == 1, first assert first["lease"]["acquire_ttl_seconds"] == 120, first + assert first["source_authority"] == provider + "_v0", first + assert first["lease"]["write_workspace"]["repository"] == ( + "git:example.invalid/loopx/lease-fixture" + ), first idempotent = payload( cli( @@ -296,30 +309,21 @@ def main() -> int: assert payload(claim_blocked_transfer)["error_code"] == ( "owner_conflicts_with_claim" ) - set_claimed_by(state_file, todo_id=TODO_A, owner=None) - transferred = payload( - cli( - registry_path, - "transfer", - "--goal-id", - GOAL_ID, - "--todo-id", - TODO_A, - "--owner", - "codex-main-control", - "--idempotency-key", - "turn-1", - "--new-owner", - "codex-side-bypass", - "--new-idempotency-key", - "side-transfer", - "--expected-version", - "2", - ) + assert inspected_lease(registry_path, TODO_A)["lease"] == renewed["lease"] + transfer_args = ( + "transfer", "--goal-id", GOAL_ID, "--todo-id", TODO_A, + "--owner", "codex-main-control", "--idempotency-key", "turn-1", + "--new-owner", "codex-side-bypass", "--new-idempotency-key", "side-transfer", + "--expected-version", "2", "--transfer-claim", ) + transferred = payload(cli(registry_path, *transfer_args)) assert transferred["lease"]["owner"] == "codex-side-bypass", transferred assert transferred["lease"]["version"] == 3, transferred assert transferred["lease"]["lease_epoch"] == 2, transferred + assert transferred["claimed_by"] == "codex-side-bypass", transferred + current = payload(command(registry_path, "todo", "list", "--goal-id", GOAL_ID, "--todo-id", TODO_A)) + assert current["todo"]["claimed_by"] == "codex-side-bypass", current + assert current["authority_read"]["legacy_fallback_used"] is False, current stale_owner = lifecycle_cli( registry_path, @@ -330,9 +334,16 @@ def main() -> int: check=False, ) assert stale_owner.returncode == 1, stale_owner.stdout - assert payload(stale_owner)["error_code"] == "lease_cas_mismatch" + # Atomic claim handover rejects the previous actor before testing its lease key. + assert payload(stale_owner)["error_code"] == "owner_conflicts_with_claim", payload(stale_owner) after_stale_owner = inspected_lease(registry_path, TODO_A) assert after_stale_owner["lease"] == transferred["lease"], after_stale_owner + stale_key = lifecycle_cli( + registry_path, "renew", "codex-side-bypass", "turn-1", 3, check=False, + ) + assert stale_key.returncode == 1, stale_key.stdout + assert payload(stale_key)["error_code"] == "lease_cas_mismatch", payload(stale_key) + assert inspected_lease(registry_path, TODO_A)["lease"] == transferred["lease"] renewed_after_transfer = payload( lifecycle_cli( @@ -458,6 +469,9 @@ def main() -> int: ) assert stale_completion.returncode == 1, stale_completion.stdout assert payload(stale_completion)["error_code"] == "lease_cas_mismatch" + assert inspected_lease(registry_path, TODO_A)["lease"] == next_generation["lease"] + pending = payload(command(registry_path, "todo", "list", "--goal-id", GOAL_ID, "--todo-id", TODO_A)) + assert pending["todo"]["status"] == "open", pending completed = payload( command( @@ -480,7 +494,23 @@ def main() -> int: ) ) assert completed["ok"] is True, completed - assert completed["task_lease_fence"]["lease_epoch"] == 3, completed + current = payload(command(registry_path, "todo", "list", "--goal-id", GOAL_ID, "--todo-id", TODO_A)) + assert current["todo"]["status"] == "done", current + final_lease = inspected_lease(registry_path, TODO_A) + assert final_lease["lease"]["lease_epoch"] == 3, final_lease + assert final_lease["lease"]["status"] == "released" and not final_lease["active"], final_lease + historical = payload(cli(registry_path, *transfer_args)) + assert historical["original_receipt"] == transferred["original_receipt"], historical + assert historical["lease"]["status"] == "active", historical + assert inspected_lease(registry_path, TODO_A)["lease"] == final_lease["lease"] + replayed = payload(command(registry_path, "todo", "list", "--goal-id", GOAL_ID, "--todo-id", TODO_A)) + assert replayed["todo"] == current["todo"], replayed + + +def main() -> int: + os.environ["NODE_OPTIONS"] = os.environ.get("NODE_OPTIONS", "") + " --experimental-sqlite" + for provider in ("file", "sqlite"): + exercise_provider(provider) print("task-lease-runtime-smoke ok") return 0 From 842003de2a4d49c04a8f3917dc0c3e5051a20316 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:33:49 +0800 Subject: [PATCH 08/38] test: qualify canonical Chat actions and display recovery Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- examples/loopx-chat-actions-smoke.py | 14 +++++- examples/loopx-chat-contract-smoke.py | 61 +++++++++++++++++++++++++-- examples/loopx-chat-server-smoke.py | 6 +++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/examples/loopx-chat-actions-smoke.py b/examples/loopx-chat-actions-smoke.py index 14d35d35ae..10ecdb045d 100644 --- a/examples/loopx-chat-actions-smoke.py +++ b/examples/loopx-chat-actions-smoke.py @@ -23,6 +23,8 @@ from loopx.chat_server import ChatHTTPServer, ChatRequestHandler # noqa: E402 from loopx.chat_store import ChatSessionStore # noqa: E402 from loopx.history import collect_history # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 def preview_request( @@ -192,6 +194,7 @@ def write_registry_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + import_fixture_authority(registry, "goal-one") return registry, state @@ -963,6 +966,8 @@ def assert_http_action_api(root: Path) -> None: # A decision outcome is only valid on a User gate; the canonical owner # rejects it before any proposal is stored. + before_wrong_role = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id="goal-one") + before_wrong_role_display = state_path.read_bytes() code, misdirected_decision = request_json( f"{base_url}/api/actions/preview", method="POST", @@ -975,7 +980,12 @@ def assert_http_action_api(root: Path) -> None: }, ) assert code == 400, misdirected_decision - assert "decision_outcome is only valid" in misdirected_decision["error"], misdirected_decision + assert misdirected_decision["error_code"] == "invalid_action_preview", misdirected_decision + assert misdirected_decision["error"] == "Todo does not have the requested role", misdirected_decision + assert not any(proposal.get("idempotency_key") == "http-misdirected-decision" + for proposal in service.store.list()) + assert state_path.read_bytes() == before_wrong_role_display + assert read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id="goal-one") == before_wrong_role for index, (action_kind, params, gate_kind) in enumerate( [ @@ -1010,7 +1020,7 @@ def assert_http_action_api(root: Path) -> None: persisted_payload = action_store.path.read_text(encoding="utf-8") assert str(root) not in persisted_payload, persisted_payload - assert str(project := registry_path.parent.parent) not in persisted_payload, persisted_payload + assert str(registry_path.parent.parent) not in persisted_payload, persisted_payload cancellable_code, cancellable = request_json( f"{base_url}/api/actions/preview", diff --git a/examples/loopx-chat-contract-smoke.py b/examples/loopx-chat-contract-smoke.py index 24a95d5a2b..766e8cfa75 100644 --- a/examples/loopx-chat-contract-smoke.py +++ b/examples/loopx-chat-contract-smoke.py @@ -19,13 +19,16 @@ build_todo_review_preview, parse_agent_response, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 +from loopx.control_plane.todos.provider_projection import project_current_canonical_todos # noqa: E402 GOAL_ID = "loopx-chat-contract-smoke" TODO_TEXT = "[P0] Add a reviewable Goal Studio interaction." -def write_fixture(root: Path) -> tuple[Path, Path, Path]: +def write_fixture(root: Path, *, todo_anchor: bool = True) -> tuple[Path, Path, Path]: project = root / "project" runtime = root / "runtime" state_file = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" @@ -39,7 +42,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: "## Objective\n\n" "Keep LoopX Chat reviewable.\n\n" "## Next Action\n\n" - "- Review the next bounded proposal.\n", + "- Review the next bounded proposal.\n" + ("\n## Agent Todo\n" if todo_anchor else ""), encoding="utf-8", ) registry_path = project / ".loopx" / "registry.json" @@ -69,6 +72,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: + "\n", encoding="utf-8", ) + import_fixture_authority(registry_path, GOAL_ID) return registry_path, state_file, project @@ -174,6 +178,7 @@ def main() -> None: assert "tool narration" not in malformed["message"], malformed original = state_file.read_text(encoding="utf-8") + authority_before = read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) preview = build_todo_review_preview( registry_path=registry_path, goal_id=GOAL_ID, @@ -191,6 +196,7 @@ def main() -> None: "action_kind": "loopx_chat_proposal", }, preview assert state_file.read_text(encoding="utf-8") == original + assert read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) == authority_before assert "state_file" not in json.dumps(preview), preview assert "project" not in json.dumps(preview), preview @@ -213,6 +219,8 @@ def main() -> None: assert str(project) not in json.dumps(no_write_receipt), no_write_receipt else: raise AssertionError("stale preview must be rejected") + assert state_file.read_text(encoding="utf-8") == original + assert read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) == authority_before applied = apply_todo_review_preview( registry_path=registry_path, @@ -222,7 +230,15 @@ def main() -> None: ) assert applied["ok"] is True, applied assert applied["applied"] is True, applied - assert applied["todo"]["todo_id"] == preview["todo"]["todo_id"], applied + # Canonical creation allocates its id at commit; preview locks the + # intent and provider revision, not a dry-run's provisional id. + for field in ("goal_id", "text", "status", "task_class", "action_kind"): + assert applied["todo"][field] == preview["todo"][field], applied + committed = read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) + assert committed["decision_read_from_provider"] is True and committed["legacy_fallback_used"] is False + assert len(committed["todos"]) == 1, committed + assert committed["todos"][0]["todo_id"] == applied["todo"]["todo_id"], committed + assert committed["todos"][0]["text"] == TODO_TEXT, committed receipt = applied["receipt"] assert receipt["schema_version"] == "loopx_chat_todo_receipt_v0", receipt assert receipt["receipt_id"], receipt @@ -236,6 +252,14 @@ def main() -> None: assert str(project) not in json.dumps(receipt), receipt assert state_file.read_text(encoding="utf-8").count(TODO_TEXT) == 1 + # Display edits cannot replace the committed record or create a + # duplicate. Restore the document before another explicit write. + display = state_file.read_text(encoding="utf-8") + state_file.write_text(display.replace(TODO_TEXT, "Spoofed display proposal"), encoding="utf-8") + assert read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) == committed + + state_file.write_text(display, encoding="utf-8") + retry_preview = build_todo_review_preview( registry_path=registry_path, goal_id=GOAL_ID, @@ -254,6 +278,37 @@ def main() -> None: assert retry_receipt["already_exists"] is True, retry_receipt assert retry_receipt["todo_id"] == receipt["todo_id"], retry_receipt assert state_file.read_text(encoding="utf-8").count(TODO_TEXT) == 1 + assert read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) == committed + + # An anchorless historical document is retained as a separate display + # recovery case. A successful canonical commit cannot repair its prose + # implicitly or turn display failure into a second business write. + recovery_root = root / "display-recovery" + recovery_registry, recovery_state, _ = write_fixture(recovery_root, todo_anchor=False) + original_narrative = recovery_state.read_text(encoding="utf-8") + recovery_preview = build_todo_review_preview(registry_path=recovery_registry, goal_id=GOAL_ID, text=TODO_TEXT) + recovery_applied = apply_todo_review_preview(registry_path=recovery_registry, goal_id=GOAL_ID, + text=TODO_TEXT, preview_id=recovery_preview["preview_id"]) + assert recovery_applied["applied"] is True, recovery_applied + assert recovery_state.read_text(encoding="utf-8") == original_narrative + accepted = read_canonical_todos_if_promoted(runtime_root=recovery_root / "runtime", goal_id=GOAL_ID) + assert len(accepted["todos"]) == 1, accepted + try: + project_current_canonical_todos(registry_path=recovery_registry, + runtime_root=recovery_root / "runtime", goal_id=GOAL_ID) + except ValueError as error: + assert "no projection anchor" in str(error), error + else: + raise AssertionError("an anchorless document must refuse implicit projection") + assert recovery_state.read_text(encoding="utf-8") == original_narrative + recovery_state.write_text(original_narrative + "\n## Agent Todo\n", encoding="utf-8") + delivered = project_current_canonical_todos(registry_path=recovery_registry, + runtime_root=recovery_root / "runtime", goal_id=GOAL_ID, + expected_provider_revision=accepted["provider_revision"]) + assert delivered["status"] == "delivered" and delivered["narrative_preserved"] is True, delivered + assert recovery_state.read_text(encoding="utf-8").startswith(original_narrative) + assert recovery_state.read_text(encoding="utf-8").count(TODO_TEXT) == 1 + assert read_canonical_todos_if_promoted(runtime_root=recovery_root / "runtime", goal_id=GOAL_ID) == accepted print("loopx-chat-contract-smoke: ok") diff --git a/examples/loopx-chat-server-smoke.py b/examples/loopx-chat-server-smoke.py index 7fdd559c5d..ddae9b7077 100644 --- a/examples/loopx-chat-server-smoke.py +++ b/examples/loopx-chat-server-smoke.py @@ -18,6 +18,10 @@ REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 + GOAL_ID = "loopx-chat-server-smoke" SECOND_GOAL_ID = "loopx-chat-server-second-goal" TODO_TEXT = "[P0] Implement the approved Goal Studio review card." @@ -168,6 +172,8 @@ def write_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + for goal_id in (GOAL_ID, SECOND_GOAL_ID): + import_fixture_authority(registry, goal_id) return registry, state_file From 7c57373fb4cca494baf19fcc47d5f5444c2cd5d6 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:11:46 +0800 Subject: [PATCH 09/38] test: qualify canonical integrated advancement and role successors Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../control-plane-integrated-canary-smoke.py | 100 ++++++++++-------- .../multi-agent-role-successor-todos-smoke.py | 30 +++++- loopx/control_plane/testing/canary_harness.py | 12 ++- 3 files changed, 93 insertions(+), 49 deletions(-) diff --git a/examples/control_plane/control-plane-integrated-canary-smoke.py b/examples/control_plane/control-plane-integrated-canary-smoke.py index 63049aa45b..9bb2b2cd11 100644 --- a/examples/control_plane/control-plane-integrated-canary-smoke.py +++ b/examples/control_plane/control-plane-integrated-canary-smoke.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Bounded canary for the status -> quota -> review-packet Todo read path.""" +"""Bounded File/SQLite canary for status, quota, successor and monitor readback.""" from __future__ import annotations @@ -17,21 +17,23 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.control_plane.testing.canary_harness import ( # noqa: E402 + import_fixture_authority, read_run_index, run_json_cli, write_fixture_registry, ) +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 GOAL_ID = "control-plane-integrated-canary" MONITOR_GOAL_ID = "control-plane-integrated-monitor-canary" -MARKDOWN_GOAL_ID = "control-plane-markdown-continuation-canary" +CONTINUATION_GOAL_ID = "control-plane-continuation-canary" AGENT_ID = "codex-product-capability" PRIMARY_AGENT_ID = "codex-main-control" CANARY_TODO_ID = "todo_integrated_canary" CANARY_TODO_TITLE = "Design bounded status/quota/review-packet/Todo/read-path canary" -SUCCESSOR_TODO_TITLE = "Continue integrated Markdown successor routing canary" -MARKDOWN_CONTINUATION_TITLE = "Continue same-agent markdown continuation through status and quota." +SUCCESSOR_TODO_TITLE = "Continue integrated canonical successor routing canary" +CONTINUATION_TITLE = "Continue same-agent canonical continuation through status and quota." MONITOR_TODO_ID = "todo_integrated_due_monitor" MONITOR_TARGET_KEY = "integrated-due-monitor-watch" VALIDATED_PROGRESS_CLASSIFICATION = "integrated_canary_validated_progress" @@ -73,7 +75,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: return registry_path, state_file, runtime -def write_monitor_fixture(root: Path) -> tuple[Path, Path]: +def write_monitor_fixture(root: Path, *, provider: str) -> tuple[Path, Path]: project = root / "monitor-project" runtime = root / "monitor-runtime" state_file = project / ".codex" / "goals" / MONITOR_GOAL_ID / "ACTIVE_GOAL_STATE.md" @@ -103,13 +105,14 @@ def write_monitor_fixture(root: Path) -> tuple[Path, Path]: registered_agents=[AGENT_ID], quota_allowed_slots=10, ) + import_fixture_authority(registry_path, MONITOR_GOAL_ID, provider=provider) return registry_path, runtime -def write_markdown_continuation_fixture(root: Path) -> tuple[Path, Path, Path]: - project = root / "markdown-project" - runtime = root / "markdown-runtime" - state_file = project / ".codex" / "goals" / MARKDOWN_GOAL_ID / "ACTIVE_GOAL_STATE.md" +def write_continuation_fixture(root: Path, *, provider: str) -> tuple[Path, Path, Path]: + project = root / "continuation-project" + runtime = root / "continuation-runtime" + state_file = project / ".codex" / "goals" / CONTINUATION_GOAL_ID / "ACTIVE_GOAL_STATE.md" registry_path = project / ".loopx" / "registry.json" state_file.parent.mkdir(parents=True) state_file.write_text( @@ -117,9 +120,9 @@ def write_markdown_continuation_fixture(root: Path) -> tuple[Path, Path, Path]: "status: active\n" "updated_at: 2026-06-27T00:00:00+00:00\n" "---\n\n" - "# Control Plane Markdown Continuation Canary\n\n" + "# Control Plane Canonical Continuation Canary\n\n" "## Next Action\n\n" - "- Preserve same-agent markdown successor routing across status and quota.\n\n" + "- Preserve same-agent canonical successor routing across status and quota.\n\n" "## Agent Todo\n\n" "- [ ] [P2] Markdown placeholder that should lose to the claimed P1 todo.\n" " ", + "- [ ] [P0] Run source action.", + " ", "", ] ), @@ -92,6 +95,9 @@ def main() -> int: runtime_root = root / "runtime" write_minimal_state(state_file) write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + import_fixture_authority(registry, GOAL_ID) + before = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + display_before = state_file.read_bytes() preview = apply_role_successor_todos( registry_path=registry, @@ -139,6 +145,8 @@ def main() -> int: ) assert skipped["needed"] is False, skipped assert skipped["reason"] == "no_second_candidate", skipped + assert read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) == before + assert state_file.read_bytes() == display_before executed = apply_role_successor_todos( registry_path=registry, @@ -158,6 +166,24 @@ def main() -> int: state_text = state_file.read_text(encoding="utf-8") assert "claimed_by=agent-b" in state_text, state_text assert f"unblocks_todo_id={SOURCE_TODO_ID}" in state_text, state_text + after = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + assert after["source_authority"] == "file_v0", after + records = {todo["todo_id"]: todo for todo in after["todos"]} + assert len(records) == 2, records + assert records[added["todo_id"]]["claimed_by"] == "agent-b", records + assert records[added["todo_id"]]["unblocks_todo_id"] == SOURCE_TODO_ID, records + original_source = next(todo for todo in before["todos"] if todo["todo_id"] == SOURCE_TODO_ID) + assert records[SOURCE_TODO_ID] == original_source, records + replay = apply_role_successor_todos( + registry_path=registry, goal_id=GOAL_ID, source_todo_id=SOURCE_TODO_ID, + current_agent_id="agent-a", role_id="evidence_runner", action="run_dev_eval", + successor_specs=[successor_spec()], decision_summary={"dev_promotion_candidate_count": 1}, + execute=True, + ) + assert replay["successors"][0]["already_exists"] is True, replay + assert replay["successors"][0]["todo_id"] == added["todo_id"], replay + assert read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) == after + display_after = state_file.read_bytes() try: apply_role_successor_todos( @@ -175,6 +201,8 @@ def main() -> int: assert "successor target_agent_id 'missing-agent' is not registered" in str(exc), exc else: raise AssertionError("missing successor target agent should fail closed") + assert read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) == after + assert state_file.read_bytes() == display_after print("multi-agent-role-successor-todos-smoke ok") return 0 diff --git a/loopx/control_plane/testing/canary_harness.py b/loopx/control_plane/testing/canary_harness.py index 724a19227c..3fb37301d4 100644 --- a/loopx/control_plane/testing/canary_harness.py +++ b/loopx/control_plane/testing/canary_harness.py @@ -184,11 +184,11 @@ def read_run_index(runtime_root: Path, goal_id: str) -> list[dict[str, Any]]: ] -def import_fixture_authority(registry_path: Path, goal_id: str) -> None: +def import_fixture_authority(registry_path: Path, goal_id: str, *, provider: str = "file") -> None: """Import a disposable smoke's complete source through the real saved plan. Fixture writers have finished before this call. Historical metadata stays - intact; normal writes afterwards use the canonical File/soft-claim owner. + intact; normal writes afterwards use the selected canonical/soft-claim owner. Existing unpromoted/refusal fixtures opt in explicitly, never by default. """ from ...state_backup import build_state_backup_plan, execute_state_backup_plan @@ -200,6 +200,10 @@ def import_fixture_authority(registry_path: Path, goal_id: str) -> None: runtime = Path(registry["common_runtime_root"]).resolve() # Bind the physical source consistently on hosts whose temp path is a link. goal["repo"] = str(project) + state_file = Path(goal["state_file"]).expanduser() + if not state_file.is_absolute(): + state_file = project / state_file + goal["state_file"] = str(state_file.resolve()) registry_path.write_text(json.dumps(registry), encoding="utf-8") runtime.mkdir(parents=True, exist_ok=True) backup = execute_state_backup_plan(build_state_backup_plan( @@ -214,7 +218,7 @@ def import_fixture_authority(registry_path: Path, goal_id: str) -> None: prepared = run_json_cli( *prefix, "prepare-import", *identity, "--backup-manifest", backup["manifest_path"], - "--provider", "file", "--target-handoff-mode", "soft_claim", + "--provider", provider, "--target-handoff-mode", "soft_claim", registry_path=registry_path, runtime_root=runtime, )["cold_import"] assert prepared["status"] == "prepared", prepared @@ -231,6 +235,6 @@ def import_fixture_authority(registry_path: Path, goal_id: str) -> None: "todo", "list", "--goal-id", goal_id, registry_path=registry_path, runtime_root=runtime, )["authority_read"] - assert readback["source_authority"] == "file_v0", readback + assert readback["source_authority"] == f"{provider}_v0", readback assert readback["decision_read_from_provider"] is True, readback assert readback["legacy_fallback_used"] is False, readback From e1304f5ae6feb6b5c6eeb901e636501ef97af258 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:50:24 +0800 Subject: [PATCH 10/38] fix: read maintainer correction ownership from canonical Todo Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../issue-fix-maintainer-correction-smoke.py | 24 ++++++++++++++++--- loopx/capabilities/issue_fix/cli.py | 5 ++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/examples/issue-fix-maintainer-correction-smoke.py b/examples/issue-fix-maintainer-correction-smoke.py index 9ef6bb1079..19a53ab67b 100644 --- a/examples/issue-fix-maintainer-correction-smoke.py +++ b/examples/issue-fix-maintainer-correction-smoke.py @@ -18,6 +18,9 @@ normalise_issue_fix_maintainer_correction_input, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.todos import list_goal_todos # noqa: E402 + GOAL_ID = "issue-fix-maintainer-correction-smoke" AGENT_ID = "issue-fix-agent" @@ -43,7 +46,7 @@ def run_cli(registry: Path, args: list[str]) -> dict[str, object]: return json.loads(result.stdout) -def write_fixture(root: Path) -> tuple[Path, Path, Path]: +def write_fixture(root: Path, provider: str) -> tuple[Path, Path, Path]: project = root / "project" project.mkdir() state_file = project / "ACTIVE_GOAL_STATE.md" @@ -57,6 +60,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: registry.write_text( json.dumps( { + "common_runtime_root": str(root / "runtime"), "goals": [ { "id": GOAL_ID, @@ -72,6 +76,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: ), encoding="utf-8", ) + import_fixture_authority(registry, GOAL_ID, provider=provider) metadata = root / "pr.json" metadata.write_text( json.dumps( @@ -107,10 +112,10 @@ def base_correction(**overrides: object) -> dict[str, object]: return payload -def main() -> int: +def check_corrections(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-maintainer-correction-") as tmpdir: root = Path(tmpdir) - project, registry, metadata = write_fixture(root) + project, registry, metadata = write_fixture(root, provider) correction = correction_file(root, "actionable.json", base_correction()) common = [ "issue-fix", @@ -237,6 +242,14 @@ def main() -> int: assert quiet["todo_write"]["skip_reason"] == "unchanged_monitor_quiet", quiet assert quiet["todo_write_performed"] is False, quiet + native = list_goal_todos(registry_path=registry, goal_id=GOAL_ID) + assert native["authority_read"]["decision_read_from_provider"] is True, native + assert native["authority_read"]["legacy_fallback_used"] is False, native + patches = [item for item in native["todos"] + if item.get("action_kind") == "issue_fix_maintainer_correction_patch"] + assert len(patches) == 1, patches + assert patches[0]["claimed_by"] == AGENT_ID, patches + for unsafe in ( base_correction(raw_comment_body="raw body must not enter the contract"), base_correction(source_ref="https://localhost/reviews/42"), @@ -250,6 +263,11 @@ def main() -> int: else: raise AssertionError(f"unsafe correction input was accepted: {unsafe}") + + +def main() -> int: + for provider in ("file", "sqlite"): + check_corrections(provider) print("issue-fix-maintainer-correction-smoke: ok") return 0 diff --git a/loopx/capabilities/issue_fix/cli.py b/loopx/capabilities/issue_fix/cli.py index 65d8ad868a..69c9caa772 100644 --- a/loopx/capabilities/issue_fix/cli.py +++ b/loopx/capabilities/issue_fix/cli.py @@ -1356,14 +1356,15 @@ def handle_issue_fix_command( or todo_write.get("metadata_updated") or todo_write.get("status_changed") ) + created_todo = todo_write["todo"] payload["todo_write"] = { "schema_version": "issue_fix_maintainer_correction_todo_write_v0", "write_performed": todo_changed, "already_exists": bool(todo_write.get("already_exists")), "todo_id": todo_write.get("todo_id"), "role": todo_write.get("role"), - "claimed_by": todo_write.get("claimed_by"), - "blocks_agent": todo_write.get("blocks_agent"), + "claimed_by": created_todo.get("claimed_by"), + "blocks_agent": created_todo.get("blocks_agent"), "path_recorded": False, } payload["todo_write_performed"] = todo_changed From 5dff84771f8a95f01259b5f2492f1a1d3aa62a52 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:50:24 +0800 Subject: [PATCH 11/38] test: migrate Explore and PR reconciliation callers to canonical Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../explore-todo-result-node-linkage-smoke.py | 24 ++++++++++++++----- examples/issue-fix-pr-lifecycle-smoke.py | 21 +++++++++++++--- .../issue-fix-pr-review-reconcile-smoke.py | 18 ++++++++++++-- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/examples/explore-todo-result-node-linkage-smoke.py b/examples/explore-todo-result-node-linkage-smoke.py index 1df43c0a6d..e7812529e3 100644 --- a/examples/explore-todo-result-node-linkage-smoke.py +++ b/examples/explore-todo-result-node-linkage-smoke.py @@ -18,6 +18,7 @@ from loopx.capabilities.explore.todo_branch_plan import ( # noqa: E402 build_explore_todo_branch_plan, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.control_plane.todos.contract import format_todo_metadata_line # noqa: E402 @@ -51,7 +52,7 @@ def run_loopx(registry: Path, *args: str) -> dict[str, object]: return json.loads(completed.stdout) -def write_fixture(root: Path) -> tuple[Path, Path]: +def write_fixture(root: Path, provider: str) -> tuple[Path, Path]: project = root / "project" project.mkdir() state_file = project / "ACTIVE_GOAL_STATE.md" @@ -84,10 +85,11 @@ def write_fixture(root: Path) -> tuple[Path, Path]: ), encoding="utf-8", ) + import_fixture_authority(registry, GOAL_ID, provider=provider) return registry, state_file -def check_todo_lifecycle() -> None: +def check_todo_lifecycle(provider: str) -> None: try: format_todo_metadata_line( todo_id="todo_invalid123", @@ -99,7 +101,7 @@ def check_todo_lifecycle() -> None: raise AssertionError("private path-shaped Explore node refs must fail closed") with tempfile.TemporaryDirectory(prefix="loopx-explore-todo-link-") as tmp: - registry, state_file = write_fixture(Path(tmp)) + registry, state_file = write_fixture(Path(tmp), provider) added = run_loopx( registry, "todo", @@ -118,7 +120,7 @@ def check_todo_lifecycle() -> None: "node_dead", ) todo_id = str(added["todo_id"]) - assert added["explore_result_node_refs"] == ["node_live", "node_dead"], added + assert added["todo"]["explore_result_node_refs"] == ["node_live", "node_dead"], added listed = run_loopx( registry, @@ -148,6 +150,10 @@ def check_todo_lifecycle() -> None: "--no-follow-up", ) assert completed["status"] == "done", completed + terminal = run_loopx(registry, "todo", "list", "--goal-id", GOAL_ID, + "--todo-id", todo_id)["todo"] + assert terminal["status"] == "done" + assert terminal["explore_result_node_refs"] == ["node_live", "node_dead"] state_text = state_file.read_text(encoding="utf-8") assert "explore_result_node_refs=node_live%2Cnode_dead" in state_text, state_text @@ -165,6 +171,11 @@ def check_todo_lifecycle() -> None: assert "Completed Work Archive" in archived, archived assert "explore_result_node_refs=node_live%2Cnode_dead" in archived, archived + archived_item = run_loopx(registry, "todo", "list", "--goal-id", GOAL_ID, + "--todo-id", todo_id)["todo"] + assert archived_item["archive_state"] == "archive", archived_item + assert archived_item["explore_result_node_refs"] == ["node_live", "node_dead"] + second = run_loopx( registry, "todo", @@ -197,7 +208,7 @@ def check_todo_lifecycle() -> None: "--todo-id", str(second["todo_id"]), )["todo"] - assert "explore_result_node_refs" not in cleared, cleared + assert cleared["explore_result_node_refs"] == [], cleared def check_planner_diagnostics() -> None: @@ -283,7 +294,8 @@ def check_planner_diagnostics() -> None: def main() -> int: - check_todo_lifecycle() + for provider in ("file", "sqlite"): + check_todo_lifecycle(provider) check_planner_diagnostics() print("explore-todo-result-node-linkage-smoke ok") return 0 diff --git a/examples/issue-fix-pr-lifecycle-smoke.py b/examples/issue-fix-pr-lifecycle-smoke.py index 854faf692b..e47bf3feae 100644 --- a/examples/issue-fix-pr-lifecycle-smoke.py +++ b/examples/issue-fix-pr-lifecycle-smoke.py @@ -21,6 +21,8 @@ ISSUE_FIX_PR_LIFECYCLE_MONITOR_SCHEMA_VERSION, build_issue_fix_pr_lifecycle_monitor_packet, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.todos import list_goal_todos # noqa: E402 from loopx.domain_packs.issue_fix import ( # noqa: E402 default_issue_fix_domain_state_ledger_path, issue_fix_pr_lifecycle_ledger_key, @@ -122,7 +124,7 @@ def assert_packet_shape(packet: dict[str, Any]) -> None: assert_public_safe(packet) -def main() -> int: +def check_lifecycle(provider: str) -> None: merged = build_issue_fix_pr_lifecycle_monitor_packet( url="https://github.com/huangruiteng/loopx/pull/1715", provider_payload={ @@ -350,6 +352,8 @@ def main() -> int: "## Agent Todo\n\n", encoding="utf-8", ) + import_fixture_authority(registry_path, "example-goal", provider=provider) + ledger = default_issue_fix_domain_state_ledger_path( project=project, goal_id="example-goal", @@ -552,8 +556,7 @@ def main() -> int: mutation_authority = reconciled_gate["todo_completion"][ "mutation_authority" ] - assert mutation_authority["mode"] == "registered_peer_actor", reconciled_gate - assert mutation_authority["actor_agent_id"] == "codex-test", reconciled_gate + assert mutation_authority["authority_mode"] == "registered_peer_actor", reconciled_gate assert reconciled_gate["rollout_event"]["appended"] is True, reconciled_gate assert "status=done" in state_file.read_text(encoding="utf-8") gate_projection = parse_active_state_todos( @@ -786,6 +789,18 @@ def main() -> int: material_rows ) + native = list_goal_todos(registry_path=registry_path, goal_id="example-goal") + assert native["authority_read"]["decision_read_from_provider"] is True, native + assert native["authority_read"]["legacy_fallback_used"] is False, native + rows = {item["todo_id"]: item for item in native["todos"]} + for todo_id in ("todo_merge_gate_1716", "todo_review_action_1716"): + assert rows[todo_id]["status"] == "done", rows[todo_id] + assert rows[todo_id]["last_actor_agent_id"] == "codex-test", rows[todo_id] + + +def main() -> int: + for provider in ("file", "sqlite"): + check_lifecycle(provider) print("issue-fix-pr-lifecycle-smoke: ok") return 0 diff --git a/examples/issue-fix-pr-review-reconcile-smoke.py b/examples/issue-fix-pr-review-reconcile-smoke.py index 040d4fc599..36075c0fcb 100644 --- a/examples/issue-fix-pr-review-reconcile-smoke.py +++ b/examples/issue-fix-pr-review-reconcile-smoke.py @@ -24,9 +24,10 @@ from loopx.capabilities.issue_fix.pr_review_ack import ( # noqa: E402 record_issue_fix_pr_review_ack, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.heartbeat_prequota import run_heartbeat_pre_quota # noqa: E402 from loopx.status import parse_active_state_todos # noqa: E402 -from loopx.todos import update_goal_todo # noqa: E402 +from loopx.todos import list_goal_todos, update_goal_todo # noqa: E402 PR_URL = "https://github.com/huangruiteng/loopx/pull/1716" @@ -89,7 +90,7 @@ def reconcile_without_provider( ) -def main() -> int: +def check_reconciliation(provider: str) -> None: with tempfile.TemporaryDirectory( prefix="loopx-issue-fix-pr-review-reconcile-" ) as tmpdir: @@ -142,6 +143,8 @@ def main() -> int: encoding="utf-8", ) + import_fixture_authority(registry_path, "example-goal", provider=provider) + missing = reconcile_without_provider( registry_path=registry_path, runtime_root=runtime_root, @@ -330,7 +333,18 @@ def main() -> int: assert cli_payload["quota_spend_required"] is False, cli_payload assert todo(state_file, "todo_other_provider")["status"] == "open" + native = list_goal_todos(registry_path=registry_path, goal_id="example-goal") + assert native["authority_read"]["decision_read_from_provider"] is True, native + assert native["authority_read"]["legacy_fallback_used"] is False, native + rows = {item["todo_id"]: item for item in native["todos"]} + assert rows["todo_github_review"]["status"] == "done" + assert rows["todo_other_provider"]["status"] == "open" + + +def main() -> int: + for provider in ("file", "sqlite"): + check_reconciliation(provider) print("issue-fix-pr-review-reconcile-smoke: ok") return 0 From f63966e936635ac7e15bc1aa1ba2199541210b6f Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:06:36 +0800 Subject: [PATCH 12/38] test: qualify canonical project reconnect and adoption callers Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../bootstrap-force-preserve-todos-smoke.py | 132 +++++++------ .../project/goal-rollout-event-log-smoke.py | 184 ++++++++++-------- .../project/project-agent-adoption-smoke.py | 54 +++-- 3 files changed, 206 insertions(+), 164 deletions(-) diff --git a/examples/project/bootstrap-force-preserve-todos-smoke.py b/examples/project/bootstrap-force-preserve-todos-smoke.py index 9a28448ded..110b28e261 100644 --- a/examples/project/bootstrap-force-preserve-todos-smoke.py +++ b/examples/project/bootstrap-force-preserve-todos-smoke.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Smoke-test canonical force refusal and explicit File-preserving reconnects.""" +"""Smoke-test canonical reconnect preservation and refusal of retired writes.""" from __future__ import annotations @@ -56,9 +56,11 @@ def main() -> int: assert canonical_state.read_bytes() == state_before assert (canonical_project / ".loopx/registry.json").read_bytes() == registry_before - # Existing force/Markdown migration assertions belong to the supported - # explicit File creation path, not the canonical default writer. - runtime = Path(tmp) / "file-runtime" + for provider in ("file", "sqlite"): + assert_canonical_reconnect(Path(tmp) / provider, provider) + + # The old opt-out no longer authorizes new Markdown Goal writers. + runtime = Path(tmp) / "unpromoted-runtime" configuration = runtime / "machine/configuration.json" configuration.parent.mkdir(parents=True) configuration.write_text(json.dumps({ @@ -71,81 +73,77 @@ def main() -> int: project = Path(tmp) / "project" project.mkdir() - initial = run_cli( - "--runtime-root", str(runtime), "bootstrap", - "--project", - str(project), - "--goal-id", - GOAL_ID, - "--objective", - "Exercise force reconnect preservation.", - "--no-global-sync", - ) - assert initial["ok"] is True, initial - assert initial["state_action"] == "created", initial - assert initial["storage_target"] == { - "schema_version": "loopx_new_goal_storage_target_v0", "provider": "file", - }, initial - state_file = Path(initial["state_file"]) - assert state_file == (project / ".loopx/goals/bootstrap-preserve-fixture/ACTIVE_GOAL_STATE.md").resolve() - - todo_state = "# Active Goal State\n\n## Agent Todo\n\n- [ ] Preserve this during reconnect.\n" - state_file.write_text(todo_state, encoding="utf-8") - - preserved = run_cli( - "--runtime-root", str(runtime), "bootstrap", - "--project", - str(project), - "--goal-id", - GOAL_ID, - "--objective", - "Exercise force reconnect preservation.", - "--write-scope", - "src/**", - "--force", - "--preserve-todos", - "--no-global-sync", - ) - assert preserved["ok"] is True, preserved - assert preserved["state_action"] == "kept-existing-preserve-todos", preserved - warning = preserved["force_bootstrap_warning"] - assert warning["will_replace_active_state"] is False, warning - assert warning["preserve_todos_requested"] is True, warning - assert "configure-goal --write-scope" in warning["recommended_scope_migration"], warning - migrated_state = state_file.read_text(encoding="utf-8") - assert "- [ ] Preserve this during reconnect." in migrated_state - assert "## User Todo / Owner Review Reading Queue" in migrated_state - assert preserved["todo_source_migration"] == { - "schema_version": "todo_source_section_migration_v0", - "added_roles": ["user"], - "applied": True, - }, preserved - assert goal_from_registry(project)["coordination"]["write_scope"] == ["src/**"] - - replaced = run_cli( + refused = run_cli( "--runtime-root", str(runtime), "bootstrap", "--project", str(project), "--goal-id", GOAL_ID, "--objective", - "Exercise force reconnect replacement warning.", - "--write-scope", - "docs/**", - "--force", + "Do not revive the retired Markdown writer.", "--no-global-sync", + check=False, ) - assert replaced["ok"] is True, replaced - assert replaced["state_action"] == "replaced", replaced - warning = replaced["force_bootstrap_warning"] - assert warning["will_replace_active_state"] is True, warning - assert warning["preserve_todos_requested"] is False, warning - assert "Preserve this during reconnect." not in state_file.read_text(encoding="utf-8") - assert goal_from_registry(project)["coordination"]["write_scope"] == ["docs/**"] + assert refused["ok"] is False, refused + assert refused["error_code"] == "canonical_authority_required", refused + assert not (project / ".loopx/registry.json").exists() + assert not (project / f".loopx/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md").exists() print("bootstrap-force-preserve-todos-smoke ok") return 0 +def assert_canonical_reconnect(root: Path, provider: str) -> None: + runtime = root / "runtime" + configuration = runtime / "machine/configuration.json" + configuration.parent.mkdir(parents=True) + configuration.write_text(json.dumps({ + "schema_version": "loopx_machine_configuration_v0", "namespaces": { + "goal_storage": {"schema_version": "loopx_goal_storage_defaults_v1", + "new_goal_provider": provider, "canonical_creation": True, + "new_goal_handoff_mode": "soft_claim"}, + }, + }), encoding="utf-8") + project = root / "project" + project.mkdir() + bootstrap = ( + "--runtime-root", str(runtime), "bootstrap", "--project", str(project), + "--goal-id", GOAL_ID, "--objective", "Preserve canonical Todos on reconnect.", + "--no-global-sync", + ) + initial = run_cli(*bootstrap) + assert initial["ok"] and initial["state_action"] == "created", initial + assert initial["storage_target"]["provider"] == provider, initial + state = Path(initial["state_file"]) + registry = project / ".loopx/registry.json" + prefix = ("--runtime-root", str(runtime), "--registry", str(registry)) + text = "[P1] Preserve canonical work — 保留任务与原始归属。" + added = run_cli(*prefix, "todo", "add", "--goal-id", GOAL_ID, + "--role", "agent", "--task-class", "advancement_task", + "--action-kind", "smoke", "--text", text) + assert added["added"] is True, added + read = ("todo", "list", "--goal-id", GOAL_ID) + before = run_cli(*prefix, *read) + assert before["authority_read"]["source_authority"] == f"{provider}_v0", before + assert before["authority_read"]["decision_read_from_provider"] is True, before + assert before["authority_read"]["legacy_fallback_used"] is False, before + stored = run_cli(*prefix, *read, "--todo-id", added["todo_id"])["todo"] + assert stored["text"] == text and stored["status"] == "open", stored + for force_args in (("--force",), ("--force", "--preserve-todos")): + state_before, registry_before = state.read_bytes(), registry.read_bytes() + refused = run_cli(*bootstrap, *force_args, check=False) + assert refused["ok"] is False, refused + assert "cannot rebuild existing state" in json.dumps(refused), refused + assert state.read_bytes() == state_before + assert registry.read_bytes() == registry_before + assert run_cli(*prefix, *read) == before + reconnected = run_cli(*bootstrap) + assert reconnected["ok"] is True, reconnected + assert reconnected["state_action"] == "kept-existing", reconnected + assert run_cli(*prefix, *read) == before + assert text in state.read_text(encoding="utf-8") + assert goal_from_registry(project)["coordination"]["storage_target"]["provider"] == provider + + if __name__ == "__main__": raise SystemExit(main()) diff --git a/examples/project/goal-rollout-event-log-smoke.py b/examples/project/goal-rollout-event-log-smoke.py index 08323d4549..d5748768dc 100644 --- a/examples/project/goal-rollout-event-log-smoke.py +++ b/examples/project/goal-rollout-event-log-smoke.py @@ -21,6 +21,7 @@ rollout_event_log_path, summarize_rollout_events, ) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 def assert_public_safe_text(text: str) -> None: @@ -347,86 +348,113 @@ def main() -> None: assert_public_safe_text(log_path.read_text(encoding="utf-8")) - registry_path, cli_runtime_root, project, cli_goal_id = write_smoke_project(tmp_root / "auto") - claim_payload = run_loopx_cli( - "--registry", - str(registry_path), - "--runtime-root", - str(cli_runtime_root), - "--format", - "json", - "todo", - "claim", - "--goal-id", - cli_goal_id, - "--todo-id", - "todo_auto_rollout", - "--claimed-by", - "codex-main-control", - ) - assert claim_payload["rollout_event"]["event_kind"] == "todo_claim", claim_payload - - refresh_payload = run_loopx_cli( - "--registry", - str(registry_path), - "--runtime-root", - str(cli_runtime_root), - "--format", - "json", - "refresh-state", - "--goal-id", - cli_goal_id, - "--classification", - "automatic_rollout_logging_smoke", - "--delivery-batch-scale", - "implementation", - "--delivery-outcome", - "outcome_progress", - "--agent-id", - "codex-main-control", - "--agent-lane", - "smoke", - "--no-global-sync", - ) - assert refresh_payload["rollout_event"]["event_kind"] == "refresh_state", refresh_payload + for provider in ("file", "sqlite"): + assert_cli_events(tmp_root / "auto" / provider, provider) - should_run_payload = run_loopx_cli( - "--registry", - str(registry_path), - "--runtime-root", - str(cli_runtime_root), - "--format", - "json", - "quota", - "should-run", - "--goal-id", - cli_goal_id, - "--agent-id", - "codex-main-control", - "--scan-root", - str(project), - "--limit", - "1", - allow_failure=True, - ) - assert should_run_payload["rollout_event"]["event_kind"] == ( - "quota_should_run" - ), should_run_payload - - auto_events = load_rollout_events(rollout_event_log_path(cli_runtime_root, cli_goal_id)) - auto_kinds = [event["event_kind"] for event in auto_events] - assert auto_kinds == [ - "todo_claim", - "refresh_state", - "quota_should_run", - ], auto_kinds - latest_event = auto_events[-1] - assert_boundary(latest_event) - auto_log_text = rollout_event_log_path(cli_runtime_root, cli_goal_id).read_text( - encoding="utf-8" - ) - assert_public_safe_text(auto_log_text) +def assert_cli_events(root: Path, provider: str) -> None: + registry_path, cli_runtime_root, project, cli_goal_id = write_smoke_project(root) + claim_args = ( + "--registry", str(registry_path), "--runtime-root", str(cli_runtime_root), + "--format", "json", "todo", "claim", "--goal-id", cli_goal_id, + "--todo-id", "todo_auto_rollout", "--claimed-by", "codex-main-control", + ) + state = project / "ACTIVE_GOAL_STATE.md" + before_state, before_registry = state.read_bytes(), registry_path.read_bytes() + refused = run_loopx_cli(*claim_args, allow_failure=True) + assert refused["ok"] is False, refused + assert refused["error_code"] == "canonical_authority_required", refused + assert state.read_bytes() == before_state + assert registry_path.read_bytes() == before_registry + import_fixture_authority(registry_path, cli_goal_id, provider=provider) + events_before = load_rollout_events(rollout_event_log_path(cli_runtime_root, cli_goal_id)) + claim_payload = run_loopx_cli( + "--registry", + str(registry_path), + "--runtime-root", + str(cli_runtime_root), + "--format", + "json", + "todo", + "claim", + "--goal-id", + cli_goal_id, + "--todo-id", + "todo_auto_rollout", + "--claimed-by", + "codex-main-control", + ) + assert claim_payload["rollout_event"]["event_kind"] == "todo_claim", claim_payload + readback = run_loopx_cli( + "--registry", str(registry_path), "--runtime-root", str(cli_runtime_root), + "--format", "json", "todo", "list", "--goal-id", cli_goal_id, + "--todo-id", "todo_auto_rollout", + ) + assert readback["authority_read"]["source_authority"] == f"{provider}_v0", readback + assert readback["authority_read"]["legacy_fallback_used"] is False, readback + assert readback["todo"]["claimed_by"] == "codex-main-control", readback + + refresh_payload = run_loopx_cli( + "--registry", + str(registry_path), + "--runtime-root", + str(cli_runtime_root), + "--format", + "json", + "refresh-state", + "--goal-id", + cli_goal_id, + "--classification", + "automatic_rollout_logging_smoke", + "--delivery-batch-scale", + "implementation", + "--delivery-outcome", + "outcome_progress", + "--agent-id", + "codex-main-control", + "--agent-lane", + "smoke", + "--no-global-sync", + ) + assert refresh_payload["rollout_event"]["event_kind"] == "refresh_state", refresh_payload + + should_run_payload = run_loopx_cli( + "--registry", + str(registry_path), + "--runtime-root", + str(cli_runtime_root), + "--format", + "json", + "quota", + "should-run", + "--goal-id", + cli_goal_id, + "--agent-id", + "codex-main-control", + "--scan-root", + str(project), + "--limit", + "1", + allow_failure=True, + ) + assert should_run_payload["rollout_event"]["event_kind"] == ( + "quota_should_run" + ), should_run_payload + + auto_events = load_rollout_events(rollout_event_log_path(cli_runtime_root, cli_goal_id)) + assert auto_events[:len(events_before)] == events_before + auto_kinds = [event["event_kind"] for event in auto_events[len(events_before):]] + assert auto_kinds == [ + "todo_claim", + "refresh_state", + "quota_should_run", + ], auto_kinds + latest_event = auto_events[-1] + assert_boundary(latest_event) + auto_log_text = rollout_event_log_path(cli_runtime_root, cli_goal_id).read_text( + encoding="utf-8" + ) + assert_public_safe_text(auto_log_text) if __name__ == "__main__": main() diff --git a/examples/project/project-agent-adoption-smoke.py b/examples/project/project-agent-adoption-smoke.py index c5316418e2..482a144802 100644 --- a/examples/project/project-agent-adoption-smoke.py +++ b/examples/project/project-agent-adoption-smoke.py @@ -20,6 +20,11 @@ REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 + GOAL_ID = "adoption-main-control" USER_TODO = "Review the P0 owner-risk checklist before approving delivery." AGENT_TODO = "Run the read-only map dry-run after the operator approval is recorded." @@ -86,7 +91,7 @@ def write_planned_fixture(root: Path) -> Path: return registry_path -def run_cli(root: Path, registry_path: Path, *args: str) -> dict: +def run_cli(root: Path, registry_path: Path, *args: str, check: bool = True) -> dict: result = subprocess.run( [ sys.executable, @@ -101,7 +106,7 @@ def run_cli(root: Path, registry_path: Path, *args: str) -> dict: *args, ], cwd=REPO_ROOT, - check=True, + check=check, text=True, capture_output=True, ) @@ -135,8 +140,8 @@ def attention_item(status_payload: dict) -> dict: return items[0] -def main() -> int: - with tempfile.TemporaryDirectory(prefix="loopx-project-agent-adoption-") as tmp: +def assert_adoption(provider: str) -> None: + with tempfile.TemporaryDirectory(prefix=f"loopx-project-agent-adoption-{provider}-") as tmp: root = Path(tmp) registry_path = write_planned_fixture(root) project = root / "project" @@ -179,24 +184,31 @@ def main() -> int: assert first_quota["agent_todo_summary"]["open_count"] == 1, first_quota assert first_quota["selected_todo"]["text"] == AGENT_TODO, first_quota - todo_payload = run_cli( - root, - registry_path, - "todo", - "add", - "--goal-id", - GOAL_ID, - "--role", - "user", - "--task-class", - "user_gate", - "--global-gate", - "--text", - USER_TODO, + add_gate = ( + "todo", "add", "--goal-id", GOAL_ID, "--role", "user", + "--task-class", "user_gate", "--global-gate", "--text", USER_TODO, ) + state = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" + before_state, before_registry = state.read_bytes(), registry_path.read_bytes() + refused = run_cli(root, registry_path, *add_gate, check=False) + assert refused["ok"] is False, refused + assert refused["error_code"] == "canonical_authority_required", refused + assert state.read_bytes() == before_state + assert registry_path.read_bytes() == before_registry + import_fixture_authority(registry_path, GOAL_ID, provider=provider) + imported = run_cli(root, registry_path, "todo", "list", "--goal-id", GOAL_ID) + assert imported["authority_read"]["source_authority"] == f"{provider}_v0", imported + assert imported["authority_read"]["legacy_fallback_used"] is False, imported + assert len(imported["todos"]) == 1, imported + original = imported["todos"][0] + assert original["text"] == AGENT_TODO and original["status"] == "open", original + todo_payload = run_cli(root, registry_path, *add_gate) assert todo_payload["ok"] is True, todo_payload assert todo_payload["added"] is True, todo_payload - assert todo_payload["section"] == "User Todo / Owner Review Reading Queue", todo_payload + gate_id = todo_payload["todo_id"] + gate = run_cli(root, registry_path, "todo", "list", "--goal-id", GOAL_ID, + "--todo-id", gate_id)["todo"] + assert gate["text"] == USER_TODO and gate["global_gate"] is True, gate status_after_todo = run_cli(root, registry_path, "status", "--scan-root", str(project)) item_after_todo = attention_item(status_after_todo) @@ -267,6 +279,10 @@ def main() -> int: assert "不要从旧聊天或旧 packet 拼当前状态" in packet["project_agent_handoff"], packet assert "【用户本地 Gate 记录草稿】" not in packet["packet"], packet + +def main() -> int: + for provider in ("file", "sqlite"): + assert_adoption(provider) print("project-agent-adoption-smoke ok") return 0 From d7a988580a133b7c58fcb6d411f90ffc1466be42 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:18:49 +0800 Subject: [PATCH 13/38] fix: project governance leases from canonical authority Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- examples/visible-governance-slice-smoke.py | 52 +++++++++++++++++----- loopx/visible_governance.py | 34 +++++++------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/examples/visible-governance-slice-smoke.py b/examples/visible-governance-slice-smoke.py index d37c32b13d..27b80bc4af 100644 --- a/examples/visible-governance-slice-smoke.py +++ b/examples/visible-governance-slice-smoke.py @@ -18,9 +18,12 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.control_plane.testing.canary_harness import ( # noqa: E402 + import_fixture_authority, run_json_cli, write_fixture_registry, ) +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 +from loopx.control_plane.work_items.task_lease import task_lease_dir # noqa: E402 from loopx.quota import build_quota_should_run # noqa: E402 from loopx.status import collect_status # noqa: E402 from loopx.todos import ( # noqa: E402 @@ -152,7 +155,7 @@ def _add_user_action( return add_goal_todo(**kwargs) -def main() -> None: +def assert_governance_slice(provider: str) -> None: import tempfile print("=== Visible Governance Slice Smoke ===\n") @@ -160,6 +163,15 @@ def main() -> None: with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) registry, runtime, state = _write_fixture(tmp_path) + import_fixture_authority(registry, GOAL_ID, provider=provider) + # Select the existing lease policy while the imported tasks are still + # unclaimed. A soft claim never grants permission to acquire a lease. + mode = run_json_cli( + "handoff-mode", "set", "--goal-id", GOAL_ID, "--mode", "hard_lease", + "--operation-id", "governance-fixture-lease-policy", + registry_path=registry, runtime_root=runtime, + ) + assert mode["ok"] is True, mode # ── 1. Create agent todos with claims ── alpha_todo = _add_agent_todo( @@ -216,6 +228,16 @@ def main() -> None: ) assert lease_result["ok"] is True, f"lease acquire failed: {lease_result}" assert lease_result["acquired"] is True + canonical_before = read_canonical_todos_if_promoted( + runtime_root=runtime, goal_id=GOAL_ID, include_leases=True, + ) + assert canonical_before["leases"] == [lease_result["lease"]], canonical_before + # A stale legacy lease file must not inject a second canonical lease. + obsolete_dir = task_lease_dir(runtime_root=runtime, goal_id=GOAL_ID) + obsolete_dir.mkdir(parents=True, exist_ok=True) + (obsolete_dir / "todo_forged.json").write_text(json.dumps({ + **lease_result["lease"], "todo_id": "todo_forged", "owner": AGENT_BETA, + }), encoding="utf-8") print(f" [OK] Task lease acquired for alpha on {alpha_todo['todo_id']}") # ── 5. Collect status ── @@ -271,9 +293,13 @@ def main() -> None: # leases leases = gov_slice["leases"] assert isinstance(leases, dict) - assert leases["active_leases"] >= 1 - assert leases["current_agent_active_leases"] >= 1 - assert leases["nature"] == "optional runtime fence, not authority" + assert leases["active_leases"] == 1, leases + assert leases["current_agent_active_leases"] == 1, leases + assert leases["leases"][0]["todo_id"] == alpha_todo["todo_id"], leases + assert read_canonical_todos_if_promoted( + runtime_root=runtime, goal_id=GOAL_ID, include_leases=True, + ) == canonical_before + assert leases["nature"] == "policy-selected runtime fence, not write authority" for lease_entry in leases["leases"]: assert "todo_id" in lease_entry assert "owner" in lease_entry @@ -353,11 +379,11 @@ def main() -> None: # ── Negative: active lease is never write authority ── assert leases["active_leases"] >= 1 - assert leases["nature"] == "optional runtime fence, not authority" + assert leases["nature"] == "policy-selected runtime fence, not write authority" lease_blob = json.dumps(leases, sort_keys=True).lower() for forbidden in ( - "write authority", - "runtime authority", + "grants write authority", + "grants runtime authority", "browser write", "write api", ): @@ -366,7 +392,7 @@ def main() -> None: ) # The coverage-only claim_work reference must not imply lease authority. assert claim_work["shipped"] is False - assert "optional runtime fence" in leases["nature"] + assert "policy-selected runtime fence" in leases["nature"] print( " [OK] Negative: active leases remain fence-only " "(not write authority / no browser write API)" @@ -408,12 +434,12 @@ def main() -> None: assert GOAL_ID in markdown assert AGENT_ALPHA in markdown assert "Claims (Soft Ownership)" in markdown - assert "Task Leases (Optional Runtime Fence)" in markdown + assert "Task Leases (Policy-Selected Runtime Fence)" in markdown assert "Quota Decision" in markdown assert "Scheduler Hints" in markdown assert "Decision Scopes" in markdown assert "Authority Boundary" in markdown - assert "optional runtime fence, not authority" in markdown + assert "policy-selected runtime fence, not write authority" in markdown assert "Proposals shipped" in markdown assert "Stage-2" in markdown or "Stage 2" in markdown or "additive" in markdown assert "not write authority" in markdown.lower() or ( @@ -432,6 +458,7 @@ def main() -> None: registry_path=registry, goal_id=GOAL_ID, todo_id=gate["todo_id"], + role="user", agent_id=AGENT_ALPHA, decision_outcome="approve", ) @@ -473,5 +500,10 @@ def main() -> None: print("\n=== All smoke checks passed ===") +def main() -> None: + for provider in ("file", "sqlite"): + assert_governance_slice(provider) + + if __name__ == "__main__": main() diff --git a/loopx/visible_governance.py b/loopx/visible_governance.py index ca89e6c912..24a6ec52ac 100644 --- a/loopx/visible_governance.py +++ b/loopx/visible_governance.py @@ -23,7 +23,7 @@ # ── schema constants ──────────────────────────────────────────────────────── GOVERNANCE_SLICE_SCHEMA = "visible_governance_slice_v0" -LEASE_NATURE = "optional runtime fence, not authority" +LEASE_NATURE = "policy-selected runtime fence, not write authority" # ── authority-boundary static table ───────────────────────────────────────── @@ -41,7 +41,8 @@ def _build_authority_boundary_table() -> list[dict[str, Any]]: "shipped": True, "shipped_equivalent": ( "Promoted Goals use native TS commands and AuthorityStore transactions. " - "Unpromoted Goals retain the Markdown/lease adapters." + "Unpromoted sources remain readable for inspection and import; " + "normal mutations require canonical authority." ), "gap": "Implementation availability does not select the default provider or migrate this Goal.", }, @@ -205,6 +206,7 @@ def _build_leases_projection( read_lease, task_lease_dir, ) + from .control_plane.coordination.local_authority import read_canonical_todos_if_promoted runtime_root_raw = status_payload.get("runtime_root") if not runtime_root_raw: @@ -212,16 +214,18 @@ def _build_leases_projection( from pathlib import Path - lease_dir = task_lease_dir( - runtime_root=Path(str(runtime_root_raw)), - goal_id=goal_id, + runtime_root = Path(str(runtime_root_raw)) + canonical = read_canonical_todos_if_promoted( + runtime_root=runtime_root, goal_id=goal_id, include_leases=True, ) - if not lease_dir.exists(): - return {"active_leases": 0, "leases": [], "nature": LEASE_NATURE} + if canonical is not None: + leases = canonical["leases"] + else: + lease_dir = task_lease_dir(runtime_root=runtime_root, goal_id=goal_id) + leases = [read_lease(path) for path in sorted(lease_dir.glob("todo_*.json"))] active_leases: list[dict[str, Any]] = [] - for path in sorted(lease_dir.glob("todo_*.json")): - lease = read_lease(path) + for lease in leases: if not lease_is_active(lease): continue active_leases.append( @@ -478,7 +482,7 @@ def render_visible_governance_markdown(slice_payload: dict[str, Any]) -> str: # ── Leases ── leases = slice_payload.get("leases") if isinstance(leases, dict): - lines.append("## Task Leases (Optional Runtime Fence)") + lines.append("## Task Leases (Policy-Selected Runtime Fence)") lines.append("") lines.append(f"- Nature: *{leases.get('nature', LEASE_NATURE)}*") lines.append( @@ -600,11 +604,11 @@ def render_visible_governance_markdown(slice_payload: dict[str, Any]) -> str: f"{len(boundary) if boundary else 0}" ) lines.append( - "- **Key takeaway**: Stage-2 ships an additive coordination head, " - "file provider, and claim_work executor plus handoff_mode; default " - "runtime still uses Markdown soft claims, file-backed task leases " - "(optional fence, not write authority), and turn-scoped settlement. " - "Shared-mode promotion remains Stage 3." + "- **Key takeaway**: The original Stage-2 prototype is retired. " + "Canonical providers own normal Todo mutations and task leases. " + "soft_claim forbids lease acquisition; hard_lease enforces the lease " + "fence. Neither policy grants general write authority. " + "Installed adoption and cross-host qualification remain separate." ) lines.append("") From beecaeac1540e83d9fd1b63c3009c3ce1e66d4db Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:18:49 +0800 Subject: [PATCH 14/38] test: migrate custom CLI and Lark gate callers to canonical Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../custom-runtime-minimal-cli-turn-smoke.py | 55 +++++++++++++++++-- ...-goal-channel-human-gate-delivery-smoke.py | 16 +++++- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/examples/custom-runtime-minimal-cli-turn-smoke.py b/examples/custom-runtime-minimal-cli-turn-smoke.py index 76c73ba995..26b42791e2 100755 --- a/examples/custom-runtime-minimal-cli-turn-smoke.py +++ b/examples/custom-runtime-minimal-cli-turn-smoke.py @@ -24,6 +24,7 @@ GOAL_ID = "custom-runtime-min" AGENT_ID = "custom-host-agent" +TURN_ID = "custom-runtime-fixture-turn" MARKER_NAME = "public-marker.txt" MARKER_BODY = "ok-from-custom-runtime\n" ADVANCED_TURN_SMOKE = ( @@ -106,8 +107,10 @@ def write_project_fixture(root: Path) -> tuple[Path, Path, Path]: return project, registry_path, state_file -def assert_mainstream_cli_turn() -> None: +def assert_mainstream_cli_turn(provider: str) -> None: from loopx.status import parse_active_state_todos + from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted + from loopx.control_plane.testing.canary_harness import import_fixture_authority assert WAKE_TRIGGER, "host must own a wake trigger" assert "quota should-run" in REENTRY_INSTRUCTION @@ -116,6 +119,7 @@ def assert_mainstream_cli_turn() -> None: with tempfile.TemporaryDirectory(prefix="loopx-custom-runtime-min-") as tmp: project, registry_path, state_file = write_project_fixture(Path(tmp)) + import_fixture_authority(registry_path, GOAL_ID, provider=provider) status = run_cli(registry_path, "status", "--scan-root", str(project)) assert status.get("ok") is True, status @@ -160,6 +164,8 @@ def assert_mainstream_cli_turn() -> None: AGENT_ID, "--runtime-profile", "generic_cli", + "--todo-id", todo_id, + "--turn-instance-id", TURN_ID, "--scan-path", str(project), ) @@ -184,12 +190,13 @@ def assert_mainstream_cli_turn() -> None: AGENT_ID, "--agent-id", AGENT_ID, + "--turn-instance-id", TURN_ID, "--evidence", "public marker written under project root", - "--no-follow-up", ) assert completed.get("changed") is True, completed - assert completed.get("no_followup") is True, completed + assert completed["completion_continuation"] == "active_goal", completed + assert completed["successor_todo_ids"] == [], completed summary = parse_active_state_todos(state_file.read_text(encoding="utf-8")) item = next( @@ -198,12 +205,33 @@ def assert_mainstream_cli_turn() -> None: if row.get("todo_id") == todo_id ) assert item.get("done") is True, item + canonical = read_canonical_todos_if_promoted( + runtime_root=Path(tmp) / "runtime", goal_id=GOAL_ID, + ) + stored = next(row for row in canonical["todos"] if row["todo_id"] == todo_id) + assert stored["status"] == "done" and stored["claimed_by"] == AGENT_ID, stored refreshed = run_cli( registry_path, "refresh-state", "--goal-id", GOAL_ID, + "--agent-id", AGENT_ID, + "--agent-lane", AGENT_ID, + "--progress-scope", "agent_lane", + "--todo-id", todo_id, + "--turn-instance-id", TURN_ID, + "--classification", "validated_progress", + "--delivery-batch-scale", "multi_surface", + "--delivery-outcome", "outcome_progress", + "--delivery-boundary", "in_flight_continuation", + "--progress-result-class", "advanced", + "--progress-surface-id", "custom-runtime-cli", + "--progress-work-item-id", todo_id, + "--progress-hypothesis-id", "marker-terminal-single-debit", + "--progress-probe-kind", "real-cli-artifact-readback", + "--progress-coverage-scope-id", "canonical-cli-turn", + "--progress-evidence-id", "artifact:public-marker", "--no-global-sync", ) assert refreshed.get("ok") is True, refreshed @@ -217,10 +245,12 @@ def assert_mainstream_cli_turn() -> None: GOAL_ID, "--agent-id", AGENT_ID, + "--todo-id", todo_id, + "--turn-instance-id", TURN_ID, "--slots", "1", "--source", - "controller", + "heartbeat", "--execute", "--scan-path", str(project), @@ -230,10 +260,25 @@ def assert_mainstream_cli_turn() -> None: after = spent["quota_event"]["after"] assert after["spent_slots"] == 1, spent assert after["allowed_slots"] == 5, spent + closed = run_cli( + registry_path, "todo", "complete", "--goal-id", GOAL_ID, + "--todo-id", todo_id, "--claimed-by", AGENT_ID, "--agent-id", AGENT_ID, + "--turn-instance-id", TURN_ID, + "--evidence", "public marker written under project root", "--no-follow-up", + ) + assert closed["completion_continuation"] == "no_followup", closed + replay = run_cli( + registry_path, "quota", "spend-slot", "--goal-id", GOAL_ID, + "--agent-id", AGENT_ID, "--todo-id", todo_id, + "--turn-instance-id", TURN_ID, "--slots", "1", "--source", "heartbeat", + "--execute", "--scan-path", str(project), + ) + assert replay["appended"] is False and replay["idempotent_replay"] is True, replay def main() -> int: - assert_mainstream_cli_turn() + for provider in ("file", "sqlite"): + assert_mainstream_cli_turn(provider) print("custom-runtime-minimal-cli-turn-smoke ok") return 0 diff --git a/examples/lark-goal-channel-human-gate-delivery-smoke.py b/examples/lark-goal-channel-human-gate-delivery-smoke.py index d2f4a344eb..3faffa02e5 100644 --- a/examples/lark-goal-channel-human-gate-delivery-smoke.py +++ b/examples/lark-goal-channel-human-gate-delivery-smoke.py @@ -13,6 +13,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from loopx.extensions.bundled import bundled_extension_manifest +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted +from loopx.control_plane.testing.canary_harness import import_fixture_authority from loopx.extensions.lark.goal_channel_contracts import ( GOAL_CHANNEL_BINDING_SCHEMA_VERSION, read_goal_channel_binding, @@ -296,12 +298,14 @@ def write_project(root: Path) -> tuple[Path, Path]: return registry_path, binding_path -def main() -> None: +def assert_gate_delivery(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-goal-channel-human-gate-") as raw: registry_path, binding_path = write_project(Path(raw)) bin_dir, fake_state_path = write_fake_lark_cli(Path(raw)) project = registry_path.parent.parent runtime_root = Path(raw) / "runtime" + import_fixture_authority(registry_path, GOAL_ID, provider=provider) + before = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) first_refresh = run_refresh( registry_path=registry_path, runtime_root=runtime_root, @@ -350,6 +354,11 @@ def main() -> None: == send_count ) assert gate_update["changed"] is True, gate_update + after = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + gate = next(row for row in after["todos"] if row["todo_id"] == GATE_TODO_ID) + assert "after reviewing the diff" in gate["text"], gate + assert gate["status"] == "open" and gate["task_class"] == "user_gate", gate + assert after["provider_revision"] != before["provider_revision"] assert third["status"] == "sent_verified", third third_key = third["notification"]["idempotency_key"] assert third_key != first_key @@ -375,5 +384,10 @@ def main() -> None: print("lark-goal-channel-human-gate-delivery-smoke ok") +def main() -> None: + for provider in ("file", "sqlite"): + assert_gate_delivery(provider) + + if __name__ == "__main__": main() From c9be1562b927a8adc1dbdcc05d6f35669dad41f3 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:43:49 +0800 Subject: [PATCH 15/38] refactor: retire isolated visible governance prototype Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- docs/development/contributor-tasks-history.md | 2 +- examples/visible-governance-slice-smoke.py | 509 --------------- loopx/visible_governance.py | 615 ------------------ ...est_canonical_goal_channel_coordination.py | 70 ++ 4 files changed, 71 insertions(+), 1125 deletions(-) delete mode 100644 examples/visible-governance-slice-smoke.py delete mode 100644 loopx/visible_governance.py diff --git a/docs/development/contributor-tasks-history.md b/docs/development/contributor-tasks-history.md index 195bec1394..387e6cfe0f 100644 --- a/docs/development/contributor-tasks-history.md +++ b/docs/development/contributor-tasks-history.md @@ -58,7 +58,7 @@ points: | Review quality | PR review now requires scope-fit evidence for production surface changes (#3090), the execution contract carries four self-dev review lenses (#3123), example-only PRs need durable smoke-value evidence (#3134), age-fair exact-head scheduling persists across restarts (#3317), durable projection ACK is required (#3744), and disproportionate changes are gated (#3731). | Add synthetic conformance and negative cases around scope-fit evidence, causal chains, exact-head review packets, durable projection ACKs, and durable-smoke-value claims. | | Task leases | Typed task-lease CLI with preserved legacy error codes landed (#3095); on-disk hard leases surface in goal-channel projection (#3039); Turn fencing uses lease fences plus an append-only journal, and the OpenCode 2 goal worker fences its own live worker lease. A task-lease generation ABA fix shipped (#3393), a Pi `loopx_task_lease` facade over the shipped `task_lease_v0` CLI merged (#3559, closing #3549), task-lease settlement cut over to TypeScript (#3674), and native task-lease acquire cut over too (#3702). | Adopt the same facade in one more real host integration (for example TraeX) or add a transfer/overlap-write-scope fixture. Keep soft-claim routing and undeclared-lease authority unchanged. | | Status, quota, monitors | Replan context is host-projected from the evidence ledger; two equivalent typed progress observations create an obligation, maintenance writes fail closed against the same full goal-frontier reducer used by quota, and an exact runnable-successor Todo carries the obligation-bound semantic receipt and turn boundary. Heartbeat todos survive capability reentry (#3321), Todo identity filtering and UTC ordering are corrected (#3311), unbound `/loopx` sessions inherit existing agent identity (#3315), declared validation gates run on user-role done updates (#3291, #3293), quota guards follow the selected Todo (#3506), and a bounded fallback action portfolio keeps quota decisions actionable (#3514). Guided start is bound to one turn (#3572), stale generated Next Actions rebind to the current todo (#3524), unknown workspace causality is repairable (#3519), malformed runtime recovery and settlement payloads fail closed (#3525), managed and queued turn creation are serialized (#3542, #3562), and guided selection packet consumption is explicit (#3707). Post-v0.5.3, `todo list` gained a bounded thin projection with an output-budget cap (#3679), the dashboard projects completed Todos with done-count run progress (#3689), guided-todo onboarding delta coverage shipped (#3716), agent-lane Next Action selection prefers the selected Todo over shared prose (#3693), typed blocker outcomes settle (#3734), Turn receipt priority wins in compact output (#3726), and CLI common command owners load lazily (#3717). Manual evidence reads, prose ACKs, and historical repair-delta claims are diagnostic only. Compact scheduler-hint and heartbeat-prompt budgets plus todo-detail cold paths remain the reference. | Extend one measured performance, detail-readback, lock-timeout, malformed-state, typed progress, or obligation-bound semantic-transition case. Keep default output bounded and cold-path detail available. | -| Governance and productization | A synthetic visible-governance slice landed (#3086) and its Stage-2 proposal-vs-shipped refresh keeps additive `claim_work` / coordination-head truth plus a lease-as-fence negative explicit; decision-context evidence cursors settle (#3079); the React homepage rebuild (#3098) landed; a deterministic project registry (#3170) serializes global sync; per-goal handoff mode gates claim/lease authority (#3164) and hard-lease gates auto-acquire completion keys (#3198); a repository change-window gate and pending ledger ship (#3319); goal channels default to human-gate auto-notify on new channels (#3523); coordination state rules are centralized (#3410) and the Stage 2 slice ships an aggregate head, file provider, and `claim_work` executor (#3529); project repository delivery lands through capability hooks (#3570) with gitless delivery workspaces settled (#3574) and CPA/provider-routing qualification recorded (#3576, #3573, #3563); a read-only stride shadow observation M1 (#3207), a synthetic stride-boundary shadow fixture (#3290), and the hierarchical stride RFC (#3204) open the next boundary; goal-artifact lifecycle projection (#3136) proposes a read-model boundary and post-outcome memory utility attribution from RFC #3215 is implemented (#3280). Post-v0.5.3, periodic-report post-writeback hooks ship (#3691) with terminal Todo closeout dispatch (#3748), pending-intent consumption (#3749), Chinese-analysis editorial requirement (#3750), rejected-draft reopen (#3751), actual work interval (#3754), approved delivery bound to the Goal Channel Bot (#3755), and normalized card readback (#3757); the dashboard retains startup error context (#3745), keeps the default status source same-origin (#3732), and avoids an unbound lark-cli variable (#3738); doctor validates deep checks by install kind (#3736); Frontstage Pages PR validation is isolated (#3728); Luna ships as a bounded account-ring extension (#3737); and structured outbound mentions are verified (#3741). | Add one synthetic lifecycle or stride-boundary fixture, extend the visible-governance walkthrough with a missing negative case, or characterize the shipped `claim_work` executor with a file-backed parity fixture. Keep activation explicit and leave source bodies, draft bodies, review text, provider payloads, private locators, cursor state, and apply/publish authority outside public fixtures. | +| Governance and productization | A synthetic visible-governance slice landed (#3086) and received a Stage-2 refresh (#3657); the isolated prototype and its sole smoke are now retired in the 2.0 candidate, while canonical Goal Channel/status ownership and lease coverage remain with their existing typed owner; decision-context evidence cursors settle (#3079); the React homepage rebuild (#3098) landed; a deterministic project registry (#3170) serializes global sync; per-goal handoff mode gates claim/lease authority (#3164) and hard-lease gates auto-acquire completion keys (#3198); a repository change-window gate and pending ledger ship (#3319); goal channels default to human-gate auto-notify on new channels (#3523); coordination state rules are centralized (#3410) and the Stage 2 slice ships an aggregate head, file provider, and `claim_work` executor (#3529); project repository delivery lands through capability hooks (#3570) with gitless delivery workspaces settled (#3574) and CPA/provider-routing qualification recorded (#3576, #3573, #3563); a read-only stride shadow observation M1 (#3207), a synthetic stride-boundary shadow fixture (#3290), and the hierarchical stride RFC (#3204) open the next boundary; goal-artifact lifecycle projection (#3136) proposes a read-model boundary and post-outcome memory utility attribution from RFC #3215 is implemented (#3280). Post-v0.5.3, periodic-report post-writeback hooks ship (#3691) with terminal Todo closeout dispatch (#3748), pending-intent consumption (#3749), Chinese-analysis editorial requirement (#3750), rejected-draft reopen (#3751), actual work interval (#3754), approved delivery bound to the Goal Channel Bot (#3755), and normalized card readback (#3757); the dashboard retains startup error context (#3745), keeps the default status source same-origin (#3732), and avoids an unbound lark-cli variable (#3738); doctor validates deep checks by install kind (#3736); Frontstage Pages PR validation is isolated (#3728); Luna ships as a bounded account-ring extension (#3737); and structured outbound mentions are verified (#3741). | Extend the existing canonical Goal Channel/status owner only for demonstrated gaps; retain lease, unavailable-provider and recovery coverage in the existing canonical coordination and task-lease tests. Do not restore the retired visible-governance or `claim_work` prototype. Keep activation explicit and leave source bodies, draft bodies, review text, provider payloads, private locators, cursor state, and apply/publish authority outside public fixtures. | | Security boundaries | Four merged hardening fixes contain state-file override writes (#3140), reject shell metacharacters in launcher worker commands (#3139), validate `goal_id` in reward routes (#3138), and stop ACAO:* on unauthenticated status reads (#3137). Loopback CORS, worker-command charset, and state-file symlink containment are pinned as regression tests (#3340). GH-C90 audited the four shipped negative fixtures and added one durable mutation per boundary: path-prefix sibling containment, worker-command input redirect, absolute `goal_id` path segments, and `file://localhost` ACAO rejection (`tests/test_state_file_containment.py`, `tests/test_worker_command_validation.py`, `tests/test_feedback_goal_id_validation.py`, `tests/test_status_server_cors.py`), completing the GH-C90 slice (#3655, closing #3636). | Keep credentials, private reproduction details, and advisory coordination out of public fixtures; extend only when a new shipped boundary lacks a distinct fail-closed mutation. | | Runtime connectors and content workflows | DeepSeek Harness Turn adapter with real e2e smoke landed (#3188); OpenCode 1 and OpenCode 2 continuous goal loops ship (#3151); content-ops gained a layout template library and dense-cover defaults (#3222, #3223); provider-neutral PR program snapshots ship (#2814); `computer_use_runtime_v0` is now a machine-checkable protocol contract (#3279); a `loopx-community-discussion` public source provider (#3299) and a `loopx-repo-health` provider (#3272) extend public source coverage; goal-channel botmux runtime integration lands with terminal dispatches preserved and uncertain dispatches persisted, desktop chat routes through a loopback service, chat-wide routing and contextual inbox replies land (#3555), and managed and queued turn creation are serialized (#3542, #3562); the desktop added workspace language settings, localized write previews, and preserved heartbeat schedule and off-hours semantics (#3594); host parity, skill-delivery, and observable-handle thin pytest now cover the expanded host list. Post-v0.5.3, bot document comment scopes read from the published app (#3722), turn-start reaction replay is bounded (#3733), and the DSH plugin is one-step ready (#3725). | Add one provider-neutral parity slice for DeepSeek Harness, OpenCode 1/2, goal-channel botmux, content-ops delivery/readback, or desktop locale parity; keep raw transcripts, provider payloads, credentials, and host-local paths out of fixtures. | | Benchmark boundary | Benchmark research was reset around native runners (#3267): native Codex Goal connects to the real runtime (#3271), workers are isolated from the host (#3277), provider env binds to installed Goal profiles (#3276) with formally verified treatments (#3275) and live continuations (#3273), runtime evidence binds to the exact container (#3303), a post-run case analyst brief ships (#3289), an integrity qualification toolkit landed (#3241), source-env usage is distinguished from credential probes (#3298, #3278), and accountable closeout requires Todo validation (#3229). A provider-neutral four-arm study contract ships (#3516), alongside restricted task-source access (#3504), non-http git clones classified as network with loopback integrity probes allowed (#3510), namespaced public case ids (#3532), locked git-clone integrity boundaries (#3515), orchestrator runtime provenance, and fail-closed runtime closeout drift (#3503). Deterministic fixtures already cover GH-C99 without live scoring or uploads: `tests/capabilities/test_benchmark_four_arm_contract.py`, `tests/capabilities/test_benchmark_experiment_board.py` (orchestrator runtime provenance negatives), `tests/capabilities/test_benchmark_runtime_continuity.py` (fail-closed closeout drift), and integrity/network/task-source cases in `tests/capabilities/test_benchmark_toolkit.py`; the covered GH-C99 fixture task was retired (#3653). Public native Goal trajectory summaries now derive from compact lifecycle facts without retaining raw artifacts (#3327), completing the GH-C16 slice. Post-v0.5.3, an external agent phase ships (#3565), restricted-access suspicion adjudicates by causal use (#3747), the plan-role fidelity gate is removed (#3753), and retired fidelity references are cleaned up (#3756). Shared lifecycle, readiness, ledger, and reducer contracts remain the public seam; generic Effect Program conformance and replay tests harden settlement infrastructure but do not change benchmark scoring or authorize live runs. Live scored comparisons stay held until a fresh task-free runner lifecycle receipt proves readiness. | Extend synthetic setup/termination attribution, derive the public trajectory summary for a second non-SkillsBench adapter, or add a SWE adapter only when a second SWE route needs shared launch/observe/ingest behavior. Do not launch scoring, duplicate the controller, or expose raw task text, logs, trajectories, verifier tails, credentials, uploads, or local paths. | diff --git a/examples/visible-governance-slice-smoke.py b/examples/visible-governance-slice-smoke.py deleted file mode 100644 index 27b80bc4af..0000000000 --- a/examples/visible-governance-slice-smoke.py +++ /dev/null @@ -1,509 +0,0 @@ -#!/usr/bin/env python3 -"""Smoke-test the ``visible_governance_slice_v0`` governance projection. - -Builds a synthetic multi-agent fixture with soft claims, a task lease, -decision scopes, and a user gate, then projects the full governance slice -and validates every section. All assertions are public-safe -- no raw -paths, credentials, or private payloads. -""" - -from __future__ import annotations - -import json -import sys -from pathlib import Path - -REPO_ROOT = Path(__file__).resolve().parents[1] -if str(REPO_ROOT) not in sys.path: - sys.path.insert(0, str(REPO_ROOT)) - -from loopx.control_plane.testing.canary_harness import ( # noqa: E402 - import_fixture_authority, - run_json_cli, - write_fixture_registry, -) -from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 -from loopx.control_plane.work_items.task_lease import task_lease_dir # noqa: E402 -from loopx.quota import build_quota_should_run # noqa: E402 -from loopx.status import collect_status # noqa: E402 -from loopx.todos import ( # noqa: E402 - add_goal_todo, - complete_goal_todo, - list_goal_todos, -) -from loopx.visible_governance import ( # noqa: E402 - build_visible_governance_slice, - render_visible_governance_markdown, -) - - -GOAL_ID = "governance-slice-smoke" -AGENT_ALPHA = "codex-alpha" -AGENT_BETA = "codex-beta" - - -def _assert_public_safe_payload(payload: dict, *, label: str) -> None: - """Verify a payload contains no raw paths, URLs, or credential markers.""" - text = json.dumps(payload, sort_keys=True, ensure_ascii=False) - assert "/Users/" not in text, f"{label}: absolute path leak" - assert "https://" not in text, f"{label}: URL leak" - assert "pass" + "word" not in text.lower(), f"{label}: credential leak" - assert "api_" + "key" not in text.lower(), f"{label}: API key leak" - assert "C:\\" not in text, f"{label}: Windows path leak" - - -def _write_fixture(tmp_path: Path) -> tuple[Path, Path, Path]: - """Create a multi-agent fixture with claims, gates, and a task lease.""" - project = tmp_path / "project" - runtime = tmp_path / "runtime" - registry = project / ".loopx" / "registry.json" - state = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" - state.parent.mkdir(parents=True) - state.write_text( - "---\nstatus: active\n---\n\n" - "# Active Goal State\n\n" - "## Agent Todo\n\n" - "- [ ] Alpha advancement task\n" - "- [ ] Beta advancement task\n\n" - "## User Todo\n\n" - "- [ ] Review and approve the release scope\n", - encoding="utf-8", - ) - write_fixture_registry( - project=project, - runtime_root=runtime, - registry_path=registry, - goal_id=GOAL_ID, - domain="loopx-platform", - adapter_kind="harness_self_improvement", - registered_agents=[AGENT_ALPHA, AGENT_BETA], - quota_allowed_slots=10, - ) - return registry, runtime, state - - -def _add_agent_todo( - registry: Path, - *, - text: str, - claimed_by: str, - task_class: str = "advancement_task", - required_decision_scopes: list[str] | None = None, -) -> dict: - """Add an agent todo, optionally with required decision scopes. - - Scopes use the ``kind:granularity:scope_key`` string format - (e.g. ``"release:action:governance-bridge"``). - """ - kwargs: dict = { - "registry_path": registry, - "goal_id": GOAL_ID, - "role": "agent", - "text": text, - "task_class": task_class, - "claimed_by": claimed_by, - } - if required_decision_scopes: - kwargs["required_decision_scopes"] = required_decision_scopes - return add_goal_todo(**kwargs) - - -def _add_user_gate( - registry: Path, - *, - text: str, - blocks_agent: str | None = None, - unblocks_todo_id: str | None = None, - decision_scope: str | None = None, -) -> dict: - """Add a user gate, optionally linked to an agent todo or scope. - - ``decision_scope`` uses the ``kind:granularity:scope_key`` string format. - """ - kwargs: dict = { - "registry_path": registry, - "goal_id": GOAL_ID, - "role": "user", - "text": text, - "task_class": "user_gate", - } - if blocks_agent: - kwargs["blocks_agent"] = blocks_agent - if unblocks_todo_id: - kwargs["unblocks_todo_id"] = unblocks_todo_id - if decision_scope: - kwargs["decision_scope"] = decision_scope - return add_goal_todo(**kwargs) - - -def _add_user_action( - registry: Path, - *, - text: str, - bound_agent: str | None = None, -) -> dict: - """Add a non-blocking user action.""" - kwargs: dict = { - "registry_path": registry, - "goal_id": GOAL_ID, - "role": "user", - "text": text, - "task_class": "user_action", - } - if bound_agent: - kwargs["bound_agent"] = bound_agent - return add_goal_todo(**kwargs) - - -def assert_governance_slice(provider: str) -> None: - import tempfile - - print("=== Visible Governance Slice Smoke ===\n") - - with tempfile.TemporaryDirectory() as tmp: - tmp_path = Path(tmp) - registry, runtime, state = _write_fixture(tmp_path) - import_fixture_authority(registry, GOAL_ID, provider=provider) - # Select the existing lease policy while the imported tasks are still - # unclaimed. A soft claim never grants permission to acquire a lease. - mode = run_json_cli( - "handoff-mode", "set", "--goal-id", GOAL_ID, "--mode", "hard_lease", - "--operation-id", "governance-fixture-lease-policy", - registry_path=registry, runtime_root=runtime, - ) - assert mode["ok"] is True, mode - - # ── 1. Create agent todos with claims ── - alpha_todo = _add_agent_todo( - registry, - text="Alpha builds the core governance bridge.", - claimed_by=AGENT_ALPHA, - required_decision_scopes=["direction:action:governance-bridge"], - ) - beta_todo = _add_agent_todo( - registry, - text="Beta reviews the integration surface.", - claimed_by=AGENT_BETA, - ) - print( - f" [OK] Agent todos: alpha={alpha_todo['todo_id']}, " - f"beta={beta_todo['todo_id']}" - ) - - # ── 2. Create a user gate that covers alpha's required scope ── - gate = _add_user_gate( - registry, - text="Approve the governance bridge release scope.", - blocks_agent=AGENT_ALPHA, - decision_scope="direction:goal:*", - ) - print(f" [OK] User gate: {gate['todo_id']} (goal-scoped, blocks alpha)") - - # ── 3. Create a non-blocking user action ── - action = _add_user_action( - registry, - text="Review the dashboard budget projection.", - bound_agent=AGENT_ALPHA, - ) - print(f" [OK] User action: {action['todo_id']}") - - # ── 4. Acquire a task lease for alpha ── - lease_result = run_json_cli( - "task-lease", - "acquire", - "--goal-id", - GOAL_ID, - "--todo-id", - alpha_todo["todo_id"], - "--owner", - AGENT_ALPHA, - "--idempotency-key", - "smoke-lease-key-001", - "--ttl-seconds", - "2700", - "--write-scope", - "governance-bridge/*", - registry_path=registry, - runtime_root=runtime, - ) - assert lease_result["ok"] is True, f"lease acquire failed: {lease_result}" - assert lease_result["acquired"] is True - canonical_before = read_canonical_todos_if_promoted( - runtime_root=runtime, goal_id=GOAL_ID, include_leases=True, - ) - assert canonical_before["leases"] == [lease_result["lease"]], canonical_before - # A stale legacy lease file must not inject a second canonical lease. - obsolete_dir = task_lease_dir(runtime_root=runtime, goal_id=GOAL_ID) - obsolete_dir.mkdir(parents=True, exist_ok=True) - (obsolete_dir / "todo_forged.json").write_text(json.dumps({ - **lease_result["lease"], "todo_id": "todo_forged", "owner": AGENT_BETA, - }), encoding="utf-8") - print(f" [OK] Task lease acquired for alpha on {alpha_todo['todo_id']}") - - # ── 5. Collect status ── - status_payload = collect_status( - registry_path=registry, - runtime_root_override=str(runtime), - scan_roots=[Path(__file__).resolve()], - limit=20, - ) - assert isinstance(status_payload, dict) - print(" [OK] Status collected") - - # ── 6. Build quota should_run for alpha ── - decision = build_quota_should_run( - status_payload, - goal_id=GOAL_ID, - agent_id=AGENT_ALPHA, - ) - assert isinstance(decision, dict) - assert "effective_action" in decision - print(f" [OK] Quota should_run built: effective={decision['effective_action']}") - - # ── 7. Build visible governance slice ── - gov_slice = build_visible_governance_slice( - status_payload, - goal_id=GOAL_ID, - agent_id=AGENT_ALPHA, - include_task_leases=True, - ) - assert gov_slice["ok"] is True, f"slice not ok: {gov_slice}" - assert gov_slice["schema_version"] == "visible_governance_slice_v0" - assert gov_slice["goal_id"] == GOAL_ID - assert gov_slice["agent_id"] == AGENT_ALPHA - - # ── 8. Validate every section ── - - # claims - claims = gov_slice["claims"] - assert isinstance(claims, dict) - assert claims["total_claims"] >= 2, f"expected >= 2 claims, got {claims}" - assert claims["claiming_agent_count"] >= 2 - assert claims["current_agent_claims"] >= 1 - assert AGENT_ALPHA in claims["by_agent"] - assert AGENT_BETA in claims["by_agent"] - for c in claims["claims"]: - assert "todo_id" in c - assert "claimed_by" in c - print( - f" [OK] Claims: {claims['total_claims']} claims " - f"across {claims['claiming_agent_count']} agents" - ) - - # leases - leases = gov_slice["leases"] - assert isinstance(leases, dict) - assert leases["active_leases"] == 1, leases - assert leases["current_agent_active_leases"] == 1, leases - assert leases["leases"][0]["todo_id"] == alpha_todo["todo_id"], leases - assert read_canonical_todos_if_promoted( - runtime_root=runtime, goal_id=GOAL_ID, include_leases=True, - ) == canonical_before - assert leases["nature"] == "policy-selected runtime fence, not write authority" - for lease_entry in leases["leases"]: - assert "todo_id" in lease_entry - assert "owner" in lease_entry - assert "expires_at" in lease_entry - print( - f" [OK] Leases: {leases['active_leases']} active, " - f"nature='{leases['nature']}'" - ) - - # quota - quota = gov_slice["quota"] - assert isinstance(quota, dict) - for key in ("should_run", "effective_action", "state"): - assert key in quota, f"quota missing {key}" - print(f" [OK] Quota: should_run={quota['should_run']}, action={quota['effective_action']}") - - # scheduler hints - sched = gov_slice["scheduler_hints"] - if sched is not None: - assert isinstance(sched, dict) - for key in ("action", "cadence_class", "arbitration"): - assert key in sched, f"scheduler_hints missing {key}" - print(f" [OK] Scheduler hints: cadence={sched['cadence_class']}") - else: - print(" [OK] Scheduler hints: None (no agent-lane context)") - - # decision scopes - scopes = gov_slice["decision_scopes"] - assert isinstance(scopes, dict) - consistency = scopes["required_scope_consistency"] - assert isinstance(consistency, dict) - assert "status" in consistency - print( - f" [OK] Decision scopes: consistency={consistency['status']}, " - f"checked={consistency['checked_agent_todo_count']} todos" - ) - - # authority boundary (implementation availability, not per-Goal promotion) - boundary = gov_slice["authority_boundary"] - assert isinstance(boundary, list) - assert len(boundary) >= 5, f"expected >= 5 RFC entries, got {len(boundary)}" - expected_shipped = { - "3 -- State Classification": True, - "4 -- Coordination Ledger Shape": False, - "5.1 -- claim_work command": False, - "7 -- Receipt Retention": True, - "8 -- Local vs Shared Mode": True, - "Appendix B -- handoff_mode": True, - "9 -- Offline/Local Boundaries": False, - } - by_section = {entry["rfc_section"]: entry for entry in boundary} - for section, expect_shipped in expected_shipped.items(): - assert section in by_section, f"missing RFC section: {section}" - entry = by_section[section] - assert "proposal" in entry - assert "shipped_equivalent" in entry - assert "gap" in entry - assert entry["shipped"] is expect_shipped, ( - f"{section}: shipped={entry['shipped']!r}, " - f"expected {expect_shipped} after native prototype retirement" - ) - claim_work = by_section["5.1 -- claim_work command"] - claim_text = ( - f"{claim_work['shipped_equivalent']} {claim_work['gap']}".lower() - ) - assert "retired" in claim_text and "todo_claim.ts" in claim_text - assert "not current execution authority" in claim_text - shipped_count = sum(1 for e in boundary if e["shipped"]) - assert shipped_count == sum( - 1 for value in expected_shipped.values() if value - ), f"unexpected shipped count: {shipped_count}" - print( - f" [OK] Authority boundary: {len(boundary)} RFC sections, " - f"{shipped_count} implemented, " - f"{len(boundary) - shipped_count} original proposals not shipped" - ) - - # ── Negative: active lease is never write authority ── - assert leases["active_leases"] >= 1 - assert leases["nature"] == "policy-selected runtime fence, not write authority" - lease_blob = json.dumps(leases, sort_keys=True).lower() - for forbidden in ( - "grants write authority", - "grants runtime authority", - "browser write", - "write api", - ): - assert forbidden not in lease_blob, ( - f"leases projection must not present '{forbidden}'" - ) - # The coverage-only claim_work reference must not imply lease authority. - assert claim_work["shipped"] is False - assert "policy-selected runtime fence" in leases["nature"] - print( - " [OK] Negative: active leases remain fence-only " - "(not write authority / no browser write API)" - ) - - # ── 9. Public safety scan ── - _assert_public_safe_payload(gov_slice, label="governance-slice") - print(" [OK] Public safety scan passes") - - # ── 10. include_task_leases=False ── - no_lease_slice = build_visible_governance_slice( - status_payload, - goal_id=GOAL_ID, - agent_id=AGENT_ALPHA, - include_task_leases=False, - ) - assert no_lease_slice["leases"] is None - print(" [OK] include_task_leases=False omits leases section") - - # ── 11. Missing goal_id ── - bad_slice = build_visible_governance_slice(status_payload, goal_id="") - assert bad_slice["ok"] is False - assert "error" in bad_slice - print(" [OK] Empty goal_id returns error") - - # ── 12. agent_id=None works ── - no_agent_slice = build_visible_governance_slice( - status_payload, - goal_id=GOAL_ID, - agent_id=None, - ) - assert no_agent_slice["ok"] is True - assert no_agent_slice["agent_id"] is None - print(" [OK] agent_id=None produces valid slice") - - # ── 13. Markdown rendering ── - markdown = render_visible_governance_markdown(gov_slice) - assert "Visible Governance Slice" in markdown - assert GOAL_ID in markdown - assert AGENT_ALPHA in markdown - assert "Claims (Soft Ownership)" in markdown - assert "Task Leases (Policy-Selected Runtime Fence)" in markdown - assert "Quota Decision" in markdown - assert "Scheduler Hints" in markdown - assert "Decision Scopes" in markdown - assert "Authority Boundary" in markdown - assert "policy-selected runtime fence, not write authority" in markdown - assert "Proposals shipped" in markdown - assert "Stage-2" in markdown or "Stage 2" in markdown or "additive" in markdown - assert "not write authority" in markdown.lower() or ( - "optional fence, not write authority" in markdown.lower() - ) - # Verify no private markers in rendered markdown - assert "/Users/" not in markdown - assert "https://" not in markdown - assert "C:\\" not in markdown - print(" [OK] Markdown render includes all sections") - print(" [OK] Markdown is public-safe") - print(" [OK] Markdown keeps Stage-2 / lease-authority boundary explicit") - - # ── 14. Complete the gate and verify standing authority ── - complete_goal_todo( - registry_path=registry, - goal_id=GOAL_ID, - todo_id=gate["todo_id"], - role="user", - agent_id=AGENT_ALPHA, - decision_outcome="approve", - ) - status2 = collect_status( - registry_path=registry, - runtime_root_override=str(runtime), - scan_roots=[Path(__file__).resolve()], - limit=20, - ) - gov_slice2 = build_visible_governance_slice( - status2, - goal_id=GOAL_ID, - agent_id=AGENT_ALPHA, - ) - standing = gov_slice2["decision_scopes"]["standing_decision_authority"] - if standing is not None: - assert standing["active_count"] >= 1 - print( - f" [OK] Standing authority after gate approval: " - f"{standing['active_count']} active receipts" - ) - else: - print(" [OK] Standing authority: None (gate scoped to different items)") - - # ── 15. Verify the slice is read-only (no mutations in builder) ── - todos_before = list_goal_todos(registry_path=registry, goal_id=GOAL_ID) - _recheck = build_visible_governance_slice( - status2, - goal_id=GOAL_ID, - agent_id=AGENT_BETA, - include_task_leases=True, - ) - todos_after = list_goal_todos(registry_path=registry, goal_id=GOAL_ID) - assert todos_before == todos_after, ( - "build_visible_governance_slice must not mutate todos" - ) - print(" [OK] Slice builder is read-only (no mutations)") - - print("\n=== All smoke checks passed ===") - - -def main() -> None: - for provider in ("file", "sqlite"): - assert_governance_slice(provider) - - -if __name__ == "__main__": - main() diff --git a/loopx/visible_governance.py b/loopx/visible_governance.py deleted file mode 100644 index 24a6ec52ac..0000000000 --- a/loopx/visible_governance.py +++ /dev/null @@ -1,615 +0,0 @@ -"""Synthetic visible-governance slice for GH-C62. - -Projects per-goal/per-agent claims, optional task leases, quota, scheduler -hints, decision scopes, and the shared-goal RFC's proposed authority/state- -provider boundary into one read-only projection. Every section makes -proposal-versus-shipped-truth explicit; no write API, no inference from -prose, no presentation of provider observations or leases as authority. -""" - -from __future__ import annotations - -from typing import Any - -from .control_plane.quota.decision_summary import compact_quota_decision -from .control_plane.todos.decision_scope import ( - build_required_decision_scope_consistency, -) -from .control_plane.todos.contract import ( - normalize_todo_claimed_by, - normalize_todo_id, -) - - -# ── schema constants ──────────────────────────────────────────────────────── -GOVERNANCE_SLICE_SCHEMA = "visible_governance_slice_v0" -LEASE_NATURE = "policy-selected runtime fence, not write authority" - - -# ── authority-boundary static table ───────────────────────────────────────── -def _build_authority_boundary_table() -> list[dict[str, Any]]: - """Implementation availability, not a claim that this Goal was promoted. - - Keep the original RFC section identifiers for report consumers. The original - prototype commands/formats are distinct from their shipped native successors. - Per-Goal authority must still be read from its actual binding and writer fence. - """ - return [ - { - "rfc_section": "3 -- State Classification", - "proposal": "Shared canonical Todo lifecycle, claims, leases and operation receipts", - "shipped": True, - "shipped_equivalent": ( - "Promoted Goals use native TS commands and AuthorityStore transactions. " - "Unpromoted sources remain readable for inspection and import; " - "normal mutations require canonical authority." - ), - "gap": "Implementation availability does not select the default provider or migrate this Goal.", - }, - { - "rfc_section": "4 -- Coordination Ledger Shape", - "proposal": "Original coordination head aggregate with an embedded receipt index", - "shipped": False, - "shipped_equivalent": ( - "The Python prototype is retired. Native File/SQLite journals retain " - "head, committed history and receipts through one AuthorityStore contract. " - "PostgreSQL and NoKV have separate candidate qualification." - ), - "gap": "The retired prototype head is not a supported Goal journal or migration source.", - }, - { - "rfc_section": "5.1 -- claim_work command", - "proposal": "Atomic claim, lease and receipt publication", - "shipped": False, - "shipped_equivalent": ( - "The experimental claim_work executor is retired. Native todo_claim.ts " - "owns atomic claim/lease acquisition on canonical providers." - ), - "gap": ( - "A historical receipt is not current execution authority. Lease/effect " - "checks and the actual Goal binding remain required; claim_work is not a public command." - ), - }, - { - "rfc_section": "7 -- Receipt Retention", - "proposal": "Retain original receipts and fail closed on unproved outcomes", - "shipped": True, - "shipped_equivalent": "Native AuthorityStore readReceipt and committed history preserve replay evidence.", - "gap": "Receipt correctness does not qualify unbounded capacity or the SQLite D2 profile.", - }, - { - "rfc_section": "8 -- Local vs Shared Mode", - "proposal": "Explicit provider binding, writer fencing and migration parity", - "shipped": True, - "shipped_equivalent": "Reviewed per-Goal promotion and fenced rollback are implemented.", - "gap": "Whole-Goal cohort qualification and default onboarding remain separate from per-Goal opt-in.", - }, - { - "rfc_section": "Appendix B -- handoff_mode", - "proposal": "Explicit legacy, soft_claim or hard_lease coordination mode", - "shipped": True, - "shipped_equivalent": "The native handoff-mode command owns transition admission and quiescence checks.", - "gap": "Provider selection does not silently replace the Goal's chosen handoff mode.", - }, - { - "rfc_section": "9 -- Offline/Local Boundaries", - "proposal": "Unavailable shared authority must not silently fall back to local writers", - "shipped": False, - "shipped_equivalent": "Promoted local providers fail closed when canonical authority cannot be read.", - "gap": "Deployed cross-host service availability and execution-interval fencing need separate qualification.", - }, - ] - - -# ── claims projection ─────────────────────────────────────────────────────── -def _build_claims_projection( - status_payload: dict[str, Any], - *, - goal_id: str, - agent_id: str | None, -) -> dict[str, Any]: - """Extract per-todo soft claims from the status attention queue. - - Looks inside ``agent_todos`` summary lists (``first_open_items``, - ``first_executable_items``) within each goal-level attention queue - item to find todos with a ``claimed_by`` field. - """ - attention = ( - status_payload.get("attention_queue") - if isinstance(status_payload.get("attention_queue"), dict) - else {} - ) - items = attention.get("items") if isinstance(attention.get("items"), list) else [] - - claims: list[dict[str, Any]] = [] - by_agent: dict[str, list[dict[str, Any]]] = {} - seen: set[str] = set() - - _SUMMARY_LIST_KEYS = ( - "first_open_items", - "first_executable_items", - "executable_backlog_items", - "backlog_items", - ) - - for item in items: - if not isinstance(item, dict): - continue - item_goal = str(item.get("goal_id") or "") - if item_goal and item_goal != goal_id: - continue - - agent_todos = ( - item.get("agent_todos") - if isinstance(item.get("agent_todos"), dict) - else {} - ) - for list_key in _SUMMARY_LIST_KEYS: - todo_list = agent_todos.get(list_key) - if not isinstance(todo_list, list): - continue - for td in todo_list: - if not isinstance(td, dict): - continue - todo_id = normalize_todo_id(td.get("todo_id")) - claimed_by = normalize_todo_claimed_by(td.get("claimed_by")) - if not claimed_by: - continue - if todo_id in seen: - continue - seen.add(todo_id) - claim_entry: dict[str, Any] = { - "todo_id": todo_id, - "claimed_by": claimed_by, - "task_class": td.get("task_class"), - "status": td.get("status") or ( - "done" if td.get("done") else "open" - ), - } - for key in ("text", "target_key", "action_kind"): - value = td.get(key) - if value: - claim_entry[key] = str(value)[:200] - claims.append(claim_entry) - by_agent.setdefault(claimed_by, []).append(claim_entry) - - normalized_agent = normalize_todo_claimed_by(agent_id) - return { - "total_claims": len(claims), - "claiming_agent_count": len(by_agent), - "current_agent_claims": len(by_agent.get(normalized_agent, [])) - if normalized_agent - else 0, - "by_agent": { - agent: len(agent_claims) - for agent, agent_claims in sorted(by_agent.items()) - }, - "claims": claims, - } - - -# ── leases projection ─────────────────────────────────────────────────────── -def _build_leases_projection( - status_payload: dict[str, Any], - *, - goal_id: str, - agent_id: str | None, - include_task_leases: bool, -) -> dict[str, Any] | None: - """Read active task leases for the goal (read-only, never writes).""" - if not include_task_leases: - return None - - from .control_plane.work_items.task_lease import ( - lease_epoch, - lease_is_active, - read_lease, - task_lease_dir, - ) - from .control_plane.coordination.local_authority import read_canonical_todos_if_promoted - - runtime_root_raw = status_payload.get("runtime_root") - if not runtime_root_raw: - return {"active_leases": 0, "leases": [], "nature": LEASE_NATURE} - - from pathlib import Path - - runtime_root = Path(str(runtime_root_raw)) - canonical = read_canonical_todos_if_promoted( - runtime_root=runtime_root, goal_id=goal_id, include_leases=True, - ) - if canonical is not None: - leases = canonical["leases"] - else: - lease_dir = task_lease_dir(runtime_root=runtime_root, goal_id=goal_id) - leases = [read_lease(path) for path in sorted(lease_dir.glob("todo_*.json"))] - - active_leases: list[dict[str, Any]] = [] - for lease in leases: - if not lease_is_active(lease): - continue - active_leases.append( - { - "todo_id": lease.get("todo_id"), - "owner": lease.get("owner"), - "expires_at": lease.get("expires_at"), - "version": lease.get("version"), - "lease_epoch": lease_epoch(lease), - "write_scopes": lease.get("write_scopes") or [], - "acquire_ttl_seconds": lease.get("acquire_ttl_seconds"), - } - ) - - normalized_agent = normalize_todo_claimed_by(agent_id) - return { - "active_leases": len(active_leases), - "current_agent_active_leases": sum( - 1 - for lease in active_leases - if lease.get("owner") == normalized_agent - ), - "leases": active_leases, - "nature": LEASE_NATURE, - } - - -# ── quota projection ──────────────────────────────────────────────────────── -def _build_quota_projection( - status_payload: dict[str, Any], - *, - goal_id: str, - agent_id: str | None, -) -> dict[str, Any] | None: - """Compact quota decision via ``build_quota_should_run``.""" - from .quota import build_quota_should_run - - try: - decision = build_quota_should_run( - status_payload, - goal_id=goal_id, - agent_id=agent_id, - ) - except (ValueError, KeyError, OSError): - return None - return compact_quota_decision(decision) - - -# ── scheduler hints projection ────────────────────────────────────────────── -def _build_scheduler_hints_projection( - status_payload: dict[str, Any], - *, - goal_id: str, - agent_id: str | None, -) -> dict[str, Any] | None: - """Extract scheduler hints from the quota should-run packet.""" - from .quota import build_quota_should_run - - try: - decision = build_quota_should_run( - status_payload, - goal_id=goal_id, - agent_id=agent_id, - include_scheduler_detail=True, - ) - except (ValueError, KeyError, OSError): - return None - - scheduler = decision.get("scheduler_hint") - if not isinstance(scheduler, dict): - return None - - return { - "action": scheduler.get("action"), - "cadence_class": scheduler.get("cadence_class"), - "recommended_interval_minutes": scheduler.get( - "recommended_interval_minutes" - ), - "max_interval_minutes": scheduler.get("max_interval_minutes"), - "reset_policy": scheduler.get("reset_policy"), - "unchanged_poll": { - k: scheduler.get("unchanged_poll", {}).get(k) - for k in ("backoff_multiplier", "max_interval_minutes") - if k in scheduler.get("unchanged_poll", {}) - } - if isinstance(scheduler.get("unchanged_poll"), dict) - else None, - "arbitration": scheduler.get("arbitration_disposition"), - } - - -# ── decision scopes projection ────────────────────────────────────────────── -def _build_decision_scopes_projection( - status_payload: dict[str, Any], - *, - goal_id: str, - agent_id: str | None, -) -> dict[str, Any]: - """Project decision scopes from quota should-run summaries. - - Uses ``build_quota_should_run`` to obtain agent/user todo summaries - and standing decision authority, then runs the consistency check. - """ - from .quota import build_quota_should_run - - try: - decision = build_quota_should_run( - status_payload, - goal_id=goal_id, - agent_id=agent_id, - ) - except (ValueError, KeyError, OSError): - return { - "standing_decision_authority": None, - "required_scope_consistency": { - "ok": None, - "status": "unavailable", - }, - } - - # standing authority already computed by the quota guard - standing = decision.get("standing_decision_authority") - - # Build consistency from the summaries in the should_run payload - agent_summary = decision.get("agent_todo_summary") - user_summary = decision.get("user_todo_summary") - consistency = build_required_decision_scope_consistency( - agent_todo_summary=agent_summary if isinstance(agent_summary, dict) else None, - user_todo_summary=user_summary if isinstance(user_summary, dict) else None, - agent_id=agent_id, - registered_agent_ids=None, - standing_decision_authority=standing, - ) - - return { - "standing_decision_authority": standing, - "required_scope_consistency": { - "ok": consistency.get("ok"), - "status": consistency.get("status"), - "checked_agent_todo_count": consistency.get( - "checked_agent_todo_count" - ), - "checked_required_scope_count": consistency.get( - "checked_required_scope_count" - ), - "standing_authority_match_count": consistency.get( - "standing_authority_match_count" - ), - "terminal_outcome_count": consistency.get("terminal_outcome_count"), - }, - } - - -# ── main builder ──────────────────────────────────────────────────────────── -def build_visible_governance_slice( - status_payload: dict[str, Any], - *, - goal_id: str, - agent_id: str | None = None, - include_task_leases: bool = True, -) -> dict[str, Any]: - """Synthesize a read-only visible-governance slice for one goal. - - Args: - status_payload: Full ``collect_status(...)`` result. - goal_id: Goal to project governance for. - agent_id: Optional agent lane to scope per-agent projections. - include_task_leases: When True, read active lease files (read-only). - - Returns: - ``{"schema_version": "visible_governance_slice_v0", "ok": True, ...}`` - with sections: claims, leases, quota, scheduler_hints, - decision_scopes, authority_boundary. - """ - safe_goal_id = str(goal_id or "").strip() - if not safe_goal_id: - return { - "schema_version": GOVERNANCE_SLICE_SCHEMA, - "ok": False, - "error": "goal_id is required", - } - - claims = _build_claims_projection( - status_payload, goal_id=safe_goal_id, agent_id=agent_id - ) - leases = _build_leases_projection( - status_payload, - goal_id=safe_goal_id, - agent_id=agent_id, - include_task_leases=include_task_leases, - ) - quota = _build_quota_projection( - status_payload, goal_id=safe_goal_id, agent_id=agent_id - ) - scheduler_hints = _build_scheduler_hints_projection( - status_payload, goal_id=safe_goal_id, agent_id=agent_id - ) - decision_scopes = _build_decision_scopes_projection( - status_payload, goal_id=safe_goal_id, agent_id=agent_id - ) - authority_boundary = _build_authority_boundary_table() - - return { - "schema_version": GOVERNANCE_SLICE_SCHEMA, - "ok": True, - "goal_id": safe_goal_id, - "agent_id": normalize_todo_claimed_by(agent_id), - "claims": claims, - "leases": leases, - "quota": quota, - "scheduler_hints": scheduler_hints, - "decision_scopes": decision_scopes, - "authority_boundary": authority_boundary, - } - - -# ── markdown renderer ─────────────────────────────────────────────────────── -def render_visible_governance_markdown(slice_payload: dict[str, Any]) -> str: - """Render a visible-governance slice as public-safe markdown.""" - - goal_id = str(slice_payload.get("goal_id") or "unknown") - agent_id = str(slice_payload.get("agent_id") or "") - - lines: list[str] = [] - lines.append("# Visible Governance Slice") - lines.append("") - lines.append(f"- **Goal**: `{goal_id}`") - if agent_id: - lines.append(f"- **Agent**: `{agent_id}`") - lines.append(f"- **Schema**: `{slice_payload.get('schema_version')}`") - lines.append("") - - # ── Claims ── - claims = slice_payload.get("claims") - if isinstance(claims, dict): - lines.append("## Claims (Soft Ownership)") - lines.append("") - lines.append( - f"- Total claims: **{claims.get('total_claims', 0)}** " - f"across {claims.get('claiming_agent_count', 0)} agent(s)" - ) - current = claims.get("current_agent_claims", 0) - if current: - lines.append(f"- Current agent claims: **{current}**") - by_agent = claims.get("by_agent") - if isinstance(by_agent, dict) and by_agent: - lines.append("") - lines.append("| Agent | Claim Count |") - lines.append("|-------|-------------|") - for agent, count in sorted(by_agent.items()): - lines.append(f"| `{agent}` | {count} |") - lines.append("") - - # ── Leases ── - leases = slice_payload.get("leases") - if isinstance(leases, dict): - lines.append("## Task Leases (Policy-Selected Runtime Fence)") - lines.append("") - lines.append(f"- Nature: *{leases.get('nature', LEASE_NATURE)}*") - lines.append( - f"- Active leases: **{leases.get('active_leases', 0)}**" - ) - lease_list = leases.get("leases") - if isinstance(lease_list, list) and lease_list: - lines.append("") - lines.append( - "| Todo ID | Owner | Expires | Version | TTL (s) |" - ) - lines.append( - "|---------|-------|---------|---------|---------|" - ) - for lease in lease_list: - lines.append( - f"| `{lease.get('todo_id', '')}` " - f"| `{lease.get('owner', '')}` " - f"| {lease.get('expires_at', '')} " - f"| {lease.get('version', '')} " - f"| {lease.get('acquire_ttl_seconds', '')} |" - ) - lines.append("") - - # ── Quota ── - quota = slice_payload.get("quota") - if isinstance(quota, dict): - lines.append("## Quota Decision") - lines.append("") - lines.append(f"- Should run: `{quota.get('should_run')}`") - lines.append( - f"- Effective action: `{quota.get('effective_action', '')}`" - ) - lines.append(f"- State: `{quota.get('state', '')}`") - compute = quota.get("compute") - if compute is not None: - lines.append(f"- Compute: {compute}") - slots_info = [] - for key in ("spent_slots", "allowed_slots", "slot_minutes"): - value = quota.get(key) - if value is not None: - slots_info.append(f"{key}: {value}") - if slots_info: - lines.append(f"- Slots: {', '.join(slots_info)}") - lines.append("") - - # ── Scheduler Hints ── - sched = slice_payload.get("scheduler_hints") - if isinstance(sched, dict): - lines.append("## Scheduler Hints") - lines.append("") - lines.append(f"- Action: `{sched.get('action', '')}`") - lines.append( - f"- Cadence class: `{sched.get('cadence_class', '')}`" - ) - interval = sched.get("recommended_interval_minutes") - if interval is not None: - lines.append(f"- Recommended interval: {interval} min") - lines.append( - f"- Arbitration: `{sched.get('arbitration', '')}`" - ) - lines.append("") - - # ── Decision Scopes ── - scopes = slice_payload.get("decision_scopes") - if isinstance(scopes, dict): - lines.append("## Decision Scopes") - lines.append("") - standing = scopes.get("standing_decision_authority") - if isinstance(standing, dict): - lines.append( - f"- Standing authority receipts: " - f"**{standing.get('active_count', 0)}** active, " - f"{standing.get('inactive_count', 0)} inactive" - ) - consistency = scopes.get("required_scope_consistency") - if isinstance(consistency, dict): - lines.append( - f"- Scope consistency: " - f"`{consistency.get('status', 'unknown')}` " - f"(checked {consistency.get('checked_agent_todo_count', 0)} " - f"todos, {consistency.get('checked_required_scope_count', 0)} " - f"scopes)" - ) - matches = consistency.get("standing_authority_match_count", 0) - if matches: - lines.append( - f"- Standing authority matches: **{matches}**" - ) - lines.append("") - - # ── Authority Boundary ── - boundary = slice_payload.get("authority_boundary") - if isinstance(boundary, list): - lines.append("## Authority Boundary: Proposal vs Shipped") - lines.append("") - lines.append( - "| RFC Section | Shipped | Gap |" - ) - lines.append( - "|-------------|---------|-----|" - ) - for entry in boundary: - shipped = "Yes" if entry.get("shipped") else "**No**" - gap = str(entry.get("gap") or "")[:120] - lines.append( - f"| {entry.get('rfc_section', '')} " - f"| {shipped} " - f"| {gap} |" - ) - lines.append("") - - # ── Summary ── - lines.append("## Summary") - lines.append("") - lines.append( - f"- **Proposals shipped**: " - f"{sum(1 for e in (boundary or []) if e.get('shipped'))} / " - f"{len(boundary) if boundary else 0}" - ) - lines.append( - "- **Key takeaway**: The original Stage-2 prototype is retired. " - "Canonical providers own normal Todo mutations and task leases. " - "soft_claim forbids lease acquisition; hard_lease enforces the lease " - "fence. Neither policy grants general write authority. " - "Installed adoption and cross-host qualification remain separate." - ) - lines.append("") - - return "\n".join(lines) diff --git a/tests/control_plane/test_canonical_goal_channel_coordination.py b/tests/control_plane/test_canonical_goal_channel_coordination.py index 6de461b1d7..7359432f40 100644 --- a/tests/control_plane/test_canonical_goal_channel_coordination.py +++ b/tests/control_plane/test_canonical_goal_channel_coordination.py @@ -100,3 +100,73 @@ def channels(value): assert all(item['active_leases'] == [] for item in found) assert all(item['coordination_observation']['source_authority'] == provider + '_v0' for item in found) assert state.read_bytes() == before + + +@pytest.mark.parametrize('provider', ['file', 'sqlite']) +def test_real_cli_reports_native_lease_and_recovers_provider_without_legacy_fallback(tmp_path, provider): + from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted + from loopx.control_plane.testing.canary_harness import import_fixture_authority, run_json_cli, run_json_cli_result + from test_goal_channel_hard_lease_visibility import _write_status_workspace + + registry, repo, runtime = _write_status_workspace(tmp_path) + state = repo / 'ACTIVE_GOAL_STATE.md' + import_fixture_authority(registry, GOAL_ID, provider=provider) + + def cli(*args): + return run_json_cli(*args, registry_path=registry, runtime_root=runtime) + + cli('handoff-mode', 'set', '--goal-id', GOAL_ID, '--mode', 'hard_lease', + '--operation-id', 'channel-lease-fixture-policy') + task = cli('todo', 'add', '--goal-id', GOAL_ID, '--role', 'agent', + '--text', 'Observe the current canonical lease.', '--task-class', 'advancement_task', + '--claimed-by', 'agent-a') + lease = cli('task-lease', 'acquire', '--goal-id', GOAL_ID, '--todo-id', task['todo_id'], + '--owner', 'agent-a', '--idempotency-key', 'channel-lease-fixture', '--ttl-seconds', '600') + assert lease['acquired'] is True + original = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID, include_leases=True) + source, registry_bytes = state.read_bytes(), registry.read_bytes() + _write_active_lease(runtime, owner='stale-agent', todo_id=task['todo_id']) + + def channel(): + status = cli('status', '--goal-id', GOAL_ID) + items = status['attention_queue']['items'] + item = next(row for row in items if row['goal_id'] == GOAL_ID) + return item['goal_channel_projection'] + + def assert_current(view): + assert view['coordination_observation']['source_authority'] == provider + '_v0' + assert view['coordination_authority'] == {'state': 'promoted'} + hard = [row for row in view['active_leases'] if row['status'] == 'hard_lease'] + assert len(hard) == 1, view + assert hard[0]['todo_id'] == task['todo_id'] and hard[0]['owner_agent'] == 'agent-a', hard + assert hard[0]['lease_version'] == lease['lease']['version'] + assert all(row.get('owner_agent') != 'stale-agent' for row in view['active_leases']) + assert view['truth_contract']['write_authority'] == 'none' + + assert_current(channel()) + state.unlink() + assert_current(channel()) + assert not state.exists(), 'status must not reconstruct the display' + state.write_bytes(source) + + authority, quarantine = runtime / 'authority', runtime / 'provider-quarantine' + authority.rename(quarantine) + try: + unavailable = build_goal_channel_projection( + goal_id=GOAL_ID, status_item=_status_item(claimed_by='stale-agent'), runtime_root=runtime, + ) + assert unavailable['coordination_observation']['status'] == 'unavailable' + assert unavailable['coordination_authority'] == {'state': 'unavailable'} + assert unavailable['active_leases'] == [] + assert unavailable['truth_contract']['write_authority'] == 'none' + code, rejected = run_json_cli_result('status', '--goal-id', GOAL_ID, + registry_path=registry, runtime_root=runtime) + assert code == 1 and rejected['ok'] is False, rejected + assert rejected['attention_queue']['items'][0]['status'] == 'status_collection_failed' + assert 'canonical Todo authority is unavailable' in rejected['error'] + assert 'stale-agent' not in json.dumps(rejected) + finally: + quarantine.rename(authority) + assert_current(channel()) + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID, include_leases=True) == original + assert state.read_bytes() == source and registry.read_bytes() == registry_bytes From 3df099234da8b802d1a2affea657cf3c9c161a9e Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:40:35 +0800 Subject: [PATCH 16/38] fix: preserve canonical failure successor lineage and historical parents Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- demo/auto_research/worker_runtime.py | 53 ++++--- demo/multi_agent/role_successor.py | 11 +- .../auto-research-role-profile-v0.md | 14 ++ examples/auto-research-worker-turn-smoke.py | 138 +++++++++++++++--- 4 files changed, 172 insertions(+), 44 deletions(-) diff --git a/demo/auto_research/worker_runtime.py b/demo/auto_research/worker_runtime.py index 9937201e44..fe90a20eb5 100644 --- a/demo/auto_research/worker_runtime.py +++ b/demo/auto_research/worker_runtime.py @@ -34,6 +34,9 @@ normalize_todo_id, ) from loopx.control_plane.todos.handoff_mode import HANDOFF_MODE_HARD_LEASE +from loopx.control_plane.coordination.local_authority import ( + LocalCoordinationAuthorityUnavailable, read_canonical_todos_if_promoted, +) from loopx.control_plane.work_items.task_lease import ( TaskLeaseError, acquire_task_lease, @@ -388,28 +391,38 @@ def _maybe_add_role_successor_todos( action=action, ) failure_continuation = decision_summary.get("failure_continuation") - if isinstance(failure_continuation, dict): - next_outcome = str(failure_continuation.get("next_outcome") or "") - successor_specs = [ - spec - for spec in successor_specs - if spec.get("action_kind") == next_outcome - ] text_source_todo_id = None if isinstance(failure_continuation, dict): - text_source_todo_id = str(failure_continuation.get("source_todo_id") or "") or None - return apply_role_successor_todos( - registry_path=registry_path, - goal_id=goal_id, - source_todo_id=source_todo_id, - current_agent_id=agent_id, - role_id=role_id, - action=action, - successor_specs=successor_specs, - decision_summary=decision_summary, - execute=execute, - text_source_todo_id=text_source_todo_id, - ) + next_outcome = str(failure_continuation.get("next_outcome") or "") + successor_specs = [spec for spec in successor_specs if spec.get("action_kind") == next_outcome] + source_todo_id = str(failure_continuation.get("source_todo_id") or "") or source_todo_id + text_source_todo_id = source_todo_id + def apply_binding(parent_id: str, *, revision: str | None = None) -> dict[str, object]: + return apply_role_successor_todos(registry_path=registry_path, goal_id=goal_id, + source_todo_id=parent_id, current_agent_id=agent_id, role_id=role_id, + action=action, successor_specs=successor_specs, decision_summary=decision_summary, + execute=execute, text_source_todo_id=text_source_todo_id, + expected_provider_revision=revision) + + try: + return apply_binding(source_todo_id) + except LocalCoordinationAuthorityUnavailable as error: + if error.code != 'todo_semantic_duplicate_conflict' or text_source_todo_id is None: + raise + # Native create owns normalized title/intent matching. Qualify only its + # exact conflicting record, without rewriting a historical parent. + snapshot = read_canonical_todos_if_promoted( + runtime_root=runtime_root_from_registry(registry_path, None), goal_id=goal_id, + ) + todos = {row['todo_id']: row for row in (snapshot or {}).get('todos', [])} + existing = todos.get(error.payload.get('todo_id')) + parent_id = existing.get('unblocks_todo_id') if existing else None + parent = todos.get(parent_id) + if not (parent and parent['role'] == 'agent' and parent.get('action_kind') in SUMMARY_ACTIONS + and parent.get('unblocks_todo_id') == text_source_todo_id): + raise + return apply_binding(parent_id, revision=snapshot['provider_revision']) + def _executed_successor_todo_ids(successor_todos: dict[str, object]) -> list[str]: diff --git a/demo/multi_agent/role_successor.py b/demo/multi_agent/role_successor.py index 5dd13cefac..4a054582a8 100644 --- a/demo/multi_agent/role_successor.py +++ b/demo/multi_agent/role_successor.py @@ -173,13 +173,13 @@ def apply_role_successor_todos( decision_summary: Mapping[str, object], execute: bool, text_source_todo_id: str | None = None, + expected_provider_revision: str | None = None, ) -> dict[str, object]: - """Preview or create role-declared successor todos through LoopX state. + """Create successors, retaining qualified historical source bindings. - When text_source_todo_id is provided, it is used for rendering the - successor text (so the title is stable across different summary todos - that review the same failure lineage). source_todo_id is still used - for the unblocks_todo_id binding. + Normally one source supplies both title and lineage. A caller recovering + an existing successor may preserve its original parent while rendering + the same title from the independently verified evidence lineage. """ specs = [dict(spec) for spec in successor_specs if isinstance(spec, Mapping)] @@ -257,6 +257,7 @@ def apply_role_successor_todos( action_kind=action_kind, claimed_by=claimed_by, unblocks_todo_id=source_todo_id, + expected_provider_revision=expected_provider_revision, dry_run=False, ) successors.append( diff --git a/docs/reference/protocols/auto-research-role-profile-v0.md b/docs/reference/protocols/auto-research-role-profile-v0.md index 4d17f3d005..38566eb70e 100644 --- a/docs/reference/protocols/auto-research-role-profile-v0.md +++ b/docs/reference/protocols/auto-research-role-profile-v0.md @@ -105,6 +105,20 @@ public/private boundary checks before another agent can run it. target is not met and a role-declared successor condition is satisfied, the worker must create or link that successor instead of closing the lane. +For failure continuation, the successor's title and `unblocks_todo_id` share +the failed evidence Todo from `research_failure_continuation_v0.source_todo_id`. +Several summaries of that lineage therefore create or reuse one identical +successor without rebinding its original parent. A historical successor may +retain its original summary parent when the complete canonical records prove +that summary belongs to the same failed evidence lineage; all other intent +fields still pass the native duplicate check. Each completed summary keeps +its own `successor_todo_ids` link to that successor. Other role handoffs still +bind the selected source Todo. These links grant no execution or claim rights; +the canonical owner continues to reject same-title requests with different +lineage or other intent. Historical-parent reuse binds create to the inspected +provider revision; a concurrent change refuses without rebinding and requires +a fresh read before retry. + ## Resolution Order Every visible auto-research worker should resolve its instructions in this diff --git a/examples/auto-research-worker-turn-smoke.py b/examples/auto-research-worker-turn-smoke.py index b6bf588022..650631c766 100644 --- a/examples/auto-research-worker-turn-smoke.py +++ b/examples/auto-research-worker-turn-smoke.py @@ -33,6 +33,11 @@ apply_role_successor_todos, ) from loopx.todos import add_goal_todo, complete_goal_todo # noqa: E402 +from loopx.control_plane.coordination.local_authority import ( # noqa: E402 + LocalCoordinationAuthorityUnavailable, + read_canonical_todos_if_promoted, +) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.control_plane.work_items.task_lease import ( # noqa: E402 TaskLeaseError, acquire_task_lease, @@ -173,7 +178,9 @@ def write_summary_state(path: Path) -> None: ) -def write_registry(path: Path, *, project: Path, state_file: Path, runtime_root: Path) -> None: +def write_registry( + path: Path, *, project: Path, state_file: Path, runtime_root: Path, provider: str, +) -> None: path.write_text( json.dumps( { @@ -207,6 +214,7 @@ def write_registry(path: Path, *, project: Path, state_file: Path, runtime_root: + "\n", encoding="utf-8", ) + import_fixture_authority(path, GOAL_ID, provider=provider) def append_real_fixture_evidence(*, registry: Path, runtime_root: Path, temp: Path) -> None: @@ -317,7 +325,7 @@ def append_negative_fixture_evidence( assert_public_safe(appended) -def main() -> int: +def assert_seeded_worker_turns() -> None: with tempfile.TemporaryDirectory() as temp_dir: temp = Path(temp_dir) supervisor = build_auto_research_demo_supervisor_plan( @@ -533,7 +541,7 @@ def main() -> int: successors = seeded_failure_turn["successor_todos"]["successors"] assert len(successors) == 1, seeded_failure_turn assert successors[0]["action_kind"] == "propose_failure_successor", seeded_failure_turn - assert successors[0]["unblocks_todo_id"] == evaluator_seed["todo_id"], seeded_failure_turn + assert successors[0]["unblocks_todo_id"] == executor_seed["todo_id"], seeded_failure_turn assert seeded_failure_turn["completion"]["successor_todo_ids"] == [ successors[0]["todo_id"] ], seeded_failure_turn @@ -562,6 +570,9 @@ def main() -> int: ) assert manual_lease["lease"] is None, manual_lease + + +def assert_evidence_lineage(provider: str) -> None: with tempfile.TemporaryDirectory() as temp_dir: temp = Path(temp_dir) project = temp / "project" @@ -570,7 +581,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) summary_todo = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -618,6 +629,12 @@ def main() -> int: for successor_id in successor_ids: assert successor_id in state_text, state_text assert_public_safe(evaluator_summary) + canonical = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + by_id = {row["todo_id"]: row for row in canonical["todos"]} + assert by_id[summary_todo_id]["status"] == "done", canonical + assert by_id[summary_todo_id]["claimed_by"] == EVALUATOR_AGENT_ID + assert by_id[successor_ids[0]]["claimed_by"] == CURATOR_AGENT_ID + assert by_id[successor_ids[0]]["unblocks_todo_id"] == summary_todo_id with tempfile.TemporaryDirectory() as temp_dir: temp = Path(temp_dir) @@ -627,7 +644,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) promotion_todo = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -732,7 +749,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) first_failure_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -808,7 +825,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) retry_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -892,7 +909,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) self_referential_summary = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -948,7 +965,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) positive_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -1069,7 +1086,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) failure_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -1122,7 +1139,7 @@ def main() -> int: failure_turn["frontier"]["frontier"]["selected"]["unblocks_todo_id"] == failure_evidence["todo_id"] ), failure_turn - assert successors[0]["unblocks_todo_id"] == failure_review["todo_id"], failure_turn + assert successors[0]["unblocks_todo_id"] == failure_evidence["todo_id"], failure_turn assert failure_turn["completion"]["successor_todo_ids"] == [successors[0]["todo_id"]], failure_turn state_text = state_file.read_text(encoding="utf-8") assert f"todo_id={failure_review['todo_id']} status=done" in state_text, state_text @@ -1153,7 +1170,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) data_gap_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -1213,7 +1230,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) retry_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -1268,7 +1285,7 @@ def main() -> int: retry_turn["evaluation_summary"]["failure_continuation"]["source_todo_id"] == retry_evidence["todo_id"] ), retry_turn - assert successors[0]["unblocks_todo_id"] == retry_review["todo_id"], retry_turn + assert successors[0]["unblocks_todo_id"] == retry_evidence["todo_id"], retry_turn assert retry_turn["completion"]["successor_todo_ids"] == [successors[0]["todo_id"]], retry_turn state_text = state_file.read_text(encoding="utf-8") assert f"todo_id={retry_review['todo_id']} status=done" in state_text, state_text @@ -1282,7 +1299,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) generic_todo = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -1324,7 +1341,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) append_fixture_evidence( registry=registry, runtime_root=runtime_root, @@ -1366,6 +1383,11 @@ def main() -> int: assert target_reached["frontier"]["frontier"]["selected"] is None, target_reached assert_public_safe(target_reached) + for historical_parent in (False, True): + assert_failure_successor_lineage(provider, historical_parent) + + +def assert_failure_successor_lineage(provider: str, historical_parent: bool) -> None: with tempfile.TemporaryDirectory() as temp_dir: temp = Path(temp_dir) project = temp / "project" @@ -1374,7 +1396,7 @@ def main() -> int: registry = temp / "registry.json" runtime_root = temp / "runtime" write_summary_state(state_file) - write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root) + write_registry(registry, project=project, state_file=state_file, runtime_root=runtime_root, provider=provider) failure_evidence = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, @@ -1385,7 +1407,7 @@ def main() -> int: claimed_by=EXECUTOR_AGENT_ID, dry_run=False, ) - add_goal_todo( + first_summary = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, role="agent", @@ -1396,7 +1418,7 @@ def main() -> int: unblocks_todo_id=failure_evidence["todo_id"], dry_run=False, ) - add_goal_todo( + second_summary = add_goal_todo( registry_path=registry, goal_id=GOAL_ID, role="agent", @@ -1417,6 +1439,21 @@ def main() -> int: workspace = project / "visible-workspace" workspace.mkdir() + if historical_parent: + # Seed the published pre-cutoff producer intent through native create; + # its parent must survive upgrade even when another summary retries. + historical = apply_role_successor_todos(registry_path=registry, goal_id=GOAL_ID, + source_todo_id=first_summary['todo_id'], text_source_todo_id=failure_evidence['todo_id'], + current_agent_id=EVALUATOR_AGENT_ID, role_id='evaluator_promoter', + action='summarize_evidence', execute=True, + successor_specs=[spec for spec in auto_research_successor_specs_for_action( + role_id='evaluator_promoter', action='summarize_evidence') + if spec['action_kind'] == 'propose_failure_successor'], + decision_summary={'failure_continuation': {'next_outcome': 'propose_failure_successor'}}) + assert historical['successors'][0]['added'] is True + original = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + original_source = next(row for row in original['todos'] if row['todo_id'] == failure_evidence['todo_id']) + first_turn = run_worker_turn( registry=registry, runtime_root=runtime_root, @@ -1432,6 +1469,56 @@ def main() -> int: first_successor_text = first_successors[0]["todo_command"] assert ".." not in first_successor_text, f"successor text contains ..: {first_successor_text!r}" assert_public_safe(first_turn) + first_snapshot = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + successor_id = first_successors[0]['todo_id'] + accepted = next(row for row in first_snapshot['todos'] if row['todo_id'] == successor_id) + assert accepted['unblocks_todo_id'] == (first_summary['todo_id'] if historical_parent else failure_evidence['todo_id']) + + # An explicit attempt to rebind this existing successor remains illegal. + # The producer fix must not weaken the canonical duplicate-intent fence. + try: + add_goal_todo(registry_path=registry, goal_id=GOAL_ID, role='agent', + text=accepted['text'], task_class='advancement_task', + action_kind='propose_failure_successor', claimed_by=HYPOTHESIS_AGENT_ID, + unblocks_todo_id=second_summary['todo_id'], dry_run=False) + except LocalCoordinationAuthorityUnavailable as error: + assert error.code == 'todo_semantic_duplicate_conflict', error + else: + raise AssertionError('same-title successor must not acquire another source binding') + assert read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) == first_snapshot + if historical_parent: + from loopx.control_plane.testing.canary_harness import run_json_cli + run_json_cli('todo', 'archive-completed', '--goal-id', GOAL_ID, '--role', 'agent', + '--max-active-done', '0', '--execute', + registry_path=registry, runtime_root=runtime_root) + + if historical_parent: + from unittest.mock import patch + from demo.auto_research import worker_runtime + observed_after_race = None + + def concurrent_read(**kwargs): + nonlocal observed_after_race + snapshot = read_canonical_todos_if_promoted(**kwargs) + add_goal_todo(registry_path=registry, goal_id=GOAL_ID, role='agent', + text='[P2] Independent concurrent planning work.', task_class='advancement_task', + claimed_by=CURATOR_AGENT_ID, dry_run=False) + observed_after_race = read_canonical_todos_if_promoted(**kwargs) + return snapshot + + # Intervene after a real read; both the update and CAS refusal use + # the real provider. No mock supplies the claimed postcondition. + with patch.object(worker_runtime, 'read_canonical_todos_if_promoted', concurrent_read): + try: + run_auto_research_worker_turn(registry_path=registry, + runtime_root_arg=str(runtime_root), goal_id=GOAL_ID, + agent_id=EVALUATOR_AGENT_ID, workspace=workspace, execute=True, + complete_selected_todo=True, lane_count=4, visible_lanes_accepted=True) + except LocalCoordinationAuthorityUnavailable as error: + assert error.code == 'provider_revision_mismatch', error + else: + raise AssertionError('stale historical-lineage proof must refuse') + assert read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) == observed_after_race second_turn = run_worker_turn( registry=registry, @@ -1451,7 +1538,20 @@ def main() -> int: f"second successor should have the same todo_id, got: {second_successor['todo_id']}" ) assert_public_safe(second_turn) + final = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=GOAL_ID) + rows = {row['todo_id']: row for row in final['todos']} + assert len(final['todos']) == len(original['todos']) + 1 + assert rows[successor_id] == accepted + assert rows[failure_evidence['todo_id']] == original_source + for summary in (first_summary, second_summary): + stored = rows[summary['todo_id']] + assert stored['status'] == 'done' and stored['claimed_by'] == EVALUATOR_AGENT_ID + assert stored['successor_todo_ids'] == [successor_id] +def main() -> int: + assert_seeded_worker_turns() + for provider in ("file", "sqlite"): + assert_evidence_lineage(provider) return 0 From 58277fb486bfe0689682f56d8a6e28f68f68cb79 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:40:35 +0800 Subject: [PATCH 17/38] test: migrate dreaming and durable lifecycle callers to canonical authority Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../todo-durability-fixture-smoke.py | 71 ++++++++++++------- examples/dreaming-proposal-decision-smoke.py | 52 ++++++++------ 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/examples/control_plane/todo-durability-fixture-smoke.py b/examples/control_plane/todo-durability-fixture-smoke.py index 6fc1c3cdbd..7dc2f5ffd8 100644 --- a/examples/control_plane/todo-durability-fixture-smoke.py +++ b/examples/control_plane/todo-durability-fixture-smoke.py @@ -4,7 +4,6 @@ from __future__ import annotations import json -import subprocess import sys import tempfile from pathlib import Path @@ -15,6 +14,8 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.status import parse_active_state_todos # noqa: E402 +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority, run_json_cli # noqa: E402 from loopx.rollout_event_log import ( # noqa: E402 append_rollout_event, build_rollout_event, @@ -35,30 +36,14 @@ def run_cli(*args: str, registry_path: Path, runtime: Path) -> dict: - result = subprocess.run( - [ - sys.executable, - "-m", - "loopx.cli", - "--registry", - str(registry_path), - "--runtime-root", - str(runtime), - "--format", - "json", - *args, - ], - cwd=REPO_ROOT, - check=True, - capture_output=True, - text=True, - ) - return json.loads(result.stdout) + return run_json_cli(*args, registry_path=registry_path, runtime_root=runtime, + cwd=REPO_ROOT, include_returncode=False) def state_text() -> str: archived_lines = "".join( f"- [x] [P1] Archived completed work item {index}.\n" + f" \n" for index in range(1, ARCHIVED_DONE_TODOS + 1) ) return ( @@ -273,11 +258,7 @@ def assert_pr_resume_repository_binding() -> None: ], ambiguous -def main() -> int: - assert_pr_resume_repository_binding() - parsed = parse_active_state_todos(state_text(), rollout_events=[pr_merged_event()]) - assert_parseable_agent_todos(parsed["agent_todos"]) - +def assert_lifecycle(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-todo-durability-") as tmp: registry_path, runtime = write_fixture(Path(tmp)) status_payload = run_cli("status", registry_path=registry_path, runtime=runtime) @@ -329,6 +310,26 @@ def main() -> int: assert "completed_todo_archive_warning" not in guard, guard first_todo_id = guard["agent_todo_summary"]["first_executable_items"][0]["todo_id"] + # Historical inspection above is read-only. Declare the fixture's + # existing claim owners before the reviewed import and normal writes. + registry = json.loads(registry_path.read_text(encoding='utf-8')) + registry['goals'][0]['coordination'] = { + 'agent_model': 'peer_v1', + 'registered_agents': ['fixture-agent', 'codex-product-capability', + 'codex-side-bypass', 'codex-reviewer', 'codex-builder'], + } + registry_path.write_text(json.dumps(registry), encoding='utf-8') + import_fixture_authority(registry_path, GOAL_ID, provider=provider) + original = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert original['source_authority'] == provider + '_v0' + assert original['decision_read_from_provider'] is True and original['legacy_fallback_used'] is False + assert len(original['todos']) == 5 + ARCHIVED_DONE_TODOS + assert sum(row['archive_state'] == 'archive' for row in original['todos']) == ARCHIVED_DONE_TODOS + native_guard = run_cli('quota', 'should-run', '--goal-id', GOAL_ID, + '--agent-id', 'fixture-agent', '--host-surface', 'generic_cli', + '--scheduler-owner', 'none', '--execution-mode', 'interactive', + registry_path=registry_path, runtime=runtime) + assert native_guard['selected_todo']['todo_id'] == first_todo_id lifecycle_result = run_cli( "todo", "complete", @@ -338,6 +339,7 @@ def main() -> int: "agent", "--todo-id", first_todo_id, + '--agent-id', 'fixture-agent', "--evidence", "fixture completed", "--next-agent-todo", @@ -350,18 +352,35 @@ def main() -> int: runtime=runtime, ) next_todo = lifecycle_result["next_todos"][0] - assert next_todo["todo"].startswith("[P1] Continue lifecycle"), lifecycle_result + canonical = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + rows = {row['todo_id']: row for row in canonical['todos']} + assert rows[next_todo['todo_id']]['text'].startswith('[P1] Continue lifecycle') + assert rows[first_todo_id]['status'] == 'done' + assert rows[first_todo_id]['successor_todo_ids'] == [next_todo['todo_id']] + assert len(canonical['todos']) == len(original['todos']) + 1 + for row in original['todos']: + if row['todo_id'] != first_todo_id: + assert rows[row['todo_id']] == row post_lifecycle_status = run_cli("status", registry_path=registry_path, runtime=runtime) post_lifecycle_item = attention_item(post_lifecycle_status) post_agent_todos = post_lifecycle_item["agent_todos"] assert post_agent_todos["open_count"] == 2, post_agent_todos + # The summary retains its documented non-open count, including deferred. assert post_agent_todos["done_count"] == 4, post_agent_todos assert post_agent_todos["deferred_count"] == 2, post_agent_todos assert post_agent_todos["first_open_items"][0]["priority"] == "P1", post_agent_todos assert post_agent_todos["first_open_items"][0]["todo_id"] == next_todo["todo_id"], post_agent_todos assert post_agent_todos["first_open_items"][0]["action_kind"] == "fixture_follow_up", post_agent_todos + + +def main() -> int: + assert_pr_resume_repository_binding() + parsed = parse_active_state_todos(state_text(), rollout_events=[pr_merged_event()]) + assert_parseable_agent_todos(parsed["agent_todos"]) + for provider in ('file', 'sqlite'): + assert_lifecycle(provider) print("todo-durability-fixture-smoke ok") return 0 diff --git a/examples/dreaming-proposal-decision-smoke.py b/examples/dreaming-proposal-decision-smoke.py index f368457487..f12326dd49 100644 --- a/examples/dreaming-proposal-decision-smoke.py +++ b/examples/dreaming-proposal-decision-smoke.py @@ -4,36 +4,24 @@ from __future__ import annotations import json -import subprocess import sys import tempfile from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority, run_json_cli # noqa: E402 + GOAL_ID = "dreaming-decision-fixture" def run_cli(*args: str, registry_path: Path, runtime: Path) -> dict: - result = subprocess.run( - [ - sys.executable, - "-m", - "loopx.cli", - "--registry", - str(registry_path), - "--runtime-root", - str(runtime), - "--format", - "json", - *args, - ], - cwd=REPO_ROOT, - check=True, - capture_output=True, - text=True, - ) - return json.loads(result.stdout) + return run_json_cli(*args, registry_path=registry_path, runtime_root=runtime, + cwd=REPO_ROOT, include_returncode=False) def append_recorded_proposal( @@ -86,7 +74,7 @@ def append_recorded_proposal( f.write(json.dumps(record, sort_keys=True) + "\n") -def write_fixture(root: Path) -> tuple[Path, Path, Path, Path]: +def write_fixture(root: Path, provider: str) -> tuple[Path, Path, Path, Path]: project = root / "project" runtime = root / "runtime" state_file = f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" @@ -139,6 +127,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path, Path]: + "\n", encoding="utf-8", ) + import_fixture_authority(registry_path, GOAL_ID, provider=provider) append_recorded_proposal( runs_dir, generated_at="2026-01-01T00:00:00+00:00", @@ -162,9 +151,10 @@ def assert_no_quota_spend(runtime: Path) -> None: assert "quota_slot_spent" not in text, text -def main() -> int: +def assert_decisions(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-dreaming-decision-") as tmp: - registry_path, runtime, state_path, index_path = write_fixture(Path(tmp)) + registry_path, runtime, state_path, index_path = write_fixture(Path(tmp), provider) + original = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) approve = run_cli( "dreaming", @@ -200,6 +190,15 @@ def main() -> int: assert approve["side_effects"]["runtime_history_appended"] is True, approve assert approve["side_effects"]["quota_spent"] is False, approve assert_no_quota_spend(runtime) + approved = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert approved['source_authority'] == provider + '_v0' + assert approved['decision_read_from_provider'] is True and approved['legacy_fallback_used'] is False + assert len(approved['todos']) == len(original['todos']) + 1 + promoted = next(row for row in approved['todos'] if row['todo_id'] == todo_id) + assert promoted['text'] == '[P1] Implement the approved dreaming follow-up as normal delivery.' + assert promoted['status'] == 'open' and promoted['task_class'] == 'advancement_task' + original_ids = {row['todo_id'] for row in original['todos']} + assert [row for row in approved['todos'] if row['todo_id'] in original_ids] == original['todos'] before_defer_state = state_path.read_text(encoding="utf-8") before_defer_index = index_path.read_text(encoding="utf-8") @@ -228,6 +227,7 @@ def main() -> int: assert defer["side_effects"]["runtime_history_appended"] is True, defer assert defer["side_effects"]["quota_spent"] is False, defer assert state_path.read_text(encoding="utf-8") == before_defer_state + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) == approved assert index_path.read_text(encoding="utf-8") != before_defer_index assert_no_quota_spend(runtime) @@ -257,6 +257,12 @@ def main() -> int: assert reject["side_effects"]["quota_spent"] is False, reject assert state_path.read_text(encoding="utf-8") == before_reject_state assert index_path.read_text(encoding="utf-8") == before_reject_index + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) == approved + + +def main() -> int: + for provider in ('file', 'sqlite'): + assert_decisions(provider) print("dreaming-proposal-decision-smoke ok") return 0 From e74a1287c39be2b62a7adc58f4b31233b9e9e74a Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:24:45 +0800 Subject: [PATCH 18/38] fix: recover cached material replan without repeating the Host Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../loopx-turn-path-delta-acceptance-smoke.py | 70 +++++++++++++++++-- loopx/control_plane/turn_driver/executor.py | 28 +++++++- tests/test_loopx_turn_executor.py | 39 ++++++++++- 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/examples/loopx-turn-path-delta-acceptance-smoke.py b/examples/loopx-turn-path-delta-acceptance-smoke.py index e0863556ea..3431108f53 100644 --- a/examples/loopx-turn-path-delta-acceptance-smoke.py +++ b/examples/loopx-turn-path-delta-acceptance-smoke.py @@ -16,6 +16,10 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.cli import main as cli_main # noqa: E402 +from loopx.control_plane.coordination.local_authority import ( # noqa: E402 + read_canonical_todos_if_promoted, +) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 GOAL_ID = "loopx-turn-path-delta-fixture" @@ -112,6 +116,8 @@ def _write_host(root: Path) -> Path: mode = sys.argv[1] marker = pathlib.Path({MARKER!r}) marker.write_text(mode, encoding="utf-8") +with marker.with_suffix(".calls").open("a", encoding="utf-8") as calls: + calls.write(mode + "\\n") material_packet = {{ "agent_id": {AGENT_ID!r}, "state": "active", @@ -181,6 +187,8 @@ def _run_turn( workspace: Path, host: Path, mode: str, + expect_canonical_refusal: bool = False, + resume_turn_key: str | None = None, ) -> dict[str, Any]: output = io.StringIO() with contextlib.redirect_stdout(output): @@ -200,8 +208,10 @@ def _run_turn( GOAL_ID, "--agent-id", AGENT_ID, - "--turn-instance-id", - f"path-delta-{mode}", + *( + ["--resume-turn-key", resume_turn_key] + if resume_turn_key else ["--turn-instance-id", f"path-delta-{mode}"] + ), "--project", str(workspace), "--host-adapter-command-json", @@ -215,6 +225,14 @@ def _run_turn( ] ) payload = json.loads(output.getvalue()) + if expect_canonical_refusal: + assert exit_code != 0, payload + assert payload["reason_code"] == "canonical_authority_required", payload + assert payload["write_admitted"] is False, payload + assert payload["journal_observation"]["recorded_effects"]["host_invoked"] is True, payload + assert payload["journal_observation"]["recorded_effects"]["quota_spent"] is False, payload + assert payload["journal_observation"]["recovery_decision"]["reinvoke_host"] is False, payload + return payload assert exit_code == 0, payload assert payload["status"] == "committed", payload assert payload["validation"]["status"] == "passed", payload @@ -226,19 +244,44 @@ def _run_records(runtime: Path) -> list[dict[str, Any]]: return [json.loads(line) for line in index.read_text(encoding="utf-8").splitlines()] -def main() -> int: - with tempfile.TemporaryDirectory(prefix="loopx-turn-path-delta-") as directory: +def assert_canonical_turn_path_delta(provider: str) -> None: + with tempfile.TemporaryDirectory(prefix=f"loopx-turn-path-delta-{provider}-") as directory: root = Path(directory) project, runtime, workspace, registry = _write_fixture(root) host = _write_host(root) - _run_turn( + state = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" + source_bytes = state.read_bytes() + refused = _run_turn( + registry=registry, + runtime=runtime, + project=project, + workspace=workspace, + host=host, + mode="material", + expect_canonical_refusal=True, + ) + # The Host has returned, but no Todo write/spend is admitted. Import + # the complete stopped fixture and recover that original Turn. + assert state.read_bytes() == source_bytes + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) is None + calls = (project / MARKER).with_suffix(".calls") + assert calls.read_text(encoding="utf-8").splitlines() == ["material"] + import_fixture_authority(registry, GOAL_ID, provider=provider) + original = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert original is not None + assert original["source_authority"] == f"{provider}_v0", original + assert {item["todo_id"] for item in original["todos"]} == {TODO_ID}, original + recovered = _run_turn( registry=registry, runtime=runtime, project=project, workspace=workspace, host=host, mode="material", + resume_turn_key=refused["resume_turn_key"], ) + assert recovered["resume_turn_key"] == refused["resume_turn_key"], recovered + assert calls.read_text(encoding="utf-8").splitlines() == ["material"] _run_turn( registry=registry, runtime=runtime, @@ -247,6 +290,19 @@ def main() -> int: host=host, mode="routine", ) + assert calls.read_text(encoding="utf-8").splitlines() == ["material", "routine"] + canonical = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert canonical is not None + assert canonical["source_authority"] == f"{provider}_v0", canonical + assert {item["todo_id"] for item in canonical["todos"]} == {TODO_ID}, canonical + before, after = original["todos"][0], canonical["todos"][0] + for field in ("todo_id", "text", "role", "status", "claimed_by", + "task_class", "action_kind", "priority", "successor_todo_ids"): + assert after.get(field) == before.get(field), (field, before, after) + mutable_result_fields = {"note", "evidence", "updated_at", "last_actor_agent_id"} + assert {k: v for k, v in after.items() if k not in mutable_result_fields} == { + k: v for k, v in before.items() if k not in mutable_result_fields + }, (before, after) records = _run_records(runtime) material = next( @@ -266,6 +322,10 @@ def main() -> int: assert "agent_vision" not in routine, routine assert routine["vision_checkpoint"]["decision"] == "unchanged_with_reason", routine + +def main() -> int: + for provider in ("file", "sqlite"): + assert_canonical_turn_path_delta(provider) print("loopx-turn-path-delta-acceptance-smoke ok") return 0 diff --git a/loopx/control_plane/turn_driver/executor.py b/loopx/control_plane/turn_driver/executor.py index d615697435..bdb7e09a79 100644 --- a/loopx/control_plane/turn_driver/executor.py +++ b/loopx/control_plane/turn_driver/executor.py @@ -239,6 +239,7 @@ def _normalize_host_path_delta( *, unchanged_reason: str, errors: list[str], + from_journal: bool = False, ) -> tuple[str, dict[str, Any] | None]: raw_path_delta_mode = result.get("path_delta_mode") path_delta_mode = "" @@ -268,7 +269,25 @@ def _normalize_host_path_delta( errors.append("path_delta_mode must be unchanged or material_replan") agent_vision: dict[str, Any] | None = None - if agent_vision_json: + if from_journal and "agent_vision" in result: + # The journal stores the typed owner's decoded packet. Revalidate it + # through that same owner, without treating derived budget diagnostics + # as a new Host wire string or widening the untrusted Host protocol. + try: + if "agent_vision_json" in result: + raise ValueError("cached vision cannot carry both packet forms") + packet = result["agent_vision"] + if not isinstance(packet, dict): + raise TypeError("cached agent_vision must be a JSON object") + envelope = plan.get("turn_envelope") or {} + agent_vision = normalize_goal_vision_packet( + packet, + goal_id=str(envelope.get("goal_id") or ""), + agent_id=str(envelope.get("agent_id") or "") or None, + ) + except (TypeError, ValueError) as exc: + errors.append(f"invalid cached agent_vision: {exc}") + elif agent_vision_json: if len(agent_vision_json) > HOST_AGENT_VISION_JSON_MAX_CHARS: errors.append( "agent_vision_json exceeds " @@ -319,10 +338,13 @@ def validate_loopx_turn_host_result( value: Mapping[str, Any], *, completion_lifecycle_configured: bool = False, + from_journal: bool = False, ) -> dict[str, Any]: result = dict(value) errors: list[str] = [] allowed_fields = HOST_RESULT_FIELDS | subagent.subagent_host_result_fields(plan) + if from_journal: + allowed_fields = allowed_fields | {"agent_vision"} unknown = sorted(set(result) - allowed_fields) if unknown: errors.append("unsupported host result fields: " + ", ".join(unknown)) @@ -413,6 +435,7 @@ def validate_loopx_turn_host_result( result, unchanged_reason=unchanged_reason, errors=errors, + from_journal=from_journal, ) if ( path_delta_mode == "material_replan" @@ -428,6 +451,7 @@ def validate_loopx_turn_host_result( elif ( str(result.get("path_delta_mode") or "").strip() or str(result.get("agent_vision_json") or "").strip() + or (from_journal and "agent_vision" in result) ): errors.append( "non-material host results cannot declare path_delta_mode or agent_vision_json" @@ -849,6 +873,7 @@ def _host_result_stage( usage_goal_id: str = "", ) -> tuple[dict[str, Any] | None, list[str], dict[str, Any] | None]: completed_phases = list(journal.get("completed_phases") or []) + from_journal = "typed_result" in completed_phases result = ( journal.get("host_result") if isinstance(journal.get("host_result"), dict) @@ -927,6 +952,7 @@ def _host_result_stage( plan, result or {}, completion_lifecycle_configured=completion_lifecycle_configured, + from_journal=from_journal, ) if not validation.get("ok"): failure = _host_failure( diff --git a/tests/test_loopx_turn_executor.py b/tests/test_loopx_turn_executor.py index a8c4a1485d..a602dda4c0 100644 --- a/tests/test_loopx_turn_executor.py +++ b/tests/test_loopx_turn_executor.py @@ -712,10 +712,11 @@ def write_source_journal() -> None: assert calls == {"writeback": 0, "spend": 0, "scheduler": 0} -def test_host_result_requires_bounded_public_material_fields() -> None: +@pytest.mark.parametrize("field", ["raw_trajectory", "agent_vision"]) +def test_host_result_requires_bounded_public_material_fields(field: str) -> None: plan = _plan() result = _host_result(plan) - result["raw_trajectory"] = "not allowed" + result[field] = "not allowed" validation = validate_loopx_turn_host_result(plan, result) @@ -723,6 +724,40 @@ def test_host_result_requires_bounded_public_material_fields() -> None: assert "unsupported host result fields" in " ".join(validation["errors"]) +@pytest.mark.parametrize("packet, expected_error", [ + (None, "cached agent_vision must be a JSON object"), + ([], "cached agent_vision must be a JSON object"), + ("not an object", "cached agent_vision must be a JSON object"), + ({"vision_patch": {"acceptance_summary": "x" * 421}}, "acceptance_summary"), +]) +def test_cached_host_result_revalidates_decoded_vision( + packet: object, expected_error: str, +) -> None: + plan = _plan() + result = _host_result(plan) + result.update(result_kind="replan_required", path_delta_mode="material_replan", + agent_vision=packet) + result.pop("vision_unchanged_reason", None) + + validation = validate_loopx_turn_host_result(plan, result, from_journal=True) + + assert validation["ok"] is False + assert expected_error in " ".join(validation["errors"]) + + +def test_cached_host_result_rejects_conflicting_vision_forms() -> None: + plan = _plan() + result = _host_result(plan) + result.update(result_kind="replan_required", path_delta_mode="material_replan", + agent_vision={}, agent_vision_json="{}") + result.pop("vision_unchanged_reason", None) + + validation = validate_loopx_turn_host_result(plan, result, from_journal=True) + + assert validation["ok"] is False + assert "cached vision cannot carry both packet forms" in " ".join(validation["errors"]) + + def test_child_receipt_schema_excludes_registered_peer_authority() -> None: schema = child_execution_receipts_json_schema() item_schema = schema["items"] From 52fbb56762e292f398938c21df9737f5468506eb Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:24:45 +0800 Subject: [PATCH 19/38] test: qualify explicit quota selection and routed user notices Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- examples/capability-gate-smoke.py | 33 +++++++++++++++--- .../quota-agent-scoped-user-gate-smoke.py | 34 ++++++++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/examples/capability-gate-smoke.py b/examples/capability-gate-smoke.py index ba4178d1ab..3c17fdd8b4 100644 --- a/examples/capability-gate-smoke.py +++ b/examples/capability-gate-smoke.py @@ -376,15 +376,38 @@ def main() -> int: "provide or authorize the missing owner-held capability: credentials " "for todo_capability_9" ], owner_with_fallback + # A runnable recommendation is not a Turn binding. Keep the owner notice, + # but require explicit selection before projecting write/spend commands. + unbound = owner_with_fallback["interaction_contract"] + assert unbound["agent_channel"]["delivery_allowed"] is False, unbound + assert unbound["cli_channel"]["selection_required"] is True, unbound + assert unbound["cli_channel"]["next_cli_actions"] == [], unbound + assert unbound["cli_channel"]["spend_after_validation"] is False, unbound + assert "--todo-id '{todo_id}'" in unbound["cli_channel"][ + "selection_command" + ]["command_args_template"], unbound + + # This pure projection receives an already-bound receipt as input; real + # receipt acquisition and canonical recovery are covered by CLI smokes. + bound_fallback = build_quota_should_run( + status_payload([credentials_todo, p1_docs], registered_agent=AGENT_ID), + goal_id=GOAL_ID, + agent_id=AGENT_ID, + available_capabilities=["shell", "filesystem_write"], + scheduler_execution_context=SCHEDULER_CONTEXT, + receipt_bound_todo_id=p1_docs["todo_id"], + ) + bound = bound_fallback["interaction_contract"] + assert bound["agent_channel"]["delivery_allowed"] is True, bound + assert bound["cli_channel"]["spend_after_validation"] is True, bound + assert bound["user_channel"] == unbound["user_channel"], bound assert any( "--task-class user_gate" in action and "--target-capability credentials" in action and f"--blocks-agent {AGENT_ID}" in action and "--unblocks-todo-id todo_capability_9" in action - for action in owner_with_fallback["interaction_contract"]["cli_channel"][ - "next_cli_actions" - ] - ), owner_with_fallback + for action in bound["cli_channel"]["next_cli_actions"] + ), bound_fallback mixed_capability_todo = todo( 8, @@ -396,6 +419,7 @@ def main() -> int: status_payload([mixed_capability_todo]), goal_id=GOAL_ID, available_capabilities=["shell", "filesystem_write"], + scheduler_execution_context=SCHEDULER_CONTEXT, ) mixed_gate = mixed_owner_gate["capability_gate"] assert mixed_gate["action"] == "ask_owner", mixed_owner_gate @@ -426,6 +450,7 @@ def main() -> int: "credentials", "external_evidence_poll", ], + scheduler_execution_context=SCHEDULER_CONTEXT, ) assert mixed_ready["capability_gate"]["action"] == "run", mixed_ready assert mixed_ready["interaction_contract"]["user_channel"]["action_required"] is False diff --git a/examples/control_plane/quota-agent-scoped-user-gate-smoke.py b/examples/control_plane/quota-agent-scoped-user-gate-smoke.py index 53525bd1c5..88c1c56a61 100644 --- a/examples/control_plane/quota-agent-scoped-user-gate-smoke.py +++ b/examples/control_plane/quota-agent-scoped-user-gate-smoke.py @@ -13,6 +13,7 @@ from loopx.control_plane.todos.decision_scope import todo_gate_relation # noqa: E402 from loopx.control_plane.scheduler.execution_context import ( # noqa: E402 + GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, scheduler_execution_context_for_runtime_profile, ) from loopx.control_plane.testing.quota_fixtures import ( # noqa: E402 @@ -46,6 +47,7 @@ def todo_item( role: str = "agent", task_class: str = "advancement_task", claimed_by: str | None = None, + bound_agent: str | None = None, action_kind: str | None = None, blocks_agent: str | None = None, global_gate: bool = False, @@ -61,6 +63,7 @@ def todo_item( "next_due_at": next_due_at, "target_key": target_key, "global_gate": True if global_gate else None, + "bound_agent": bound_agent, }.items() if value is not None } @@ -245,7 +248,7 @@ def other_agent_gate_with_current_user_action_payload() -> dict: role="user", task_class="user_action", action_kind="review_handoff_material", - claimed_by="codex-value-explorer", + bound_agent=VALUE_AGENT, priority="P1", ) value_agent_todo = todo_item( @@ -276,6 +279,7 @@ def assert_other_agent_user_gate_does_not_block_current_agent() -> None: status_payload(), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is True, payload assert payload["normal_delivery_allowed"] is True, payload @@ -307,13 +311,16 @@ def assert_current_user_action_does_not_preserve_other_agent_operator_gate() -> other_agent_gate_with_current_user_action_payload(), goal_id=GOAL_ID, agent_id=VALUE_AGENT, + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["decision"] == "run", payload assert payload["state"] == "eligible", payload assert payload["should_run"] is True, payload assert payload["normal_delivery_allowed"] is True, payload - assert payload["requires_user_action"] is True, payload - assert payload["notify_user_on_open_todo"] is True, payload + # A routed user_action is a non-blocking notice while live work proceeds; + # the foreign user_gate must not turn that notice into an execution gate. + assert payload["requires_user_action"] is False, payload + assert "notify_user_on_open_todo" not in payload, payload assert "notify_user_on_gate" not in payload, payload summary = payload["user_todo_summary"] @@ -331,9 +338,13 @@ def assert_current_user_action_does_not_preserve_other_agent_operator_gate() -> assert "scoped_user_gate_fallback" not in payload, payload contract = payload["interaction_contract"] - assert contract["mode"] == "bounded_delivery_with_user_notice", contract - assert contract["user_channel"]["action_required"] is True, contract + assert contract["mode"] == "bounded_delivery", contract + assert contract["user_channel"]["action_required"] is False, contract + assert contract["user_channel"]["non_blocking"] is True, contract assert contract["user_channel"]["notify"] == "NOTIFY", contract + assert contract["user_channel"]["actions"] == [ + "Review the value-lane handoff material when convenient." + ], contract assert contract["agent_channel"]["must_attempt"] is True, contract assert contract["agent_channel"]["delivery_allowed"] is True, contract @@ -343,6 +354,7 @@ def assert_other_agent_user_gate_does_not_notify_non_due_monitor_lane() -> None: other_agent_gate_with_non_due_monitor_payload(), goal_id=GOAL_ID, agent_id="codex-value-explorer", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["decision"] == "skip", payload assert payload["effective_action"] == "monitor_quiet_skip", payload @@ -369,6 +381,7 @@ def assert_target_agent_still_blocks_on_its_user_gate() -> None: status_payload(), goal_id=GOAL_ID, agent_id="codex-product-capability", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is False, payload assert payload["normal_delivery_allowed"] is False, payload @@ -389,6 +402,7 @@ def assert_unscoped_user_gate_requires_projection_repair() -> None: status_payload(blocks_agent=None), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["decision"] == "self_repair", payload assert payload["should_run"] is True, payload @@ -412,6 +426,7 @@ def assert_explicit_global_gate_blocks_goal() -> None: status_payload(blocks_agent=None, global_gate=True), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is False, payload assert payload["requires_user_action"] is True, payload @@ -463,6 +478,7 @@ def assert_unrelated_user_gate_allows_feishu_fallback() -> None: unrelated_gate_status_payload(), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is True, payload assert payload["decision"] == "safe_bypass_user_gate_fallback", payload @@ -715,6 +731,7 @@ def assert_exact_todo_gate_only_blocks_target_todo() -> None: exact_todo_gate_status_payload(), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is True, payload assert payload["decision"] == "safe_bypass_user_gate_fallback", payload @@ -742,6 +759,7 @@ def assert_exact_todo_gate_only_blocks_target_todo() -> None: exact_todo_gate_status_payload(include_fallback=False), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert blocked_payload["should_run"] is False, blocked_payload assert blocked_payload["interaction_contract"]["mode"] == "user_gate", blocked_payload @@ -791,6 +809,7 @@ def assert_exact_todo_gate_survives_decision_scope_migration() -> None: exact_todo_gate_with_decision_scope_migration_payload(), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is True, payload assert payload["decision"] == "safe_bypass_user_gate_fallback", payload @@ -819,6 +838,7 @@ def assert_exact_todo_gate_allows_independent_due_monitor() -> None: goal_id=GOAL_ID, agent_id="codex-main-control", available_capabilities=["network", "external_write"], + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is True, payload assert payload["decision"] == "safe_bypass_user_gate_fallback", payload @@ -842,6 +862,7 @@ def assert_exact_todo_gate_allows_independent_due_monitor() -> None: goal_id=GOAL_ID, agent_id="codex-main-control", available_capabilities=["network"], + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert blocked_payload["should_run"] is False, blocked_payload assert "scoped_user_gate_fallback" not in blocked_payload, blocked_payload @@ -888,6 +909,7 @@ def assert_monitor_debt_yields_to_equal_priority_advancement() -> None: goal_id=GOAL_ID, agent_id="codex-main-control", available_capabilities=["network", "external_write"], + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) arbitration = payload["monitor_debt_arbitration"] assert arbitration["active"] is True, arbitration @@ -909,6 +931,7 @@ def assert_monitor_debt_yields_to_equal_priority_advancement() -> None: goal_id=GOAL_ID, agent_id="codex-main-control", available_capabilities=["network", "external_write"], + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert "monitor_debt_arbitration" not in terminal_transition, terminal_transition terminal_fallback = terminal_transition["scoped_user_gate_fallback"] @@ -959,6 +982,7 @@ def assert_decision_scope_overrides_shared_action_kind_tokens() -> None: decision_scope_status_payload(), goal_id=GOAL_ID, agent_id="codex-main-control", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT, ) assert payload["should_run"] is True, payload assert payload["decision"] == "safe_bypass_user_gate_fallback", payload From a21153e4f49cc37046b3bcae02f8a298a97d4ab8 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:23:10 +0800 Subject: [PATCH 20/38] test: qualify project vision and next-step canonical callers Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../goal-vision-refresh-state-budget-smoke.py | 46 ++++++++++++++-- .../next-action-projection-contract-smoke.py | 55 ++++++++++++++++--- 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/examples/project/goal-vision-refresh-state-budget-smoke.py b/examples/project/goal-vision-refresh-state-budget-smoke.py index 5a3af07ffe..73de3068c9 100644 --- a/examples/project/goal-vision-refresh-state-budget-smoke.py +++ b/examples/project/goal-vision-refresh-state-budget-smoke.py @@ -11,6 +11,16 @@ REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.coordination.local_authority import ( # noqa: E402 + LocalCoordinationAuthorityUnavailable, + read_canonical_todos_if_promoted, +) +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 +from loopx.control_plane.todos.mutation_api import add_goal_todo # noqa: E402 + GOAL_ID = "goal-vision-budget-fixture" AGENT_ID = "research-curator" @@ -184,10 +194,27 @@ def write_json(path: Path, value: dict) -> None: path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") -def main() -> int: +def assert_vision_budget(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-goal-vision-budget-") as tmp: root = Path(tmp) registry_path, runtime, project = write_fixture(root) + state_path = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" + # A source fixture must be imported before any normal Todo writer. + source_before = state_path.read_bytes() + create_args = dict(registry_path=registry_path, goal_id=GOAL_ID, role="agent", + text="Validate the scoped delivery evidence.", task_class="advancement_task", + claimed_by=AGENT_ID, agent_id=AGENT_ID) + try: + add_goal_todo(**create_args) + except LocalCoordinationAuthorityUnavailable as error: + assert error.code == "canonical_authority_required", error + else: + raise AssertionError("unmigrated fixture must not admit a normal Todo write") + assert state_path.read_bytes() == source_before + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) is None + import_fixture_authority(registry_path, GOAL_ID, provider=provider) + original = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert original["source_authority"] == provider + "_v0" valid_path = root / "valid-vision.json" invalid_path = root / "invalid-vision.json" drift_path = root / "drift-vision.json" @@ -603,10 +630,14 @@ def main() -> int: assert "limit is 80" in long_todo["error"], long_todo assert "suggested compact value" in long_todo["error"], long_todo - from loopx.todos import add_goal_todo - add_goal_todo(registry_path=registry_path, goal_id=GOAL_ID, role="agent", - text="Validate the scoped delivery evidence.", task_class="advancement_task", - claimed_by=AGENT_ID) + created = add_goal_todo(**create_args) + canonical = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert len(canonical["todos"]) == len(original["todos"]) + 1 + rows = {row["todo_id"]: row for row in canonical["todos"]} + assert rows[created["todo_id"]]["claimed_by"] == AGENT_ID + assert rows[created["todo_id"]]["text"] == create_args["text"] + for row in original["todos"]: + assert rows[row["todo_id"]] == row local_next_action = "/Users/example/private/raw-task-note" private_next_action_result = payload( run_cli( @@ -689,6 +720,11 @@ def main() -> int: assert "vision_summary" in invalid["error"], invalid assert "suggested compact value" in invalid["error"], invalid + + +def main() -> int: + for provider in ("file", "sqlite"): + assert_vision_budget(provider) print("goal-vision-refresh-state-budget-smoke ok") return 0 diff --git a/examples/project/next-action-projection-contract-smoke.py b/examples/project/next-action-projection-contract-smoke.py index 314987dce5..75223875a1 100644 --- a/examples/project/next-action-projection-contract-smoke.py +++ b/examples/project/next-action-projection-contract-smoke.py @@ -8,10 +8,17 @@ import sys import tempfile -from loopx.control_plane.scheduler.execution_context import GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT -from loopx.quota import build_quota_should_run -from loopx.state_refresh import refresh_state_run -from loopx.status import collect_status +REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 +from loopx.control_plane.scheduler.execution_context import GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority, run_json_cli # noqa: E402 +from loopx.control_plane.todos.mutation_api import complete_goal_todo # noqa: E402 +from loopx.quota import build_quota_should_run # noqa: E402 +from loopx.state_refresh import refresh_state_run # noqa: E402 +from loopx.status import collect_status # noqa: E402 GOAL_ID = "next-action-projection-goal" ACTIVE_NEXT_ACTION = "Shared compatibility guidance." @@ -93,9 +100,18 @@ def assert_state_next_action(path: Path, expected: str) -> None: -def main() -> None: +def assert_bound_projection(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-bound-recommendation-") as directory: registry, runtime, project, state = write_fixture(Path(directory)) + import_fixture_authority(registry, GOAL_ID, provider=provider) + original = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + assert original["source_authority"] == provider + "_v0" + assert {row["todo_id"] for row in original["todos"]} == {"todo_primary", "todo_side"} + # Finish the import's display delivery before checking that a task + # step does not change the already-current Markdown projection. + run_json_cli("todo", "project-markdown", "--goal-id", GOAL_ID, + "--provider-revision", original["provider_revision"], "--execute", + registry_path=registry, runtime_root=runtime, cwd=REPO_ROOT) before = state.read_bytes() common = dict(registry_path=registry, runtime_root_override=str(runtime), goal_id=GOAL_ID, project=project, state_file=None, classification="state_refreshed", @@ -133,7 +149,7 @@ def main() -> None: command = [sys.executable, "-m", "loopx.cli", "--registry", str(registry), "--runtime-root", str(runtime), "--format", "json", "status", "--goal-id", GOAL_ID, "--agent-id", "codex-side-bypass"] - readback = subprocess.run(command, capture_output=True, text=True, timeout=30) + readback = subprocess.run(command, cwd=REPO_ROOT, capture_output=True, text=True, timeout=30) assert readback.returncode == 0, readback.stdout + readback.stderr item = next(item for item in json.loads(readback.stdout)["attention_queue"]["items"] if item["goal_id"] == GOAL_ID) projected = item.get("agent_lane_next_action") or item["project_asset"]["agent_lane_next_action"] @@ -141,14 +157,37 @@ def main() -> None: assert projected["next_action_basis"] == lane["next_action_basis"] assert "next_action_basis" not in item # the selected route owns the basis markdown = subprocess.run([*command[:command.index("--format")], "--format", "markdown", - *command[command.index("--format") + 2:]], capture_output=True, text=True, timeout=30) + *command[command.index("--format") + 2:]], cwd=REPO_ROOT, capture_output=True, text=True, timeout=30) assert markdown.returncode == 0 and "next_step: " + step in markdown.stdout - # A different selected task does not inherit the old experiment. + # Display metadata cannot complete the authoritative selected task. state.write_text(state.read_text().replace("todo_side status=open", "todo_side status=done")) + assert state.read_bytes() != before + spoofed = collect_status(registry_path=registry, runtime_root_override=str(runtime), scan_roots=[project], limit=2) + spoofed_decision = build_quota_should_run(spoofed, goal_id=GOAL_ID, agent_id="codex-side-bypass", + scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT) + assert spoofed_decision["agent_lane_next_action"]["todo_id"] == "todo_side" + assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) == original + state.write_bytes(before) + + # Complete through the real owner: another selected task must not + # inherit the original task's retained recommendation receipt. + complete_goal_todo(registry_path=registry, goal_id=GOAL_ID, + todo_id="todo_side", agent_id="codex-side-bypass", evidence="Scoped fixture work verified.") + completed = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + before_rows = {row["todo_id"]: row for row in original["todos"]} + after_rows = {row["todo_id"]: row for row in completed["todos"]} + assert after_rows["todo_side"]["status"] == "done" + assert after_rows["todo_side"]["claimed_by"] == before_rows["todo_side"]["claimed_by"] + assert after_rows["todo_primary"] == before_rows["todo_primary"] changed = collect_status(registry_path=registry, runtime_root_override=str(runtime), scan_roots=[project], limit=2) next_decision = build_quota_should_run(changed, goal_id=GOAL_ID, agent_id="codex-side-bypass", scheduler_execution_context=GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT) assert step not in next_decision["interaction_contract"]["agent_channel"]["primary_action"] + + +def main() -> None: + for provider in ("file", "sqlite"): + assert_bound_projection(provider) print("next-action-projection-contract-smoke ok") From 604482f0cbd8ddc08ebae158a3c5d2effaaea63d Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 05:14:02 +0800 Subject: [PATCH 21/38] fix: classify existing CLI help and sync candidate manual version Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/help_surface.py | 2 ++ man/loopx.1 | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/loopx/help_surface.py b/loopx/help_surface.py index 4e0324396d..899b848941 100644 --- a/loopx/help_surface.py +++ b/loopx/help_surface.py @@ -383,6 +383,7 @@ def register_command_reference(subparsers: argparse._SubParsersAction) -> None: "global-todos", "goal-actions", "goal-alignment", + "goal-direction", "amendment-proposal", "goal-amendment-proposal", "handoff-mode", @@ -410,6 +411,7 @@ def register_command_reference(subparsers: argparse._SubParsersAction) -> None: "value-connectors", "version", "worker-bridge", + "zcode-goal", } ) diff --git a/man/loopx.1 b/man/loopx.1 index 156ebef2a2..6ac065d1ca 100644 --- a/man/loopx.1 +++ b/man/loopx.1 @@ -1,4 +1,4 @@ -.TH LOOPX 1 "" "LoopX 1.4.0" "User Commands" +.TH LOOPX 1 "" "LoopX 2.0.0a1" "User Commands" .SH NAME loopx \- control-plane helper for long-running agent work .SH SYNOPSIS From 20c61653295016865fc10b83887e4b7dfcaa8d66 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 05:14:02 +0800 Subject: [PATCH 22/38] test: qualify public smoke prerequisites and structured contracts Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .github/workflows/full-public-smokes.yml | 5 ++- examples/frontstage-pages-workflow-smoke.py | 25 ++++++++++++++- examples/full-public-smokes-workflow-smoke.py | 2 +- ...acos-dashboard-launchagent-status-smoke.py | 12 +++++-- ...alue-connectors-finance-probe-doc-smoke.py | 32 +++++++++++++++---- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/.github/workflows/full-public-smokes.yml b/.github/workflows/full-public-smokes.yml index 587d151042..0f5d34e08c 100644 --- a/.github/workflows/full-public-smokes.yml +++ b/.github/workflows/full-public-smokes.yml @@ -95,7 +95,10 @@ jobs: run: sudo apt-get update && sudo apt-get install -y zsh - name: Install smoke runtime dependencies - run: python -m pip install --disable-pip-version-check "jsonschema>=4.23,<5" + # Budget probes import pytest. LaunchAgent qualification resolves the + # selected distribution outside the checkout, so install this source + # explicitly rather than relying on an unrelated runner installation. + run: python -m pip install --disable-pip-version-check "jsonschema>=4.23,<5" "pytest>=9.0.3,<10" --editable . - name: Install locked TypeScript parser for semantic production checks run: npm ci --ignore-scripts diff --git a/examples/frontstage-pages-workflow-smoke.py b/examples/frontstage-pages-workflow-smoke.py index 3795c23598..1eb78867ef 100644 --- a/examples/frontstage-pages-workflow-smoke.py +++ b/examples/frontstage-pages-workflow-smoke.py @@ -20,6 +20,12 @@ def assert_absent(text: str, needle: str) -> None: raise AssertionError(f"workflow must not reference {needle!r}") +def step_source(text: str, name: str) -> str: + marker = f" - name: {name}\n" + assert text.count(marker) == 1, name + return text.split(marker, 1)[1].split("\n - ", 1)[0] + + def _extract_paths_block(text: str, event: str) -> str: """Extract the paths list under a given event (pull_request or push).""" marker = f"\n {event}:" @@ -129,7 +135,6 @@ def main() -> int: ".codex/goals", ".goal-" + "harness/", "registry.global.json", - "GH_TOKEN: ${{ github.token }}", "enable-reward-write-api", "npm run dev", "npm run preview", @@ -144,6 +149,24 @@ def main() -> int: ]: assert_absent(text, forbidden) + # Release-asset resolution and complete stargazer history use distinct + # credentials. The ordinary token must never replace the stargazer PAT. + release_steps = ( + "Validate public-safe frontstage bundle", + "Export Pages frontstage artifact", + ) + for name in release_steps: + step = step_source(text, name) + assert_contains(step, "GH_TOKEN: ${{ github.token }}") + assert_contains(step, 'LOOPX_SITE_RESOLVE_DESKTOP_RELEASE: "1"') + assert_absent(step, "scripts/fetch-github-stargazers.sh") + assert text.count("GH_TOKEN: ${{ github.token }}") == len(release_steps) + stargazers = step_source(text, "Fetch complete GitHub stargazer history") + assert_contains(stargazers, "if: github.event_name != 'pull_request'") + assert_contains(stargazers, "GH_TOKEN: ${{ secrets.STAR_HISTORY_READ_TOKEN }}") + assert_contains(stargazers, "scripts/fetch-github-stargazers.sh") + assert_absent(stargazers, "GH_TOKEN: ${{ github.token }}") + # Detaching a non-published product must preserve its own qualification lane. python_tests = (WORKFLOW.parent / "python-tests.yml").read_text(encoding="utf-8") for required in [ diff --git a/examples/full-public-smokes-workflow-smoke.py b/examples/full-public-smokes-workflow-smoke.py index 486a68bf21..3f874bd781 100644 --- a/examples/full-public-smokes-workflow-smoke.py +++ b/examples/full-public-smokes-workflow-smoke.py @@ -49,7 +49,7 @@ def main() -> int: "--jobs \"${SMOKE_JOBS}\"", "--no-execute", "--json", - 'python -m pip install --disable-pip-version-check "jsonschema>=4.23,<5"', + 'python -m pip install --disable-pip-version-check "jsonschema>=4.23,<5" "pytest>=9.0.3,<10" --editable .', "actions/upload-artifact@", "$RUNNER_TEMP/loopx-smoke-results/full-public-shard-${{ matrix.shard }}.json", "${{ runner.temp }}/loopx-smoke-results/full-public-shard-${{ matrix.shard }}.json", diff --git a/examples/macos-dashboard-launchagent-status-smoke.py b/examples/macos-dashboard-launchagent-status-smoke.py index a005124053..32ebd20062 100644 --- a/examples/macos-dashboard-launchagent-status-smoke.py +++ b/examples/macos-dashboard-launchagent-status-smoke.py @@ -388,10 +388,12 @@ def main() -> int: "#!/usr/bin/env bash\n" "if [[ \"$*\" == *\"--format json doctor\"* ]]; then\n" " if [[ -n \"${FAKE_RUNTIME_IDENTITY:-}\" ]]; then\n" - " printf '{\"service_runtime_identity\":%s}\\n' \"$FAKE_RUNTIME_IDENTITY\"\n" + " printf '{\"service_runtime_identity\":%s,\"python\":{\"executable\":%s}}\\n' \"$FAKE_RUNTIME_IDENTITY\" " + + shlex.quote(json.dumps(sys.executable)) + "\n" " exit 0\n" " fi\n" - " printf '%s\\n' '{\"release_manifest\":{\"manifest\":{\"release_id\":\"current-release\",\"package\":{\"version\":\"0.5.3\"},\"source\":{\"git_commit\":\"current-revision\"}}}}'\n" + " printf '{\"release_manifest\":{\"manifest\":{\"release_id\":\"current-release\",\"package\":{\"version\":\"0.5.3\"},\"source\":{\"git_commit\":\"current-revision\"}}},\"python\":{\"executable\":%s}}\\n' " + + shlex.quote(json.dumps(sys.executable)) + "\n" " exit 0\n" "fi\n" "echo loopx \"$@\"\n", @@ -456,6 +458,12 @@ def main() -> int: assert f"export CODEX_HOME={(home / '.codex').resolve()};" in default_chat_plist, default_chat_plist assert "export LOOPX_PYTHON=" in default_plist, default_plist assert "export LOOPX_PYTHON=" in default_chat_plist, default_chat_plist + # The selected console distribution owns its interpreter; another + # Python version on PATH must not replace this current doctor fact. + for plist in (status_plist, chat_plist): + shell = plistlib.loads(plist.read_bytes())["ProgramArguments"][2] + words = [word.removesuffix(";") for word in shlex.split(shell)] + assert f"LOOPX_PYTHON={sys.executable}" in words, shell assert "/loopx --registry" in default_plist, default_plist assert "/loopx-canary" not in default_plist, default_plist assert not (home / "Library" / "LaunchAgents" / "com.loopx.dashboard.plist").exists(), "retired dashboard LaunchAgent should not be installed" diff --git a/examples/value-connectors-finance-probe-doc-smoke.py b/examples/value-connectors-finance-probe-doc-smoke.py index 921132a7ba..e5bf0f992e 100644 --- a/examples/value-connectors-finance-probe-doc-smoke.py +++ b/examples/value-connectors-finance-probe-doc-smoke.py @@ -3,6 +3,7 @@ from __future__ import annotations +import json from pathlib import Path import subprocess import sys @@ -43,13 +44,30 @@ def main() -> int: "finance_market_snapshot", ) assert completed.returncode == 0, completed.stderr - assert '"status": "migrated_to_extension"' in completed.stdout - assert ( - '"replacement_extension_id": "loopx-finance-value-discovery"' - in completed.stdout - ) - assert '"replacement_capability_id": null' in completed.stdout - assert '"legacy_connector_executes_finance": false' in completed.stdout + packet = json.loads(completed.stdout) + assert packet["ok"] is True, packet + if command == "source-map": + profiles = packet["source_profiles"] + elif command == "install-check": + profiles = packet["checks"] + else: + profiles = None + if profiles is not None: + assert len(profiles) == 1, profiles + profile = profiles[0] + assert profile["connector_id"] == "finance_market_snapshot", profile + assert profile["status"] == "migrated_to_extension", profile + migration = profile["migration"] + else: + migration = packet + assert migration["schema_version"] == "value_connector_extension_migration_v0", migration + assert migration["status"] == "migrated_to_extension", migration + assert migration["replacement_extension_id"] == "loopx-finance-value-discovery", migration + assert migration["replacement_capability_id"] is None, migration + truth = migration["truth_contract"] + assert truth["legacy_connector_executes_finance"] is False, truth + assert truth["external_reads_performed"] is False, truth + assert truth["external_writes_performed"] is False, truth doc = " ".join(DOC.read_text(encoding="utf-8").lower().split()) for marker in ( From 131203faee7b707e3f3bc6b3cfb27057a29341b5 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:04:30 +0800 Subject: [PATCH 23/38] fix: preserve settings readback in contributor integration Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../goal-capability-settings.tsx | 53 +++++++----- .../replan-cadence.mjs | 83 +++++++++++++------ .../scenario-context.mjs | 13 +++ 3 files changed, 100 insertions(+), 49 deletions(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx index af7f15dc16..cce3e0aa60 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx @@ -24,6 +24,7 @@ type CapabilityCatalogProps = Readonly<{ callbacks: PersonalWorkspaceCallbacks; catalog: CapabilityConfigurationCatalog; goalId: string; + loading: boolean; onApplied: () => Promise; notification?: WorkspaceGoalNotification; onNotificationChanged: () => void; @@ -60,8 +61,9 @@ function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"] return writable; } -function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ +function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Readonly<{ goalId: string; + loading: boolean; onApplied: () => Promise; selected: CapabilityConfigurationCatalog["capabilities"][number] | undefined; t: ReturnType["t"]; @@ -104,7 +106,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ }, [selected]); async function preview(configuration: Record | null) { - if (!selected || mutation.busy || !jsonValid) return; + if (!selected || loading || mutation.busy || !jsonValid) return; setMutation((current) => ({ ...current, busy: "preview", partialWrite: null })); setError(null); try { @@ -118,7 +120,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ } async function apply() { - if (!selected || !mutation.preview || mutation.busy || !jsonValid) return; + if (!selected || loading || !mutation.preview || mutation.busy || !jsonValid) return; setMutation((current) => ({ ...current, busy: "apply" })); setError(null); try { @@ -144,6 +146,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ } function changeDraft(key: string, value: unknown) { + if (loading || mutation.busy) return; setMutation((current) => ({ ...current, draft: selected?.capability_id === "periodic_report" @@ -156,7 +159,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ } function changeJson(text: string) { - if (!selected || mutation.busy) return; + if (!selected || loading || mutation.busy) return; setJsonDraft(text); const parsed = parseEditableCapabilityJson(selected.configuration_editor, text); setMutation((current) => ({ ...current, preview: null, draft: parsed @@ -170,7 +173,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ } function changeMode() { - if (mutation.busy || !jsonValid) return; + if (loading || mutation.busy || !jsonValid) return; if (editorMode === "guided" && selected) { const jsonValue = selected.capability_id === "todo_replan_cadence" ? goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride) @@ -191,7 +194,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ function CapabilityMutationFeedback({ mutationError, onApplied, partialWrite, preview }: Readonly<{ mutationError: string | null; - onApplied: () => Promise; + onApplied: () => void; partialWrite: GoalConfigurationPartialWrite | null; preview: GoalConfigurationPreview | null; }>) { @@ -211,7 +214,7 @@ function CapabilityMutationFeedback({ mutationError, onApplied, partialWrite, pr : "capabilities.partialWriteDescription")}

{partialWrite.recommended_action} - + ) : null} {preview ? ( @@ -234,7 +237,7 @@ function CapabilityMutationFeedback({ mutationError, onApplied, partialWrite, pr ); } -function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied, onNotificationChanged }: CapabilityCatalogProps) { +function CapabilityCatalog({ callbacks, catalog, goalId, loading, notification, onApplied, onNotificationChanged }: CapabilityCatalogProps) { const { locale, t } = useWorkspaceI18n(); const orderedCapabilities = useMemo( () => orderCapabilitiesForPresentation( @@ -255,9 +258,12 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied () => selected ? localizeCapability(selected, locale) : undefined, [locale, selected], ); - const capabilityMutation = useCapabilityMutation({ goalId, onApplied, selected: localizedSelected, t }); + const capabilityMutation = useCapabilityMutation({ goalId, loading, onApplied, selected: localizedSelected, t }); const { apply, changeDraft, changeJson, changeMode, editorMode, jsonDraft, jsonValid, error: mutationError, mutation, preview: requestPreview } = capabilityMutation; const { busy, draft, partialWrite, preview } = mutation; + // A refreshed observation can replace `selected` and reset local mutation + // state. The existing catalog read still owns admission until it settles. + const editorBusy = loading || Boolean(busy); if (!selected || !localizedSelected) { return

{t("capabilities.empty")}

; @@ -269,12 +275,12 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied ?? (!supportsGoal ? t("capabilities.machineOnly") : t("capabilities.previewOnly")); async function createPreview() { - if (!localizedSelected || !editorAvailable || busy || !jsonValid) return; + if (!localizedSelected || !editorAvailable || editorBusy || !jsonValid) return; await requestPreview(draft); } async function createClearPreview() { - if (!editorAvailable || busy) return; + if (!editorAvailable || editorBusy) return; await requestPreview(null); } @@ -298,7 +304,7 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied {selected.capability_id === "progress_review" && selected.observation ?

{locale === "zh-CN" ? "审查证据读回" : "Review evidence readback"}

- +

{selected.observation.read_state === "unavailable" ? (locale === "zh-CN" ? "审查存储不可读,当前判断未知。" : "Review storage unavailable; current judgment unknown.") : selected.observation.read_state === "missing" ? (locale === "zh-CN" ? "尚无审查回执。" : "No review receipts yet.") : `${selected.observation.receipt_count} ${locale === "zh-CN" ? "条已读取回执" : "loaded receipts"}`}

@@ -357,7 +363,7 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied {editorAvailable ? <> {editorMode === "json" || !localizedSelected.configuration_editor.fields.some((field) => field.key === "enabled" && field.input_kind === "boolean") ? (
- +
) : null} {editorMode === "guided" ?
@@ -367,16 +373,16 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied : `Review interval follows ${selected.machine_current ? "device" : "product"} defaults; this Goal sets the stride strategy.`}

: null} {t("machine.editJson")}} + enabledAction={} />
: