feat(identifier): declare helixforge fleet namespace
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
parent
3c6887a3e0
commit
956efbb7ae
9 changed files with 2915 additions and 17 deletions
|
|
@ -43,7 +43,7 @@
|
|||
| task | RMGR-WP-0004-T09 | done | — | workplans/RMGR-WP-0004-repository-standards-conformance.md |
|
||||
| task | RMGR-WP-0005-T01 | done | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
| task | RMGR-WP-0005-T02 | wait | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
| task | RMGR-WP-0005-T03 | progress | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
| task | RMGR-WP-0005-T03 | done | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
| task | RMGR-WP-0005-T04 | progress | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
| task | RMGR-WP-0005-T05 | wait | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
| task | RMGR-WP-0005-T06 | done | — | workplans/RMGR-WP-0005-registrar-consolidation-deterministic-ids.md |
|
||||
|
|
|
|||
12
config/fleet-namespace.yaml
Normal file
12
config/fleet-namespace.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
schema: repo-manager.fleet-namespace.v1
|
||||
namespace: helixforge
|
||||
scope: HelixForge fleet
|
||||
lineage:
|
||||
kind: root
|
||||
parent: null
|
||||
posture:
|
||||
current: {N: 2, U: 1, R: 1, S: 1}
|
||||
target: {N: 3, U: 2, R: 2, S: 1}
|
||||
declared_by: Bernd Worsch
|
||||
declared: "2026-08-21"
|
||||
decision_ref: RMGR-WP-0005-T03
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
id: RMGR-EVIDENCE-0005-HELIXFORGE-MIGRATION-20260821
|
||||
type: evidence
|
||||
workplan_task: RMGR-WP-0005-T04
|
||||
created: "2026-08-21"
|
||||
---
|
||||
|
||||
# HelixForge deterministic-identifier migration readiness
|
||||
|
||||
Fleet owner Bernd Worsch declared `helixforge` as the authoritative current
|
||||
namespace. Repo Manager records it in `config/fleet-namespace.yaml`; omitting
|
||||
`--namespace` now resolves to that checked-in declaration.
|
||||
|
||||
## Source preflight
|
||||
|
||||
- 114 repository workplan roots scanned under `/home/worsch`;
|
||||
- 242 live workplan/task identifiers;
|
||||
- zero live identifier collisions;
|
||||
- one initial ineligible repository (`net-kingdom`) due to 14 legacy task ids
|
||||
written as `-T1`…`-T8`;
|
||||
- those ids and their four documentation references were normalized to
|
||||
`-T01`…`-T08` in NetKingdom commit `25ed928`;
|
||||
- regenerated plan: 42/42 repositories eligible, 242 records, 209 replacements,
|
||||
33 assignments, zero skipped;
|
||||
- plan SHA-256:
|
||||
`98a7d876d49fbec9c062f970c18e25750da4cd4b5c89ab0d4ed24d50183abbda`.
|
||||
|
||||
The full old-to-derived mapping is
|
||||
`docs/evidence/RMGR-WP-0005-helixforge-uuid-migration-plan-2026-08-21.json`.
|
||||
It carries canonical UTC generation time plus per-repository Git HEAD and
|
||||
authoritative-source fingerprints. `rmgr identifier migration-verify` validates
|
||||
the seal and all 42 source preconditions.
|
||||
|
||||
## Central-projection gate
|
||||
|
||||
No UUID was rewritten. State Hub uses these UUIDs as primary keys, with
|
||||
references from tasks, progress and token events, decisions, dependencies,
|
||||
launch requests, intakes, capability requests, contributions, technical debt,
|
||||
extension points, and legacy suggestions. The checked-in foreign keys specify
|
||||
delete behavior but no `ON UPDATE CASCADE` behavior.
|
||||
|
||||
Consequently, changing the repository fields first would detach the central
|
||||
projection, while directly updating database primary keys would fail or leave
|
||||
references split. Apply remains unauthorized until State Hub has:
|
||||
|
||||
1. a transactional old→derived projection migration covering every referencing
|
||||
table;
|
||||
2. durable alias/provenance for old ids used by saved links and events;
|
||||
3. per-repository prepare/apply/rollback behavior coordinated with the Git
|
||||
commit and push seal; and
|
||||
4. an isolated PostgreSQL rehearsal proving forward and reverse mappings.
|
||||
|
||||
This is a discovered compatibility requirement, not a source-data blocker. The
|
||||
sealed plan remains evidence; any source or HEAD drift requires regeneration.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -25,10 +25,12 @@ fixed value above rather than recomputing it. Fleet namespace names are
|
|||
lowercase DNS-label style. Canonical identifiers are
|
||||
`PREFIX-WP-NNNN` or `PREFIX-WP-NNNN-TNN`.
|
||||
|
||||
The repository is not a namespace. Under the current N1 posture, the fleet must
|
||||
declare one shared namespace name before activation. A future fork uses its own
|
||||
namespace name and therefore derives different UUIDs for the same unqualified
|
||||
identifier, as required by ADR-011.
|
||||
The repository is not a namespace. The fleet owner declared the current shared
|
||||
namespace as **`helixforge`** on 2026-08-21; the versioned declaration is
|
||||
`config/fleet-namespace.yaml`. This raises the namespace posture from implied
|
||||
N1 to declared N2. A future fork uses its own namespace name and therefore
|
||||
derives different UUIDs for the same unqualified identifier, as required by
|
||||
ADR-011.
|
||||
|
||||
Only proposed, ready, active, blocked, or backlog workplans and their unfinished
|
||||
tasks enter the live derivation set. Finished and archived history retains its
|
||||
|
|
@ -37,10 +39,10 @@ the live-collision preflight. Any duplicate is a hard refusal; it is never
|
|||
silently disambiguated with a repository slug.
|
||||
|
||||
```bash
|
||||
rmgr identifier derive --namespace <fleet-namespace> --record-id RMGR-WP-0005
|
||||
rmgr identifier derive --record-id RMGR-WP-0005
|
||||
rmgr identifier preflight --root /path/to/fleet
|
||||
rmgr identifier migration-plan --root /path/to/fleet \
|
||||
--namespace <fleet-namespace> --output uuid-migration.json
|
||||
rmgr identifier migration-plan --root /path/to/fleet --output uuid-migration.json
|
||||
rmgr identifier migration-verify --plan uuid-migration.json
|
||||
```
|
||||
|
||||
`migration-plan` is non-mutating. Its versioned JSON output preserves every
|
||||
|
|
@ -49,7 +51,12 @@ 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.
|
||||
|
||||
Plans carry a canonical UTC creation time, SHA-256 integrity seal, and per-repo
|
||||
Git HEAD/source fingerprints. `migration-verify` rejects a modified plan, a
|
||||
namespace mismatch, an ineligible repository, or any source/HEAD change after
|
||||
planning. A new plan is required after any such drift.
|
||||
|
||||
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.
|
||||
identifiers. `--namespace` remains an explicit override for a foreign/forked
|
||||
fleet; omitting it uses the checked-in `helixforge` declaration.
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
p_identifier = sub.add_parser("identifier", help="Deterministic work-record identifiers")
|
||||
identifier_sub = p_identifier.add_subparsers(dest="identifier_command")
|
||||
p_id_derive = identifier_sub.add_parser("derive", help="Derive one UUIDv5")
|
||||
p_id_derive.add_argument("--namespace", required=True)
|
||||
p_id_derive.add_argument("--namespace", default=None, help="Override declared fleet namespace")
|
||||
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=".")
|
||||
|
|
@ -291,9 +291,14 @@ def main(argv: list[str] | None = None) -> int:
|
|||
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("--namespace", default=None, help="Override declared fleet namespace")
|
||||
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")
|
||||
p_id_verify = identifier_sub.add_parser(
|
||||
"migration-verify",
|
||||
help="Verify a sealed migration plan against current repository sources",
|
||||
)
|
||||
p_id_verify.add_argument("--plan", required=True)
|
||||
|
||||
p_sbom = sub.add_parser("sbom", help="Derive SBOM snapshots and licence reports from repository files")
|
||||
sbom_sub = p_sbom.add_subparsers(dest="sbom_command")
|
||||
|
|
@ -616,27 +621,40 @@ def main(argv: list[str] | None = None) -> int:
|
|||
return 2
|
||||
from repo_manager.identifiers import (
|
||||
derive_work_record_uuid,
|
||||
load_fleet_namespace,
|
||||
plan_identifier_migration,
|
||||
scan_live_identifier_collisions,
|
||||
verify_identifier_migration_plan,
|
||||
)
|
||||
|
||||
namespace = getattr(args, "namespace", None) or load_fleet_namespace()
|
||||
|
||||
if args.identifier_command == "derive":
|
||||
try:
|
||||
derived = derive_work_record_uuid(args.namespace, args.record_id)
|
||||
derived = derive_work_record_uuid(namespace, args.record_id)
|
||||
except ValueError as exc:
|
||||
print(json.dumps({"ok": False, "error": str(exc)}, indent=2))
|
||||
return 1
|
||||
result = {
|
||||
"ok": True,
|
||||
"namespace": args.namespace,
|
||||
"namespace": namespace,
|
||||
"record_id": args.record_id,
|
||||
"uuid": str(derived),
|
||||
}
|
||||
elif args.identifier_command == "preflight":
|
||||
result = scan_live_identifier_collisions(Path(args.root))
|
||||
elif args.identifier_command == "migration-verify":
|
||||
try:
|
||||
plan = json.loads(Path(args.plan).read_text(encoding="utf-8"))
|
||||
if not isinstance(plan, dict):
|
||||
raise TypeError("migration plan must be a JSON object")
|
||||
result = verify_identifier_migration_plan(plan)
|
||||
except (OSError, TypeError, ValueError, json.JSONDecodeError) as exc:
|
||||
print(json.dumps({"ok": False, "error": str(exc)}, indent=2))
|
||||
return 1
|
||||
else:
|
||||
try:
|
||||
result = plan_identifier_migration(Path(args.root), args.namespace)
|
||||
result = plan_identifier_migration(Path(args.root), namespace)
|
||||
except ValueError as exc:
|
||||
print(json.dumps({"ok": False, "error": str(exc)}, indent=2))
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -2,22 +2,41 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
from repo_manager.cache import source_fingerprint
|
||||
from repo_manager.gitops import head_sha
|
||||
from repo_manager.parse.workplan import iter_workplan_files, parse_workplan_file
|
||||
from repo_manager.prefix_registry import iter_repo_roots
|
||||
from repo_manager.time import utc_now_text
|
||||
|
||||
DERIVATION_NAMESPACE_UUID = uuid.UUID("a4058507-5c4a-5a00-ab06-fffa4fb46009")
|
||||
DERIVATION_VERSION = "repo-manager.work-record-uuid.v1"
|
||||
FLEET_NAMESPACE_SCHEMA = "repo-manager.fleet-namespace.v1"
|
||||
DEFAULT_NAMESPACE_CONTRACT = Path(__file__).resolve().parents[2] / "config" / "fleet-namespace.yaml"
|
||||
LIVE_WORKPLAN_STATUSES = frozenset({"proposed", "ready", "active", "blocked", "backlog"})
|
||||
_NAMESPACE_RE = re.compile(r"^[a-z0-9][a-z0-9.-]{0,62}$")
|
||||
_RECORD_ID_RE = re.compile(r"^[A-Z][A-Z0-9-]*-WP-[0-9]{4}(?:-T[0-9]{2,})?$")
|
||||
|
||||
|
||||
def load_fleet_namespace(path: Path = DEFAULT_NAMESPACE_CONTRACT) -> str:
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(data, dict) or data.get("schema") != FLEET_NAMESPACE_SCHEMA:
|
||||
raise ValueError(f"unsupported fleet namespace contract: {path}")
|
||||
namespace = str(data.get("namespace") or "").strip()
|
||||
if not _NAMESPACE_RE.fullmatch(namespace):
|
||||
raise ValueError(f"invalid fleet namespace in contract: {path}")
|
||||
return namespace
|
||||
|
||||
|
||||
def derivation_name(namespace: str, identifier: str) -> str:
|
||||
"""Return the exact UTF-8 UUIDv5 name input defined by contract v1."""
|
||||
namespace = namespace.strip()
|
||||
|
|
@ -110,6 +129,7 @@ def plan_identifier_migration(root: Path, namespace: str) -> 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):
|
||||
fingerprint, _source_files = source_fingerprint(repo)
|
||||
mappings: list[dict[str, str | None]] = []
|
||||
blockers: list[dict[str, str]] = []
|
||||
for path in iter_workplan_files(repo):
|
||||
|
|
@ -164,6 +184,8 @@ def plan_identifier_migration(root: Path, namespace: str) -> dict[str, Any]:
|
|||
{
|
||||
"repo": repo.name,
|
||||
"path": str(repo),
|
||||
"planned_head_sha": head_sha(repo),
|
||||
"source_fingerprint": fingerprint,
|
||||
"eligible": eligible,
|
||||
"atomic_unit": True,
|
||||
"blockers": blockers,
|
||||
|
|
@ -176,7 +198,7 @@ def plan_identifier_migration(root: Path, namespace: str) -> dict[str, Any]:
|
|||
for mapping in mappings:
|
||||
totals[str(mapping["action"])] += 1
|
||||
|
||||
return {
|
||||
report = {
|
||||
"schema": "repo-manager.identifier-migration-plan.v1",
|
||||
"ok": totals["skipped"] == 0,
|
||||
"ready_to_apply": totals["skipped"] == 0,
|
||||
|
|
@ -186,6 +208,66 @@ def plan_identifier_migration(root: Path, namespace: str) -> dict[str, Any]:
|
|||
"namespace_uuid": str(DERIVATION_NAMESPACE_UUID),
|
||||
"scope": "live workplans and unfinished tasks",
|
||||
"apply_policy": "all-or-nothing per repository",
|
||||
"generated_at": utc_now_text(),
|
||||
"totals": totals,
|
||||
"repositories": repositories,
|
||||
}
|
||||
canonical = json.dumps(report, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||
report["plan_sha256"] = hashlib.sha256(canonical).hexdigest()
|
||||
return report
|
||||
|
||||
|
||||
def verify_identifier_migration_plan(plan: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Verify a saved plan against its seal and current repository sources."""
|
||||
errors: list[dict[str, str]] = []
|
||||
if plan.get("schema") != "repo-manager.identifier-migration-plan.v1":
|
||||
errors.append({"scope": "plan", "reason": "unsupported schema"})
|
||||
expected_seal = plan.get("plan_sha256")
|
||||
unsealed = {key: value for key, value in plan.items() if key != "plan_sha256"}
|
||||
canonical = json.dumps(unsealed, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||
actual_seal = hashlib.sha256(canonical).hexdigest()
|
||||
if expected_seal != actual_seal:
|
||||
errors.append({"scope": "plan", "reason": "plan SHA-256 mismatch"})
|
||||
try:
|
||||
declared_namespace = load_fleet_namespace()
|
||||
except (OSError, ValueError) as exc:
|
||||
errors.append({"scope": "namespace", "reason": str(exc)})
|
||||
declared_namespace = None
|
||||
if plan.get("namespace") != declared_namespace:
|
||||
errors.append(
|
||||
{
|
||||
"scope": "namespace",
|
||||
"reason": f"plan={plan.get('namespace')!r}, declared={declared_namespace!r}",
|
||||
}
|
||||
)
|
||||
if not plan.get("ready_to_apply"):
|
||||
errors.append({"scope": "plan", "reason": "plan is not ready_to_apply"})
|
||||
|
||||
checked_repositories = 0
|
||||
for repository in plan.get("repositories") or []:
|
||||
repo = Path(str(repository.get("path") or ""))
|
||||
scope = str(repository.get("repo") or repo.name or "repository")
|
||||
if not repository.get("eligible"):
|
||||
errors.append({"scope": scope, "reason": "repository is ineligible"})
|
||||
continue
|
||||
if not repo.is_dir():
|
||||
errors.append({"scope": scope, "reason": f"repository path is missing: {repo}"})
|
||||
continue
|
||||
checked_repositories += 1
|
||||
if head_sha(repo) != repository.get("planned_head_sha"):
|
||||
errors.append({"scope": scope, "reason": "Git HEAD changed after planning"})
|
||||
current_fingerprint, _source_files = source_fingerprint(repo)
|
||||
if current_fingerprint != repository.get("source_fingerprint"):
|
||||
errors.append({"scope": scope, "reason": "authoritative source changed after planning"})
|
||||
|
||||
return {
|
||||
"schema": "repo-manager.identifier-migration-verification.v1",
|
||||
"ok": not errors,
|
||||
"namespace": plan.get("namespace"),
|
||||
"plan_sha256": expected_seal,
|
||||
"checked_repositories": checked_repositories,
|
||||
"errors": errors,
|
||||
"source_preconditions_satisfied": not errors,
|
||||
"apply_authorized": False,
|
||||
"remaining_gate": "transactional central-projection migration and rollback",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from repo_manager.identifiers import (
|
||||
derive_work_record_uuid,
|
||||
load_fleet_namespace,
|
||||
plan_identifier_migration,
|
||||
scan_live_identifier_collisions,
|
||||
verify_identifier_migration_plan,
|
||||
)
|
||||
|
||||
|
||||
def test_declared_fleet_namespace_is_helixforge() -> None:
|
||||
assert load_fleet_namespace() == "helixforge"
|
||||
|
||||
|
||||
def _workplan(path: Path, identifier: str, status: str, task_status: str = "todo") -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(
|
||||
|
|
@ -80,6 +87,8 @@ def test_migration_plan_preserves_mapping_and_is_atomic_per_repo(tmp_path: Path)
|
|||
"ONE-WP-0001-T01",
|
||||
}
|
||||
assert all(item["current_uuid"] is None for item in one_plan["mappings"])
|
||||
assert len(report["plan_sha256"]) == 64
|
||||
assert report["generated_at"].endswith("Z")
|
||||
|
||||
|
||||
def test_migration_plan_skips_entire_repo_affected_by_collision(tmp_path: Path) -> None:
|
||||
|
|
@ -97,3 +106,33 @@ def test_migration_plan_skips_entire_repo_affected_by_collision(tmp_path: Path)
|
|||
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"])
|
||||
|
||||
|
||||
def test_migration_verification_detects_tampering_and_source_drift(tmp_path: Path) -> None:
|
||||
repo = tmp_path / "one"
|
||||
path = repo / "workplans" / "one.md"
|
||||
_workplan(path, "ONE-WP-0001", "active")
|
||||
subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True)
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True)
|
||||
subprocess.run(
|
||||
["git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
plan = plan_identifier_migration(tmp_path, "helixforge")
|
||||
assert verify_identifier_migration_plan(plan)["ok"] is True
|
||||
|
||||
path.write_text(path.read_text(encoding="utf-8") + "\nchanged\n", encoding="utf-8")
|
||||
verification = verify_identifier_migration_plan(plan)
|
||||
assert verification["ok"] is False
|
||||
assert any(
|
||||
error["reason"] == "authoritative source changed after planning"
|
||||
for error in verification["errors"]
|
||||
)
|
||||
|
||||
plan["namespace"] = "tampered"
|
||||
assert any(
|
||||
error["reason"] == "plan SHA-256 mismatch"
|
||||
for error in verify_identifier_migration_plan(plan)["errors"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ before then.
|
|||
|
||||
```task
|
||||
id: RMGR-WP-0005-T03
|
||||
status: progress
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "28067729-498d-4f47-89bd-5b9718e999c7"
|
||||
```
|
||||
|
|
@ -330,6 +330,14 @@ 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.
|
||||
|
||||
**Result (2026-08-21):** fleet owner Bernd Worsch declared `helixforge` as the
|
||||
authoritative namespace. `config/fleet-namespace.yaml` records the decision,
|
||||
root lineage, and accurate `N2 U1 R1 S1` posture with `N3 U2 R2 S1` target.
|
||||
Identifier commands now use that declaration by default while retaining an
|
||||
explicit override for foreign namespaces. The derivation contract, fixed UUID,
|
||||
collision/unarchive guard, and independent reproduction test are all active;
|
||||
bulk replacement remains the separately governed T04 operation.
|
||||
|
||||
## Migrate the fleet
|
||||
|
||||
```task
|
||||
|
|
@ -358,6 +366,20 @@ malformed live identifier. Applying those plans remains gated on the namespace
|
|||
declaration and a compatibility write path that updates file and central
|
||||
projection together.
|
||||
|
||||
**Progress (2026-08-21, namespace declared):** the `helixforge` fleet preflight
|
||||
scanned 114 repository roots and found 242 live identifiers with zero
|
||||
collisions. Fourteen non-canonical NetKingdom task ids were the sole initial
|
||||
repository blocker and were normalized in commit `25ed928`. The regenerated,
|
||||
SHA-256-sealed mapping is source-ready across 42/42 affected repositories: 209
|
||||
UUID replacements and 33 assignments, with per-repository HEAD/source
|
||||
fingerprints verified by `rmgr identifier migration-verify`.
|
||||
|
||||
Apply is still deliberately unauthorized. State Hub's workplan/task primary
|
||||
keys are referenced by numerous foreign keys without `ON UPDATE CASCADE`; a
|
||||
file-first or direct-PK rewrite would split the projection. Evidence and the
|
||||
required transactional/alias/rollback contract are recorded in
|
||||
`docs/evidence/RMGR-WP-0005-helixforge-migration-readiness-2026-08-21.md`.
|
||||
|
||||
## Retire the interim rule
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue