diff --git a/config/hub-record-authority.yaml b/config/hub-record-authority.yaml new file mode 100644 index 0000000..7d5f80a --- /dev/null +++ b/config/hub-record-authority.yaml @@ -0,0 +1,64 @@ +schema: repo-manager.hub-record-authority.v1 +updated: "2026-08-21" +workplan_task: RMGR-WP-0005-T08 + +classes: + file-derived: + authority: repository + offline: write-and-commit-source-file + hub-native-append: + authority: hub-core + offline: append-to-idempotent-outbox + hub-native-control: + authority: owning-central-hub + offline: no-generic-replay + derived-cache: + authority: upstream-source + offline: rebuild-or-refresh + retired: + authority: archived-provenance + offline: reject-writes + +records: + agent_messages: {class: hub-native-control, owner: hub-core} + capability_catalog: {class: file-derived, owner: repo-manager, transition: retire-direct-db-writes} + capability_requests: {class: file-derived, owner: repo-manager, transition: register-entry-files} + contributions: {class: file-derived, owner: repo-manager} + decisions: {class: file-derived, owner: repo-manager, transition: retire-direct-db-writes} + doi_cache: {class: derived-cache, owner: repo-manager} + domain_goals: {class: hub-native-control, owner: hub-core} + domains: {class: hub-native-control, owner: hub-core} + extension_points: {class: file-derived, owner: repo-manager} + fabric_graph_edges: {class: derived-cache, owner: fin-hub} + fabric_graph_imports: {class: derived-cache, owner: fin-hub} + fabric_graph_nodes: {class: derived-cache, owner: fin-hub} + intake_notes: {class: file-derived, owner: repo-manager, transition: retire-direct-db-writes} + intakes: {class: file-derived, owner: repo-manager, transition: retire-direct-db-writes} + interface_changes: {class: hub-native-control, owner: hub-core} + legacy_interface_usage_buckets: {class: derived-cache, owner: hub-core} + legacy_interfaces: {class: derived-cache, owner: hub-core} + managed_repos: {class: file-derived, owner: repo-manager, note: composite-repository-projection} + progress_events: {class: hub-native-append, owner: hub-core} + repo_goals: {class: file-derived, owner: repo-manager} + sbom_entries: {class: derived-cache, owner: repo-manager} + sbom_snapshots: {class: derived-cache, owner: repo-manager} + service_catalog: {class: hub-native-control, owner: ops-hub} + service_cloud: {class: hub-native-control, owner: ops-hub} + service_first_party: {class: hub-native-control, owner: ops-hub} + service_self_hosted: {class: hub-native-control, owner: ops-hub} + service_third_party: {class: hub-native-control, owner: ops-hub} + suggestion_notes: {class: retired, owner: archive} + suggestion_relevance_bumps: {class: retired, owner: archive} + suggestions: {class: retired, owner: archive} + tasks: {class: file-derived, owner: repo-manager} + td_notes: {class: file-derived, owner: repo-manager} + technical_debt: {class: file-derived, owner: repo-manager} + token_events: {class: hub-native-append, owner: hub-core} + topics: {class: hub-native-control, owner: hub-core, transition: retire-legacy-topic-spine} + tpsc_catalog: {class: hub-native-control, owner: ops-hub} + tpsc_entries: {class: derived-cache, owner: repo-manager} + tpsc_snapshots: {class: derived-cache, owner: repo-manager} + workplan_dependencies: {class: file-derived, owner: repo-manager} + workplan_launch_requests: {class: hub-native-control, owner: activity-core} + workplans: {class: file-derived, owner: repo-manager} + write_idempotency_keys: {class: derived-cache, owner: hub-core} diff --git a/docs/evidence/RMGR-WP-0005-orphan-disposition-2026-08-21.md b/docs/evidence/RMGR-WP-0005-orphan-disposition-2026-08-21.md new file mode 100644 index 0000000..4cc51ca --- /dev/null +++ b/docs/evidence/RMGR-WP-0005-orphan-disposition-2026-08-21.md @@ -0,0 +1,46 @@ +--- +id: RMGR-EVIDENCE-0005-ORPHANS-20260821 +type: evidence +workplan_task: RMGR-WP-0005-T09 +created: "2026-08-21" +source: local State Hub at http://127.0.0.1:8000 +--- + +# Local-cache orphan disposition rebaseline + +The 2026-08-17 baseline of 28 workplans without `backing_filename` is stale. +On 2026-08-21 the local State Hub returned 224 unbound rows: + +| Status | Count | Disposition class | +| --- | ---: | --- | +| `active` | 1 | live, broken link | +| `blocked` | 1 | live, broken link | +| `proposed` | 3 | live, broken link | +| `ready` | 2 | live, broken link | +| `finished` | 201 | closed provenance | +| `archived` | 16 | closed provenance | + +Every one of the seven live rows has a matching repository file. There are no +confirmed live hub-first workplans in this snapshot. + +| Hub slug | Repository file | +| --- | --- | +| `nk-wp-0027` | `net-kingdom/workplans/NK-WP-0027-reef-placement-reconciliation.md` | +| `state-wp-0081` | `state-hub/workplans/STATE-WP-0081-cluster-self-sufficiency-and-registrar.md` | +| `railiance-wp-0022` | `railiance-platform/workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md` | +| `railiance-wp-0023` | `railiance-platform/workplans/RAILIANCE-WP-0023-hub-core-candidate-credential-lanes.md` | +| `rail-en-wp-0001` | `railiance-enablement/workplans/RAIL-EN-WP-0001-private-by-default-templates.md` | +| `rail-ho-wp-0010` | `railiance-infra/workplans/RAIL-HO-WP-0010-new-reef-ports-need-a-grant.md` | +| `ops-wp-0003` | `ops-hub/workplans/OPS-WP-0003-extension-contract-alignment.md` | + +The 217 closed rows remain cheap historical provenance. They need not be +reconstructed as files, but the cache-rebuild procedure must export or retain +them before replacing the local database. Repairing the seven live bindings is +the remaining prerequisite for T07. + +## Reproduction + +The counts came from `GET /workplans/`, selecting rows whose +`backing_filename` is null and grouping by status. Repository UUIDs were joined +to `GET /repos/`; canonical ids and exact titles were then searched in each +reported checkout. This evidence records no secret or credential value. diff --git a/docs/hub-record-authority_v1.md b/docs/hub-record-authority_v1.md new file mode 100644 index 0000000..d5237d9 --- /dev/null +++ b/docs/hub-record-authority_v1.md @@ -0,0 +1,55 @@ +--- +id: RMGR-CONTRACT-AUTHORITY-0001 +type: contract +title: "State Hub record authority and offline behaviour" +version: "1" +status: draft-reviewable +created: "2026-08-21" +updated: "2026-08-21" +workplan_task: RMGR-WP-0005-T08 +--- + +# State Hub record authority and offline behaviour v1 + +`config/hub-record-authority.yaml` classifies every table imported by the State +Hub model package on 2026-08-21. It is the machine-readable routing contract; +this document defines the meaning of its classes. + +| Class | Truth source | Offline behaviour | Central write rule | +| --- | --- | --- | --- | +| `file-derived` | Repository file and Git history | Change the source file and commit. The commit is the write. | Re-index after Git settles; reject direct state pushes. | +| `hub-native-append` | The named central hub | Append to an idempotent local outbox and replay. | Accept append-only events with a stable source identity. | +| `hub-native-control` | The named central hub | Do not apply generic append-only replay. Stay read-only unless the record type has an ordered, idempotent command contract. | Accept commands at the one authoritative owner. | +| `derived-cache` | A named upstream source, repository file, log, or runtime observation | Do not buffer. Rebuild or refresh. | Replaceable projection only. | +| `retired` | Archived provenance | Reject new writes. | Read-only until physical deletion. | + +The first class is ADR-010's file-derived side. The next two are subdivisions of +its hub-native side. This subdivision is necessary because the current schema +contains mutable control records: message read/archive markers, launch-request +status, service catalog changes, and domain/topic administration are not +append-only events and cannot safely be reconciled by unordered union. A generic +offline buffer is therefore valid only for `hub-native-append`. + +Several current State Hub REST endpoints still mutate records classified as +file-derived. Those endpoints are compatibility surfaces during extraction; +their existence does not confer authority. Decisions and intakes are +file-derived under the ratified work-record standard and Repo Manager's governed +commands, superseding ADR-010's older examples. + +`managed_repos` is a composite projection. Repository identity, purpose, and +classification derive from repository declarations and Forge; host paths and +observation timestamps are replaceable observations. No field in that row makes +the database a source of repository truth. + +## Consequences for the offline edge + +- Buffer only progress and token event appends under the generic replay path. +- Route file-derived commands to Repo Manager; when central is absent, commit + the file and let later indexing observe it. +- Keep control-plane commands online-only until their owning hub publishes + ordering and idempotency semantics. Queuing them in the append-only stream + would silently invent a conflict policy. +- Rebuild caches and reject writes to retired suggestion tables. + +This narrows the offline-buffer part of `STATE-WP-0068`; it does not authorize +changes in repositories owned by hub-core, activity-core, ops-hub, or fin-hub. diff --git a/docs/work-record-uuid-derivation_v1.md b/docs/work-record-uuid-derivation_v1.md index 2affe4f..c54bd19 100644 --- a/docs/work-record-uuid-derivation_v1.md +++ b/docs/work-record-uuid-derivation_v1.md @@ -39,8 +39,17 @@ silently disambiguated with a repository slug. ```bash rmgr identifier derive --namespace --record-id RMGR-WP-0005 rmgr identifier preflight --root /path/to/fleet +rmgr identifier migration-plan --root /path/to/fleet \ + --namespace --output uuid-migration.json ``` -Activation and bulk migration remain separate governed steps. Publishing this -function does not retroactively rewrite existing identifiers. +`migration-plan` is non-mutating. Its versioned JSON output preserves every +current-to-derived UUID mapping and marks each repository as one atomic apply +unit. A collision or malformed live identifier makes that whole repository +ineligible while leaving unaffected repositories visible in the plan. Existing +output files are not replaced unless `--force` is explicit. +Activation and applying a bulk migration remain separate governed steps. +Publishing or planning this function does not retroactively rewrite existing +identifiers. The caller must supply the namespace explicitly until the current +fleet namespace is declared by the namespace owner under ADR-011. diff --git a/src/repo_manager/cli.py b/src/repo_manager/cli.py index 9a1e21e..52b46ac 100644 --- a/src/repo_manager/cli.py +++ b/src/repo_manager/cli.py @@ -286,6 +286,14 @@ def main(argv: list[str] | None = None) -> int: p_id_derive.add_argument("--record-id", required=True) p_id_preflight = identifier_sub.add_parser("preflight", help="Scan live identifier collisions") p_id_preflight.add_argument("--root", default=".") + p_id_plan = identifier_sub.add_parser( + "migration-plan", + help="Emit a non-mutating, per-repository old-to-derived UUID plan", + ) + p_id_plan.add_argument("--root", default=".") + p_id_plan.add_argument("--namespace", required=True) + p_id_plan.add_argument("--output", default=None, help="Write the provenance mapping as JSON") + p_id_plan.add_argument("--force", action="store_true", help="Replace an existing --output file") args = parser.parse_args(argv) @@ -569,7 +577,11 @@ def main(argv: list[str] | None = None) -> int: if not args.identifier_command: p_identifier.print_help() return 2 - from repo_manager.identifiers import derive_work_record_uuid, scan_live_identifier_collisions + from repo_manager.identifiers import ( + derive_work_record_uuid, + plan_identifier_migration, + scan_live_identifier_collisions, + ) if args.identifier_command == "derive": try: @@ -583,8 +595,26 @@ def main(argv: list[str] | None = None) -> int: "record_id": args.record_id, "uuid": str(derived), } - else: + elif args.identifier_command == "preflight": result = scan_live_identifier_collisions(Path(args.root)) + else: + try: + result = plan_identifier_migration(Path(args.root), args.namespace) + except ValueError as exc: + print(json.dumps({"ok": False, "error": str(exc)}, indent=2)) + return 1 + if args.output: + output = Path(args.output) + if output.exists() and not args.force: + print( + json.dumps( + {"ok": False, "error": f"output exists: {output}; use --force to replace"}, + indent=2, + ) + ) + return 1 + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") print(json.dumps(result, indent=2)) return 0 if result.get("ok") else 1 diff --git a/src/repo_manager/identifiers.py b/src/repo_manager/identifiers.py index c93f07e..9781dda 100644 --- a/src/repo_manager/identifiers.py +++ b/src/repo_manager/identifiers.py @@ -33,6 +33,33 @@ def derive_work_record_uuid(namespace: str, identifier: str) -> uuid.UUID: return uuid.uuid5(DERIVATION_NAMESPACE_UUID, derivation_name(namespace, identifier)) +def _migration_mapping( + *, + namespace: str, + repo: Path, + path: str, + kind: str, + identifier: str, + current_uuid: str | None, +) -> dict[str, str | None]: + derived_uuid = str(derive_work_record_uuid(namespace, identifier)) + if not current_uuid: + action = "assign" + elif current_uuid == derived_uuid: + action = "unchanged" + else: + action = "replace" + return { + "repo": repo.name, + "path": path, + "kind": kind, + "record_id": identifier, + "current_uuid": current_uuid, + "derived_uuid": derived_uuid, + "action": action, + } + + def scan_live_identifier_collisions(root: Path) -> dict[str, Any]: """Report live workplan/task identifiers that cannot safely be derived.""" by_id: dict[str, list[dict[str, str]]] = defaultdict(list) @@ -66,3 +93,99 @@ def scan_live_identifier_collisions(root: Path) -> dict[str, Any]: "safe_to_derive": not collisions, } + +def plan_identifier_migration(root: Path, namespace: str) -> dict[str, Any]: + """Plan a live-record UUID migration without changing files or databases. + + The report is the durable old-to-derived mapping required by RMGR-WP-0005. + Eligibility is deliberately computed per repository: a repository with one + colliding or malformed live identifier is skipped as a whole. + """ + # Validate even for an empty fleet. + derivation_name(namespace, "RMGR-WP-0000") + root = root.resolve() + collision_report = scan_live_identifier_collisions(root) + collision_ids = set(collision_report["collisions"]) + repositories: list[dict[str, Any]] = [] + totals = {"repositories": 0, "eligible": 0, "skipped": 0, "records": 0, "replace": 0, "assign": 0, "unchanged": 0} + + for repo in iter_repo_roots(root): + mappings: list[dict[str, str | None]] = [] + blockers: list[dict[str, str]] = [] + for path in iter_workplan_files(repo): + parsed = parse_workplan_file(path, repo_root=repo) + if parsed.status not in LIVE_WORKPLAN_STATUSES: + continue + if not parsed.id: + blockers.append({"path": parsed.path, "reason": "live workplan has no canonical id"}) + continue + if parsed.id in collision_ids: + blockers.append({"path": parsed.path, "record_id": parsed.id, "reason": "live identifier collision"}) + try: + mappings.append( + _migration_mapping( + namespace=namespace, + repo=repo, + path=parsed.path, + kind="workplan", + identifier=parsed.id, + current_uuid=parsed.state_hub_workstream_id, + ) + ) + except ValueError as exc: + blockers.append({"path": parsed.path, "record_id": parsed.id, "reason": str(exc)}) + + for task in parsed.tasks: + if task.status in {"done", "cancel"}: + continue + if not task.id: + blockers.append({"path": parsed.path, "reason": "unfinished task has no canonical id"}) + continue + if task.id in collision_ids: + blockers.append({"path": parsed.path, "record_id": task.id, "reason": "live identifier collision"}) + try: + mappings.append( + _migration_mapping( + namespace=namespace, + repo=repo, + path=parsed.path, + kind="task", + identifier=task.id, + current_uuid=task.state_hub_task_id, + ) + ) + except ValueError as exc: + blockers.append({"path": parsed.path, "record_id": task.id, "reason": str(exc)}) + + if not mappings and not blockers: + continue + eligible = not blockers + repositories.append( + { + "repo": repo.name, + "path": str(repo), + "eligible": eligible, + "atomic_unit": True, + "blockers": blockers, + "mappings": mappings, + } + ) + totals["repositories"] += 1 + totals["eligible" if eligible else "skipped"] += 1 + totals["records"] += len(mappings) + for mapping in mappings: + totals[str(mapping["action"])] += 1 + + return { + "schema": "repo-manager.identifier-migration-plan.v1", + "ok": totals["skipped"] == 0, + "ready_to_apply": totals["skipped"] == 0, + "root": str(root), + "namespace": namespace.strip(), + "derivation_version": DERIVATION_VERSION, + "namespace_uuid": str(DERIVATION_NAMESPACE_UUID), + "scope": "live workplans and unfinished tasks", + "apply_policy": "all-or-nothing per repository", + "totals": totals, + "repositories": repositories, + } diff --git a/tests/test_hub_record_authority.py b/tests/test_hub_record_authority.py new file mode 100644 index 0000000..af58161 --- /dev/null +++ b/tests/test_hub_record_authority.py @@ -0,0 +1,59 @@ +from pathlib import Path + +import yaml + +EXPECTED_STATE_HUB_TABLES = { + "agent_messages", + "capability_catalog", + "capability_requests", + "contributions", + "decisions", + "doi_cache", + "domain_goals", + "domains", + "extension_points", + "fabric_graph_edges", + "fabric_graph_imports", + "fabric_graph_nodes", + "intake_notes", + "intakes", + "interface_changes", + "legacy_interface_usage_buckets", + "legacy_interfaces", + "managed_repos", + "progress_events", + "repo_goals", + "sbom_entries", + "sbom_snapshots", + "service_catalog", + "service_cloud", + "service_first_party", + "service_self_hosted", + "service_third_party", + "suggestion_notes", + "suggestion_relevance_bumps", + "suggestions", + "tasks", + "td_notes", + "technical_debt", + "token_events", + "topics", + "tpsc_catalog", + "tpsc_entries", + "tpsc_snapshots", + "workplan_dependencies", + "workplan_launch_requests", + "workplans", + "write_idempotency_keys", +} + + +def test_every_state_hub_table_has_one_authority_class() -> None: + path = Path(__file__).parents[1] / "config" / "hub-record-authority.yaml" + document = yaml.safe_load(path.read_text(encoding="utf-8")) + classes = set(document["classes"]) + records = document["records"] + + assert set(records) == EXPECTED_STATE_HUB_TABLES + assert {record["class"] for record in records.values()} <= classes + assert all(record.get("owner") for record in records.values()) diff --git a/tests/test_identifiers.py b/tests/test_identifiers.py index 4171f6b..202d013 100644 --- a/tests/test_identifiers.py +++ b/tests/test_identifiers.py @@ -4,7 +4,11 @@ from pathlib import Path import pytest -from repo_manager.identifiers import derive_work_record_uuid, scan_live_identifier_collisions +from repo_manager.identifiers import ( + derive_work_record_uuid, + plan_identifier_migration, + scan_live_identifier_collisions, +) def _workplan(path: Path, identifier: str, status: str, task_status: str = "todo") -> None: @@ -48,3 +52,48 @@ def test_preflight_blocks_live_collision_but_ignores_archived_history(tmp_path: _workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "archived") report = scan_live_identifier_collisions(tmp_path) assert report["ok"] is True + + +def test_migration_plan_preserves_mapping_and_is_atomic_per_repo(tmp_path: Path) -> None: + one = tmp_path / "one" + two = tmp_path / "two" + _workplan(one / "workplans" / "one.md", "ONE-WP-0001", "active") + _workplan(two / "workplans" / "two.md", "TWO-WP-0001", "ready", task_status="done") + + report = plan_identifier_migration(tmp_path, "fleet-a") + + assert report["ok"] is True + assert report["apply_policy"] == "all-or-nothing per repository" + assert report["totals"] == { + "repositories": 2, + "eligible": 2, + "skipped": 0, + "records": 3, + "replace": 0, + "assign": 3, + "unchanged": 0, + } + one_plan = next(item for item in report["repositories"] if item["repo"] == "one") + assert one_plan["atomic_unit"] is True + assert {item["record_id"] for item in one_plan["mappings"]} == { + "ONE-WP-0001", + "ONE-WP-0001-T01", + } + assert all(item["current_uuid"] is None for item in one_plan["mappings"]) + + +def test_migration_plan_skips_entire_repo_affected_by_collision(tmp_path: Path) -> None: + one = tmp_path / "one" + two = tmp_path / "two" + _workplan(one / "workplans" / "one.md", "SHARED-WP-0001", "active") + _workplan(one / "workplans" / "safe.md", "ONE-WP-0002", "active") + _workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "ready") + + report = plan_identifier_migration(tmp_path, "fleet-a") + + assert report["ok"] is False + assert report["totals"]["skipped"] == 2 + one_plan = next(item for item in report["repositories"] if item["repo"] == "one") + assert one_plan["eligible"] is False + assert any(item["record_id"] == "ONE-WP-0002" for item in one_plan["mappings"]) + assert any(item["reason"] == "live identifier collision" for item in one_plan["blockers"]) diff --git a/workplans/RMGR-WP-0004-repository-standards-conformance.md b/workplans/RMGR-WP-0004-repository-standards-conformance.md index 0b61aff..cdbf029 100644 --- a/workplans/RMGR-WP-0004-repository-standards-conformance.md +++ b/workplans/RMGR-WP-0004-repository-standards-conformance.md @@ -4,7 +4,7 @@ type: workplan title: "Repository standards conformance and governed scaffolding" domain: infotech repo: repo-manager -status: active +status: finished owner: codex topic_slug: infotech created: "2026-08-16" @@ -86,7 +86,7 @@ anti-patterns (`INTENT.md`+`GOAL.md`, `PRJ-WP-`). Index: ```task id: RMGR-WP-0004-T02 -status: progress +status: done priority: high state_hub_task_id: "2c9b0cb1-f0f0-40f7-83ef-1828298338d3" ``` @@ -106,11 +106,14 @@ Known first findings, both live today: - every `prj-` repo would collide on workplan prefix `PRJ-WP-`, because the prefix is derived from the flavor marker rather than from the project. -**Progress (2026-08-21):** `rmgr conform` reports `missing` vs -`contradictory` and flags both known findings. Remaining: call from the -consistency lane / STATE-WP-0080 guard. The earlier C-15 gate is gone: +**Result (2026-08-21):** `rmgr conform` reports `missing` vs +`contradictory` and flags both known findings. The earlier C-15 gate is gone: `RMGR-WP-0005-T06` is complete, so this task is no longer `todo` in substance. +The compatibility call is now live as State Hub consistency check C-35 +(`state-hub` commit `b9d9ffe`). It executes the Repo Manager command and +projects its findings without copying flavor rules into the retiring hub. + ## Own governed scaffolding ```task @@ -151,7 +154,7 @@ Durable flavors get `INTENT.md` and an optional foundation workplan. ```task id: RMGR-WP-0004-T04 -status: wait +status: done priority: medium state_hub_task_id: "8cd3ba11-b16b-4910-8bf6-651a24d47b8c" ``` @@ -166,6 +169,13 @@ running it on a `prj-` repo produces the standard's session order — This matters beyond first registration: regeneration runs fleet-wide, so a flavor-blind template silently re-breaks conformant repos. +Result (2026-08-21): current generator and fleet output were re-audited rather +than changed from the stale task premise. Templates are brief-first and contain +no `INTENT.md` assumption; the four `prj-` repositories have zero incorrect +purpose-document references. The two literal `INTENT.md` mentions explicitly +say project repositories use `GOAL.md`, which is the desired safeguard. +Repo Manager conformance is now the regeneration guard via C-35. + ## Accept the handoff from state-hub ```task @@ -293,7 +303,7 @@ ADR-007 § Migration. ```task id: RMGR-WP-0004-T09 -status: progress +status: done priority: high state_hub_task_id: "3a6ba954-4e35-4b2d-8dff-d3db84ed832c" ``` @@ -459,6 +469,14 @@ to the next free `KONT-WP-` number. every clash is against an archived file. Assign them before either repo allocates its next number, or they become live collisions. +Result (2026-08-21): all 11 records named by the live-collision section are now +terminal (`finished` or legacy `completed`). `rmgr identifier preflight --root +/home/worsch` sees 263 live identifiers across 113 repositories with zero +collisions, so T03's gate is cleared without rewriting frozen history. Dormant +future ownership for `WHYNOT-WP`, `OPS-WP`, and `REPO-WP` is handed off as live +residual `RMGR-IN-0002`; it must be resolved before the next allocation under +any of those prefixes. + ## Confirm the canon boundary ```task @@ -482,3 +500,10 @@ Result (2026-08-21): the seam is explicit in standards and Repo Manager implements conformance and governed mutation. The canon ADR for this workplan links the enforcement placement; no competing semantic standard was introduced here. + +## Closure + +Finished 2026-08-21. Standards modeling, conformance, governed scaffolding, +compatibility coverage, identifier uniqueness, prefix ownership, and the canon +boundary are live. Dormant prefix ownership is preserved as registered residual +`RMGR-IN-0002` rather than keeping this implementation workplan open. diff --git a/workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md b/workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md index 502e37f..a06c5e5 100644 --- a/workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md +++ b/workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md @@ -313,8 +313,9 @@ derivation scope this task needs: checked against the live namespace and renumbered if it clashes. Build this check alongside derivation, not after. -Remaining prerequisite is the live-collision remediation below, tracked on -`RMGR-WP-0004-T09`. 11 files, 5 identifiers. +The live-collision remediation tracked on `RMGR-WP-0004-T09` completed +2026-08-21. All 11 affected files are terminal history, and a fresh fleet +preflight found 263 live identifiers with zero collisions. Progress (2026-08-21): the versioned derivation function and collision guard are implemented in Repo Manager and published as @@ -322,15 +323,18 @@ implemented in Repo Manager and published as `a4058507-5c4a-5a00-ab06-fffa4fb46009` and exact name bytes `\n`. `rmgr identifier derive|preflight` provides independent reproduction and a hard live-collision refusal, including the -unarchive hazard. Activation remains correctly gated on T09's 11-file -remediation and declaration of the current N1 fleet namespace name; no existing -minted identifier was rewritten implicitly. +unarchive hazard. `rmgr identifier migration-plan` now emits the versioned, +non-mutating old-to-derived mapping needed by T04 and refuses a whole repository +when any live record is unsafe. Activation is gated only on declaration of the +current fleet namespace boundary/name: ADR-011 intentionally leaves that an +open governance question, so Repo Manager does not infer it from a hostname. +No existing minted identifier was rewritten implicitly. ## Migrate the fleet ```task id: RMGR-WP-0005-T04 -status: wait +status: progress priority: high state_hub_task_id: "503a23a9-ede1-4cf1-bd32-e9669b84ce58" ``` @@ -346,6 +350,14 @@ value, update the database to match, and write the file. - Repositories with unresolved identifier collisions cannot migrate until `ADR-007` § Migration is ruled on; skip and report them rather than guessing. +**Progress (2026-08-21):** `rmgr identifier migration-plan --root ... +--namespace ... --output ...` implements the non-mutating first phase. Its +versioned JSON retains every current-to-derived UUID, labels the repository as +the atomic unit, and skips an entire affected repository on a collision or +malformed live identifier. Applying those plans remains gated on the namespace +declaration and a compatibility write path that updates file and central +projection together. + ## Retire the interim rule ```task @@ -388,7 +400,7 @@ portion of the divergence is redundant and needs no merge — only a rebuild. ```task id: RMGR-WP-0005-T08 -status: wait +status: done priority: high state_hub_task_id: "241cf058-2f3e-4d49-8cc9-5c714be4a1cf" ``` @@ -411,11 +423,22 @@ this split most of what it buffers does not need buffering, and only the append-only stream does. Re-examine before building further on it — this likely reduces its scope. +**Result (2026-08-21):** `config/hub-record-authority.yaml` classifies all 42 +tables imported by the current State Hub model package; a regression test keeps +the inventory explicit. `docs/hub-record-authority_v1.md` defines truth source, +offline behaviour, write rule, and target owner. The audit adapted the original +binary wording: hub-native origin contains both append-only events and mutable +control records. Only `progress_events` and `token_events` enter the generic +append/replay path; mutable message, launch, domain/topic, interface, and catalog +state requires an owner-specific ordered command or stays online-only. Decisions +and intakes are file-derived under the ratified work-record standard, superseding +ADR-010's older example list. + ## Disposition the orphaned hub-first records ```task id: RMGR-WP-0005-T09 -status: wait +status: progress priority: high state_hub_task_id: "d40cc4a8-4280-4940-ac1d-dc1049f1b678" ``` @@ -442,6 +465,16 @@ Note: one of these records is already labelled `SPURIOUS bootstrap (statehub register collision)` in `repo-manager`, independent corroboration of the `STATE-WP-0080` defect. +**Rebaselined 2026-08-21:** the local cache now has 224 rows without a backing +filename, not 28: 217 are closed (`finished`/`archived`) provenance and seven are +live. All seven live rows have matching files across `net-kingdom`, `state-hub`, +`railiance-platform`, `railiance-enablement`, `railiance-infra`, and `ops-hub`; +there are zero confirmed live hub-first rows in this snapshot. Exact ids and +paths are captured in +`docs/evidence/RMGR-WP-0005-orphan-disposition-2026-08-21.md`. Remaining work is +to repair those seven bindings and retain/export the 217 closed rows before T07 +replaces the cache. + ## Assign one authoritative hub per record ```task