docs: complete repository rename operator handoff
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
0ec5b9a98d
commit
f7402bd711
11 changed files with 735 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
70
dashboard/src/docs/repository-renames.md
Normal file
70
dashboard/src/docs/repository-renames.md
Normal 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`.
|
||||
43
docs/examples/repository-rename-handoff-v1.json
Normal file
43
docs/examples/repository-rename-handoff-v1.json
Normal 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
|
||||
}
|
||||
332
docs/repository-rename-operations.md
Normal file
332
docs/repository-rename-operations.md
Normal 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.
|
||||
160
docs/schemas/repository-rename-handoff-v1.schema.json
Normal file
160
docs/schemas/repository-rename-handoff-v1.schema.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
88
tests/test_repository_rename_docs.py
Normal file
88
tests/test_repository_rename_docs.py
Normal 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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue