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:
parent
72856a00ad
commit
6e9c93e7ca
2 changed files with 60 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue