From e656f7f6ee138bf1dcd0b63831e405030da3b413 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 22 Aug 2026 09:53:11 +0200 Subject: [PATCH] feat(registrar): repair deterministic projections Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d --- src/repo_manager/cli.py | 7 +++ .../commands/registrar_reconcile.py | 63 ++++++++++++++++++- tests/test_registrar_reconcile.py | 46 ++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/repo_manager/cli.py b/src/repo_manager/cli.py index 3334fb3..b499e52 100644 --- a/src/repo_manager/cli.py +++ b/src/repo_manager/cli.py @@ -63,6 +63,12 @@ def main(argv: list[str] | None = None) -> int: help="Confirm that --api-base is the authoritative hub", ) p_registrar.add_argument("--push", action="store_true", help="Push the registrar commit") + p_registrar.add_argument( + "--repair-workplan", + default=None, + metavar="ID", + help="Rebuild and verify one already-identified workplan projection", + ) p_cmd = sub.add_parser( "update-task-status", @@ -412,6 +418,7 @@ def main(argv: list[str] | None = None) -> int: statehub_bin=args.statehub_bin, confirm_primary=args.confirm_primary, push=args.push, + repair_workplan=args.repair_workplan, ) print(json.dumps(result.to_dict(), indent=2)) return 0 if result.status in {"applied", "noop"} else 1 diff --git a/src/repo_manager/commands/registrar_reconcile.py b/src/repo_manager/commands/registrar_reconcile.py index db004d3..5e22d5e 100644 --- a/src/repo_manager/commands/registrar_reconcile.py +++ b/src/repo_manager/commands/registrar_reconcile.py @@ -141,6 +141,34 @@ def _check_primary(api_base: str) -> tuple[dict[str, Any], str | None]: return payload, None +def _workplan_projection_id(repo: Path, canonical_id: str) -> str | None: + """Return the authoritative projection UUID for one open workplan.""" + workplans_dir = repo / "workplans" + if not workplans_dir.is_dir(): + return None + for path in sorted(workplans_dir.glob("*.md")): + parsed = parse_workplan_file(path, repo_root=repo) + if parsed.id != canonical_id: + continue + return parsed.state_hub_workstream_id or None + return None + + +def _projection_exists(api_base: str, projection_id: str) -> tuple[dict[str, Any], str | None]: + try: + response = httpx.get( + f"{api_base.rstrip('/')}/workplans/{projection_id}", + timeout=10.0, + ) + response.raise_for_status() + payload = response.json() + except (httpx.HTTPError, ValueError) as exc: + return {}, f"workplan projection verification failed: {exc}" + if str(payload.get("id")) != projection_id: + return payload, "workplan projection returned an unexpected identity" + return payload, None + + def _run_statehub(command: list[str], *, env: dict[str, str]) -> subprocess.CompletedProcess[str]: return subprocess.run( command, @@ -159,6 +187,7 @@ def registrar_reconcile( statehub_bin: str | None = None, confirm_primary: bool = False, push: bool = False, + repair_workplan: str | None = None, ) -> RegistrarResult: """Register missing workplan/task UUIDs through one scoped child process.""" cid = str(uuid.uuid4()) @@ -171,6 +200,22 @@ def registrar_reconcile( "missing_before": before, } + repair_projection_id = None + if repair_workplan: + repair_projection_id = _workplan_projection_id(repo, repair_workplan) + evidence["repair_workplan"] = repair_workplan + evidence["repair_projection_id"] = repair_projection_id + if not repair_projection_id: + return RegistrarResult( + "rejected", + evidence, + { + "code": "repair_target_invalid", + "message": "repair workplan is absent or has no authoritative projection UUID", + }, + cid, + ) + if not confirm_primary: return RegistrarResult( "rejected", @@ -181,7 +226,7 @@ def registrar_reconcile( }, cid, ) - if not any(before.values()): + if not any(before.values()) and not repair_projection_id: evidence["missing_after"] = before return RegistrarResult("noop", evidence, None, cid) @@ -236,7 +281,21 @@ def registrar_reconcile( evidence["statehub_stderr_tail"] = completed.stderr[-2000:] after = _missing_identifiers(repo) evidence["missing_after"] = after - if completed.returncode not in {0, 2} or any(after.values()): + repair_verified = False + if repair_projection_id: + projection, projection_error = _projection_exists(api_base, repair_projection_id) + evidence["repair_projection"] = projection + evidence["repair_projection_verified"] = projection_error is None + if projection_error: + evidence["repair_projection_error"] = projection_error + repair_verified = projection_error is None + + accepted_exit_codes = {0, 2} + if repair_verified: + # A repository-scoped projection repair may coexist with legacy stale + # references that correctly keep the broader consistency report red. + accepted_exit_codes.add(1) + if completed.returncode not in accepted_exit_codes or any(after.values()): return RegistrarResult( "failed", evidence, diff --git a/tests/test_registrar_reconcile.py b/tests/test_registrar_reconcile.py index 0512ceb..549935c 100644 --- a/tests/test_registrar_reconcile.py +++ b/tests/test_registrar_reconcile.py @@ -134,3 +134,49 @@ def test_scopes_registrar_env_and_commits_assigned_ids(tmp_path: Path, monkeypat ["git", "log", "-1", "--format=%s"], cwd=repo, capture_output=True, text=True, check=True ).stdout.strip() assert subject == "chore(registrar): assign State Hub identifiers" + + +def test_repairs_an_already_identified_workplan_projection(tmp_path: Path, monkeypatch) -> None: + repo = _fixture(tmp_path) + workplan = repo / "workplans" / "DEMO-WP-0001.md" + text = workplan.read_text(encoding="utf-8") + text = text.replace( + "status: active\n---", + 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', + ) + text = text.replace( + "priority: high\n```", + 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', + ) + workplan.write_text(text, encoding="utf-8") + _git(repo, "add", ".") + _git(repo, "commit", "-m", "add authoritative identifiers") + _git(repo, "push") + + monkeypatch.setattr( + rr, + "_check_primary", + lambda _api: ({"status": "ok", "db": "connected"}, None), + ) + monkeypatch.setattr( + rr, + "_projection_exists", + lambda _api, projection_id: ({"id": projection_id}, None), + ) + + def fake_run(command, *, env): + assert env["STATEHUB_REGISTRAR"] == "1" + return subprocess.CompletedProcess(command, 1, "legacy stale references remain", "") + + monkeypatch.setattr(rr, "_run_statehub", fake_run) + + result = rr.registrar_reconcile( + repo, + statehub_bin="statehub", + confirm_primary=True, + repair_workplan="DEMO-WP-0001", + ) + + assert result.status == "applied" + assert result.evidence["repair_projection_verified"] is True + assert result.evidence["repair_projection_id"] == "11111111-1111-4111-8111-111111111111"