RMGR-WP-0019-T03: add read-only retirement disposition check

Re-ran the registrar suite on current source and pinned the three empty
duplicate Activity projections the receiving run refused, so the Activity
disposition can be verified before STATE-WP-0095-T04 applies it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 57383@bnt-lap001
Assistant-Session: ef71febe-2a37-4c34-a979-2508ca973c91
This commit is contained in:
tegwick 2026-10-06 09:56:54 +02:00
parent 8a47a5c102
commit 65b7919ec7
3 changed files with 133 additions and 1 deletions

View file

@ -0,0 +1,81 @@
"""Read-only check of an Activity Core retirement disposition (RMGR-WP-0019-T03).
The 2026-10-04 Activity receiving run refused exactly three empty duplicate
projections. Before STATE-WP-0095-T04 may apply them, the owner's
``expected_retirement_uuids`` must match what Repo Manager observed: the same
three projections, none bound to tasks, and never the source-bound parent.
This module only compares data; it performs no hub or source writes.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# Projections the receiving run refused as "would be retired" (evidence file
# docs/evidence/2026-10-04-historical-registrar-verification.json) with the
# hub ids observed read-only on 2026-10-06.
REFUSED_DUPLICATE_PROJECTIONS: dict[str, dict[str, str]] = {
"683724d9-4249-5e0e-88e0-7baff4330d58": {
"slug": "custodian-wp-0003",
"backing_relative_path": "workplans/archived/260707-custodian-WP-0003-event-bridge.md",
},
"644017c7-c5f2-5a64-81c3-d8ee806f8251": {
"slug": "custodian-wp-0003a",
"backing_relative_path": "workplans/custodian-WP-0003a-event-bridge-model-rules-registry.md",
},
"a5806df9-5b9a-50da-81c1-7e606076bed9": {
"slug": "custodian-wp-0003b",
"backing_relative_path": "workplans/custodian-WP-0003b-event-bridge-parser-workflow-triggers.md",
},
}
# Source-bound parent every custodian-WP-0003* file names; it must survive.
PRESERVED_PARENT_UUIDS = frozenset({"b4eb45a9-69e3-4ab0-b00c-67a53c3117c5"})
def check_disposition(
expected_retirement_uuids: list[str],
task_counts: dict[str, int] | None = None,
) -> list[str]:
"""Return problems; an empty list means the disposition is consistent."""
problems: list[str] = []
given = [u.lower() for u in expected_retirement_uuids]
if len(given) != len(set(given)):
problems.append("duplicate UUIDs in expected_retirement_uuids")
given_set = set(given)
known = set(REFUSED_DUPLICATE_PROJECTIONS)
for uuid in sorted(given_set & PRESERVED_PARENT_UUIDS):
problems.append(f"{uuid} is the preserved source-bound parent")
for uuid in sorted(given_set - known - PRESERVED_PARENT_UUIDS):
problems.append(f"{uuid} was not refused by the receiving run")
for uuid in sorted(known - given_set):
slug = REFUSED_DUPLICATE_PROJECTIONS[uuid]["slug"]
problems.append(f"{uuid} ({slug}) refused but missing from disposition")
for uuid in sorted(given_set & known):
count = (task_counts or {}).get(uuid)
if count is None:
problems.append(f"{uuid} task count not verified")
elif count != 0:
problems.append(f"{uuid} holds {count} tasks; not an empty projection")
return problems
def main(argv: list[str]) -> int:
"""Usage: python -m repo_manager.retirement_disposition DISPOSITION.json
The JSON needs ``expected_retirement_uuids`` and may carry ``task_counts``.
"""
if len(argv) != 2:
print(main.__doc__, file=sys.stderr)
return 2
data = json.loads(Path(argv[1]).read_text())
problems = check_disposition(
data.get("expected_retirement_uuids", []), data.get("task_counts")
)
print(json.dumps({"ok": not problems, "problems": problems}, indent=2))
return 0 if not problems else 1
if __name__ == "__main__":
raise SystemExit(main(sys.argv))

View file

@ -0,0 +1,38 @@
from repo_manager.retirement_disposition import (
PRESERVED_PARENT_UUIDS,
REFUSED_DUPLICATE_PROJECTIONS,
check_disposition,
)
EXACT = sorted(REFUSED_DUPLICATE_PROJECTIONS)
EMPTY = {u: 0 for u in EXACT}
def test_exact_empty_set_is_consistent():
assert check_disposition(EXACT, EMPTY) == []
def test_missing_projection_is_reported():
problems = check_disposition(EXACT[:2], EMPTY)
assert any("missing from disposition" in p for p in problems)
def test_preserved_parent_is_refused():
parent = next(iter(PRESERVED_PARENT_UUIDS))
problems = check_disposition(EXACT + [parent], EMPTY)
assert any("preserved source-bound parent" in p for p in problems)
def test_unknown_uuid_and_nonempty_projection_are_reported():
counts = dict(EMPTY, **{EXACT[0]: 3})
problems = check_disposition(EXACT + ["00000000-0000-0000-0000-000000000000"], counts)
assert any("not refused" in p for p in problems)
assert any("holds 3 tasks" in p for p in problems)
def test_unverified_task_counts_are_not_assumed_empty():
assert all("not verified" in p for p in check_disposition(EXACT))
def test_duplicates_are_reported():
assert "duplicate UUIDs" in check_disposition(EXACT + [EXACT[0].upper()], EMPTY)[0]

View file

@ -7,7 +7,7 @@ repo: repo-manager
status: active
owner: codex
created: "2026-10-04"
updated: "2026-10-04"
updated: "2026-10-06"
flavor: implementation
depends_on: []
state_hub_workstream_id: "efe0ecb2-2f3f-5f49-baee-b90ad1d353ab"
@ -93,3 +93,16 @@ normal path; frozen historical source files stayed unchanged. T03 remains wait
for explicit disposition and bounded receiving support under STATE-WP-0095-T04
(decision91b7c633-a66f-5d43-9f4b-6565aefb9356).
Evidence: docs/evidence/2026-10-04-historical-registrar-verification.json.
## Ungated T03 preparation, 2026-10-06
Re-ran the registrar suite at 8a47a5c (registrar source unchanged since
c41d36c2): 89 focused registrar/identity/historical cases and the full suite
(226) passed. Read-only hub check: the three refused projections are
683724d9 (custodian-wp-0003), 644017c7 (custodian-wp-0003a) and a5806df9
(custodian-wp-0003b), each with zero tasks; every custodian-WP-0003* source
file binds parent b4eb45a9, which holds the tasks and must not be retired.
Added repo_manager.retirement_disposition, a read-only check that an Activity
expected_retirement_uuids set equals exactly those three empty projections and
excludes the preserved parent. It authorizes nothing; retirement stays with
Activity receiving under STATE-WP-0095-T04.