diff --git a/docs/evidence/2026-09-09-natural-assistant-provenance.json b/docs/evidence/2026-09-09-natural-assistant-provenance.json new file mode 100644 index 0000000..073e419 --- /dev/null +++ b/docs/evidence/2026-09-09-natural-assistant-provenance.json @@ -0,0 +1,59 @@ +{ + "ok": true, + "repo": "/home/worsch/repo-manager", + "revision": "5ad49dd79e0ebc2f46bf5df68d2c1a079dc358c7", + "cutover_commit": "329af60753b20896f30f098e8f75074652780032", + "commit_count": 100, + "assistants": { + "claude-code": { + "commit_count": 15, + "models": [ + "opus" + ], + "sessions": [ + "f2bff2d5-e9b2-4338-92ca-10282a927006", + "f40c8f53-fb65-4980-9d29-bcdb3dd946f7" + ], + "processes": [ + "2583210@bnt-lap001", + "272883@bnt-lap001" + ] + }, + "codex": { + "commit_count": 84, + "models": [ + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-6-astra" + ], + "sessions": [ + "01a023c0-a0a3-7c03-b395-5a0d2757214d", + "01a028f0-a42f-7582-89a8-ebaad7343834", + "01a02b22-9638-76d2-bbff-b7ea1770b118", + "01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c", + "01a070b5-4994-7271-bd8b-7c3dbcedec4b", + "01a07ff8-19d0-7820-b4d0-1353833cb7fc" + ], + "processes": [] + }, + "grok": { + "commit_count": 1, + "models": [], + "sessions": [ + "01a04996-76e8-7f53-b971-1885cfbed436" + ], + "processes": [] + } + }, + "interleaved_sessions": [ + [ + "01a023c0-a0a3-7c03-b395-5a0d2757214d", + "01a028f0-a42f-7582-89a8-ebaad7343834" + ] + ], + "unattributed": { + "before_cutover": 0, + "after_cutover": 0, + "known_automation": 0 + } +} diff --git a/intakes/intakes.md b/intakes/intakes.md index 62b592a..18f825a 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -6,7 +6,7 @@ id: RMGR-IN-0001 kind: intake title: Validate assistant provenance on a natural multi-session window -status: open +status: closed origin: residual origin_ref: RMGR-WP-0009 priority: low @@ -15,8 +15,10 @@ description: When two coding-assistant sessions naturally commit to one reposito run rmgr assistant-provenance report and retain the non-secret interleaving evidence. Do not manufacture or infer a collision. created: '2026-08-21T20:09:23.942286+00:00' -updated: '2026-08-21T20:09:23.942286+00:00' +updated: '2026-09-09T20:31:04.003556Z' state_hub_intake_id: "01a025f3-40b0-7243-8002-dbe4609a6245" +closed_at: '2026-09-09T20:31:04.003556Z' +outcome: Natural multi-session evidence captured in docs/evidence/2026-09-09-natural-assistant-provenance.json. ``` ## RMGR-IN-0002 — Assign dormant shared prefixes before their next allocation @@ -45,7 +47,7 @@ state_hub_intake_id: "01a02611-a663-7712-bd4b-e39c13e7bf20" id: RMGR-IN-0003 kind: intake title: Scope normal sync identity validation to actionable records -status: open +status: closed origin: residual origin_ref: RMGR-WP-0005 priority: low @@ -57,6 +59,14 @@ description: Normal rmgr sync currently refuses net-kingdom because the archived visible as conformance evidence but do not block deterministic assignment or forge-derived reconciliation of unrelated canonical records. created: '2026-08-31T17:30:00+00:00' -updated: '2026-08-31T17:30:00+00:00' +updated: '2026-09-09T20:31:04.003556Z' state_hub_intake_id: "4f1d9836-e0a6-56ea-bf19-2587c153f338" +closed_at: '2026-09-09T20:31:04.003556Z' +outcome: Implemented and verified in RMGR-WP-0015. ``` + +2026-09-09 resolutions: RMGR-IN-0001 is evidenced by +`docs/evidence/2026-09-09-natural-assistant-provenance.json`: 100 existing +commits, three assistants, and one naturally interleaved session pair. This +is a Git sequence signal, not proof of concurrent writes or a collision. +RMGR-IN-0003 is implemented and verified in RMGR-WP-0015. diff --git a/src/repo_manager/identifiers.py b/src/repo_manager/identifiers.py index 21d5d70..f658ca2 100644 --- a/src/repo_manager/identifiers.py +++ b/src/repo_manager/identifiers.py @@ -850,10 +850,37 @@ def ensure_missing_work_record_identifiers( repo_root = repo_root.resolve() namespace = namespace or load_fleet_namespace() identity = scan_record_identities(repo_root) - if identity["invalid_identifiers"] or identity["identity_collisions"]: + # Completed, already-bound suffix tasks are historical evidence, not + # allocation candidates. Keep conformance reporting strict while allowing + # ordinary registration to preserve those bindings. Local aliases still + # require the explicit parent allowlist in the kind registry. + historical_sources: set[str] = set() + for path in iter_workplan_files(repo_root): + parsed = parse_workplan_file(path, repo_root=repo_root) + if parsed.status not in {"finished", "archived", "completed"} or not parsed.id: + continue + for number, task in enumerate(parsed.tasks, start=1): + if task.status not in {"done", "cancel"} or not task.state_hub_task_id: + continue + if not re.fullmatch(re.escape(parsed.id) + r"-T[0-9]{2}[a-z]", task.id or ""): + continue + try: + uuid.UUID(task.state_hub_task_id) + except ValueError: + continue + historical_sources.add(f"{parsed.path}#task-block-{number}") + invalid = [ + item for item in identity["invalid_identifiers"] + if item["source"] not in historical_sources + ] + historical = [ + item for item in identity["invalid_identifiers"] + if item["source"] in historical_sources + ] + if invalid or identity["identity_collisions"]: raise ValueError( "work-record identities are not safe to derive: " - f"invalid={identity['invalid_identifiers']!r}, " + f"invalid={invalid!r}, " f"collisions={identity['identity_collisions']!r}" ) @@ -926,6 +953,7 @@ def ensure_missing_work_record_identifiers( "repo": repo_root.name, "namespace": namespace, "assignments": assignments, + "historical_invalid_identifiers": historical, "files_changed": sorted(str(path.relative_to(repo_root)) for path in rendered), } diff --git a/tests/test_historical_sync_validation.py b/tests/test_historical_sync_validation.py new file mode 100644 index 0000000..1c32032 --- /dev/null +++ b/tests/test_historical_sync_validation.py @@ -0,0 +1,63 @@ +from pathlib import Path + +import pytest + +from repo_manager.identifiers import ensure_missing_work_record_identifiers +from repo_manager.record_identity import scan_record_identities + + +def _sources(root: Path, *, status='completed', task_status='done', + task_id='NK-WP-0003-T08a', binding='22222222-2222-4222-8222-222222222222'): + archive = root / 'workplans' / 'archived' / 'NK-WP-0003.md' + archive.parent.mkdir(parents=True) + archive.write_text( + f'---\nid: NK-WP-0003\nstatus: {status}\n' + 'state_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---\n' + f'\n```task\nid: {task_id}\nstatus: {task_status}\n' + f'state_hub_task_id: "{binding}"\n```\n' + ) + live = root / 'workplans' / 'NK-WP-0099.md' + live.write_text('---\nid: NK-WP-0099\nstatus: active\n---\n' + '\n```task\nid: NK-WP-0099-T01\nstatus: todo\n```\n') + return archive, live + + +@pytest.mark.parametrize('status', ['finished', 'archived', 'completed']) +def test_historical_warning_preserves_source_and_allows_allocation(tmp_path, status): + archive, live = _sources(tmp_path, status=status) + before = {p: p.read_bytes() for p in (archive, live)} + preview = ensure_missing_work_record_identifiers(tmp_path, execute=False) + assert all(p.read_bytes() == content for p, content in before.items()) + result = ensure_missing_work_record_identifiers(tmp_path) + assert result['assignments'] == preview['assignments'] + assert {x['record_id'] for x in result['assignments']} == {'NK-WP-0099', 'NK-WP-0099-T01'} + assert archive.read_bytes() == before[archive] + assert result['historical_invalid_identifiers'] == scan_record_identities(tmp_path)['invalid_identifiers'] + assert result['historical_invalid_identifiers'][0]['id'] == 'NK-WP-0003-T08a' + assert not scan_record_identities(tmp_path)['ok'] + assert ensure_missing_work_record_identifiers(tmp_path)['assignments'] == [] + + +@pytest.mark.parametrize('kwargs', [ + {'status': 'active'}, {'status': 'unknown'}, {'task_status': 'todo'}, + {'binding': ''}, {'binding': 'not-a-uuid'}, {'task_id': 'T08a'}, + {'task_id': 'T01'}, {'task_id': 'NK-WP-0099-T08a'}, +]) +def test_unsafe_history_refuses_before_any_allocation(tmp_path, kwargs): + paths = _sources(tmp_path, **kwargs) + before = {p: p.read_bytes() for p in paths} + with pytest.raises(ValueError, match='identities are not safe'): + ensure_missing_work_record_identifiers(tmp_path) + assert all(p.read_bytes() == content for p, content in before.items()) + + +def test_historical_collision_still_blocks(tmp_path): + archive, live = _sources(tmp_path) + duplicate = archive.with_name('duplicate.md') + duplicate.write_text(archive.read_text().replace( + '22222222-2222-4222-8222-222222222222', + '33333333-3333-4333-8333-333333333333')) + before = live.read_bytes() + with pytest.raises(ValueError, match='collisions='): + ensure_missing_work_record_identifiers(tmp_path) + assert live.read_bytes() == before diff --git a/workplans/ADHOC-2026-09-09.md b/workplans/ADHOC-2026-09-09.md new file mode 100644 index 0000000..ce87744 --- /dev/null +++ b/workplans/ADHOC-2026-09-09.md @@ -0,0 +1,27 @@ +--- +id: RMGR-WP-ADHOC-2026-09-09 +type: workplan +title: "Validate natural assistant provenance" +domain: infotech +repo: repo-manager +status: finished +owner: codex +created: "2026-09-09" +updated: "2026-09-09" +state_hub_workstream_id: "b4258afa-f8db-51a3-957d-f5aae3531117" +--- + +## Capture the natural multi-session window + +```task +id: RMGR-WP-ADHOC-2026-09-09-T01 +status: done +priority: low +state_hub_task_id: "714af2ff-83ac-5dc6-a070-8eb5bf547090" +``` + +Resolved RMGR-IN-0001 by running the existing assistant provenance report over +100 commits ending at 5ad49dd79e0ebc2f46bf5df68d2c1a079dc358c7. +Evidence: docs/evidence/2026-09-09-natural-assistant-provenance.json. +The existing history contains three assistants and one naturally interleaved +session pair. No collision or concurrent-write claim is inferred. diff --git a/workplans/RMGR-WP-0015-historical-sync-validation.md b/workplans/RMGR-WP-0015-historical-sync-validation.md new file mode 100644 index 0000000..d1f5d43 --- /dev/null +++ b/workplans/RMGR-WP-0015-historical-sync-validation.md @@ -0,0 +1,48 @@ +--- +id: RMGR-WP-0015 +type: workplan +title: "Preserve bound historical suffix tasks during normal sync" +domain: infotech +repo: repo-manager +status: finished +owner: codex +topic_slug: infotech +created: "2026-09-09" +updated: "2026-09-09" +state_hub_workstream_id: "20b82674-0459-5075-9fc9-8f95ccb940bb" +--- + +Origin: RMGR-IN-0003. Net Kingdom has already repaired the original T08a +source label, but the general ordinary-registration refusal remains. + +## Scope the ordinary registration gate + +```task +id: RMGR-WP-0015-T01 +status: done +priority: medium +state_hub_task_id: "b45b78c9-720b-50fe-86de-d263f8a9e813" +``` + +Preserve existing UUIDs on parent-qualified TNN suffix tasks marked done/cancel +in finished, archived, or legacy completed workplans. Report their invalid +labels in the assignment receipt and retain strict conformance diagnostics. +Do not allocate historical UUIDs, accept unknown local aliases, or bypass any +identity collision. Active or unbound invalid records still fail before writes. + +## Prove preservation and rejection boundaries + +```task +id: RMGR-WP-0015-T02 +status: done +priority: medium +state_hub_task_id: "46f87efc-363b-5345-bea9-8bf8b99596a4" +``` + +Cover unrelated canonical allocation, unchanged historical source bytes, +idempotency, dry run, invalid/missing UUIDs, active records, local aliases, +and collisions. Run the complete suite and a read-only Net Kingdom preflight. + +Validation: 181 tests passed; targeted Ruff checks passed. Net Kingdom +read-only identifier preflight passed with no assignments or file changes. +RMGR-IN-0003 resolved; no implementation residuals.