diff --git a/docs/architecture/rfcs/STATUS.md b/docs/architecture/rfcs/STATUS.md index 1614dff04b..76b8757020 100644 --- a/docs/architecture/rfcs/STATUS.md +++ b/docs/architecture/rfcs/STATUS.md @@ -68,7 +68,7 @@ appendix may keep dated history, but no dated log heading may precede it. | [RFC: Shared Goal Alignment and Governed Amendment Protocol (v0)](shared-goal-alignment-and-governed-amendment-v0.md) | Accepted | none | [2 entries](ledger/shared-goal-alignment-and-governed-amendment-v0/) | | [RFC: LoopX Shared Control-Plane Authority and Pluggable State Providers (v0)](shared-goal-authority-state-provider-v0.md) | Accepted | none | [24 entries](ledger/shared-goal-authority-state-provider-v0/) | | [RFC: Single-Owner Local Daemon (v0)](single-owner-local-daemon-v0.md) | Accepted | none | — | -| [RFC: TypeScript Control-Plane Migration Direction v0](typescript-control-plane-migration-v0.md) | Accepted | none | [15 entries](ledger/typescript-control-plane-migration-v0/) | +| [RFC: TypeScript Control-Plane Migration Direction v0](typescript-control-plane-migration-v0.md) | Accepted | none | [16 entries](ledger/typescript-control-plane-migration-v0/) | ## Superseded (1) diff --git a/docs/architecture/rfcs/STATUS.zh-CN.md b/docs/architecture/rfcs/STATUS.zh-CN.md index 3c479b6daa..5686b66d77 100644 --- a/docs/architecture/rfcs/STATUS.zh-CN.md +++ b/docs/architecture/rfcs/STATUS.zh-CN.md @@ -65,7 +65,7 @@ | [RFC:共享 Goal 对齐与受治理 Amendment 协议(v0)](shared-goal-alignment-and-governed-amendment-v0.zh-CN.md) | 已接受 | 无 | [2 条](ledger/shared-goal-alignment-and-governed-amendment-v0/) | | [RFC:LoopX 共享控制面权威与可插拔状态 Provider(v0)](shared-goal-authority-state-provider-v0.zh-CN.md) | 已接受 | 无 | [24 条](ledger/shared-goal-authority-state-provider-v0/) | | [RFC: Single-Owner Local Daemon (v0)](single-owner-local-daemon-v0.md) | 已接受 | none | — | -| [RFC:LoopX 控制面 TypeScript 渐进迁移方向 v0](typescript-control-plane-migration-v0.zh-CN.md) | 已接受 | 无 | [15 条](ledger/typescript-control-plane-migration-v0/) | +| [RFC:LoopX 控制面 TypeScript 渐进迁移方向 v0](typescript-control-plane-migration-v0.zh-CN.md) | 已接受 | 无 | [16 条](ledger/typescript-control-plane-migration-v0/) | ## 已被替代 (1) diff --git a/docs/concepts/interaction-pattern-catalog.md b/docs/concepts/interaction-pattern-catalog.md index 43a0f3a8bc..944581b480 100644 --- a/docs/concepts/interaction-pattern-catalog.md +++ b/docs/concepts/interaction-pattern-catalog.md @@ -1211,13 +1211,14 @@ main blocker or keeps following a stale local-only benchmark staging todo. **Trigger** - a typed machine-configuration namespace is about to change; the built-in - namespaces are `change_quality_qualification`, `codex_chat`, `goal_storage`, - `manager_runtime`, `periodic_report`, `pull_request_review`, + namespaces are `change_quality_qualification`, `codex_chat`, `context_delivery`, + `explore_harness`, `goal_storage`, `manager_runtime`, `periodic_report`, `pull_request_review`, `steward_executor`, and `todo_replan_cadence`. The public catalog returned by `loopx machine-config describe` is authoritative, so this inventory has to stay complete rather than approximate; - `goal_storage` carries the storage target a newly created Goal is fixed to - (`new_goal_provider`, `file` by default or `sqlite` after reviewed promotion). + (`new_goal_provider`, canonical `sqlite` with hard leases by default; explicit + `file` and soft-claim creation choices remain available). It does not promote existing Goals or migrate their data, and existing Goals keep the selection they were created with; see `docs/reference/local-authority-provider-selection.md`; @@ -1227,6 +1228,14 @@ main blocker or keeps following a stale local-only benchmark staging todo. attached Agent, or a LoopX Agent Loop, and it grants no permissions and stores no credentials; see `docs/integrations/session-runtime-control-plane-adapter.md#projection-boundary`; +- `context_delivery` controls differential native Codex context (`off` by default). + A saved preference neither proves host readiness nor actual delta use; known + context loss keeps full context and unsupported declarations refuse. It grants + no execution authority; see `docs/reference/required-work-context.md#machine-default`; +- `explore_harness` sets the default exploration mode (`off` by default) for new + and existing Goals; explicit Goal modes take precedence. Planning supplies + evidence and read-only guidance, while execution authority stays separate; + see `loopx/capabilities/explore/README.md#machine-default`; - `pull_request_review` carries `review_order` (`forward` by default, or `reverse`) and Goal-owned `agent_orders` for registered Agent overrides. Commands resolve explicit direction, Agent, Goal, then machine; reverse inverts diff --git a/examples/agent-diagnose-packet-smoke.py b/examples/agent-diagnose-packet-smoke.py index 54ccea1275..968eafe478 100644 --- a/examples/agent-diagnose-packet-smoke.py +++ b/examples/agent-diagnose-packet-smoke.py @@ -239,6 +239,8 @@ def write_agent_scoped_registry(root: Path, runtime: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, SCOPED_GOAL_ID, provider="sqlite") return registry @@ -302,6 +304,8 @@ def write_capability_scoped_registry(root: Path, runtime: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, CAPABILITY_GOAL_ID, provider="sqlite") return registry diff --git a/examples/auto-research-rollout-readpath-smoke.py b/examples/auto-research-rollout-readpath-smoke.py index 81afe9a45b..b229870efb 100644 --- a/examples/auto-research-rollout-readpath-smoke.py +++ b/examples/auto-research-rollout-readpath-smoke.py @@ -192,6 +192,9 @@ def main() -> None: encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, GOAL_ID, provider="sqlite") + packet_path = temp / "packet.json" contract, dev, holdout = write_contract_and_results(temp) packet_path.write_text( diff --git a/examples/blocker-push-runtime-smoke.py b/examples/blocker-push-runtime-smoke.py index 348e0b79b1..58636449e4 100644 --- a/examples/blocker-push-runtime-smoke.py +++ b/examples/blocker-push-runtime-smoke.py @@ -62,7 +62,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path, Path]: registry.write_text( json.dumps( { - "runtime_root": str(runtime_root), + "common_runtime_root": str(runtime_root), "goals": [ { "id": GOAL_ID, @@ -89,6 +89,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path, Path]: encoding="utf-8", ) append_state_refreshed_run(runtime_root) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, GOAL_ID, provider="sqlite") return registry, runtime_root, project, state_file diff --git a/examples/bootstrap-command-pack-smoke.py b/examples/bootstrap-command-pack-smoke.py index 520fe53f08..10916a206d 100644 --- a/examples/bootstrap-command-pack-smoke.py +++ b/examples/bootstrap-command-pack-smoke.py @@ -105,6 +105,7 @@ def write_connected_goal_fixture( json.dumps( { "schema_version": "0.1", + "common_runtime_root": str(project / ".loopx" / "runtime"), "goals": [ { "id": goal_id, @@ -123,7 +124,10 @@ def write_connected_goal_fixture( + "\n" ) registry.write_text(registry_text, encoding="utf-8") - return {registry: registry_text, state_file: state_text} + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, goal_id, provider="sqlite") + return {registry: registry.read_text(encoding="utf-8"), + state_file: state_file.read_text(encoding="utf-8")} def assert_fixture_unchanged(snapshot: dict[Path, str]) -> None: @@ -946,7 +950,11 @@ def test_skill_slash_fallback_contract() -> None: assert "`review_groups`" in pr_review_skill_text assert "ranked actionable `review_sequence`" in pr_review_skill_text assert "must not appear in `review_sequence`" in pr_review_skill_text - assert "complete Chinese five-block review" in pr_review_normalized + for section in ("动机 / Motivation", "改动思路 / Approach", "具体改动 / Changes", + "对主干的风险 / Risks", "我的整体评价 / Verdict"): + assert section in pr_review_skill_text + assert "Chinese prose" in pr_review_skill_text + assert "English verdict:" in pr_review_skill_text assert "capability owns review depth, evidence requirements" in pr_review_normalized diff --git a/examples/codex-app-thread-agent-identity-smoke.py b/examples/codex-app-thread-agent-identity-smoke.py index 8b762cd267..f461df0db9 100755 --- a/examples/codex-app-thread-agent-identity-smoke.py +++ b/examples/codex-app-thread-agent-identity-smoke.py @@ -54,6 +54,11 @@ def main() -> None: ) runtime = root / "runtime" runtime.mkdir() + from loopx.control_plane.testing.canary_harness import import_fixture_authority + config = json.loads(registry.read_text(encoding="utf-8")) + config["common_runtime_root"] = str(runtime) + registry.write_text(json.dumps(config), encoding="utf-8") + import_fixture_authority(registry, "goal", provider="sqlite") global_payload = json.loads(registry.read_text(encoding="utf-8")) global_payload["registry_role"] = "global-local" global_payload["goals"][0]["source_registry"] = str(registry) @@ -163,8 +168,15 @@ def main() -> None: goal_text="start another task", ) gate = unbound["guided_transaction"]["identity_selection_gate"] - assert gate["default_action"] == "select_agent_identity", gate - assert gate["fresh_agent_registration"] is None, gate + assert gate["default_action"] == "register_fresh_agent", gate + registration = gate["fresh_agent_registration"] + assert registration["mode"] == "register_fresh_agent" + assert registration["recommended"] is True + assert registration["continuation_contract"]["requires_execute_result"] is True + assert registration["continuation_contract"]["required_result"] == { + "ok": True, "changed": True, "written": True, + "global_sync": {"ok": True}, "registration_readback": {"verified": True}, + } assert {choice["agent_id"] for choice in gate["choices"]} == { "codex-a", "codex-b", diff --git a/examples/contract-reward-overlay-smoke.py b/examples/contract-reward-overlay-smoke.py index c4bf40fd79..24f5fd6052 100644 --- a/examples/contract-reward-overlay-smoke.py +++ b/examples/contract-reward-overlay-smoke.py @@ -164,7 +164,7 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: registry.write_text( json.dumps( { - "runtime_root": str(runtime_root), + "common_runtime_root": str(runtime_root), "goals": [ { "id": "reward-overlay-goal", @@ -206,6 +206,10 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry.read_text())["goals"]: + import_fixture_authority(registry, record["id"], provider="sqlite") return registry, runtime_root, project diff --git a/examples/control_plane/active-state-todo-attention-fallback-smoke.py b/examples/control_plane/active-state-todo-attention-fallback-smoke.py index 8db061631f..61faa169e5 100644 --- a/examples/control_plane/active-state-todo-attention-fallback-smoke.py +++ b/examples/control_plane/active-state-todo-attention-fallback-smoke.py @@ -76,6 +76,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path, Path]: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path, runtime, project, state_path diff --git a/examples/control_plane/agent-onboard-host-loop-activation-smoke.py b/examples/control_plane/agent-onboard-host-loop-activation-smoke.py index 28d5d1fcfe..bde96ccfa7 100644 --- a/examples/control_plane/agent-onboard-host-loop-activation-smoke.py +++ b/examples/control_plane/agent-onboard-host-loop-activation-smoke.py @@ -23,6 +23,7 @@ ) from loopx.cli import build_parser # noqa: E402 from loopx.control_plane.quota.usage_summary import is_automation_run # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.control_plane.scheduler.automation_liveness import ( # noqa: E402 build_automation_liveness, ) @@ -303,6 +304,7 @@ def main() -> int: global_registry.parent.mkdir(parents=True) state_file.write_text("# Active State\n\n## Agent Todo\n", encoding="utf-8") registry = { + "common_runtime_root": str(root / "runtime"), "goals": [ { "id": "multi-agent-goal", @@ -323,6 +325,9 @@ def main() -> int: } serialized_registry = json.dumps(registry) project_registry.write_text(serialized_registry, encoding="utf-8") + import_fixture_authority(project_registry, "multi-agent-goal", provider="sqlite") + registry = json.loads(project_registry.read_text(encoding="utf-8")) + serialized_registry = json.dumps(registry) global_registry.write_text(serialized_registry, encoding="utf-8") cli_bin = str(REPO_ROOT / "scripts" / "loopx") diff --git a/examples/control_plane/control-plane-configure-api-smoke.py b/examples/control_plane/control-plane-configure-api-smoke.py index 663457a819..1b85056a29 100644 --- a/examples/control_plane/control-plane-configure-api-smoke.py +++ b/examples/control_plane/control-plane-configure-api-smoke.py @@ -99,7 +99,7 @@ def write_fixture(root: Path) -> tuple[Path, Path]: registry.write_text( json.dumps( { - "runtime_root": str(runtime_root), + "common_runtime_root": str(runtime_root), "goals": [ { "id": GOAL_ID, @@ -131,6 +131,9 @@ def write_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + import_fixture_authority(registry, GOAL_ID, provider="sqlite") return registry, runtime_root diff --git a/examples/control_plane/peer-agent-hard-cut-boundary-smoke.py b/examples/control_plane/peer-agent-hard-cut-boundary-smoke.py index f6404e67d7..3a39e788c9 100644 --- a/examples/control_plane/peer-agent-hard-cut-boundary-smoke.py +++ b/examples/control_plane/peer-agent-hard-cut-boundary-smoke.py @@ -47,6 +47,18 @@ LEGACY_STABLE_IDENTIFIERS = ("showcase-side-agent-self-iteration",) +def hierarchy_scan_line(path: Path, line: str) -> str: + # The source decoder diagnoses these retired values; it never admits them. + if (path == REPO_ROOT / "loopx/control_plane/todos/read_codec.ts" + and line.strip() == '["removed_continuation_policy", ["primary_review", "review_handoff"]]] as const) {'): + return line.replace('"primary_review"', '').replace('"review_handoff"', '') + # This accepted RFC names native session roles, not Agent hierarchy. + if (path == REPO_ROOT / "docs/architecture/rfcs/controller-executor-unified-contract-v0.md" + and line.strip() == 'the current single Agent-scoped slot. One controller owns control dialogue;'): + return line.replace("controller owns", "native session role owns") + return line + + def candidate_files() -> list[Path]: files = list(SCAN_FILES) for root in SCAN_ROOTS: @@ -72,13 +84,18 @@ def main() -> int: for domain_term in ("primary_reviewers", "primary_review_score"): assert not LEGACY_PATTERN.search(domain_term), domain_term + decoder = REPO_ROOT / "loopx/control_plane/todos/read_codec.ts" + assert LEGACY_PATTERN.search(hierarchy_scan_line(decoder, 'const policy = "primary_review";')) + assert LEGACY_PATTERN.search(hierarchy_scan_line(REPO_ROOT / "other.ts", + '["removed_continuation_policy", ["primary_review", "review_handoff"]]] as const) {')) + violations: list[str] = [] for path in candidate_files(): if path in ALLOWED_LEGACY_PATHS: continue text = path.read_text(encoding="utf-8") for line_number, line in enumerate(text.splitlines(), start=1): - scan_line = line + scan_line = hierarchy_scan_line(path, line) for identifier in LEGACY_STABLE_IDENTIFIERS: scan_line = scan_line.replace(identifier, "") if LEGACY_PATTERN.search(scan_line): diff --git a/examples/control_plane/peer-supervisor-smoke.py b/examples/control_plane/peer-supervisor-smoke.py index 4a85399142..5781b067e8 100644 --- a/examples/control_plane/peer-supervisor-smoke.py +++ b/examples/control_plane/peer-supervisor-smoke.py @@ -142,6 +142,12 @@ def main() -> int: encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + config = json.loads(registry_path.read_text(encoding="utf-8")) + config["common_runtime_root"] = str(root / "runtime") + registry_path.write_text(json.dumps(config), encoding="utf-8") + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") + assert peer_supervisor_for_goal(read_goal(registry_path)) is None preview = configure_goal( diff --git a/examples/control_plane/quota-spend-workspace-causality-smoke.py b/examples/control_plane/quota-spend-workspace-causality-smoke.py index 493d339da5..754f214a6f 100644 --- a/examples/control_plane/quota-spend-workspace-causality-smoke.py +++ b/examples/control_plane/quota-spend-workspace-causality-smoke.py @@ -143,6 +143,9 @@ def write_goal_fixture( + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path @@ -233,6 +236,14 @@ def main() -> None: # worktree. An explicit causal path preserves the delivery identity # without persisting that local path. split_runtime = root / "split-runtime" + # This second owned runtime is a separate canonical authority, not a + # Markdown fallback selected by the override. Import its source once. + split_registry = root / "split-registry.json" + split_source = json.loads(registry_path.read_text()) + split_source["common_runtime_root"] = str(split_runtime) + split_registry.write_text(json.dumps(split_source)) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(split_registry, GOAL_ID, provider="sqlite") with working_directory(delivery): split_refresh = refresh_state_run( registry_path=registry_path, @@ -279,6 +290,12 @@ def main() -> None: explicit_preview ) + invalid_runtime = root / "invalid-runtime" + invalid_source = {**split_source, "common_runtime_root": str(invalid_runtime)} + invalid_registry = root / "invalid-registry.json" + invalid_registry.write_text(json.dumps(invalid_source)) + import_fixture_authority(invalid_registry, GOAL_ID, provider="sqlite") + # An explicit canonical path cannot bless a peer delivery. Omitting the # override retains the existing fail-closed recorded-history behavior. with working_directory(delivery): diff --git a/examples/control_plane/refresh-state-agent-lane-scope-smoke.py b/examples/control_plane/refresh-state-agent-lane-scope-smoke.py index d5f4b67f17..8e3489eb03 100644 --- a/examples/control_plane/refresh-state-agent-lane-scope-smoke.py +++ b/examples/control_plane/refresh-state-agent-lane-scope-smoke.py @@ -94,6 +94,9 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path, runtime, project diff --git a/examples/control_plane/refresh-state-unique-run-path-smoke.py b/examples/control_plane/refresh-state-unique-run-path-smoke.py index 76062c3cc8..de6cb3bfe7 100644 --- a/examples/control_plane/refresh-state-unique-run-path-smoke.py +++ b/examples/control_plane/refresh-state-unique-run-path-smoke.py @@ -87,6 +87,8 @@ def write_fixture(root: Path) -> tuple[Path, Path, Path]: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path, runtime, project diff --git a/examples/control_plane/review_packet_cli_fixtures.py b/examples/control_plane/review_packet_cli_fixtures.py index b5edb2a7cf..e6e50e77be 100644 --- a/examples/control_plane/review_packet_cli_fixtures.py +++ b/examples/control_plane/review_packet_cli_fixtures.py @@ -186,6 +186,8 @@ def write_planned_registry(root: Path) -> Path: ), encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path @@ -245,6 +247,18 @@ def mark_owner_review_todo_done(root: Path) -> None: encoding="utf-8", ) + from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted + from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection + from loopx.control_plane.testing.canary_harness import advance_fixture_authority + current = read_canonical_todos_if_promoted(runtime_root=root / "runtime", goal_id=GOAL_ID) + reviews = [row for row in current["todos"] + if row["role"] == "user" and row["text"] == "Read owner review worksheet first."] + assert len(reviews) == 1, reviews + reviews[0].update(status="done", done=True) + advance_fixture_authority(root / "runtime", GOAL_ID, + build_todo_runtime_shadow_projection(goal_id=GOAL_ID, todos=current["todos"], handoff_mode="soft_claim"), + operation_id="owner-review-completed") + def run_cli(root: Path, registry_path: Path, *args: str) -> subprocess.CompletedProcess[str]: return subprocess.run( diff --git a/examples/control_plane/shared-runtime-material-projection-smoke.py b/examples/control_plane/shared-runtime-material-projection-smoke.py index a315869694..dbbf4d00f0 100644 --- a/examples/control_plane/shared-runtime-material-projection-smoke.py +++ b/examples/control_plane/shared-runtime-material-projection-smoke.py @@ -81,6 +81,11 @@ def write_source( + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry.read_text())["goals"]: + import_fixture_authority(registry, record["id"], provider="sqlite") + goal = next(record for record in json.loads(registry.read_text())["goals"] if record["id"] == GOAL_ID) return project, registry, goal @@ -112,7 +117,7 @@ def read_rows(runtime: Path) -> list[dict]: return [json.loads(line) for line in index.read_text(encoding="utf-8").splitlines()] -def run_cli(registry: Path, runtime: Path, *args: str) -> dict: +def run_cli(registry: Path, runtime: Path, *args: str, runtime_override: bool = True, expected_code: int = 0) -> dict: result = subprocess.run( [ sys.executable, @@ -122,8 +127,7 @@ def run_cli(registry: Path, runtime: Path, *args: str) -> dict: "json", "--registry", str(registry), - "--runtime-root", - str(runtime), + *(["--runtime-root", str(runtime)] if runtime_override else []), *args, ], cwd=REPO_ROOT, @@ -132,7 +136,7 @@ def run_cli(registry: Path, runtime: Path, *args: str) -> dict: text=True, env={**os.environ, "PYTHONPATH": str(REPO_ROOT), "LOOPX_RUNTIME_ROOT": str(runtime)}, ) - assert result.returncode == 0, result.stdout + result.stderr + assert result.returncode == expected_code, result.stdout + result.stderr return json.loads(result.stdout) @@ -218,7 +222,12 @@ def exercise_split_runtime(root: Path) -> None: assert mapped["shared_runtime_material_projection"]["status"] == "projected", mapped map_row = assert_public_projection(read_rows(shared_runtime), "read_only_project_map") assert isinstance(map_row.get("project_map"), dict), map_row - map_status = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID) + # A forced empty target runtime must refuse rather than borrow source + # authority. The ordinary global read below follows its declared route. + refused = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID, expected_code=1) + assert refused["error_code"] == "canonical_authority_required" + assert refused["authority_read"]["legacy_fallback_used"] is False + map_status = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID, runtime_override=False) assert map_status["run_history"]["goals"][0]["latest_status_run"][ "classification" ] == "read_only_project_map" @@ -227,7 +236,7 @@ def exercise_split_runtime(root: Path) -> None: assert gate["shared_runtime_material_projection"]["readback_verified"] is True, gate gate_row = assert_public_projection(read_rows(shared_runtime), "operator_gate_decision") assert gate_row["operator_gate"]["decision"] == "defer", gate_row - gate_status = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID) + gate_status = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID, runtime_override=False) latest_gate = gate_status["run_history"]["goals"][0]["latest_status_run"] assert latest_gate["classification"] == "operator_gate_deferred", latest_gate assert latest_gate["operator_gate"]["operator_question"], latest_gate @@ -240,6 +249,7 @@ def exercise_split_runtime(root: Path) -> None: GOAL_ID, "--agent-id", AGENT_ID, + runtime_override=False, ) assert gate_quota["state"] == "operator_gate", gate_quota @@ -260,7 +270,7 @@ def exercise_split_runtime(root: Path) -> None: dream_row = assert_public_projection(read_rows(shared_runtime), "dreaming_decision") assert dream_row["dreaming_decision"]["decision"] == "defer", dream_row assert dream_row["source_dreaming_proposal"]["proposal_id"] == proposal_id, dream_row - dream_status = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID) + dream_status = run_cli(shared_registry, shared_runtime, "status", "--goal-id", GOAL_ID, runtime_override=False) assert dream_status["run_history"]["goals"][0]["latest_status_run"][ "classification" ] == "dreaming_proposal_deferred" diff --git a/examples/control_plane/status-collection-readmodel-smoke.py b/examples/control_plane/status-collection-readmodel-smoke.py index 8aa479a5eb..c9d36de2bc 100644 --- a/examples/control_plane/status-collection-readmodel-smoke.py +++ b/examples/control_plane/status-collection-readmodel-smoke.py @@ -55,6 +55,8 @@ def write_registry(root: Path) -> Path: { "id": GOAL_ID, "domain": "loopx-platform", + "coordination": {"agent_model": "peer_v1", + "registered_agents": ["codex-product-capability"]}, "status": "active", "repo": str(project), "state_file": state_file, @@ -75,6 +77,8 @@ def write_registry(root: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path diff --git a/examples/control_plane/status-markdown-smoke.py b/examples/control_plane/status-markdown-smoke.py index 5962c53283..2d1e7fe5e3 100644 --- a/examples/control_plane/status-markdown-smoke.py +++ b/examples/control_plane/status-markdown-smoke.py @@ -785,7 +785,8 @@ def assert_dependency_blockers_stay_separate(payload: dict, markdown: str) -> No blocker_item = items_by_goal[DEPENDENCY_BLOCKER_GOAL_ID] assert current_item["waiting_on"] == "codex", current_item assert current_item["agent_todos"]["open_count"] == 2, current_item - assert "user_todos" not in current_item, current_item + assert current_item["user_todos"]["open_count"] == 0, current_item + assert current_item["user_todos"]["items"] == [], current_item blockers = current_item["dependency_blockers"] assert blockers["open_count"] == 1, blockers assert blockers["source"] == "attention_queue.user_todos", blockers @@ -996,7 +997,10 @@ def assert_project_asset_secret_scanner_boundaries() -> None: def assert_explicit_delivery_refresh(payload: dict, markdown: str) -> None: item = payload["attention_queue"]["items"][0] assert item["goal_id"] == DELIVERY_GOAL_ID, item - assert item["status"] == EXPLICIT_REFRESH_CLASSIFICATION, item + # Current canonical work stays primary; progress classification remains in history. + assert item["source"] == "active_state", item + assert item["status"] == "active_state_agent_todo", item + assert item["agent_todos"]["items"][0]["text"] == DELIVERY_AGENT_TODO, item readiness = item["handoff_readiness"] assert readiness["post_handoff_latest_run"]["classification"] == EXPLICIT_REFRESH_CLASSIFICATION, readiness assert readiness["post_handoff_latest_run"]["delivery_batch_scale"] == "multi_surface", readiness diff --git a/examples/control_plane/status-neutral-run-window-smoke.py b/examples/control_plane/status-neutral-run-window-smoke.py index 346866766b..1aaea8d4b5 100644 --- a/examples/control_plane/status-neutral-run-window-smoke.py +++ b/examples/control_plane/status-neutral-run-window-smoke.py @@ -96,6 +96,9 @@ def write_registry(root: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path @@ -157,6 +160,9 @@ def write_agent_lane_registry(root: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + import_fixture_authority(registry_path, AGENT_LANE_GOAL_ID, provider="sqlite") return registry_path diff --git a/examples/control_plane/status-quota-perf-budget-smoke.py b/examples/control_plane/status-quota-perf-budget-smoke.py index 2583d31739..98840073cd 100644 --- a/examples/control_plane/status-quota-perf-budget-smoke.py +++ b/examples/control_plane/status-quota-perf-budget-smoke.py @@ -242,8 +242,8 @@ def main() -> int: 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. + # Malformed historical source must refuse within the same read budget, + # without falling back to its display or changing 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") @@ -272,7 +272,7 @@ def main() -> int: or [] ) assert any( - item.get("code") == "todo_status_invalid" + item.get("code") == "canonical_authority_required" for item in error_diagnostics if isinstance(item, dict) ), malformed_status_payload @@ -303,13 +303,14 @@ def main() -> int: check=False, ) assert malformed_quota_returncode != 0, malformed_quota_payload - assert malformed_quota_payload.get("status_health_ok") is False, ( - malformed_quota_payload - ) + assert malformed_quota_payload["ok"] is False, malformed_quota_payload + assert malformed_quota_payload["error_code"] == "canonical_authority_required" + assert malformed_quota_payload["write_admitted"] is False + assert malformed_quota_payload["legacy_fallback_used"] is False assert malformed_quota_payload.get("should_run") is False, ( malformed_quota_payload ) - assert malformed_quota_payload.get("effective_action") == "quota_skip", ( + assert malformed_quota_payload.get("decision") == "skip", ( malformed_quota_payload ) assert PRIVATE_DOC_MARKER not in json.dumps(malformed_quota_payload), ( diff --git a/examples/control_plane/status_markdown_fixtures.py b/examples/control_plane/status_markdown_fixtures.py index 2d40464163..802a771cd5 100644 --- a/examples/control_plane/status_markdown_fixtures.py +++ b/examples/control_plane/status_markdown_fixtures.py @@ -326,16 +326,24 @@ def write_planned_registry(root: Path) -> Path: ), encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, goal_id, provider="sqlite") return registry_path def mark_planned_todos_done(root: Path) -> None: goal_id = "planned-main-control" - state_path = root / "project" / ".codex" / "goals" / goal_id / "ACTIVE_GOAL_STATE.md" - state_text = state_path.read_text(encoding="utf-8") - state_text = state_text.replace(f"- [ ] {USER_TODO_TEXT}", f"- [x] {USER_TODO_TEXT}") - state_text = state_text.replace(f"- [ ] {AGENT_TODO_TEXT}", f"- [x] {AGENT_TODO_TEXT}") - state_path.write_text(state_text, encoding="utf-8") + from loopx.control_plane.testing.canary_harness import advance_fixture_authority, run_json_cli + from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection + registry = root / "project/.loopx/registry.json" + todos = run_json_cli("todo", "list", "--goal-id", goal_id, + registry_path=registry, runtime_root=root / "runtime")["todos"] + for todo in todos: + if todo["text"] in {USER_TODO_TEXT, AGENT_TODO_TEXT}: + todo.update(status="done", done=True) + advance_fixture_authority(root / "runtime", goal_id, + build_todo_runtime_shadow_projection(goal_id=goal_id, todos=todos, handoff_mode="soft_claim"), + operation_id="fixture-owner-review-completed") def write_connected_delivery_registry(root: Path) -> Path: @@ -438,6 +446,8 @@ def write_connected_delivery_registry(root: Path) -> Path: ), encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, DELIVERY_GOAL_ID, provider="sqlite") return registry_path @@ -512,6 +522,8 @@ def write_connected_readonly_registry(root: Path) -> Path: ), encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, CONNECTED_READONLY_GOAL_ID, provider="sqlite") return registry_path @@ -586,4 +598,7 @@ def write_dependency_blocker_registry(root: Path) -> Path: ), encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, DEPENDENCY_CURRENT_GOAL_ID, provider="sqlite") + import_fixture_authority(registry_path, DEPENDENCY_BLOCKER_GOAL_ID, provider="sqlite") return registry_path diff --git a/examples/control_plane/todo-archive-completed-smoke.py b/examples/control_plane/todo-archive-completed-smoke.py index 9b676cb372..3d6d5329f9 100644 --- a/examples/control_plane/todo-archive-completed-smoke.py +++ b/examples/control_plane/todo-archive-completed-smoke.py @@ -461,8 +461,8 @@ def main() -> int: 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. + # Ordinary reads refuse an unimported malformed source before consulting + # its display diagnostics. The imported collection above stays unchanged. source_registry, source_runtime, source_state = write_fixture( Path(tmp) / "invalid-source", text=state_text() + invalid_rows, import_authority=False, @@ -471,18 +471,11 @@ def main() -> int: 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"] - ), invalid_status - assert any( - "todo_archived_malformed" in item and "malformed status metadata" in item - for item in invalid_status["contract_errors"] - ), invalid_status - assert any( - "todo_archived_empty" in item and "malformed status metadata" in item - for item in invalid_status["contract_errors"] - ), invalid_status + refused = next(item for item in invalid_status["attention_queue"]["items"] + if item.get("goal_id") == GOAL_ID and item.get("todo_source") == "unavailable") + assert refused["error_code"] == "canonical_authority_required" + assert refused["authority_read"]["write_admitted"] is False + assert refused["authority_read"]["legacy_fallback_used"] is False assert_user_role_archive_retains_standing_decision() diff --git a/examples/control_plane/todo-durability-fixture-smoke.py b/examples/control_plane/todo-durability-fixture-smoke.py index 7dc2f5ffd8..73bdd66994 100644 --- a/examples/control_plane/todo-durability-fixture-smoke.py +++ b/examples/control_plane/todo-durability-fixture-smoke.py @@ -120,12 +120,15 @@ def write_fixture(root: Path) -> tuple[Path, Path]: def assert_parseable_agent_todos(agent_todos: dict, *, hot_path: bool = False) -> None: assert agent_todos["schema_version"] == "todo_summary_v0", agent_todos assert agent_todos["total_count"] == 5, agent_todos - assert agent_todos["open_count"] == 2, agent_todos + assert agent_todos["open_count"] == (1 if hot_path else 2), agent_todos assert agent_todos["done_count"] == 3, agent_todos assert agent_todos["deferred_count"] == 2, agent_todos item_lane = "first_executable_items" if hot_path else "first_open_items" visible_items = agent_todos[item_lane] - assert [item["index"] for item in visible_items] == [1, 5], agent_todos + assert [item["index"] for item in visible_items] == ([1] if hot_path else [1, 5]), agent_todos + if hot_path: + assert agent_todos["claim_scope"]["other_agent_claimed_open_count"] == 1 + assert agent_todos["claim_scope"]["other_agent_claimed_weight"] == "diagnostic_only" first_open = visible_items[0] assert first_open["schema_version"] == "todo_item_v0", first_open @@ -143,6 +146,14 @@ def assert_parseable_agent_todos(agent_todos: dict, *, hot_path: bool = False) - ), first_open assert str(first_open["todo_id"]).startswith("todo_"), first_open + if hot_path: + assert "first_open_items" not in agent_todos, agent_todos + assert "deferred_items" not in agent_todos, agent_todos + assert agent_todos["payload_compaction"]["schema_version"] == ( + "quota_cli_todo_summary_compaction_v0" + ), agent_todos + return + second_open = visible_items[1] assert second_open["priority"] == "P2", second_open assert second_open["status"] == "open", second_open @@ -156,13 +167,6 @@ def assert_parseable_agent_todos(agent_todos: dict, *, hot_path: bool = False) - assert handoff_note["intent"] == "review_pr", handoff_note assert handoff_note["evidence_refs"] == ["todo:todo_handoff_review:evidence"], handoff_note - if hot_path: - assert "first_open_items" not in agent_todos, agent_todos - assert "deferred_items" not in agent_todos, agent_todos - assert agent_todos["payload_compaction"]["schema_version"] == ( - "quota_cli_todo_summary_compaction_v0" - ), agent_todos - return deferred_by_id = {item["todo_id"]: item for item in agent_todos["deferred_items"]} assert set(deferred_by_id) == {"todo_deferred_surface", "todo_pr_deferred"}, agent_todos @@ -261,6 +265,15 @@ def assert_pr_resume_repository_binding() -> None: def assert_lifecycle(provider: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-todo-durability-") as tmp: registry_path, runtime = write_fixture(Path(tmp)) + # Declare every existing claim owner before the one canonical import. + 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) status_payload = run_cli("status", registry_path=registry_path, runtime=runtime) item = attention_item(status_payload) @@ -304,22 +317,14 @@ def assert_lifecycle(provider: str) -> None: assert indexed_handoff["handoff_note"]["goal_id"] == GOAL_ID, indexed_handoff assert indexed_handoff["handoff_note"]["to_agent"] == "codex-reviewer", indexed_handoff - guard = run_cli("quota", "should-run", "--goal-id", GOAL_ID, registry_path=registry_path, runtime=runtime) + 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 guard["should_run"] is True, guard assert_parseable_agent_todos(guard["agent_todo_summary"], hot_path=True) 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 diff --git a/examples/delivery-batch-scale-enum-smoke.py b/examples/delivery-batch-scale-enum-smoke.py index 7f04175bfb..683f989d4b 100644 --- a/examples/delivery-batch-scale-enum-smoke.py +++ b/examples/delivery-batch-scale-enum-smoke.py @@ -74,6 +74,8 @@ def write_fixture(root: Path) -> tuple[Path, Path]: ], }, ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path, runtime diff --git a/examples/delivery-outcome-enum-smoke.py b/examples/delivery-outcome-enum-smoke.py index e1fca6d526..a2dc07ad61 100644 --- a/examples/delivery-outcome-enum-smoke.py +++ b/examples/delivery-outcome-enum-smoke.py @@ -68,6 +68,8 @@ def write_fixture(root: Path) -> tuple[Path, Path]: ], }, ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path, runtime diff --git a/examples/explore-feishu-singleflight-smoke.py b/examples/explore-feishu-singleflight-smoke.py index 5a5fa6ee44..1266fad7e8 100644 --- a/examples/explore-feishu-singleflight-smoke.py +++ b/examples/explore-feishu-singleflight-smoke.py @@ -76,7 +76,7 @@ def main() -> None: nested_calls: list[str] = [] @singleflight_issue_fix_material_sync( - lambda _path, _goal_id: config_path + lambda _path, _goal_id, _runtime_override: config_path ) def nested_material(**_kwargs): nested_calls.append("material") diff --git a/examples/explore-result-layer-smoke.py b/examples/explore-result-layer-smoke.py index 73a9c67bb5..405e5b63fb 100644 --- a/examples/explore-result-layer-smoke.py +++ b/examples/explore-result-layer-smoke.py @@ -28,6 +28,7 @@ sys.path.insert(0, str(REPO_ROOT)) from loopx.capabilities.explore import result_log # noqa: E402 +from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 from loopx.capabilities.explore.router_state import ( # noqa: E402 advance_epoch, scope_family_key, @@ -1190,6 +1191,8 @@ def check_cli_surface() -> None: encoding="utf-8", ) + import_fixture_authority(registry, goal_id, provider="sqlite") + def run_cli(*extra_args: str) -> dict[str, object]: result = subprocess.run( [ diff --git a/examples/history-reserved-run-path-smoke.py b/examples/history-reserved-run-path-smoke.py index 450531fb79..55d5c8eec1 100644 --- a/examples/history-reserved-run-path-smoke.py +++ b/examples/history-reserved-run-path-smoke.py @@ -11,7 +11,7 @@ REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT)) -from loopx import history, state_refresh +from loopx import history, state_refresh # noqa: E402 GOAL_ID = "reserved-run-path-fixture" @@ -31,7 +31,8 @@ def write_registry(root: Path) -> Path: "---\n\n" "# Reserved Run Path Fixture\n\n" "## Next Action\n\n" - "- continue reserved run path validation\n\n", + "- continue reserved run path validation\n\n" + "## Agent Todo\n\n", encoding="utf-8", ) registry_path = project / ".loopx" / "registry.json" @@ -59,6 +60,8 @@ def write_registry(root: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path diff --git a/examples/issue-meta-surface-smoke.py b/examples/issue-meta-surface-smoke.py index ecd37ff72e..2bffb1678a 100644 --- a/examples/issue-meta-surface-smoke.py +++ b/examples/issue-meta-surface-smoke.py @@ -106,6 +106,10 @@ def write_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry_path.read_text())["goals"]: + import_fixture_authority(registry_path, record["id"], provider="sqlite") return registry_path, runtime diff --git a/examples/lark-extension-activation-smoke.py b/examples/lark-extension-activation-smoke.py index 8b810bea15..ed7097173e 100644 --- a/examples/lark-extension-activation-smoke.py +++ b/examples/lark-extension-activation-smoke.py @@ -104,6 +104,17 @@ def run_cli( ), encoding="utf-8", ) + # Capability availability is tested on a valid canonical Goal, independently + # of the explicit unmigrated-Goal refusal covered by authority smokes. + state = project / "STATE.md" + state.write_text("# Extension fixture\n\n## Objective\nObserve local extension availability.\n\n## Next Action\nInspect installed extension availability.\n\n## User Todo / Owner Review Reading Queue\n\n## Agent Todo\n") + data = json.loads(registry.read_text()) + data["common_runtime_root"] = str(runtime_root) + data["goals"][0]["state_file"] = state.name + registry.write_text(json.dumps(data)) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, "lark-extension-fixture", provider="sqlite") + drain_args = ( "lark-inbox", "drain", diff --git a/examples/long-task-cadence-policy-smoke.py b/examples/long-task-cadence-policy-smoke.py index a283a54bc4..16f7a937b9 100644 --- a/examples/long-task-cadence-policy-smoke.py +++ b/examples/long-task-cadence-policy-smoke.py @@ -232,6 +232,9 @@ def assert_status_and_quota_projection() -> None: encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry, "cadence", provider="sqlite") + status = subprocess.check_output( [ sys.executable, diff --git a/examples/loopx-chat-acp-adapter-smoke.py b/examples/loopx-chat-acp-adapter-smoke.py index 0c47bba404..c27df124c8 100644 --- a/examples/loopx-chat-acp-adapter-smoke.py +++ b/examples/loopx-chat-acp-adapter-smoke.py @@ -188,7 +188,7 @@ def main() -> None: ) assert visible.strip() == "ACP 回答。", events assert "" not in visible, visible - assert any(kind == "agent.phase" and "读取 [project]" in str(payload.get("label")) for kind, payload in events) + assert any(kind == "agent.phase" and "读取 [local-path]" in str(payload.get("label")) for kind, payload in events) assert str(root) not in str(events), events assert not any("private thought" in str(payload) for _, payload in events), events adapter.close_session() diff --git a/examples/loopx-chat-contract-smoke.py b/examples/loopx-chat-contract-smoke.py index 766e8cfa75..bb97cc60f1 100644 --- a/examples/loopx-chat-contract-smoke.py +++ b/examples/loopx-chat-contract-smoke.py @@ -108,7 +108,7 @@ def main() -> None: ) parsed = parse_agent_response(raw_response, protected_paths=[project, root]) assert parsed["schema_version"] == "loopx_chat_agent_response_v0", parsed - assert parsed["message"] == "I inspected [project] and recommend one bounded step.", parsed + assert parsed["message"] == "I inspected [local-path] and recommend one bounded step.", parsed assert parsed["proposals"] == [ { "kind": "todo", diff --git a/examples/loopx-chat-server-smoke.py b/examples/loopx-chat-server-smoke.py index 882e3b041f..8f5aaa4ac1 100644 --- a/examples/loopx-chat-server-smoke.py +++ b/examples/loopx-chat-server-smoke.py @@ -352,10 +352,12 @@ def main() -> None: with urllib.request.urlopen(f"{base_url}/chat/", timeout=4) as response: html = response.read().decode("utf-8") assert "LoopX 个人 Agent 工作区" in html, html - stylesheet_match = re.search(r'href="(/chat/assets/[^"]+\.css)"', html) - assert stylesheet_match, html - with urllib.request.urlopen(f"{base_url}{stylesheet_match.group(1)}", timeout=4) as response: - stylesheet = response.read().decode("utf-8") + stylesheets = re.findall(r'href="(/chat/assets/[^"]+\.css)"', html) + assert stylesheets, html + stylesheet = "" + for asset in stylesheets: + with urllib.request.urlopen(f"{base_url}{asset}", timeout=4) as response: + stylesheet += response.read().decode("utf-8") assert ".personal-workspace" in stylesheet, "packaged workspace stylesheet is incomplete" code, targets_payload = request_json(f"{base_url}/api/chat/goal-channel/targets") diff --git a/examples/loopx-turn-path-delta-acceptance-smoke.py b/examples/loopx-turn-path-delta-acceptance-smoke.py index 2ce08a5da6..8c6005bacc 100644 --- a/examples/loopx-turn-path-delta-acceptance-smoke.py +++ b/examples/loopx-turn-path-delta-acceptance-smoke.py @@ -17,7 +17,7 @@ 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, + read_canonical_todos_if_promoted, CanonicalAuthorityRequired, ) from loopx.control_plane.testing.canary_harness import import_fixture_authority # noqa: E402 @@ -239,17 +239,22 @@ def assert_unpromoted_replan_preserves_todo_authority() -> None: project, runtime, workspace, registry = _write_fixture(root) state = project / ".codex" / "goals" / GOAL_ID / "ACTIVE_GOAL_STATE.md" original_state = state.read_bytes() - assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) is None - result = _run_turn( - registry=registry, runtime=runtime, project=project, - workspace=workspace, host=_write_host(root), mode="material", - ) - # Planning-only settlement does not grant a Todo writer or promote legacy - # state. The synthetic Host marker is the independently validated effect. - assert result["status"] == "committed", result - assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) is None + try: + read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + except CanonicalAuthorityRequired as error: + assert error.payload["write_admitted"] is False + assert error.payload["legacy_fallback_used"] is False + else: + raise AssertionError("Unmigrated authority must refuse ordinary reads") + with contextlib.redirect_stdout(io.StringIO()) as output: + code = cli_main(["--registry", str(registry), "--runtime-root", str(runtime), + "--format", "json", "quota", "should-run", "--goal-id", GOAL_ID, + "--agent-id", AGENT_ID]) + refusal = json.loads(output.getvalue()) + assert code == 1 and refusal["error_code"] == "canonical_authority_required", refusal + assert refusal["write_admitted"] is False and refusal["legacy_fallback_used"] is False assert state.read_bytes() == original_state - assert (project / MARKER).with_suffix(".calls").read_text().splitlines() == ["material"] + assert not (project / MARKER).with_suffix(".calls").exists() def assert_canonical_turn_path_delta(provider: str) -> None: @@ -258,9 +263,7 @@ def assert_canonical_turn_path_delta(provider: str) -> None: project, runtime, workspace, registry = _write_fixture(root) host = _write_host(root) # Select the provider explicitly to test canonical Todo preservation. - # The separate unpromoted case covers planning without a delivery Task; - # it must neither initialize Todo authority nor mutate the legacy state. - assert read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) is None + # The separate unmigrated case proves explicit refusal without effects. calls = (project / MARKER).with_suffix(".calls") assert not calls.exists() import_fixture_authority(registry, GOAL_ID, provider=provider) diff --git a/examples/manager-goal-lifecycle-readback-smoke.py b/examples/manager-goal-lifecycle-readback-smoke.py index 7c4221d626..bc23d44f24 100644 --- a/examples/manager-goal-lifecycle-readback-smoke.py +++ b/examples/manager-goal-lifecycle-readback-smoke.py @@ -104,6 +104,9 @@ def build_fixture(root: Path) -> Path: } ], ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + for goal in goals: + import_fixture_authority(registry_path, goal["id"], provider="sqlite") return registry_path diff --git a/examples/meta-source-registry-self-repair-smoke.py b/examples/meta-source-registry-self-repair-smoke.py index b185cea0ef..588921ad62 100644 --- a/examples/meta-source-registry-self-repair-smoke.py +++ b/examples/meta-source-registry-self-repair-smoke.py @@ -132,6 +132,7 @@ def write_global_registry( def queue_item(status_payload: dict, goal_id: str) -> dict: items = status_payload.get("attention_queue", {}).get("items") or [] matches = [item for item in items if isinstance(item, dict) and item.get("goal_id") == goal_id] + matches = [item for item in matches if item.get("project_asset")] assert len(matches) == 1, items return matches[0] @@ -140,6 +141,17 @@ def assert_meta_source_shadow(finding_kind: str) -> None: with tempfile.TemporaryDirectory(prefix="loopx-meta-source-shadow-") as tmp: root = Path(tmp) registry_path, runtime, project = write_global_registry(root, finding_kind=finding_kind) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + # Author canonical fixture data via its owned project registry, then + # retain the deliberately broken shared source declaration for diagnostics. + fixture_registry = root / "fixture-source.json" + source = json.loads(registry_path.read_text()) + source.pop("registry_role", None) + for record in source["goals"]: + record.pop("source_registry", None) + fixture_registry.write_text(json.dumps(source), encoding="utf-8") + for record in source["goals"]: + import_fixture_authority(fixture_registry, record["id"], provider="sqlite") status_payload = collect_status( registry_path=registry_path, runtime_root_override=str(runtime), diff --git a/examples/periodic-report-html-smoke.py b/examples/periodic-report-html-smoke.py index 5c0769fd98..14c9bd5715 100644 --- a/examples/periodic-report-html-smoke.py +++ b/examples/periodic-report-html-smoke.py @@ -59,7 +59,6 @@ def main() -> None: assert expected in markdown_artifact["content"] assert "snapshot_example_delivery" not in primary_body assert "snapshot_example_delivery" in supporting - assert "Outcome: Stable release reached all production regions" in primary_body assert document["editorial"]["orchestration"]["summary_source"] == ( "typed_primary_items" ) diff --git a/examples/platform-migration-material-registry-smoke.py b/examples/platform-migration-material-registry-smoke.py index 84278980d1..dadfd5c8be 100644 --- a/examples/platform-migration-material-registry-smoke.py +++ b/examples/platform-migration-material-registry-smoke.py @@ -151,6 +151,8 @@ def write_platform_migration_fixture(root: Path) -> Path: ), encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path diff --git a/examples/project/bootstrap-force-preserve-todos-smoke.py b/examples/project/bootstrap-force-preserve-todos-smoke.py index 110b28e261..347c038cc1 100644 --- a/examples/project/bootstrap-force-preserve-todos-smoke.py +++ b/examples/project/bootstrap-force-preserve-todos-smoke.py @@ -11,6 +11,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(REPO_ROOT)) GOAL_ID = "bootstrap-preserve-fixture" @@ -129,6 +130,7 @@ def assert_canonical_reconnect(root: Path, provider: str) -> None: 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 + from loopx.control_plane.testing.canary_harness import assert_todo_read_unchanged 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) @@ -136,11 +138,11 @@ def assert_canonical_reconnect(root: Path, provider: str) -> None: 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 + assert_todo_read_unchanged(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_todo_read_unchanged(run_cli(*prefix, *read), before) assert text in state.read_text(encoding="utf-8") assert goal_from_registry(project)["coordination"]["storage_target"]["provider"] == provider diff --git a/examples/project/configure-goal-smoke.py b/examples/project/configure-goal-smoke.py index aeea283397..3fe4be2c85 100644 --- a/examples/project/configure-goal-smoke.py +++ b/examples/project/configure-goal-smoke.py @@ -135,6 +135,8 @@ def write_registry(root: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, "configure-goal-fixture", provider="sqlite") return registry_path diff --git a/examples/project/global-manager-command-cli-smoke.py b/examples/project/global-manager-command-cli-smoke.py index faf75948ef..6a292eee6f 100644 --- a/examples/project/global-manager-command-cli-smoke.py +++ b/examples/project/global-manager-command-cli-smoke.py @@ -21,7 +21,7 @@ build_todo_runtime_shadow_projection, capture_todo_archive_dependencies, ) -from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted # noqa: E402 +from loopx.control_plane.coordination.local_authority import CanonicalAuthorityRequired, read_canonical_todos_if_promoted # noqa: E402 from loopx.control_plane.todos.goal_todo_projection import project_goal_todo_items # noqa: E402 GOAL_ID = "smoke-goal" @@ -651,8 +651,21 @@ def run_scenario(provider: str | None) -> None: def main() -> int: os.environ["NODE_OPTIONS"] = os.environ.get("NODE_OPTIONS", "") + " --experimental-sqlite" - # Keep historical read compatibility alongside both canonical providers. - for provider in (None, "file", "sqlite"): + # Unmigrated ordinary reads refuse; both real providers retain the complete journey. + with tempfile.TemporaryDirectory(prefix="loopx-global-manager-unmigrated-") as tmp: + root = Path(tmp) + registry, runtime, _, _, fixture_files = write_fixture(root) + before = snapshot_files([registry, *fixture_files]) + for goal_id in (GOAL_ID, RUNNABLE_GOAL_ID, DEFERRED_GOAL_ID): + try: + read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=goal_id) + except CanonicalAuthorityRequired as error: + assert error.payload["write_admitted"] is False + assert error.payload["legacy_fallback_used"] is False + else: + raise AssertionError("Ordinary manager reads require canonical authority") + assert snapshot_files([registry, *fixture_files]) == before + for provider in ("file", "sqlite"): run_scenario(provider) print("global-manager-command-cli-smoke ok") return 0 diff --git a/examples/project/goal-channel-frontstage-fixture-smoke.py b/examples/project/goal-channel-frontstage-fixture-smoke.py index 97686fb5c0..2e5ad3f37a 100644 --- a/examples/project/goal-channel-frontstage-fixture-smoke.py +++ b/examples/project/goal-channel-frontstage-fixture-smoke.py @@ -33,7 +33,11 @@ def test_fixture_projection_and_html_are_read_only() -> None: assert projection["decision_frame"]["user_action_required"] is True, projection assert projection["decision_frame"]["agent_action_required"] is True, projection assert projection["agent_todos"][1]["claimed_by"] == "codex-side-bypass", projection - assert projection["active_leases"][1]["owner_agent"] == "codex-side-bypass", projection + # The renderer fixture carries display metadata without a canonical provider. + assert projection["active_leases"] == [], projection + assert projection["coordination_observation"]["status"] == "unavailable", projection + assert projection["coordination_authority"] == {"state": "unavailable"}, projection + assert projection["truth_contract"]["write_authority"] == "none", projection assert 'data-schema="goal_channel_projection_v0"' in html, html assert 'data-mode="read_only"' in html, html diff --git a/examples/project/goal-channel-projection-smoke.py b/examples/project/goal-channel-projection-smoke.py index daf19ae4b5..0f4f38909d 100644 --- a/examples/project/goal-channel-projection-smoke.py +++ b/examples/project/goal-channel-projection-smoke.py @@ -143,7 +143,11 @@ def test_basic_projection() -> None: assert payload["decision_frame"]["quiet_noop_allowed"] is False, payload assert payload["quota"]["state"] == "eligible", payload assert payload["agent_todos"][0]["claimed_by"] == "codex-side-bypass", payload - assert payload["active_leases"][0]["status"] == "soft_claim", payload + # A display-only sample cannot manufacture canonical ownership. + assert payload["active_leases"] == [], payload + assert payload["coordination_observation"]["status"] == "unavailable", payload + assert payload["coordination_authority"] == {"state": "unavailable"}, payload + assert payload["truth_contract"]["write_authority"] == "none", payload assert ( payload["source_refs"]["latest_run_generated_at"] == "2026-06-20T00:01:00Z" diff --git a/examples/project/goal-channel-status-export-smoke.py b/examples/project/goal-channel-status-export-smoke.py index 3f37cb3643..1acf316835 100644 --- a/examples/project/goal-channel-status-export-smoke.py +++ b/examples/project/goal-channel-status-export-smoke.py @@ -60,6 +60,7 @@ def write_registry(project: Path, runtime: Path) -> Path: "repo": str(project), "state_file": write_state(project), "adapter": {"kind": "smoke_v0", "status": "connected"}, + "coordination": {"registered_agents": ["codex-side-bypass"]}, "authority_sources": [], } ], @@ -70,6 +71,10 @@ def write_registry(project: Path, runtime: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry_path.read_text())["goals"]: + import_fixture_authority(registry_path, record["id"], provider="sqlite") return registry_path diff --git a/examples/project/goal-vision-closed-successor-smoke.py b/examples/project/goal-vision-closed-successor-smoke.py index fd1a213caf..fc5180b45c 100644 --- a/examples/project/goal-vision-closed-successor-smoke.py +++ b/examples/project/goal-vision-closed-successor-smoke.py @@ -11,6 +11,9 @@ 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 = "closed-vision-successor-fixture" AGENT_ID = "fixture-agent" @@ -91,6 +94,8 @@ def run_cli(registry: Path, runtime: Path, *args: str) -> dict[str, object]: encoding="utf-8", ) + import_fixture_authority(registry, GOAL_ID, provider="sqlite") + closed = run_cli( registry, runtime, diff --git a/examples/project/goal-vision-refresh-state-budget-smoke.py b/examples/project/goal-vision-refresh-state-budget-smoke.py index 12f3e2bc52..854fd688dc 100644 --- a/examples/project/goal-vision-refresh-state-budget-smoke.py +++ b/examples/project/goal-vision-refresh-state-budget-smoke.py @@ -211,7 +211,14 @@ def assert_vision_budget(provider: str) -> None: 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 + try: + read_canonical_todos_if_promoted(runtime_root=runtime, goal_id=GOAL_ID) + except LocalCoordinationAuthorityUnavailable as error: + assert error.code == "canonical_authority_required" + assert error.payload["write_admitted"] is False + assert error.payload["legacy_fallback_used"] is False + else: + raise AssertionError("an unmigrated read must refuse without Markdown fallback") 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" diff --git a/examples/project/project-agent-adoption-smoke.py b/examples/project/project-agent-adoption-smoke.py index 0ab64edc18..f088d467c3 100644 --- a/examples/project/project-agent-adoption-smoke.py +++ b/examples/project/project-agent-adoption-smoke.py @@ -144,6 +144,25 @@ def assert_adoption(provider: str) -> None: registry_path = write_planned_fixture(root) project = root / "project" + 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 + missing_context_quota = quota_should_run( root, registry_path, @@ -182,24 +201,6 @@ def assert_adoption(provider: str) -> None: assert first_quota["agent_todo_summary"]["open_count"] == 1, first_quota assert first_quota["selected_todo"]["text"] == AGENT_TODO, first_quota - 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 diff --git a/examples/project/project-asset-todo-projection-gap-smoke.py b/examples/project/project-asset-todo-projection-gap-smoke.py index 03d2396ef3..c8f9d052ee 100644 --- a/examples/project/project-asset-todo-projection-gap-smoke.py +++ b/examples/project/project-asset-todo-projection-gap-smoke.py @@ -70,6 +70,12 @@ def write_fixture(root: Path) -> tuple[Path, Path]: + "\n", encoding="utf-8", ) + original_display = state_path.read_bytes() + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry_path.read_text())["goals"]: + import_fixture_authority(registry_path, record["id"], provider="sqlite") + state_path.write_bytes(original_display) return registry_path, runtime @@ -143,9 +149,11 @@ def main() -> int: with tempfile.TemporaryDirectory(prefix="loopx-project-asset-todo-gap-") as raw_tmp: registry_path, runtime = write_fixture(Path(raw_tmp)) incomplete = run_cli(registry_path, runtime, "status", check=False) - assert incomplete["ok"] is False, incomplete - assert any("missing required Todo section(s): Agent Todo" in error - for error in incomplete["contract_errors"]), incomplete + assert incomplete["ok"] is True, incomplete + assert incomplete["contract_errors"] == [], incomplete + assert any("state_projection_gap" in warning + for warning in incomplete["contract_warnings"]), incomplete + before = run_cli(registry_path, runtime, "todo", "list", "--goal-id", GOAL_ID) state = registry_path.parent.parent / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" state.write_text(state.read_text(encoding="utf-8") + "\n## Agent Todo\n", encoding="utf-8") @@ -153,16 +161,17 @@ def main() -> int: assert payload["ok"] is True, payload item = attention_item(payload) # type: ignore[arg-type] project_asset = item["project_asset"] - projected_gap = project_asset["todo_projection_gap"] - assert projected_gap["kind"] == "project_asset_todo_projection_gap", projected_gap - assert projected_gap["missing_roles"] == ["user"], projected_gap - assert "user_todos" not in project_asset, project_asset - assert project_asset["agent_todos"]["open"] == 0, project_asset - assert item["todo_projection_gap"]["missing_roles"] == ["user"], item - + # Canonical empty roles are available facts even when the old display + # omitted a section. The pure gap cases above still cover missing data. + assert "todo_projection_gap" not in project_asset + assert "todo_projection_gap" not in item + assert project_asset["user_todos"]["open"] == 0 + assert project_asset["agent_todos"]["open"] == 0 + after = run_cli(registry_path, runtime, "todo", "list", "--goal-id", GOAL_ID) + assert after["todos"] == before["todos"] == [] + assert after["authority_read"] == before["authority_read"] markdown = run_cli(registry_path, runtime, "status", output_format="markdown") - assert "todo_projection_gap" in markdown, markdown - assert "missing_roles=user" in markdown, markdown + assert "todo_projection_gap" not in markdown, markdown print("project-asset-todo-projection-gap-smoke ok") return 0 diff --git a/examples/project/subagent-status-projection-smoke.py b/examples/project/subagent-status-projection-smoke.py index 94e7c72800..0a34a87b0a 100644 --- a/examples/project/subagent-status-projection-smoke.py +++ b/examples/project/subagent-status-projection-smoke.py @@ -130,6 +130,10 @@ def write_fixture(root: Path) -> tuple[Path, Path]: with (run_dir / "index.jsonl").open("w", encoding="utf-8") as f: for run in runs: f.write(json.dumps(run, ensure_ascii=False, sort_keys=True) + "\n") + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry_path.read_text())["goals"]: + import_fixture_authority(registry_path, record["id"], provider="sqlite") return registry_path, runtime diff --git a/examples/public_entry/readme-demo-surface-smoke.py b/examples/public_entry/readme-demo-surface-smoke.py index 3127f068c1..5211e37993 100644 --- a/examples/public_entry/readme-demo-surface-smoke.py +++ b/examples/public_entry/readme-demo-surface-smoke.py @@ -30,8 +30,8 @@ def main() -> int: compact_demo = compact(demo) for required in [ '
', - "long-horizon agents", - "personal agent teams", + "long-horizon goals", + "long-running agent teams", "## Meet the Personal Agent Workspace", "docs/assets/personal-workspace/research-workbench-hero.webp", "docs/architecture/rfcs/capable-manager-semantic-handoff-v0.md", diff --git a/examples/serve-status-global-registry-smoke.py b/examples/serve-status-global-registry-smoke.py index 8ba098cdf9..0aed384a83 100644 --- a/examples/serve-status-global-registry-smoke.py +++ b/examples/serve-status-global-registry-smoke.py @@ -68,6 +68,10 @@ def write_project_registry(project: Path, runtime: Path, goal_id: str, title: st + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry_path.read_text())["goals"]: + import_fixture_authority(registry_path, record["id"], provider="sqlite") return registry_path @@ -90,7 +94,7 @@ def main() -> None: runtime = root / "runtime" local_project = root / "local-project" global_project = root / "global-project" - local_registry = write_project_registry(local_project, runtime, "local-only-goal", "Local Only Goal") + local_registry = write_project_registry(local_project, root / "local-runtime", "local-only-goal", "Local Only Goal") global_source_registry = write_project_registry(global_project, runtime, "global-goal", "Global Goal") global_registry = runtime / "registry.global.json" diff --git a/examples/session_runtime/session-runtime-readonly-projection-smoke.py b/examples/session_runtime/session-runtime-readonly-projection-smoke.py index 27ee560a75..3b83482bd5 100644 --- a/examples/session_runtime/session-runtime-readonly-projection-smoke.py +++ b/examples/session_runtime/session-runtime-readonly-projection-smoke.py @@ -5,6 +5,7 @@ import json import sys +import tempfile from pathlib import Path @@ -262,6 +263,17 @@ def test_word_level_classification_flags_every_raw_material_key() -> None: def test_status_ingests_projection_first_screen() -> None: + with tempfile.TemporaryDirectory(prefix="loopx-session-readonly-") as temporary: + _assert_status_ingests_projection_first_screen(Path(temporary)) + + +def _assert_status_ingests_projection_first_screen(root: Path) -> None: + from loopx.control_plane.testing.canary_harness import import_fixture_authority + runtime, state, registry = root / "runtime", root / "STATE.md", root / "registry.json" + state.write_text("# Session Goal\n\n## Objective\nObserve compact session readback.\n\n## Agent Todo\n") + goal = {"id": "demo-goal", "repo": str(root), "state_file": str(state)} + registry.write_text(json.dumps({"common_runtime_root": str(runtime), "goals": [goal]})) + import_fixture_authority(registry, "demo-goal", provider="sqlite") payload = build_session_runtime_readonly_projection( goal_id="demo-goal", sessions=[ @@ -311,6 +323,7 @@ def test_status_ingests_projection_first_screen() -> None: "runs": [run], } queue = build_attention_queue( + runtime_root=runtime, registry_path=registry, contract={"ok": True}, history=history, global_registry={"ok": True, "findings": []}, @@ -330,7 +343,7 @@ def test_status_ingests_projection_first_screen() -> None: run_history = build_status_runtime_summaries( history=history, queue=queue, - runtime_root=REPO_ROOT, + runtime_root=runtime, goal_id_filter=None, display_limit=10, todo_index_limit=10, diff --git a/examples/state-migration-smoke.py b/examples/state-migration-smoke.py index fa655d70e3..658717cf9f 100644 --- a/examples/state-migration-smoke.py +++ b/examples/state-migration-smoke.py @@ -205,6 +205,10 @@ def main() -> int: assert migrated_run_payload["goal_id"] == NEW_GOAL_ID, migrated_run_payload assert "goal-harness" not in migrated_run_payload["recommended_action"], migrated_run_payload + from loopx.control_plane.testing.canary_harness import import_fixture_authority + # Path migration preserves source data; authority conversion is explicit. + import_fixture_authority(target_registry, NEW_GOAL_ID, provider="sqlite") + status = run_loopx( "--registry", str(target_registry), diff --git a/examples/state-projection-gap-smoke.py b/examples/state-projection-gap-smoke.py index 7f3c0c2f27..a03d220603 100644 --- a/examples/state-projection-gap-smoke.py +++ b/examples/state-projection-gap-smoke.py @@ -51,6 +51,8 @@ def write_registry(root: Path, state_text: str) -> tuple[Path, Path]: ], }, ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + import_fixture_authority(registry_path, GOAL_ID, provider="sqlite") return registry_path, runtime diff --git a/examples/subcommand-format-smoke.py b/examples/subcommand-format-smoke.py index 01cc1a4216..20d53050d4 100644 --- a/examples/subcommand-format-smoke.py +++ b/examples/subcommand-format-smoke.py @@ -67,6 +67,10 @@ def write_fixture(root: Path) -> Path: + "\n", encoding="utf-8", ) + from loopx.control_plane.testing.canary_harness import import_fixture_authority + + for record in json.loads(registry_path.read_text())["goals"]: + import_fixture_authority(registry_path, record["id"], provider="sqlite") return registry_path diff --git a/loopx/control_plane/testing/canary_harness.py b/loopx/control_plane/testing/canary_harness.py index 3fb37301d4..1991cb0c96 100644 --- a/loopx/control_plane/testing/canary_harness.py +++ b/loopx/control_plane/testing/canary_harness.py @@ -3,6 +3,7 @@ import json import subprocess import sys +from datetime import datetime from pathlib import Path from typing import Any, Iterable @@ -208,7 +209,7 @@ def import_fixture_authority(registry_path: Path, goal_id: str, *, provider: str runtime.mkdir(parents=True, exist_ok=True) backup = execute_state_backup_plan(build_state_backup_plan( project=project, runtime_root=runtime, - output_dir=runtime.parent / "fixture-backups", backup_id=goal_id, + output_dir=runtime.parent / f"{runtime.name}-fixture-backups", backup_id=goal_id, include_automations=False, include_skills=False, include_registry_projects=False, registry_path=registry_path, )) @@ -238,3 +239,64 @@ def import_fixture_authority(registry_path: Path, goal_id: str, *, provider: str 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 + + +def assert_todo_read_unchanged(current, previous): + """Compare complete readback except validated evaluator/observation clocks.""" + def stable(payload): + payload = without_resume_observation_time(payload) + envelope = payload["projection_envelope"] + assert envelope["schema_version"] == "loopx_projection_envelope_v0" + for key in ("observed_at", "served_at"): + assert datetime.fromisoformat(envelope.pop(key).replace("Z", "+00:00")).tzinfo is not None + for source in envelope["sources"]: + assert datetime.fromisoformat(source.pop("last_read_at").replace("Z", "+00:00")).tzinfo is not None + return payload + + assert stable(current) == stable(previous) + + + +def without_resume_observation_time(value): + """Compare all readback facts except the typed evaluator's observation time.""" + if isinstance(value, list): + return [without_resume_observation_time(item) for item in value] + if not isinstance(value, dict): + return value + is_resume = value.get("schema_version") == "todo_resume_condition_v0" + if is_resume and "evaluated_at" in value: + assert datetime.fromisoformat(value["evaluated_at"].replace("Z", "+00:00")).tzinfo is not None + return {key: without_resume_observation_time(item) for key, item in value.items() + if not (is_resume and key == "evaluated_at")} + + +def advance_fixture_authority( + runtime_root: Path, goal_id: str, projection: dict, *, operation_id: str, +) -> dict: + """Advance an already initialized disposable store through its real CAS. + + Consumer fixtures may author a later synthetic state, but must not replace + storage bytes, reset lineage, re-import Markdown or re-engage its fence. + """ + module = REPO_ROOT / "loopx/control_plane/coordination/local_authority_provider.ts" + script = ( + f"import {{openLocalAuthorityStore}} from {json.dumps(module.as_uri())};" + "let input='';for await(const chunk of process.stdin)input+=chunk;" + "const request=JSON.parse(input);" + "const store=await openLocalAuthorityStore(request.root,request.goal);" + "const head=await store.loadAuthority();" + "if(head.status!=='loaded')throw new Error(JSON.stringify(head));" + "const result=await store.commitAuthority({expected_provider_revision:head.provider_revision," + "operation_id:request.operation_id,events:[],next_projection:request.projection,receipts:[]});" + "process.stdout.write(JSON.stringify(result));" + ) + process = subprocess.run( + ["node", "--no-warnings", "--experimental-strip-types", "--input-type=module", "-e", script], + input=json.dumps({"root": str(runtime_root), "goal": goal_id, + "projection": projection, "operation_id": operation_id}), + capture_output=True, text=True, encoding="utf-8", check=False, timeout=45, + ) + assert process.returncode == 0, process.stderr + result = json.loads(process.stdout) + assert result["status"] == "applied", result + return result