docs: complete repository rename operator handoff
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
tegwick 2026-08-29 15:46:43 +02:00
parent 0ec5b9a98d
commit f7402bd711
11 changed files with 735 additions and 10 deletions

View file

@ -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

View file

@ -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 |

View file

@ -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
```
```

View file

@ -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/<old-slug>`. An amber banner must identify the requested slug
as an alias and link to the canonical repository page.
2. Open `/repos/<new-slug>`. 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 <operation-id> --json
```
## Read the achieved phase
```text
statehub repo rename status <operation-id> --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`.

View file

@ -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
}

View file

@ -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 <old-slug> <new-slug> --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
<operation-id> = <uuid-generated-by-operator-or-orchestrator>
<confirmation> = rename:<state-hub-repository-uuid>:<old-slug>:<new-slug>
```
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 <old-slug> <new-slug> \
--operation-id <operation-id> \
--output <private-mode-0600-preflight.json> --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 <old-slug> <new-slug> \
--operation-id <operation-id> \
--preflight-file <private-mode-0600-preflight.json> \
--actor <operator-id> --confirm '<confirmation>' --json
```
The operation is now `preflighted`. Apply only the phase named by
`next_safe_action`:
```text
statehub repo rename apply <operation-id> --phase forge-renamed \
--confirm '<confirmation>' --json
statehub repo rename apply <operation-id> --phase statehub-rebound \
--confirm '<confirmation>' --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": "<new-local-checkout>",
"registration_evidence_ref": "<progress-event-or-file-reference>",
"consistency_evidence_ref": "<progress-event-or-file-reference>"
}
```
```text
statehub repo rename apply <operation-id> --phase source-synced \
--evidence-file <private-mode-0600-source-evidence.json> \
--confirm '<confirmation>' --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 <operation-id> --json
statehub repo rename apply <operation-id> --phase consumers-verified \
--checks-file <private-mode-0600-checks.json> \
--evidence-file <private-mode-0600-consumer-evidence.json> \
--confirm '<confirmation>' --json
statehub repo rename apply <operation-id> --phase completed \
--confirm '<confirmation>' --json
```
## Read status without source code
At any time run:
```text
statehub repo rename status <operation-id> --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/<old-slug>` after `statehub-rebound`. The amber alias banner must
identify `<old-slug>` as an alias and link to `<new-slug>`.
- 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 <operation-id> \
--confirm 'rollback:<operation-id>' --json
# Only after reviewing safe_to_rollback, blockers, and irreversible effects:
statehub repo rename rollback <operation-id> \
--confirm 'rollback:<operation-id>' --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.

View file

@ -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."
}
}
}

View file

@ -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 `<owning-repository>` 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 <operation-id> --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

View file

@ -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 "<private-mode-0600-preflight.json>" in text
def test_dashboard_reference_explains_alias_and_status_boundaries():
text = DASHBOARD_DOC.read_text(encoding="utf-8")
assert "/repos/<old-slug>" in text
assert "same repository UUID" in text
assert "statehub repo rename status <operation-id> --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

View file

@ -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):

View file

@ -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