fix: reconcile intake and decision identifiers

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
tegwick 2026-08-21 22:11:11 +02:00
parent 60f4a5ca93
commit 165fc3d50f
2 changed files with 72 additions and 21 deletions

View file

@ -20,6 +20,7 @@ from typing import Any
import httpx
from repo_manager.gitops import GitError, commit_paths, push_ff
from repo_manager.parse.record import iter_record_files, parse_record_file
from repo_manager.parse.workplan import parse_workplan_file
LOCK_PATH = Path("/tmp/repo-manager-identifier-registrar.lock")
@ -57,25 +58,37 @@ def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
def _missing_identifiers(repo: Path) -> dict[str, list[str]]:
workplans: list[str] = []
tasks: list[str] = []
intakes: list[str] = []
decisions: list[str] = []
workplans_dir = repo / "workplans"
if not workplans_dir.is_dir():
return {"workplans": workplans, "tasks": tasks}
if workplans_dir.is_dir():
# Closed archives are frozen under ADR-007 and are not registration work.
for path in sorted(workplans_dir.glob("*.md")):
parsed = parse_workplan_file(path, repo_root=repo)
if parsed.frontmatter.get("type") != "workplan" or not parsed.id:
continue
# Closed records are frozen provenance under ADR-007. State Hub also
# deliberately refuses to create missing task rows for them.
if (parsed.status or "").strip().lower() in {"finished", "archived"}:
continue
if not parsed.state_hub_workstream_id:
workplans.append(parsed.id)
for task in parsed.tasks:
if task.id and not task.state_hub_task_id:
tasks.append(task.id)
# Closed archives are frozen under ADR-007 and are not registration work.
for path in sorted(workplans_dir.glob("*.md")):
parsed = parse_workplan_file(path, repo_root=repo)
if parsed.frontmatter.get("type") != "workplan" or not parsed.id:
continue
# Closed records are frozen provenance under ADR-007. State Hub also
# deliberately refuses to create missing task rows for them.
if (parsed.status or "").strip().lower() in {"finished", "archived"}:
continue
if not parsed.state_hub_workstream_id:
workplans.append(parsed.id)
for task in parsed.tasks:
if task.id and not task.state_hub_task_id:
tasks.append(task.id)
return {"workplans": workplans, "tasks": tasks}
for path in iter_record_files(repo):
for record in parse_record_file(path, repo_root=repo):
if record.uuid:
continue
target = intakes if record.kind == "intake" else decisions
target.append(record.id)
return {
"workplans": workplans,
"tasks": tasks,
"intakes": intakes,
"decisions": decisions,
}
def _check_git(repo: Path) -> tuple[dict[str, Any], str | None]:
@ -168,7 +181,7 @@ def registrar_reconcile(
},
cid,
)
if not before["workplans"] and not before["tasks"]:
if not any(before.values()):
evidence["missing_after"] = before
return RegistrarResult("noop", evidence, None, cid)
@ -223,7 +236,7 @@ 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 after["workplans"] or after["tasks"]:
if completed.returncode not in {0, 2} or any(after.values()):
return RegistrarResult(
"failed",
evidence,

View file

@ -56,7 +56,12 @@ def test_closed_records_are_not_registrar_work(tmp_path: Path) -> None:
workplan.read_text(encoding="utf-8").replace("status: active", "status: finished"),
encoding="utf-8",
)
assert rr._missing_identifiers(repo) == {"workplans": [], "tasks": []}
assert rr._missing_identifiers(repo) == {
"workplans": [],
"tasks": [],
"intakes": [],
"decisions": [],
}
def test_rejects_dirty_or_unsynced_repository(tmp_path: Path, monkeypatch) -> None:
@ -68,6 +73,34 @@ def test_rejects_dirty_or_unsynced_repository(tmp_path: Path, monkeypatch) -> No
assert dirty.error and dirty.error["code"] == "git_precondition_failed"
def test_missing_scan_includes_intakes_and_decisions(tmp_path: Path) -> None:
repo = _fixture(tmp_path)
records = repo / "intakes" / "records.md"
records.parent.mkdir()
records.write_text(
"""# Records
```yaml
id: DEMO-IN-0001
kind: intake
title: Intake
status: open
```
```yaml
id: DEMO-DEC-0001
kind: decision
title: Decision
status: open
```
""",
encoding="utf-8",
)
missing = rr._missing_identifiers(repo)
assert missing["intakes"] == ["DEMO-IN-0001"]
assert missing["decisions"] == ["DEMO-DEC-0001"]
def test_scopes_registrar_env_and_commits_assigned_ids(tmp_path: Path, monkeypatch) -> None:
repo = _fixture(tmp_path)
monkeypatch.setattr(rr, "_check_primary", lambda _api: ({"status": "ok", "db": "connected"}, None))
@ -91,7 +124,12 @@ def test_scopes_registrar_env_and_commits_assigned_ids(tmp_path: Path, monkeypat
)
assert result.status == "applied"
assert result.evidence["missing_after"] == {"workplans": [], "tasks": []}
assert result.evidence["missing_after"] == {
"workplans": [],
"tasks": [],
"intakes": [],
"decisions": [],
}
subject = subprocess.run(
["git", "log", "-1", "--format=%s"], cwd=repo, capture_output=True, text=True, check=True
).stdout.strip()