fix(registrar): scope identity preflight

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b22-9638-76d2-bbff-b7ea1770b118
This commit is contained in:
tegwick 2026-08-23 11:49:59 +02:00
parent 4398167580
commit 7a15f1da21
4 changed files with 152 additions and 2 deletions

View file

@ -101,6 +101,42 @@ def _missing_identifiers(repo: Path) -> dict[str, list[str]]:
}
def _requested_identity_findings(
identity: dict[str, Any],
requested: dict[str, list[str]],
*,
full_repository: bool = False,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
"""Return invalid IDs and collisions that can affect this invocation.
Ordinary registrar reconciliation is deliberately scoped to the records
missing projection UUIDs. Historical defects elsewhere in the repository
remain visible in ``record_identity`` evidence, but cannot prevent safe
assignment of an unrelated canonical ID. A full empty-projection rebuild
still requires the entire repository identity set to be valid.
"""
if full_repository:
return (
list(identity["invalid_identifiers"]),
list(identity["identity_collisions"]),
)
requested_by_kind = {
"workplan": set(requested["workplans"]),
"task": set(requested["tasks"]),
"intake": set(requested["intakes"]),
"decision": set(requested["decisions"]),
}
def affects_request(item: dict[str, Any]) -> bool:
return item.get("id") in requested_by_kind.get(str(item.get("kind")), set())
return (
[item for item in identity["invalid_identifiers"] if affects_request(item)],
[item for item in identity["identity_collisions"] if affects_request(item)],
)
def _check_git(repo: Path) -> tuple[dict[str, Any], str | None]:
status = _git(repo, "status", "--porcelain")
if status.returncode != 0:
@ -390,13 +426,30 @@ def registrar_reconcile(
"record_identity": identity,
}
if identity["identity_collisions"]:
blocking_invalid, blocking_collisions = _requested_identity_findings(
identity,
before,
full_repository=bootstrap_empty_projection,
)
evidence["blocking_invalid_identifiers"] = blocking_invalid
evidence["blocking_identity_collisions"] = blocking_collisions
if blocking_invalid:
return RegistrarResult(
"rejected",
evidence,
{
"code": "record_identifier_invalid",
"message": "a requested work-record id is not accepted by the canon registry",
},
cid,
)
if blocking_collisions:
return RegistrarResult(
"rejected",
evidence,
{
"code": "record_identity_collision",
"message": "same canonical work-record id has conflicting or incomplete UUID assignments",
"message": "a requested canonical work-record id has conflicting or incomplete UUID assignments",
},
cid,
)