Compare commits
No commits in common. "ad621d6c0d4c3c5eda28e2451f726add17d07e8b" and "85eba7edd6a9de6dd7ee47b885e9ec7246a04f2d" have entirely different histories.
ad621d6c0d
...
85eba7edd6
11 changed files with 20 additions and 532 deletions
|
|
@ -1,64 +0,0 @@
|
||||||
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}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
---
|
|
||||||
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.
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
---
|
|
||||||
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.
|
|
||||||
|
|
@ -39,17 +39,8 @@ silently disambiguated with a repository slug.
|
||||||
```bash
|
```bash
|
||||||
rmgr identifier derive --namespace <fleet-namespace> --record-id RMGR-WP-0005
|
rmgr identifier derive --namespace <fleet-namespace> --record-id RMGR-WP-0005
|
||||||
rmgr identifier preflight --root /path/to/fleet
|
rmgr identifier preflight --root /path/to/fleet
|
||||||
rmgr identifier migration-plan --root /path/to/fleet \
|
|
||||||
--namespace <fleet-namespace> --output uuid-migration.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`migration-plan` is non-mutating. Its versioned JSON output preserves every
|
Activation and bulk migration remain separate governed steps. Publishing this
|
||||||
current-to-derived UUID mapping and marks each repository as one atomic apply
|
function does not retroactively rewrite existing identifiers.
|
||||||
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.
|
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,3 @@ created: '2026-08-21T20:09:23.942286+00:00'
|
||||||
updated: '2026-08-21T20:09:23.942286+00:00'
|
updated: '2026-08-21T20:09:23.942286+00:00'
|
||||||
state_hub_intake_id: "01a025f3-40b0-7243-8002-dbe4609a6245"
|
state_hub_intake_id: "01a025f3-40b0-7243-8002-dbe4609a6245"
|
||||||
```
|
```
|
||||||
|
|
||||||
## RMGR-IN-0002 — Assign dormant shared prefixes before their next allocation
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
id: RMGR-IN-0002
|
|
||||||
kind: intake
|
|
||||||
title: Assign dormant shared prefixes before their next allocation
|
|
||||||
status: open
|
|
||||||
origin: residual
|
|
||||||
origin_ref: RMGR-WP-0004
|
|
||||||
priority: medium
|
|
||||||
owner: repo-manager
|
|
||||||
description: Before any new workplan is allocated under WHYNOT-WP, OPS-WP, or REPO-WP,
|
|
||||||
assign one owner and distinct successor prefixes to the other repositories. Historical
|
|
||||||
finished/archived files remain frozen. Re-run rmgr prefix-uniqueness and identifier
|
|
||||||
preflight after assignment.
|
|
||||||
created: '2026-08-21T20:34:42.177493+00:00'
|
|
||||||
updated: '2026-08-21T20:34:42.177493+00:00'
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -286,14 +286,6 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
p_id_derive.add_argument("--record-id", required=True)
|
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 = identifier_sub.add_parser("preflight", help="Scan live identifier collisions")
|
||||||
p_id_preflight.add_argument("--root", default=".")
|
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)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
@ -577,11 +569,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
if not args.identifier_command:
|
if not args.identifier_command:
|
||||||
p_identifier.print_help()
|
p_identifier.print_help()
|
||||||
return 2
|
return 2
|
||||||
from repo_manager.identifiers import (
|
from repo_manager.identifiers import derive_work_record_uuid, scan_live_identifier_collisions
|
||||||
derive_work_record_uuid,
|
|
||||||
plan_identifier_migration,
|
|
||||||
scan_live_identifier_collisions,
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.identifier_command == "derive":
|
if args.identifier_command == "derive":
|
||||||
try:
|
try:
|
||||||
|
|
@ -595,26 +583,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
"record_id": args.record_id,
|
"record_id": args.record_id,
|
||||||
"uuid": str(derived),
|
"uuid": str(derived),
|
||||||
}
|
}
|
||||||
elif args.identifier_command == "preflight":
|
|
||||||
result = scan_live_identifier_collisions(Path(args.root))
|
|
||||||
else:
|
else:
|
||||||
try:
|
result = scan_live_identifier_collisions(Path(args.root))
|
||||||
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))
|
print(json.dumps(result, indent=2))
|
||||||
return 0 if result.get("ok") else 1
|
return 0 if result.get("ok") else 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,33 +33,6 @@ def derive_work_record_uuid(namespace: str, identifier: str) -> uuid.UUID:
|
||||||
return uuid.uuid5(DERIVATION_NAMESPACE_UUID, derivation_name(namespace, identifier))
|
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]:
|
def scan_live_identifier_collisions(root: Path) -> dict[str, Any]:
|
||||||
"""Report live workplan/task identifiers that cannot safely be derived."""
|
"""Report live workplan/task identifiers that cannot safely be derived."""
|
||||||
by_id: dict[str, list[dict[str, str]]] = defaultdict(list)
|
by_id: dict[str, list[dict[str, str]]] = defaultdict(list)
|
||||||
|
|
@ -93,99 +66,3 @@ def scan_live_identifier_collisions(root: Path) -> dict[str, Any]:
|
||||||
"safe_to_derive": not collisions,
|
"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,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
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())
|
|
||||||
|
|
@ -4,11 +4,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from repo_manager.identifiers import (
|
from repo_manager.identifiers import derive_work_record_uuid, scan_live_identifier_collisions
|
||||||
derive_work_record_uuid,
|
|
||||||
plan_identifier_migration,
|
|
||||||
scan_live_identifier_collisions,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _workplan(path: Path, identifier: str, status: str, task_status: str = "todo") -> None:
|
def _workplan(path: Path, identifier: str, status: str, task_status: str = "todo") -> None:
|
||||||
|
|
@ -52,48 +48,3 @@ def test_preflight_blocks_live_collision_but_ignores_archived_history(tmp_path:
|
||||||
_workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "archived")
|
_workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "archived")
|
||||||
report = scan_live_identifier_collisions(tmp_path)
|
report = scan_live_identifier_collisions(tmp_path)
|
||||||
assert report["ok"] is True
|
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"])
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ type: workplan
|
||||||
title: "Repository standards conformance and governed scaffolding"
|
title: "Repository standards conformance and governed scaffolding"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: repo-manager
|
repo: repo-manager
|
||||||
status: finished
|
status: active
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: infotech
|
topic_slug: infotech
|
||||||
created: "2026-08-16"
|
created: "2026-08-16"
|
||||||
|
|
@ -86,7 +86,7 @@ anti-patterns (`INTENT.md`+`GOAL.md`, `PRJ-WP-`). Index:
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RMGR-WP-0004-T02
|
id: RMGR-WP-0004-T02
|
||||||
status: done
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "2c9b0cb1-f0f0-40f7-83ef-1828298338d3"
|
state_hub_task_id: "2c9b0cb1-f0f0-40f7-83ef-1828298338d3"
|
||||||
```
|
```
|
||||||
|
|
@ -106,14 +106,11 @@ Known first findings, both live today:
|
||||||
- every `prj-` repo would collide on workplan prefix `PRJ-WP-`, because the
|
- every `prj-` repo would collide on workplan prefix `PRJ-WP-`, because the
|
||||||
prefix is derived from the flavor marker rather than from the project.
|
prefix is derived from the flavor marker rather than from the project.
|
||||||
|
|
||||||
**Result (2026-08-21):** `rmgr conform` reports `missing` vs
|
**Progress (2026-08-21):** `rmgr conform` reports `missing` vs
|
||||||
`contradictory` and flags both known findings. The earlier C-15 gate is gone:
|
`contradictory` and flags both known findings. Remaining: call from the
|
||||||
|
consistency lane / STATE-WP-0080 guard. The earlier C-15 gate is gone:
|
||||||
`RMGR-WP-0005-T06` is complete, so this task is no longer `todo` in substance.
|
`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
|
## Own governed scaffolding
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
@ -154,7 +151,7 @@ Durable flavors get `INTENT.md` and an optional foundation workplan.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RMGR-WP-0004-T04
|
id: RMGR-WP-0004-T04
|
||||||
status: done
|
status: wait
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "8cd3ba11-b16b-4910-8bf6-651a24d47b8c"
|
state_hub_task_id: "8cd3ba11-b16b-4910-8bf6-651a24d47b8c"
|
||||||
```
|
```
|
||||||
|
|
@ -169,13 +166,6 @@ running it on a `prj-` repo produces the standard's session order —
|
||||||
This matters beyond first registration: regeneration runs fleet-wide, so a
|
This matters beyond first registration: regeneration runs fleet-wide, so a
|
||||||
flavor-blind template silently re-breaks conformant repos.
|
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
|
## Accept the handoff from state-hub
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
@ -303,7 +293,7 @@ ADR-007 § Migration.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RMGR-WP-0004-T09
|
id: RMGR-WP-0004-T09
|
||||||
status: done
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "3a6ba954-4e35-4b2d-8dff-d3db84ed832c"
|
state_hub_task_id: "3a6ba954-4e35-4b2d-8dff-d3db84ed832c"
|
||||||
```
|
```
|
||||||
|
|
@ -469,14 +459,6 @@ to the next free `KONT-WP-` number.
|
||||||
every clash is against an archived file. Assign them before either repo
|
every clash is against an archived file. Assign them before either repo
|
||||||
allocates its next number, or they become live collisions.
|
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
|
## Confirm the canon boundary
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
@ -500,10 +482,3 @@ Result (2026-08-21): the seam is explicit in
|
||||||
standards and Repo Manager implements conformance and governed mutation. The
|
standards and Repo Manager implements conformance and governed mutation. The
|
||||||
canon ADR for this workplan links the enforcement placement; no competing
|
canon ADR for this workplan links the enforcement placement; no competing
|
||||||
semantic standard was introduced here.
|
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.
|
|
||||||
|
|
|
||||||
|
|
@ -313,9 +313,8 @@ derivation scope this task needs:
|
||||||
checked against the live namespace and renumbered if it clashes. Build this
|
checked against the live namespace and renumbered if it clashes. Build this
|
||||||
check alongside derivation, not after.
|
check alongside derivation, not after.
|
||||||
|
|
||||||
The live-collision remediation tracked on `RMGR-WP-0004-T09` completed
|
Remaining prerequisite is the live-collision remediation below, tracked on
|
||||||
2026-08-21. All 11 affected files are terminal history, and a fresh fleet
|
`RMGR-WP-0004-T09`. 11 files, 5 identifiers.
|
||||||
preflight found 263 live identifiers with zero collisions.
|
|
||||||
|
|
||||||
Progress (2026-08-21): the versioned derivation function and collision guard are
|
Progress (2026-08-21): the versioned derivation function and collision guard are
|
||||||
implemented in Repo Manager and published as
|
implemented in Repo Manager and published as
|
||||||
|
|
@ -323,18 +322,15 @@ implemented in Repo Manager and published as
|
||||||
`a4058507-5c4a-5a00-ab06-fffa4fb46009` and exact name bytes
|
`a4058507-5c4a-5a00-ab06-fffa4fb46009` and exact name bytes
|
||||||
`<fleet-namespace>\n<canonical-id>`. `rmgr identifier derive|preflight` provides
|
`<fleet-namespace>\n<canonical-id>`. `rmgr identifier derive|preflight` provides
|
||||||
independent reproduction and a hard live-collision refusal, including the
|
independent reproduction and a hard live-collision refusal, including the
|
||||||
unarchive hazard. `rmgr identifier migration-plan` now emits the versioned,
|
unarchive hazard. Activation remains correctly gated on T09's 11-file
|
||||||
non-mutating old-to-derived mapping needed by T04 and refuses a whole repository
|
remediation and declaration of the current N1 fleet namespace name; no existing
|
||||||
when any live record is unsafe. Activation is gated only on declaration of the
|
minted identifier was rewritten implicitly.
|
||||||
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
|
## Migrate the fleet
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RMGR-WP-0005-T04
|
id: RMGR-WP-0005-T04
|
||||||
status: progress
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "503a23a9-ede1-4cf1-bd32-e9669b84ce58"
|
state_hub_task_id: "503a23a9-ede1-4cf1-bd32-e9669b84ce58"
|
||||||
```
|
```
|
||||||
|
|
@ -350,14 +346,6 @@ value, update the database to match, and write the file.
|
||||||
- Repositories with unresolved identifier collisions cannot migrate until
|
- Repositories with unresolved identifier collisions cannot migrate until
|
||||||
`ADR-007` § Migration is ruled on; skip and report them rather than guessing.
|
`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
|
## Retire the interim rule
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
@ -400,7 +388,7 @@ portion of the divergence is redundant and needs no merge — only a rebuild.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RMGR-WP-0005-T08
|
id: RMGR-WP-0005-T08
|
||||||
status: done
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "241cf058-2f3e-4d49-8cc9-5c714be4a1cf"
|
state_hub_task_id: "241cf058-2f3e-4d49-8cc9-5c714be4a1cf"
|
||||||
```
|
```
|
||||||
|
|
@ -423,22 +411,11 @@ 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
|
append-only stream does. Re-examine before building further on it — this likely
|
||||||
reduces its scope.
|
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
|
## Disposition the orphaned hub-first records
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: RMGR-WP-0005-T09
|
id: RMGR-WP-0005-T09
|
||||||
status: progress
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "d40cc4a8-4280-4940-ac1d-dc1049f1b678"
|
state_hub_task_id: "d40cc4a8-4280-4940-ac1d-dc1049f1b678"
|
||||||
```
|
```
|
||||||
|
|
@ -465,16 +442,6 @@ Note: one of these records is already labelled `SPURIOUS bootstrap (statehub
|
||||||
register collision)` in `repo-manager`, independent corroboration of the
|
register collision)` in `repo-manager`, independent corroboration of the
|
||||||
`STATE-WP-0080` defect.
|
`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
|
## Assign one authoritative hub per record
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue