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:
parent
6854ea10b2
commit
89524d83e4
5 changed files with 657 additions and 10 deletions
|
|
@ -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}$"
|
||||
|
|
|
|||
366
docs/evidence/2026-09-09-custodian-legacy-identities.json
Normal file
366
docs/evidence/2026-09-09-custodian-legacy-identities.json
Normal 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"
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
130
tests/test_custodian_legacy_identity.py
Normal file
130
tests/test_custodian_legacy_identity.py
Normal 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"]
|
||||
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue