fix: preserve scoped Custodian historical work identities

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-09 12:47:43 +02:00
parent 6854ea10b2
commit 89524d83e4
5 changed files with 657 additions and 10 deletions

View file

@ -9,6 +9,18 @@ kinds:
- "^[A-Z][A-Z0-9]*(?:-[A-Z][A-Z0-9]*)*-WP-[0-9]{4}$"
- "^[A-Z][A-Z0-9]*(?:-[A-Z][A-Z0-9]*)*-WP-ADHOC-[0-9]{4}-[0-9]{2}-[0-9]{2}$"
legacy_patterns:
- pattern: "^CUST-WP-0000b$"
source: Custodian historical workplan suffix retained by source and hub
grandfathered: true
- pattern: "^CUST-WP-0000c$"
source: Custodian archived historical workplan suffix retained by source and hub
grandfathered: true
- pattern: "^CUST-WP-0010b$"
source: Custodian historical workplan suffix retained by source and hub
grandfathered: true
- pattern: "^CUST-WP-(0017|0053)-LEGACY$"
source: Custodian archived legacy workplan identity retained by source and hub
grandfathered: true
- pattern: "^ADHOC-[0-9]{4}-[0-9]{2}-[0-9]{2}$"
source: daily ad-hoc containers created before repository-qualified ad-hoc ids
grandfathered: true
@ -32,6 +44,26 @@ kinds:
- pattern: "^MASON-0001-T[0-9]{2,3}$"
source: ops-mason bootstrap tasks created before WP infix convention
grandfathered: true
- pattern: "^CUST-WP-(?:0000c-T0[1-4]|0010b-T0[1-2])$"
source: Custodian historical task identity retained by source and hub
grandfathered: true
scoped_legacy_patterns:
- pattern: "^(?:T01|T02|T03|T04|T05)$"
parent_pattern: "^CUST-WP-0006$"
source: Custodian historical task blocks with parent-qualified UUID bindings
grandfathered: true
- pattern: "^(?:T01|T02|T03|T04|T05|T06|T07|T08|T09|T10|T11|T12|T13|T14|T15|T16|T17)$"
parent_pattern: "^CUST-WP-0007$"
source: Custodian historical task blocks with parent-qualified UUID bindings
grandfathered: true
- pattern: "^(?:T01|T02|T03|T04|T05|T06|T07|T08|T09)$"
parent_pattern: "^CUST-WP-0030$"
source: Custodian historical task blocks with parent-qualified UUID bindings
grandfathered: true
- pattern: "^(?:T01|T02|T03|T04|T05|T06|T07)$"
parent_pattern: "^CUST-WP-0031$"
source: Custodian historical task blocks with parent-qualified UUID bindings
grandfathered: true
- kind: intake
id_patterns:
- "^[A-Z]+-IN-[0-9]{4}$"

View file

@ -0,0 +1,366 @@
{
"schema": "hfact.legacy-task-preservation.v1",
"production_path": "derive_from_checkout -> _sync_existing_workplan_tasks",
"database": "read-only HTTP snapshot; merge in memory",
"source": "the-custodian",
"verified_count": 54,
"records": [
{
"uuid": "b771f327-128e-4923-a7ad-2080b4e49eb9",
"parent_uuid": "7ce13282-d534-492a-8d42-b3a134028823",
"record_id": "CUST-WP-0006-T01",
"status": "done"
},
{
"uuid": "2be639ea-a19e-4c80-bcde-c3da06ec5a49",
"parent_uuid": "7ce13282-d534-492a-8d42-b3a134028823",
"record_id": "CUST-WP-0006-T02",
"status": "done"
},
{
"uuid": "da639706-3b14-42b8-92de-b9de84dbb2be",
"parent_uuid": "7ce13282-d534-492a-8d42-b3a134028823",
"record_id": "CUST-WP-0006-T03",
"status": "done"
},
{
"uuid": "96dd379a-02b4-4629-8db7-3bef15b9639d",
"parent_uuid": "7ce13282-d534-492a-8d42-b3a134028823",
"record_id": "CUST-WP-0006-T04",
"status": "done"
},
{
"uuid": "032649fb-2d21-44b7-9735-346405168d8e",
"parent_uuid": "7ce13282-d534-492a-8d42-b3a134028823",
"record_id": "CUST-WP-0006-T05",
"status": "done"
},
{
"uuid": "1c21c419-30f8-4208-9a55-c2fd83d5005a",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T01",
"status": "done"
},
{
"uuid": "fe4f5673-6053-404a-8930-4bc0c7d29fd9",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T02",
"status": "done"
},
{
"uuid": "c1ccf2ae-6241-4281-a443-12953796c1ee",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T03",
"status": "done"
},
{
"uuid": "4997aa59-39c0-46d6-8c63-f13fffd8d6ea",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T04",
"status": "done"
},
{
"uuid": "50fdb7ee-91c3-4a2b-be27-e171c144aec6",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T05",
"status": "done"
},
{
"uuid": "4b19bb95-7200-4fa4-a240-afe14012bafa",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T06",
"status": "done"
},
{
"uuid": "b34b6bb0-3968-464f-b340-389c4758821e",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T07",
"status": "done"
},
{
"uuid": "6f1fcf2c-824b-4e3e-884f-5e48b5dea51d",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T08",
"status": "done"
},
{
"uuid": "58a23afa-601a-40a5-b658-2603dc006d13",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T09",
"status": "done"
},
{
"uuid": "b0bf1338-b097-4130-ab18-95b4980cf551",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T10",
"status": "done"
},
{
"uuid": "2a49ad8e-8d6d-4082-8833-a79d9ace0b34",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T11",
"status": "done"
},
{
"uuid": "1ab3b919-64f7-432a-b173-7b66b042955f",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T12",
"status": "done"
},
{
"uuid": "015462de-2095-48ff-8b2e-3f53e41dfe32",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T13",
"status": "done"
},
{
"uuid": "f0f1a2d0-f0a3-45a4-ad10-b86f32849a84",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T14",
"status": "done"
},
{
"uuid": "2a90b3f7-4938-4235-8ab6-1f9ad9cb06a7",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T15",
"status": "done"
},
{
"uuid": "081ef72e-e19c-4938-b6de-c0c17b98d99a",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T16",
"status": "done"
},
{
"uuid": "5626cd89-ff77-4f45-90e6-2059673e4247",
"parent_uuid": "22e18151-fc83-438c-b732-10e056e64a20",
"record_id": "CUST-WP-0007-T17",
"status": "done"
},
{
"uuid": "8ee527cf-436b-4bab-bdb8-406314a38d99",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T01",
"status": "done"
},
{
"uuid": "02c27d25-d744-4da0-9bcb-b40ada54d5a5",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T02",
"status": "done"
},
{
"uuid": "7721d884-bd70-459f-b36d-450d69aac549",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T03",
"status": "done"
},
{
"uuid": "dc63746d-74b3-434a-925c-1cead480198f",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T04",
"status": "done"
},
{
"uuid": "3225cc6c-2574-41e9-b8fd-e5e703a9dd7c",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T05",
"status": "done"
},
{
"uuid": "c01cead4-ff9c-4533-b3d8-9f7554387771",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T07",
"status": "done"
},
{
"uuid": "84183245-5016-4d87-ad6a-9cd5f6873245",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T08",
"status": "done"
},
{
"uuid": "3ef43135-fb65-4cca-b8c3-4c7eeb52107c",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T09",
"status": "done"
},
{
"uuid": "107cb5bb-ff0c-4c97-af04-2cdeff11f0b2",
"parent_uuid": "9d8e1c33-2067-4593-a5d8-d28dda3b1d21",
"record_id": "CUST-WP-0030-T06",
"status": "done"
},
{
"uuid": "8d5e3e37-c753-4cdc-9211-83ee39f6b0f2",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T01",
"status": "done"
},
{
"uuid": "ac47994c-efe9-404f-8065-9adf2e923d4c",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T02",
"status": "done"
},
{
"uuid": "b380fd2b-28fa-4c47-96d6-4c65a0300c44",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T03",
"status": "done"
},
{
"uuid": "00d44110-fcb4-45cc-8bc8-454af2629d2f",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T04",
"status": "done"
},
{
"uuid": "7f0748c7-bdee-4801-b870-d4940a5a2e63",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T05",
"status": "done"
},
{
"uuid": "c6e1be52-961c-4e34-96b1-e450d64298df",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T06",
"status": "done"
},
{
"uuid": "7e07e32c-683d-47a1-951c-beace9f245a6",
"parent_uuid": "7b480543-b18a-4d79-a30b-7b34bce27ee5",
"record_id": "CUST-WP-0031-T07",
"status": "done"
},
{
"uuid": "f0d2ad65-c8ed-4f36-bb48-ca4b6b033439",
"parent_uuid": "b802bcf5-da82-4778-a2d8-fec2b02d877e",
"record_id": "CUST-WP-0000c-T01",
"status": "done"
},
{
"uuid": "77276b8b-fff3-474b-ab27-bd99f1f1a54c",
"parent_uuid": "b802bcf5-da82-4778-a2d8-fec2b02d877e",
"record_id": "CUST-WP-0000c-T02",
"status": "done"
},
{
"uuid": "cbca1200-26c2-43e7-a92f-97fc2e53a27b",
"parent_uuid": "b802bcf5-da82-4778-a2d8-fec2b02d877e",
"record_id": "CUST-WP-0000c-T03",
"status": "cancel"
},
{
"uuid": "d53e1c7f-acea-458b-b6c2-0ce384fa946e",
"parent_uuid": "b802bcf5-da82-4778-a2d8-fec2b02d877e",
"record_id": "CUST-WP-0000c-T04",
"status": "cancel"
},
{
"uuid": "051fd9e1-d15c-4665-adc9-704c4d2b24ea",
"parent_uuid": "def8455a-9de4-4891-a15a-5b09ca98ab1e",
"record_id": "CUST-WP-0010b-T01",
"status": "done"
},
{
"uuid": "103b46d9-6241-4ef1-9063-054478900b5c",
"parent_uuid": "def8455a-9de4-4891-a15a-5b09ca98ab1e",
"record_id": "CUST-WP-0010b-T02",
"status": "done"
},
{
"uuid": "a90728f0-4882-4bd5-8b27-531a4f33ba12",
"parent_uuid": "d3e1e974-377b-4b2d-a373-af696035a516",
"record_id": "CUST-WP-0017-LEGACY-T01",
"status": "done"
},
{
"uuid": "9b7c4ff3-befb-4d38-aa2d-4af7b74979b6",
"parent_uuid": "d3e1e974-377b-4b2d-a373-af696035a516",
"record_id": "CUST-WP-0017-LEGACY-T02",
"status": "done"
},
{
"uuid": "bf491d00-5f9f-495e-b94d-2480f37e2fc1",
"parent_uuid": "d3e1e974-377b-4b2d-a373-af696035a516",
"record_id": "CUST-WP-0017-LEGACY-T03",
"status": "cancel"
},
{
"uuid": "cd2eefb5-3238-42c8-aa9c-0819141dde35",
"parent_uuid": "d3e1e974-377b-4b2d-a373-af696035a516",
"record_id": "CUST-WP-0017-LEGACY-T04",
"status": "cancel"
},
{
"uuid": "2f6cfff1-a5bf-4d59-82e1-c7a907ea9a42",
"parent_uuid": "d3e1e974-377b-4b2d-a373-af696035a516",
"record_id": "CUST-WP-0017-LEGACY-T05",
"status": "cancel"
},
{
"uuid": "5c4769cc-e8f6-46d9-ac65-e11aafb2fb21",
"parent_uuid": "d3e1e974-377b-4b2d-a373-af696035a516",
"record_id": "CUST-WP-0017-LEGACY-T06",
"status": "cancel"
},
{
"uuid": "b5ae1c35-3bfa-4410-ba7a-79a61b1e9a2d",
"parent_uuid": "a4422777-8d93-4aab-8380-23773bd1525f",
"record_id": "CUST-WP-0053-LEGACY-T01",
"status": "cancel"
},
{
"uuid": "dca40d4e-338d-4abe-9e95-9f5514fc314e",
"parent_uuid": "a4422777-8d93-4aab-8380-23773bd1525f",
"record_id": "CUST-WP-0053-LEGACY-T02",
"status": "cancel"
},
{
"uuid": "1e0997db-01bb-4293-811b-8c8087a3a7fd",
"parent_uuid": "a4422777-8d93-4aab-8380-23773bd1525f",
"record_id": "CUST-WP-0053-LEGACY-T03",
"status": "cancel"
},
{
"uuid": "deff9b36-cbd0-4630-a34a-095098f50293",
"parent_uuid": "a4422777-8d93-4aab-8380-23773bd1525f",
"record_id": "CUST-WP-0053-LEGACY-T04",
"status": "cancel"
}
],
"created": 0,
"cancelled": 0,
"identity_or_status_changes": 0,
"repo_manager_tests_passed": 169,
"source_workplans": 9,
"parent_local_aliases": 38,
"remaining_missing_bindings": [
{
"kind": "task",
"record_id": "CUST-WP-ADHOC-2026-05-02-T01",
"uuid": "be709e52-5e23-5339-8aa3-a2ea5412c9a9",
"path": "workplans/ADHOC-2026-05-02.md"
},
{
"kind": "task",
"record_id": "CUST-WP-ADHOC-2026-07-02-T01",
"uuid": "1d2714e2-6d21-50c3-88ce-8cbb8c9f904e",
"path": "workplans/ADHOC-2026-07-02.md"
},
{
"kind": "task",
"record_id": "CUST-WP-ADHOC-2026-08-25-T01",
"uuid": "9a7e4024-23ea-59a9-8f1a-68284db059a1",
"path": "workplans/ADHOC-2026-08-25.md"
}
],
"missing_bindings_match_existing_hub": true,
"residuals": [
"CUST-IN-0017",
"HFACT-WP-0001-T02"
],
"delegated_model": "gpt-5.6-luna",
"reviewed_by": "codex/root"
}

View file

@ -24,7 +24,11 @@ def load_kind_registry(path: Path | None = None) -> dict[str, Any]:
def classify_record_id(
kind: str, identifier: str, *, registry_path: Path | None = None
kind: str,
identifier: str,
*,
parent_id: str | None = None,
registry_path: Path | None = None,
) -> str | None:
"""Return canonical/grandfathered when the canon registry accepts an id."""
registry = load_kind_registry(registry_path)
@ -38,35 +42,77 @@ def classify_record_id(
str(legacy.get("pattern") or ""), identifier
):
return "grandfathered"
# Historical task blocks may use a local TNN label. Accept these only
# under an explicitly enumerated parent workplan; the parent scope is
# part of identity and no source id/UUID is rewritten.
if parent_id:
for legacy in entry.get("scoped_legacy_patterns") or []:
if (
legacy.get("grandfathered")
and re.fullmatch(str(legacy.get("pattern") or ""), identifier)
and re.fullmatch(str(legacy.get("parent_pattern") or ""), parent_id)
):
return "grandfathered"
return None
def scan_record_identities(repo_root: Path, *, registry_path: Path | None = None) -> dict[str, Any]:
"""Describe invalid ids, benign duplicate sources, and hard collisions."""
occurrences: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list)
occurrences: dict[tuple[str, str, str | None], list[dict[str, Any]]] = defaultdict(list)
invalid: list[dict[str, str]] = []
def add(kind: str, identifier: str | None, record_uuid: str | None, source: str) -> None:
def add(
kind: str,
identifier: str | None,
record_uuid: str | None,
source: str,
*,
parent_id: str | None = None,
) -> None:
if not identifier:
return
classification = classify_record_id(kind, identifier, registry_path=registry_path)
item = {"kind": kind, "id": identifier, "uuid": record_uuid, "source": source}
occurrences[(kind, identifier)].append(item)
if classification is None:
classification = classify_record_id(
kind, identifier, parent_id=parent_id, registry_path=registry_path
)
item = {
"kind": kind,
"id": identifier,
"uuid": record_uuid,
"source": source,
**({"parent_id": parent_id} if parent_id else {}),
}
# Only the explicitly scoped local aliases use parent scope. Qualified
# canonical IDs remain globally collision checked.
local_alias = (
kind == "task"
and parent_id is not None
and re.fullmatch(r"T[0-9]{2}", identifier) is not None
and classification == "grandfathered"
)
occurrences[(kind, identifier, parent_id if local_alias else None)].append(item)
if classification is None or (local_alias and not record_uuid):
invalid.append({"kind": kind, "id": identifier, "source": source})
for path in iter_workplan_files(repo_root):
workplan = parse_workplan_file(path, repo_root=repo_root)
add("workplan", workplan.id, workplan.state_hub_workstream_id, workplan.path)
for number, task in enumerate(workplan.tasks, start=1):
add("task", task.id, task.state_hub_task_id, f"{workplan.path}#task-block-{number}")
add(
"task",
task.id,
task.state_hub_task_id,
f"{workplan.path}#task-block-{number}",
parent_id=workplan.id,
)
for path in iter_record_files(repo_root):
for number, record in enumerate(parse_record_file(path, repo_root=repo_root), start=1):
add(record.kind, record.id, record.uuid, f"{record.source_path}#record-block-{number}")
duplicates: list[dict[str, Any]] = []
collisions: list[dict[str, Any]] = []
for (kind, identifier), items in sorted(occurrences.items()):
for (kind, identifier, _parent_id), items in sorted(
occurrences.items(), key=lambda pair: (pair[0][0], pair[0][1], pair[0][2] or "")
):
if len(items) < 2:
continue
uuids = {item["uuid"] for item in items}
@ -74,8 +120,10 @@ def scan_record_identities(repo_root: Path, *, registry_path: Path | None = None
"kind": kind,
"id": identifier,
"uuid": items[0]["uuid"] if len(uuids) == 1 else None,
"sources": [item["source"] for item in items],
"sources": sorted(item["source"] for item in items),
}
if items[0].get("parent_id"):
result["parent_id"] = items[0]["parent_id"]
if len(uuids) == 1 and None not in uuids:
duplicates.append(result)
else:

View file

@ -0,0 +1,130 @@
from pathlib import Path
from repo_manager.identifiers import ensure_missing_work_record_identifiers
from repo_manager.record_identity import scan_record_identities
def test_custodian_scoped_legacy_ids_are_grandfathered_and_parent_scoped(tmp_path: Path) -> None:
root = tmp_path
for number, workplan_id in enumerate(
("CUST-WP-0000c", "CUST-WP-0010b", "CUST-WP-0017-LEGACY", "CUST-WP-0053-LEGACY"), start=1
):
_write_workplan(
root / "workplans" / f"{number}.md",
workplan_id,
f"{workplan_id}-T01",
f"{number:08d}-1111-4111-8111-111111111111",
)
report = scan_record_identities(root)
assert report["invalid_identifiers"] == []
assert report["identity_collisions"] == []
assert report["duplicate_source_occurrences"] == []
def test_full_legacy_bindings_dry_run_has_no_assignments(tmp_path: Path) -> None:
for number, workplan_id in enumerate(
("CUST-WP-0000c", "CUST-WP-0010b", "CUST-WP-0017-LEGACY", "CUST-WP-0053-LEGACY"), start=1
):
_write_workplan(
tmp_path / "workplans" / f"{number}.md",
workplan_id,
f"{workplan_id}-T01",
f"{number:08d}-1111-4111-8111-111111111111",
)
result = ensure_missing_work_record_identifiers(tmp_path, execute=False)
assert result["assignments"] == []
assert result["files_changed"] == []
def test_unqualified_task_remains_invalid_outside_allowlisted_parents(tmp_path: Path) -> None:
workplan = tmp_path / "workplans" / "CUST-WP-0099.md"
workplan.parent.mkdir()
workplan.write_text(
'---\nid: CUST-WP-0099\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---\n'
'\n```task\nid: T01\nstatus: done\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```\n',
encoding="utf-8",
)
report = scan_record_identities(tmp_path)
assert report["invalid_identifiers"][0]["id"] == "T01"
def _write_workplan(path: Path, workplan_id: str, task_id: str, task_uuid: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
f'---\nid: {workplan_id}\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---\n'
f'\n```task\nid: {task_id}\nstatus: done\nstate_hub_task_id: "{task_uuid}"\n```\n',
encoding="utf-8",
)
def test_canonical_qualified_task_collision_remains_global(tmp_path: Path) -> None:
_write_workplan(
tmp_path / "workplans" / "a.md",
"CUST-WP-0099",
"CUST-WP-0099-T01",
"22222222-2222-4222-8222-222222222222",
)
_write_workplan(
tmp_path / "workplans" / "b.md",
"CUST-WP-0100",
"CUST-WP-0099-T01",
"33333333-3333-4333-8333-333333333333",
)
report = scan_record_identities(tmp_path)
assert report["identity_collisions"][0]["id"] == "CUST-WP-0099-T01"
def test_same_parent_local_alias_collision_fails_closed(tmp_path: Path) -> None:
path = tmp_path / "workplans" / "CUST-WP-0006.md"
path.parent.mkdir()
path.write_text(
'---\nid: CUST-WP-0006\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---\n'
'\n```task\nid: T01\nstatus: done\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```\n'
'\n```task\nid: T01\nstatus: done\nstate_hub_task_id: "33333333-3333-4333-8333-333333333333"\n```\n',
encoding="utf-8",
)
report = scan_record_identities(tmp_path)
assert report["identity_collisions"][0]["id"] == "T01"
def test_unknown_alias_and_missing_managed_uuid_are_invalid(tmp_path: Path) -> None:
_write_workplan(
tmp_path / "workplans" / "unknown.md",
"CUST-WP-0006",
"T99",
"22222222-2222-4222-8222-222222222222",
)
_write_workplan(tmp_path / "workplans" / "missing.md", "CUST-WP-0006", "T01", "")
report = scan_record_identities(tmp_path)
assert {item["id"] for item in report["invalid_identifiers"]} == {"T99", "T01"}
def test_all_38_local_aliases_preserve_binding_and_source_bytes(tmp_path):
before = {}
for parent, count in (
("CUST-WP-0006", 5),
("CUST-WP-0007", 17),
("CUST-WP-0030", 9),
("CUST-WP-0031", 7),
):
path = tmp_path / "workplans" / (parent + ".md")
path.parent.mkdir(exist_ok=True)
body = f'---\nid: {parent}\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---\n'
for n in range(1, count + 1):
body += f'\n```task\nid: T{n:02d}\nstatus: done\nstate_hub_task_id: "{int(parent[-4:]):08d}-1111-4111-8111-{n:012d}"\n```\n'
path.write_text(body)
before[path] = path.read_bytes()
assert scan_record_identities(tmp_path)["ok"]
result = ensure_missing_work_record_identifiers(tmp_path, execute=True)
assert result["assignments"] == [] and result["files_changed"] == []
assert all(path.read_bytes() == body for path, body in before.items())
def test_new_suffix_task_is_not_grandfathered(tmp_path):
_write_workplan(
tmp_path / "workplans" / "new.md",
"CUST-WP-0000c",
"CUST-WP-0000c-T99",
"22222222-2222-4222-8222-222222222222",
)
assert not scan_record_identities(tmp_path)["ok"]

View file

@ -0,0 +1,71 @@
---
id: RMGR-WP-0014
type: workplan
title: "Preserve Custodian historical identities during ordinary synchronization"
domain: infotech
repo: repo-manager
status: finished
owner: codex
topic_slug: infotech
created: "2026-09-09"
updated: "2026-09-09"
---
# Custodian historical identity compatibility
Origin: the user's HFACT-WP-0001 critical-path continuation and delegated legacy
ID side quest; historical reconciliation remains CUST-IN-0017 in the-custodian.
Review found valid historical suffix IDs and parent-local TNN aliases rejected by
the new global identity gate. Preserve the existing source labels and UUIDs.
## Audit the exact source and Hub bindings
```task
id: RMGR-WP-0014-T01
status: done
priority: medium
```
Read-only audit covers nine historical workplans and 54 existing task UUIDs.
The 38 local aliases are qualified by their original parent in the projection.
The actual State Hub derive/merge path retained all 54 task UUIDs, parents and
statuses in memory against a current HTTP snapshot; no rows were created or
cancelled. Three unrelated missing qualified ad-hoc source bindings already
match the deterministic UUIDs held by the Hub and may be restored by the normal
identifier tool. See docs/evidence/2026-09-09-custodian-legacy-identities.json.
## Add bounded compatibility without weakening new identities
```task
id: RMGR-WP-0014-T02
status: done
priority: medium
```
The kind registry enumerates the historical suffix IDs and each observed local
alias range. Parent scoping applies only to those accepted local aliases with
an existing managed UUID. Canonical qualified IDs remain globally checked.
Unknown aliases, same-parent UUID collisions and missing local UUIDs refuse
ordinary registration. The Custodian machine canon carries the same historical
interpretation; no new identifier convention is introduced.
## Verify safe registration and hand off broader reconciliation
```task
id: RMGR-WP-0014-T03
status: done
priority: medium
```
Repo Manager suite: 169 passed. Portable tests cover global canonical collisions,
parent-local collisions, unknown parent/alias, missing UUID, immutable source
bindings and all 38 aliases. Full Custodian dry run now passes the identity gate.
Ordinary sync must still prove its exact pushed commit and primary receipt.
No retirement acknowledgement is granted by this compatibility change.
Residual handoff: CUST-IN-0017 remains open for historical duplicate projection
rows (including CUST-WP-0010b source UUID def8455a-9de4-4891-a15a-5b09ca98ab1e
versus stale duplicate 416ff571-18cf-5cb2-9c8b-836f68a301b3), retired/orphan
history and the CUST/THE instruction-generation conflict. HFACT-WP-0001-T02
retains task-field projection completeness and semantic idempotency keys. This
workplan closes only the bounded identifier-compatibility defect.