feat(registrar): verify file-backed record rebuilds

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
tegwick 2026-08-22 12:23:44 +02:00
parent 72856a00ad
commit 6e9c93e7ca
2 changed files with 60 additions and 10 deletions

View file

@ -193,31 +193,50 @@ def _check_empty_repo_projection(
return evidence, None
def _authoritative_projection_ids(repo: Path) -> tuple[set[str], set[str], str | None]:
def _authoritative_projection_ids(
repo: Path,
) -> tuple[set[str], set[str], dict[str, set[str]], str | None]:
workplan_ids: set[str] = set()
task_ids: set[str] = set()
record_ids: dict[str, set[str]] = {"intake": set(), "decision": set()}
for path in sorted((repo / "workplans").glob("*.md")):
parsed = parse_workplan_file(path, repo_root=repo)
if parsed.frontmatter.get("type") != "workplan" or not parsed.id:
continue
if not parsed.state_hub_workstream_id:
return set(), set(), f"{parsed.id} has no authoritative projection UUID"
return set(), set(), record_ids, f"{parsed.id} has no authoritative projection UUID"
workplan_ids.add(parsed.state_hub_workstream_id)
for task in parsed.tasks:
if task.id and not task.state_hub_task_id:
return set(), set(), f"{task.id} has no authoritative projection UUID"
return set(), set(), record_ids, f"{task.id} has no authoritative projection UUID"
if task.state_hub_task_id:
task_ids.add(task.state_hub_task_id)
if not workplan_ids:
return set(), set(), "repository has no root workplans to rebuild"
return workplan_ids, task_ids, None
return set(), set(), record_ids, "repository has no root workplans to rebuild"
for path in iter_record_files(repo):
for record in parse_record_file(path, repo_root=repo):
if record.kind not in record_ids:
continue
if not record.uuid:
return (
set(),
set(),
record_ids,
f"{record.id} has no authoritative projection UUID",
)
record_ids[record.kind].add(record.uuid)
return workplan_ids, task_ids, record_ids, None
def _verify_full_projection(
api_base: str, workplan_ids: set[str], task_ids: set[str]
api_base: str,
workplan_ids: set[str],
task_ids: set[str],
record_ids: dict[str, set[str]],
) -> tuple[dict[str, Any], str | None]:
missing_workplans: list[str] = []
missing_tasks: list[str] = []
missing_records: dict[str, list[str]] = {"intake": [], "decision": []}
try:
for projection_id in sorted(workplan_ids):
response = httpx.get(
@ -233,6 +252,15 @@ def _verify_full_projection(
missing_tasks.append(projection_id)
else:
response.raise_for_status()
for kind, endpoint in (("intake", "intakes"), ("decision", "decisions")):
for projection_id in sorted(record_ids[kind]):
response = httpx.get(
f"{api_base.rstrip('/')}/{endpoint}/{projection_id}", timeout=10.0
)
if response.status_code == 404:
missing_records[kind].append(projection_id)
else:
response.raise_for_status()
except httpx.HTTPError as exc:
return {}, f"full projection verification failed: {exc}"
evidence = {
@ -240,8 +268,12 @@ def _verify_full_projection(
"expected_tasks": len(task_ids),
"missing_workplans": missing_workplans,
"missing_tasks": missing_tasks,
"expected_intakes": len(record_ids["intake"]),
"expected_decisions": len(record_ids["decision"]),
"missing_intakes": missing_records["intake"],
"missing_decisions": missing_records["decision"],
}
if missing_workplans or missing_tasks:
if missing_workplans or missing_tasks or any(missing_records.values()):
return evidence, "full projection rebuild is incomplete"
return evidence, None
@ -359,6 +391,7 @@ def registrar_reconcile(
bootstrap_workplans: set[str] = set()
bootstrap_tasks: set[str] = set()
bootstrap_records: dict[str, set[str]] = {"intake": set(), "decision": set()}
if bootstrap_empty_projection:
bootstrap_before, bootstrap_error = _check_empty_repo_projection(api_base, repo.name)
evidence["bootstrap_projection_before"] = bootstrap_before
@ -369,10 +402,17 @@ def registrar_reconcile(
{"code": "bootstrap_precondition_failed", "message": bootstrap_error},
cid,
)
bootstrap_workplans, bootstrap_tasks, source_error = _authoritative_projection_ids(repo)
(
bootstrap_workplans,
bootstrap_tasks,
bootstrap_records,
source_error,
) = _authoritative_projection_ids(repo)
evidence["bootstrap_source"] = {
"workplans": len(bootstrap_workplans),
"tasks": len(bootstrap_tasks),
"intakes": len(bootstrap_records["intake"]),
"decisions": len(bootstrap_records["decision"]),
}
if source_error:
return RegistrarResult(
@ -427,6 +467,7 @@ def registrar_reconcile(
api_base,
bootstrap_workplans,
bootstrap_tasks,
bootstrap_records,
)
evidence["bootstrap_projection"] = projection
evidence["bootstrap_projection_verified"] = projection_error is None

View file

@ -219,12 +219,16 @@ def test_bootstraps_and_verifies_a_completely_empty_projection(tmp_path: Path, m
monkeypatch.setattr(
rr,
"_verify_full_projection",
lambda _api, workplans, tasks: (
lambda _api, workplans, tasks, records: (
{
"expected_workplans": len(workplans),
"expected_tasks": len(tasks),
"expected_intakes": len(records["intake"]),
"expected_decisions": len(records["decision"]),
"missing_workplans": [],
"missing_tasks": [],
"missing_intakes": [],
"missing_decisions": [],
},
None,
),
@ -245,7 +249,12 @@ def test_bootstraps_and_verifies_a_completely_empty_projection(tmp_path: Path, m
)
assert result.status == "applied"
assert result.evidence["bootstrap_source"] == {"workplans": 1, "tasks": 1}
assert result.evidence["bootstrap_source"] == {
"workplans": 1,
"tasks": 1,
"intakes": 0,
"decisions": 0,
}
assert result.evidence["bootstrap_projection_verified"] is True