# 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.
