From f7402bd711b263ea9d6067fffd14499129609b22 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 15:46:43 +0200 Subject: [PATCH] docs: complete repository rename operator handoff Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3 --- README.md | 3 + WORK-RECORDS.md | 2 +- dashboard/src/docs/repos.md | 7 +- dashboard/src/docs/repository-renames.md | 70 ++++ .../repository-rename-handoff-v1.json | 43 +++ docs/repository-rename-operations.md | 332 ++++++++++++++++++ .../repository-rename-handoff-v1.schema.json | 160 +++++++++ repository_rename_workplan.py | 22 +- tests/test_repository_rename_docs.py | 88 +++++ tests/test_repository_rename_workplan.py | 6 + ...85-repository-lineage-preserving-rename.md | 12 +- 11 files changed, 735 insertions(+), 10 deletions(-) create mode 100644 dashboard/src/docs/repository-renames.md create mode 100644 docs/examples/repository-rename-handoff-v1.json create mode 100644 docs/repository-rename-operations.md create mode 100644 docs/schemas/repository-rename-handoff-v1.schema.json create mode 100644 tests/test_repository_rename_docs.py diff --git a/README.md b/README.md index 8dbe5b6..7292f98 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ port `8000` reaches it through the ops-bridge `state-hub-primary` tunnel. See [`docs/cluster-operating-model.md`](docs/cluster-operating-model.md) for access, rollback, backups, and pragmatic limitations. +Repository rename operations use the phased, UUID-preserving workflow in +[`docs/repository-rename-operations.md`](docs/repository-rename-operations.md). + --- ## Setup diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 1b25d10..4fc3bdc 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -328,5 +328,5 @@ | task | STATE-WP-0085-T05 | done | — | workplans/STATE-WP-0085-repository-lineage-preserving-rename.md | | task | STATE-WP-0085-T06 | done | — | workplans/STATE-WP-0085-repository-lineage-preserving-rename.md | | task | STATE-WP-0085-T07 | done | — | workplans/STATE-WP-0085-repository-lineage-preserving-rename.md | -| task | STATE-WP-0085-T08 | todo | — | workplans/STATE-WP-0085-repository-lineage-preserving-rename.md | +| task | STATE-WP-0085-T08 | done | — | workplans/STATE-WP-0085-repository-lineage-preserving-rename.md | | task | STATE-WP-0085-T09 | wait | — | workplans/STATE-WP-0085-repository-lineage-preserving-rename.md | diff --git a/dashboard/src/docs/repos.md b/dashboard/src/docs/repos.md index 83fa7bf..45befb0 100644 --- a/dashboard/src/docs/repos.md +++ b/dashboard/src/docs/repos.md @@ -27,6 +27,11 @@ be followed by classification registration. For the full onboarding journey see **[Repo Integration](/docs/repo-integration)**. +For a lineage-preserving Forgejo coordinate change, see +**[Repository Renames](/docs/repository-renames)**. The old repository route +continues to resolve through a protected alias; the dashboard is verification, +not the mutation surface. + --- ## KPI row @@ -86,4 +91,4 @@ Use the **Add Repo** form or: # 2. Register / reclassify make register-from-classification PATH=/path/to/repo statehub fix-consistency -``` \ No newline at end of file +``` diff --git a/dashboard/src/docs/repository-renames.md b/dashboard/src/docs/repository-renames.md new file mode 100644 index 0000000..3e92dbe --- /dev/null +++ b/dashboard/src/docs/repository-renames.md @@ -0,0 +1,70 @@ +--- +title: Repository Renames — Operator Reference +--- + +# Repository renames + +State Hub preserves repository history across a Forgejo name change by keeping +the repository UUID and Forge numeric ID unchanged. The new slug becomes +canonical and the former slug remains a protected alias. + +The dashboard is a read surface. Execute and recover renames with the +`statehub repo rename` CLI and the repository-owned migration workplan. + +## What to inspect here + +After the `statehub-rebound` phase: + +1. Open `/repos/`. An amber banner must identify the requested slug + as an alias and link to the canonical repository page. +2. Open `/repos/`. Both pages must report the same repository UUID; + the canonical page lists the old alias. +3. Review workplans/tasks, dispatch, token totals, SBOM, services, + capabilities, inbox, interface changes, and stale external references. +4. Treat stale Fabric or external projections as owner handoffs. Their source + is not silently rewritten by State Hub. + +Matching dashboard counts are useful but insufficient. Final verification uses +the per-record relationship checksums returned by: + +```text +statehub repo rename verify --json +``` + +## Read the achieved phase + +```text +statehub repo rename status --json +``` + +The `phase` is the last durable achievement. An `error_code` describes the last +failed attempt and does not advance the phase. Follow `next_safe_action`; reuse +the same operation UUID for retries. + +| Phase | Next normal action | +| --- | --- | +| `preflighted` | Apply `forge-renamed`. | +| `forge-renamed` | Apply `statehub-rebound`; never register a replacement repository. | +| `statehub-rebound` | Verify/register a fresh canonical clone, then apply `source-synced`. | +| `source-synced` | Collect owner handoff evidence and apply `consumers-verified`. | +| `consumers-verified` | Re-run verification and apply `completed`. | +| `completed` | Soak, hand off residuals, then request cleanup approval. | +| `rollback-preflight` | Review blockers and irreversible effects before `--execute`. | +| `rolled-back` | Verify old canonical routes and affected consumers. | + +## Safety boundaries + +- Repository rename approval does not rename products, packages, APIs, + namespaces, environment variables, policies, or telemetry labels. +- Forgejo credentials stay in their approved custodian; they never enter State + Hub commands or evidence. +- External repositories own and close their own file-backed handoff tasks. +- Rollback does not undo consumer commits, deployments, published artifacts, + external caches, or historical telemetry. +- The old checkout is removed last, after terminal verification, soak, live + residual handoffs, and explicit path-specific approval. +- The protected old alias remains. Alias retirement is a separate decision. + +The complete source runbook is `docs/repository-rename-operations.md`; its +versioned handoff contract is +`docs/schemas/repository-rename-handoff-v1.schema.json`. diff --git a/docs/examples/repository-rename-handoff-v1.json b/docs/examples/repository-rename-handoff-v1.json new file mode 100644 index 0000000..5cc96f5 --- /dev/null +++ b/docs/examples/repository-rename-handoff-v1.json @@ -0,0 +1,43 @@ +{ + "schema_version": "state-hub.repository-rename-handoff.v1", + "handoff_id": "FABRIC-WP-0042-T03", + "operation_id": "11111111-1111-4111-8111-111111111111", + "renamed_repository_id": "22222222-2222-4222-8222-222222222222", + "source_repository": "railiance-fabric", + "old_slug": "old-repository", + "new_slug": "new-repository", + "surface": "fabric-projection", + "affected": { + "paths": [ + "graph/repositories.yaml" + ], + "graph_ids": [ + "repo:old-repository" + ] + }, + "required_reingest": { + "required": true, + "procedure": "Run the owning repository's documented fabric import after committing the canonical slug.", + "expected_source_revision": "0123456789abcdef0123456789abcdef01234567" + }, + "verification": [ + { + "check": "Canonical repository node is projected from the owning source revision.", + "expected": "repo:new-repository resolves to the unchanged State Hub repository UUID", + "evidence_ref": "docs/evidence/repository-rename-fabric-verification.json" + }, + { + "check": "Historical graph evidence remains queryable.", + "expected": "Prior repo:old-repository observations remain historical evidence", + "evidence_ref": null + } + ], + "owning_work_record": { + "repository": "railiance-fabric", + "workplan_id": "FABRIC-WP-0042", + "task_id": "FABRIC-WP-0042-T03" + }, + "status": "open", + "evidence": [], + "sensitive_data_prohibited": true +} diff --git a/docs/repository-rename-operations.md b/docs/repository-rename-operations.md new file mode 100644 index 0000000..117d3ba --- /dev/null +++ b/docs/repository-rename-operations.md @@ -0,0 +1,332 @@ +# Repository rename operations + +This is the operator runbook for a lineage-preserving repository rename within +one Forgejo instance and owner. It coordinates State Hub, Forgejo, HelixForge, +and repository-owned work without changing the repository UUID or Forge numeric +ID. It does not authorize a live rename; the target repository's reviewed +migration workplan and a human cutover decision do that. + +The identity and failure contract is +[`ADR-001`](adr/ADR-001-repository-rename-identity-contract.md). The automated +recovery proof is +[`STATE-WP-0085-T07`](evidence/STATE-WP-0085-T07-repository-rename-recovery-matrix.md). + +## Ownership + +| Owner | Owns | Must not do | +| --- | --- | --- | +| State Hub | Repository UUID, canonical slug, protected aliases, verified Forge identity, operation journal, continuity verification, lifecycle events | Rename Forgejo directly, rewrite another repository's source, or register a replacement repository | +| Forgejo | Git repository, numeric repository ID, canonical Forge name, Git data, redirects and Forge-owned settings | Decide State Hub UUID/alias history or consumer completion | +| HelixForge/operator | Cutover window, credentials routed through their approved custodian, Forgejo rename call, pause/continue/rollback decision, destructive cleanup approval | Put credentials in CLI arguments, evidence, Git, State Hub, or chat | +| Target repository | Repository-native migration plan, product/runtime naming decisions, source updates, fresh clone, file-first work records, aggregation of owned handoff evidence | Claim another repository's source change complete | +| External source repository | Its own CI, deployment, policy, fabric, SBOM, credential-route, package, or documentation source and its live handoff work record | Let the target repository edit or close that obligation on its behalf | + +Forgejo credentials are never accepted by `statehub repo rename`. Resolve +credential ownership before the window with `warden route find` and use the +operator-approved Forgejo/HelixForge mechanism outside State Hub. Examples below +use identifiers and placeholder paths only. + +## Choose the scope before planning + +### Repository-only rename checklist + +Use this when the Git repository coordinate changes but the product/runtime +identity does not. + +- [ ] Forgejo repository name and canonical clone/web URL change. +- [ ] State Hub canonical slug changes on the existing UUID; old slug remains a + protected alias. +- [ ] Local checkout and automation checkout coordinates are re-registered. +- [ ] Repository metadata and intentional live source references are updated. +- [ ] Consumers that name the Git coordinate receive owned handoffs. +- [ ] Binary, module/import path, API names, environment variables, Kubernetes + namespace/services, packages/images, policy vocabulary, and telemetry labels + explicitly remain unchanged. + +### Product/runtime rename checklist + +Use this only when separate product decisions authorize broader changes. Treat +each item as independently deployable and independently reversible. + +- [ ] Record keep/rename decisions for binary and module/import path. +- [ ] Record compatibility policy for APIs, CLI names, environment variables, + configuration keys, policy vocabulary, and user-facing names. +- [ ] Plan package/image coordinates, releases, redirects, and deprecation + windows without assuming Forge redirects cover registries. +- [ ] Plan Kubernetes namespace, Service/DNS, Helm chart/release, secrets paths, + dashboards, alerts, and telemetry labels. +- [ ] Give each owning repository/platform a live workplan/task handoff. +- [ ] Define deployment order, compatibility window, rollback boundary, and + irreversible migrations separately from the repository rename. + +A repository-only approval never implies the product/runtime checklist. + +## Prepare the operation + +1. Generate and review the target-owned adoption plan: + + ```text + statehub repo rename generate-workplan --json + ``` + +2. Secure all branches, tags, and local changes to Forgejo. Capture the State + Hub repository UUID, Forge numeric ID, default branch, and head commit. +3. Inventory every slug-bearing source. Create one live handoff record in each + owning repository using the format below. Unknown ownership is a blocker. +4. Resolve or deliberately coordinate active work and drain/replay edge outbox + writes. A queued receipt is not central evidence. +5. Choose a client-owned operation UUID and keep it for every retry: + + ```text + = + = rename::: + ``` + +6. Capture a new preflight immediately before the window. The output file is + private and single-use; do not commit it: + + ```text + statehub repo rename preflight \ + --operation-id \ + --output --json + ``` + +Proceed only when `ok` and `result.safe_to_apply` are true, `blockers` is empty, +the immutable IDs/head match the approved baseline, and every warning has a +recorded disposition. + +## Execute one phase at a time + +Start the durable journal before asking HelixForge to mutate Forgejo: + +```text +statehub repo rename start \ + --operation-id \ + --preflight-file \ + --actor --confirm '' --json +``` + +The operation is now `preflighted`. Apply only the phase named by +`next_safe_action`: + +```text +statehub repo rename apply --phase forge-renamed \ + --confirm '' --json +statehub repo rename apply --phase statehub-rebound \ + --confirm '' --json +``` + +After the State Hub rebind, make a fresh clone from the canonical URL. Do not +trust an old-slug redirect as identity. Verify the Forge numeric ID and head, +register the new path against the existing State Hub UUID, commit canonical +source/frontmatter updates, and run `statehub fix-consistency` in the new clone. + +The private source evidence file must include the assertions enforced by State +Hub. Extra non-secret references are allowed: + +```json +{ + "fresh_clone": true, + "forge_repository_id": 12345, + "head_commit": "0123456789abcdef0123456789abcdef01234567", + "clone_path": "", + "registration_evidence_ref": "", + "consistency_evidence_ref": "" +} +``` + +```text +statehub repo rename apply --phase source-synced \ + --evidence-file \ + --confirm '' --json +``` + +Collect evidence from every owning handoff. The checks file is a JSON object of +named booleans; false or missing required checks block the phase: + +```json +{ + "old-and-new-slugs-one-uuid": true, + "forge-id-and-head": true, + "work-record-relationships": true, + "telemetry-relationships": true, + "external-handoffs-verified": true, + "build-and-deployment-health": true +} +``` + +```text +statehub repo rename verify --json +statehub repo rename apply --phase consumers-verified \ + --checks-file \ + --evidence-file \ + --confirm '' --json +statehub repo rename apply --phase completed \ + --confirm '' --json +``` + +## Read status without source code + +At any time run: + +```text +statehub repo rename status --json +``` + +Interpret the stable CLI envelope as follows: + +| Field | Meaning | +| --- | --- | +| `state` | `planned` means resumable/incomplete, `achieved` means completed, `failed` means the last attempt failed, and `rolled-back` is terminal rollback. | +| `phase` | Last durably achieved phase. An `error_code` does not advance it. | +| `result.no_op` | The submitted phase was already achieved; operation identity was preserved. | +| `next_safe_action.action` | Operator action permitted by the recorded phase. | +| `next_safe_action.command` | Exact credential-free command skeleton for that action. | +| `result.error_code` | Code from the last failed attempt; inspect it before retrying. | +| `result.evidence.telemetry` | Phase duration, attempts, retries, failures, rollback, and verification outcome. | + +The phase table is the recovery map: + +| Achieved phase | What is true | Normal next command | +| --- | --- | --- | +| `preflighted` | Journal and immutable baseline exist; Forge may still use old name. | Apply `forge-renamed`. | +| `forge-renamed` | Forge numeric ID is visible at the new name; State Hub may still be canonical on old slug. | Apply `statehub-rebound`. | +| `statehub-rebound` | New slug is canonical and old slug is a protected alias on the same UUID. | Fresh clone/register, then apply `source-synced`. | +| `source-synced` | Canonical clone and source projection are recorded. | Verify owned handoffs; apply `consumers-verified`. | +| `consumers-verified` | Required checks and handoff evidence passed. | Re-run verify, then apply `completed`. | +| `completed` | Rename is terminal; aliases/history remain. | Soak, residual handoff, then separately approved cleanup. | +| `rollback-preflight` | Rollback safety and irreversible effects are recorded. | Review; use `rollback --execute` only if still safe. | +| `rolled-back` | Old canonical coordinate is restored and the attempt remains auditable. | Verify consumers; a new attempt needs a new operation. | + +The REST equivalents are repository-UUID addressed: + +| CLI operation | REST operation | +| --- | --- | +| preflight | `POST /repos/{repo_id}/rename/preflight` | +| start | `POST /repos/{repo_id}/rename/operations` | +| status | `GET /repository-renames/operations/{operation_id}` | +| apply phase | `POST /repos/{repo_id}/rename/operations/{operation_id}/phases/{phase}` | +| verify | `GET /repos/{repo_id}/rename/operations/{operation_id}/verify` | +| rollback preflight | `POST /repos/{repo_id}/rename/operations/{operation_id}/rollback-preflight` | +| execute rollback | `POST /repos/{repo_id}/rename/operations/{operation_id}/rollback` | + +Do not construct mutating REST requests when the CLI can express the operation; +the CLI supplies confirmation, private-file, redaction, and next-action guards. + +## Dashboard interpretation + +The dashboard is a read surface, not the rename executor. + +- Open `/repos/` after `statehub-rebound`. The amber alias banner must + identify `` as an alias and link to ``. +- Old and new routes must show the same repository UUID. The canonical page must + list the protected alias. +- Review repository dispatch, workplans/tasks, token totals, SBOM, services, + capabilities, interface changes, inbox, and stale external-reference notices. +- A stale Fabric/external projection is a visible handoff, not permission for + State Hub to rewrite its source. +- The dashboard does not currently render the phase journal. Use `statehub repo + rename status` and `verify` as the authoritative operational views. +- A healthy dashboard count is insufficient: final acceptance requires the + relationship checksums in `verify`. + +## Recovery playbook + +Always reuse the same operation UUID. First run `status`; treat `phase` as the +achieved state and the error as the failed attempt. + +| Observation | Action | +| --- | --- | +| State Hub unavailable | Stop mutations. Restore central connectivity; then query the operation UUID. Rename phases are not edge-queueable. | +| Preflight expired or evidence/head changed | Do not start. Resolve drift and issue a fresh preflight; keep or abandon the unused client operation UUID deliberately. | +| Forge rename committed but journal remains `preflighted` | Retry `forge-renamed`; State Hub recognizes the expected numeric ID at the new coordinate and records a resumed phase. | +| Forge old-slug lookup redirects to new slug | Treat the redirect as compatibility only. Verify numeric ID at the requested canonical coordinate; never use the redirect as identity evidence. | +| Journal is `forge-renamed` | Prefer `statehub-rebound`; do not register a new State Hub repository. | +| Journal is `statehub-rebound` but clone evidence is wrong | Keep both checkouts. Correct the fresh clone/remote/Forge-ID/head evidence and retry `source-synced`. | +| Consumer check fails | Remain `source-synced` or `consumers-verified`; repair the named owner handoff or enter rollback preflight. Do not complete. | +| Same phase submitted twice | A matching retry returns `no_op`. A changed operation/target/actor is a conflict, not a retry. | +| Edge outbox contains old-slug writes | Pause preflight until replayed or explicitly resolved. Alias-aware central handlers preserve idempotency after rebind. | +| Private Forge repository unreadable | Stop. Restore approved Forge access; never infer identity from a local checkout or redirect. | + +## Rollback limits + +Rollback is a separately confirmed two-step operation: + +```text +statehub repo rename rollback \ + --confirm 'rollback:' --json +# Only after reviewing safe_to_rollback, blockers, and irreversible effects: +statehub repo rename rollback \ + --confirm 'rollback:' --execute --json +``` + +Rollback is unavailable when the old Forge slug is claimed, the expected Forge +numeric ID/head is not observable, a competing operation exists, or State Hub +routes are inconsistent. Never steal/delete a slug to make rollback appear +safe. + +The following are not automatically reversed: + +- consumer commits, deployments, package/image publication, releases, or cache + propagation; +- external redirects and links already observed by users/automation; +- credential-route, policy, DNS, namespace, or product/runtime changes; +- fabric/SBOM/source projections owned by another repository; +- the operation journal, lifecycle events, historical messages, aliases, or + telemetry evidence. + +After `source-synced`, every reversed external effect needs its own owner +handoff and evidence. If an irreversible effect makes rollback unsafe, continue +forward or stop for an explicit incident decision. + +## Repository-boundary handoff format + +Every external slug-bearing source is represented by both: + +1. a live file-backed workplan/task or residual intake in the owning repository; +2. a payload conforming to + [`repository-rename-handoff-v1.schema.json`](schemas/repository-rename-handoff-v1.schema.json), + attached to or referenced by that work record. + +The example is +[`repository-rename-handoff-v1.json`](examples/repository-rename-handoff-v1.json). +The `handoff_id` is the live owning task/intake ID—not an informal label. The +record names the source repository, old/new slug, affected paths or graph IDs, +re-ingest procedure, verification checks, owning workplan/task, status, and +non-secret evidence references. + +Handoff rules: + +- The target repository creates/routes the obligation but does not edit or + close another repository's source task. +- The owner acknowledges it in its own file-first workplan and synchronizes it + to State Hub. +- `verified` requires evidence from the owner. Equal counts, a message saying + "done", or an uncommitted local edit are insufficient. +- Evidence contains commit/check/report/receipt references and optional + checksums only. Never include credentials, authorization headers, cookies, + private preflight tokens, secret-bearing URLs, or secret values. +- Unresolved handoffs become live residual records before the target workplan + finishes. They are never parked only in this runbook, `SCOPE.md`, or prose. + +## Cleanup is deliberately last + +The old local checkout is the final removable asset because it is a recovery +input, a comparison point, and evidence of the pre-cutover source. Removing it +before terminal verification can strand the operator after an external rename, +hide unpushed work, and destroy the easiest rollback comparison. + +Only remove the old checkout after all of the following: + +- operation is `completed` or intentionally `rolled-back`; +- relationship verification passes; +- canonical fresh clone is registered against the same State Hub UUID; +- required owner handoffs are verified or represented as accepted live + residuals; +- the soak window covers deployments, automation, policies, packages, alerts, + dashboards, and telemetry; +- explicit cleanup approval identifies the exact old path and recovery source. + +Record what was removed and whether recovery remains possible from Forgejo. +The protected old slug alias is not cleanup material in this workflow; alias +retirement requires a separate compatibility decision and operation. diff --git a/docs/schemas/repository-rename-handoff-v1.schema.json b/docs/schemas/repository-rename-handoff-v1.schema.json new file mode 100644 index 0000000..f99d63c --- /dev/null +++ b/docs/schemas/repository-rename-handoff-v1.schema.json @@ -0,0 +1,160 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://state-hub.coulomb.social/schemas/repository-rename-handoff-v1.schema.json", + "title": "State Hub repository rename handoff v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "handoff_id", + "operation_id", + "renamed_repository_id", + "source_repository", + "old_slug", + "new_slug", + "surface", + "affected", + "required_reingest", + "verification", + "owning_work_record", + "status", + "evidence", + "sensitive_data_prohibited" + ], + "properties": { + "schema_version": { + "const": "state-hub.repository-rename-handoff.v1" + }, + "handoff_id": { + "type": "string", + "minLength": 1, + "description": "Canonical live task or intake record ID in the owning repository." + }, + "operation_id": { + "type": "string", + "format": "uuid" + }, + "renamed_repository_id": { + "type": "string", + "format": "uuid" + }, + "source_repository": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$", + "description": "Repository that owns the slug-bearing source to change or re-ingest." + }, + "old_slug": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "new_slug": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "surface": { + "enum": [ + "fabric-projection", + "ci", + "package", + "deployment", + "credential-route", + "policy", + "sbom", + "documentation", + "consumer", + "other" + ] + }, + "affected": { + "type": "object", + "additionalProperties": false, + "required": ["paths", "graph_ids"], + "properties": { + "paths": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + }, + "graph_ids": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + } + }, + "anyOf": [ + {"properties": {"paths": {"minItems": 1}}}, + {"properties": {"graph_ids": {"minItems": 1}}} + ] + }, + "required_reingest": { + "type": "object", + "additionalProperties": false, + "required": ["required", "procedure"], + "properties": { + "required": {"type": "boolean"}, + "procedure": {"type": "string", "minLength": 1}, + "expected_source_revision": {"type": ["string", "null"]} + } + }, + "verification": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["check", "expected", "evidence_ref"], + "properties": { + "check": {"type": "string", "minLength": 1}, + "expected": {"type": "string", "minLength": 1}, + "evidence_ref": {"type": ["string", "null"]} + } + } + }, + "owning_work_record": { + "type": "object", + "additionalProperties": false, + "required": ["repository", "workplan_id", "task_id"], + "properties": { + "repository": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "workplan_id": {"type": "string", "minLength": 1}, + "task_id": {"type": "string", "minLength": 1} + } + }, + "status": { + "enum": ["open", "acknowledged", "verified", "cancelled"] + }, + "evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "ref"], + "properties": { + "kind": { + "enum": [ + "commit", + "progress-event", + "decision", + "test-report", + "deployment-observation", + "reingest-receipt", + "other" + ] + }, + "ref": {"type": "string", "minLength": 1}, + "checksum": { + "type": ["string", "null"], + "pattern": "^[a-fA-F0-9]{64}$" + } + } + } + }, + "sensitive_data_prohibited": { + "const": true, + "description": "Evidence contains references and checksums only, never credentials or secret values." + } + } +} diff --git a/repository_rename_workplan.py b/repository_rename_workplan.py index d8269d3..4f67fcf 100644 --- a/repository_rename_workplan.py +++ b/repository_rename_workplan.py @@ -525,8 +525,13 @@ Owner: `{old_slug}` for inventory and handoff creation only. docs, badges, webhooks, mirrors, caches, dashboards, alerts, and local clones. - For each external effect, replace `` in the ledger with the exact repository slug and create a live residual/intake/workplan there. -- Record each external work-record ID here. Do not mark that external work done - from this repository; completion evidence must come from its owner. +- Record each external work-record ID here and attach a + `state-hub.repository-rename-handoff.v1` payload following + `state-hub/docs/schemas/repository-rename-handoff-v1.schema.json`. It names + the source repository, old/new slug, affected paths or graph IDs, required + re-ingest and verification, owning workplan/task, and non-secret evidence. +- Do not mark that external work done from this repository; completion evidence + must come from its owner. - Include every row in the preflight risk register, even when it is only a warning or currently zero-count projection. @@ -602,8 +607,10 @@ Owner: `{new_slug}` after rebind. - Register the fresh path against the existing State Hub UUID, update canonical source metadata/frontmatter, commit the source-synchronization revision, and run `statehub fix-consistency` from the new clone. -- Apply `source-synced` with mode-0600 evidence naming clone path, remote, head, - registration result, and consistency result. +- Apply `source-synced` with mode-0600 evidence containing + `fresh_clone: true`, Forge numeric `forge_repository_id`, exact `head_commit`, + clone path, registration result, and consistency result. Remote coordinates + contain no embedded credential. Gate: no duplicate repository registration exists and the journal is `source-synced`. @@ -634,8 +641,8 @@ statehub repo rename apply --phase completed \\ --confirm '{confirmation}' --json ``` -Gate: every required local check passes and every external handoff has evidence -from its owning repository before completion. +Gate: every required local check passes and every external handoff is `verified` +with evidence from its owning repository before completion. ''' output += _task(snapshot, 10, "Exercise rollback decision points", "wait") @@ -666,7 +673,8 @@ Owner: `{new_slug}`; destructive cleanup requires separate human approval. - Define and observe a soak window covering deployments, policy decisions, alerts, telemetry, packages, automation, and old-slug compatibility reads. - Convert every unresolved item into a live residual (`origin: residual`, - `origin_ref: {snapshot.workplan_id}`) in its owning repository before finishing. + `origin_ref: {snapshot.workplan_id}`) in its owning repository before finishing; + retain its repository-rename handoff payload and owner work-record ID. - Retain the protected `{old_slug}` alias. Alias retirement is out of scope. - Only after terminal verification, soak, residual handoff, and explicit cleanup approval may the old local checkout be removed. Record what was removed and diff --git a/tests/test_repository_rename_docs.py b/tests/test_repository_rename_docs.py new file mode 100644 index 0000000..bf68af3 --- /dev/null +++ b/tests/test_repository_rename_docs.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from jsonschema import Draft202012Validator, FormatChecker + + +ROOT = Path(__file__).resolve().parents[1] +RUNBOOK = ROOT / "docs" / "repository-rename-operations.md" +HANDOFF_SCHEMA = ( + ROOT / "docs" / "schemas" / "repository-rename-handoff-v1.schema.json" +) +HANDOFF_EXAMPLE = ( + ROOT / "docs" / "examples" / "repository-rename-handoff-v1.json" +) +DASHBOARD_DOC = ROOT / "dashboard" / "src" / "docs" / "repository-renames.md" + + +def test_repository_rename_handoff_example_validates_and_contains_no_secret_value(): + schema = json.loads(HANDOFF_SCHEMA.read_text(encoding="utf-8")) + example = json.loads(HANDOFF_EXAMPLE.read_text(encoding="utf-8")) + Draft202012Validator.check_schema(schema) + Draft202012Validator(schema, format_checker=FormatChecker()).validate(example) + + assert example["handoff_id"] == example["owning_work_record"]["task_id"] + assert example["source_repository"] == example["owning_work_record"]["repository"] + assert example["affected"]["paths"] or example["affected"]["graph_ids"] + assert example["verification"] + assert example["sensitive_data_prohibited"] is True + serialized = json.dumps(example).lower() + for forbidden in ( + "bearer ", + "basic ", + "password=", + "token=", + "authorization=", + "begin private key", + "://user:", + ): + assert forbidden not in serialized + + +def test_operator_runbook_names_phases_commands_owners_and_last_cleanup(): + text = RUNBOOK.read_text(encoding="utf-8") + for owner in ("State Hub", "Forgejo", "HelixForge/operator", "Target repository"): + assert f"| {owner} |" in text + for phase in ( + "preflighted", + "forge-renamed", + "statehub-rebound", + "source-synced", + "consumers-verified", + "completed", + "rollback-preflight", + "rolled-back", + ): + assert f"`{phase}`" in text + for command in ( + "statehub repo rename preflight", + "statehub repo rename start", + "statehub repo rename apply", + "statehub repo rename status", + "statehub repo rename verify", + "statehub repo rename rollback", + ): + assert command in text + assert "### Repository-only rename checklist" in text + assert "### Product/runtime rename checklist" in text + assert "## Dashboard interpretation" in text + assert "## Recovery playbook" in text + assert "## Rollback limits" in text + assert "## Repository-boundary handoff format" in text + assert "## Cleanup is deliberately last" in text + assert "The old local checkout is the final removable asset" in text + assert "The protected old slug alias is not cleanup material" in text + assert "never parked only" in text + assert "" in text + + +def test_dashboard_reference_explains_alias_and_status_boundaries(): + text = DASHBOARD_DOC.read_text(encoding="utf-8") + assert "/repos/" in text + assert "same repository UUID" in text + assert "statehub repo rename status --json" in text + assert "relationship checksums" in text + assert "dashboard is a read surface" in text.lower() + assert "old checkout is removed last" in text diff --git a/tests/test_repository_rename_workplan.py b/tests/test_repository_rename_workplan.py index c00bee6..e191f17 100644 --- a/tests/test_repository_rename_workplan.py +++ b/tests/test_repository_rename_workplan.py @@ -154,6 +154,12 @@ def test_flex_auth_snapshot_is_deterministic_and_complete(tmp_path): assert "flexauth-legacy" in first assert "| Credential-route catalog | `ops-warden` |" in first assert "only that repository closes" in first + assert "state-hub.repository-rename-handoff.v1" in first + assert "state-hub/docs/schemas/repository-rename-handoff-v1.schema.json" in first + assert "fresh_clone: true" in first + assert "Forge numeric `forge_repository_id`" in first + assert "exact `head_commit`" in first + assert "every external handoff is `verified`" in first def test_generated_snapshot_passes_parser_and_quality_debt(tmp_path): diff --git a/workplans/STATE-WP-0085-repository-lineage-preserving-rename.md b/workplans/STATE-WP-0085-repository-lineage-preserving-rename.md index fa34cf1..f0b9d8f 100644 --- a/workplans/STATE-WP-0085-repository-lineage-preserving-rename.md +++ b/workplans/STATE-WP-0085-repository-lineage-preserving-rename.md @@ -528,7 +528,7 @@ SQLAlchemy async cancellation warning remain outside this task. ```task id: STATE-WP-0085-T08 -status: todo +status: done priority: medium state_hub_task_id: "06b6cde4-0428-5467-b2db-d957fde3d8e9" ``` @@ -551,6 +551,16 @@ Acceptance: - State Hub/Forgejo/HelixForge/target-repository ownership is unambiguous; - examples use placeholder credentials and never secret values. +Result (2026-08-29): Delivered the operator runbook in +`docs/repository-rename-operations.md`, the dashboard reference in +`dashboard/src/docs/repository-renames.md`, and the versioned +`state-hub.repository-rename-handoff.v1` schema plus placeholder-only example. +The generated target workplan now requires owner-backed handoff records and +exact fresh-clone/Forge-ID/head evidence before continuity completion. Focused +contract tests pass (29), as do all 813 Python tests and the 71-page dashboard +build. The existing `/docs/intakes` to `/suggestions` broken-link warning and +one existing async cancellation warning remain outside T08. + ## Generate and review the flex-auth adoption plan ```task