From 165fc3d50f461d141917c5b10e79a2da595fc59b Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 21 Aug 2026 22:11:11 +0200 Subject: [PATCH] fix: reconcile intake and decision identifiers Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d --- .../commands/registrar_reconcile.py | 51 ++++++++++++------- tests/test_registrar_reconcile.py | 42 ++++++++++++++- 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/repo_manager/commands/registrar_reconcile.py b/src/repo_manager/commands/registrar_reconcile.py index 561ed2b..db004d3 100644 --- a/src/repo_manager/commands/registrar_reconcile.py +++ b/src/repo_manager/commands/registrar_reconcile.py @@ -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, diff --git a/tests/test_registrar_reconcile.py b/tests/test_registrar_reconcile.py index 15dafcf..0512ceb 100644 --- a/tests/test_registrar_reconcile.py +++ b/tests/test_registrar_reconcile.py @@ -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()