feat(registrar): repair deterministic projections
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
parent
9bdc79a0d1
commit
e656f7f6ee
3 changed files with 114 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue