state-hub/docs/adr/ADR-001-repository-rename-identity-contract.md
tegwick 8754983a0d
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 1s
docs: define repository rename identity contract
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
2026-08-28 21:24:50 +02:00

25 KiB

id type title status owner date deciders related
STATE-ADR-001 architecture-decision-record Preserve repository identity across canonical-name changes accepted state-hub 2026-08-28
operator
state-hub
STATE-WP-0085
CUST-ADR-007
CUST-ADR-012

Preserve repository identity across canonical-name changes

Status and scope

Accepted for the implementation governed by STATE-WP-0085. This decision defines the State Hub compatibility contract. It does not authorize a live Forgejo rename.

The contract covers a repository name change within one Forgejo instance and owner. A Forgejo owner transfer is a different identity and authorization problem and is out of scope. A product or runtime rename is also out of scope: module paths, binaries, packages, image names, namespaces, releases, DNS names, environment variables, policy vocabulary, and user-facing names change only when the target repository's migration workplan says so.

Context

State Hub already anchors most durable relationships to managed_repos.id, but it also exposes the current repository slug through URLs, commands, generated files, routing keys, cached projections, and historical evidence. Treating the slug as identity would split history when a repository is renamed. Rewriting every occurrence would instead erase provenance and make interrupted migration recovery unsafe.

Forgejo is authoritative for the Git repository. State Hub is authoritative for its repository UUID, canonical slug, protected aliases, and rename-operation evidence. Repository files remain authoritative for work records; State Hub continues to be their read model in accordance with CUST-ADR-012.

Decision

A repository is identified by an immutable State Hub UUID and a verified immutable Forge identity. Its slug, name, URL, and checkout paths are mutable coordinates. A rename changes coordinates on the existing repository row, retains the former slug as a protected alias, and records an idempotent phased operation. It never registers a replacement repository.

Identity model

Element Authority Mutability Rename rule
State Hub repository UUID State Hub Immutable The operation is addressed by UUID; it must be identical before and after every phase.
Forge provider/instance Estate configuration Immutable in this contract A move between instances is not a rename.
Forge owner Forgejo Immutable in this contract Owner transfer requires a separate contract.
Forge repository numeric ID Forgejo Immutable Preflight must verify it; any mismatch fails closed.
Canonical repository slug/name Forgejo, mirrored by State Hub Mutable Exactly one canonical slug names the repository at a time.
Prior slugs State Hub Append-only Retained as protected aliases; they resolve to the same UUID and cannot be claimed by another live repository.
Clone/web URL Forgejo Mutable projection Recomputed from verified forge coordinates; never used as durable identity.
Local and host checkout paths Operator/registration Mutable projection Re-registered after a new clone; old paths remain evidence until explicit cleanup.
Default branch Forgejo/Git Mutable outside the rename Captured by preflight and compared before mutation; rename does not change it.
Expected source commit Git Immutable preflight assertion Apply fails if the selected branch head has moved; a fresh preflight is required.
Workplan/task identifiers and Hub UUIDs Repository files/registrar Immutable Preserved without re-keying. Existing prefix values remain valid.
Historical event/message/provenance values Producing system Immutable evidence Retain the value observed at event time; resolve aliases only when a live action is requested.

An implementation may normalize Forge identity into dedicated tables, but the uniqueness boundary is (provider instance, owner, forge repository ID). It must not infer or backfill the numeric repository ID from a slug, URL, checkout, or initial Git commit.

Invariants

ID Invariant Required proof
I1 A rename uses one managed_repos row. Repository-row count and UUID are unchanged.
I2 Forge repository identity is unchanged. Provider, owner, and numeric ID match the accepted preflight.
I3 The selected source is unchanged at cutover. Default branch and expected head commit match the accepted preflight.
I4 Work history and telemetry retain identity. Workplan, task, progress, decision, token, SBOM, service, DoI, and TPSC relationships still reference the same repository UUID and their baseline counts/checksums match.
I5 Existing work-record identifiers are not renamed. No identifier or filename rewrite is caused solely by the slug change.
I6 The former slug remains usable as an alias. Old and new slug resolve to the same UUID; canonical responses identify the new slug.
I7 Historical evidence is not rewritten. Stored event, message, review, contribution, raw metadata, and source-provenance values remain byte-for-byte intact.
I8 Each phase is idempotent. Repeating a completed phase with the same operation ID is a no-op returning the achieved state.
I9 Every incomplete state has one safe next action. The operation exposes its achieved phase and recovery instruction.
I10 Cleanup follows verification. No alias, checkout, route, or rollback input is removed by this operation.

Slug treatment vocabulary

Every slug-bearing surface has exactly one primary treatment:

Treatment Meaning
foreign-key anchored Durable relationship already uses the repository UUID. Preserve the row; render the current canonical slug when needed.
alias-resolved The value requests a live lookup, route, or mutation. Accept canonical or protected alias, resolve to one UUID, then emit the canonical slug.
reprojected Derived data is rebuilt from repository files or current coordinates after cutover. Do not hand-edit cached rows.
historically retained The string records what a producer observed. Never rewrite it; a separate repository UUID may be added for future records.
target-repo handoff State Hub does not own the source. Generate an explicit task for its owner and verify the result.

Slug-bearing surface inventory

This inventory is the baseline for STATE-WP-0085. New slug-bearing fields must declare one of the treatments above before they are added.

Persistence and domain models

Surface Treatment Owner and required behavior
managed_repos.slug, name, remote_url, local_path, host_paths, git_fingerprint alias-resolved State Hub updates canonical coordinates on the existing UUID. Previous slug becomes a protected alias; previous paths/URL remain operation evidence.
workplans.repo_id, repo_goals.repo_id, workplan_launch_requests.repo_id foreign-key anchored State Hub preserves relationships and renders the canonical slug. Repository-file bindings are reprojected.
token_events.repo_id, sbom_snapshots.repo_id, sbom_entries.repo_id, tpsc_snapshots.repo_id, service_first_party.repo_id, doi_cache.repo_id, capability_catalog.repo_id foreign-key anchored State Hub preserves rows and history. Cached/materialized responses use the canonical slug after invalidation.
intakes.repo_id foreign-key anchored State Hub preserves the relationship. source_repo_path remains historical provenance.
contributions.repo_id foreign-key anchored State Hub preserves a known local target relationship.
contributions.target_repo historically retained Contribution producer owns the observed string; do not rewrite past records. New live inputs resolve aliases when a matching managed repository exists.
agent_messages.from_agent, agent_messages.to_agent historically retained State Hub retains delivered envelope values. Message creation and inbox lookup are alias-resolved, so the old repository agent name continues to reach the canonical inbox. Message bodies are never rewritten.
interface_changes.repo_id foreign-key anchored State Hub preserves the origin repository relationship.
interface_changes.affected_repo_slugs historically retained State Hub retains the audience recorded at publication. New publication, pending-recipient queries, and acknowledgements resolve aliases to UUID-equivalent recipients.
work_record_identifier_aliases.repo_slug historically retained Registrar owns identifier provenance. A repository-name alias must not rewrite or repurpose a work-record identifier alias.
review_contracts.source_repo, review_receipts.source_repo, and their source_path values historically retained Review producer owns source provenance. Future records may additionally bind a repository UUID.
fabric_graph_imports.source_repo_slug, fabric_graph_nodes.source_repo_slug, fabric_graph_nodes.repo_slug, fabric_graph_edges.source_repo_slug target-repo handoff Fabric source owns these projections. Re-import from the updated source and verify lineage; State Hub does not silently rewrite them.
Any source_path, raw_metadata, progress payload, decision text, or event payload containing the old slug historically retained The producing component owns the evidence. Search results may enrich it with current canonical identity, but storage is unchanged.

REST API and serialization

Surface Treatment Owner and required behavior
/repos/{slug} and /repos/{slug}/paths; repo lookup, dispatch, DoI, sync, remote-URL and fingerprint lookup responses alias-resolved Repositories router resolves aliases to UUID. A response includes canonical slug and whether the request used an alias; canonical links use the new slug. Mutations bind to UUID.
/repo-goals?repo_slug, /sbom/{repo_slug}, SBOM ingest/list filters, /tpsc/snapshots?repo_slug alias-resolved Owning routers resolve once through the repository resolver and query by UUID.
Capability-catalog, service-first-party, contribution, and workplan-file-binding request repo_slug fields alias-resolved Request handlers resolve old/new slug to the same UUID. Generated and response values use the canonical slug unless explicitly historical.
Message from_agent/to_agent creation and inbox query parameters alias-resolved Message router canonicalizes repository-agent routing for matching while retaining the original envelope. Non-repository agent names are unaffected.
Interface-change repo_slug, affected_repo_slugs, pending origin_repo_slug, and affected-repo filters alias-resolved Interface-change router resolves local repository recipients by UUID-equivalence and retains original publication evidence.
Fabric graph source_repo_slug and repo filters and payloads target-repo handoff Fabric importer accepts old evidence but refreshed canonical data comes from its source projection.
Review contract/receipt source_repo and contribution target_repo representations historically retained APIs return the recorded value and may add canonical-resolution metadata; they do not mutate history on read.
Repo token summaries, DoI, SBOM, TPSC, service, capability, consistency, and dispatch response slugs derived from repo_id foreign-key anchored Serializers render the canonical slug from the repository relationship.

CLI, MCP, consistency, and generated files

Surface Treatment Owner and required behavior
statehub register, repo update/paths/dispatch/DoI/sync commands, and repo-slug options alias-resolved State Hub CLI resolves existing aliases, refuses duplicate registration, and prints UUID plus canonical slug. A new checkout updates the same UUID.
MCP tools accepting repo_slug for repository updates, ADR validation, consistency, SBOM, TPSC, repo goals, dispatch, interface changes, and ad-hoc tasks alias-resolved MCP adapters use the same REST resolver and return canonical identity. They do not implement a second rename path.
scripts/statehub_register.py slug/path/remote/fingerprint discovery and scaffold output alias-resolved Registration script recognizes the Forge identity/alias before POST or PATCH and generates only canonical new material.
scripts/consistency_check.py repository lookup, host paths, file bindings, inbox queries, prefixes, and briefs reprojected Consistency tooling resolves aliases, reads the canonical repository files, and regenerates bindings/briefs without changing stable work-record IDs.
.custodian-brief.md, WORK-RECORDS.md, workplan-file bindings, repository indexes, and other generated repository-name views reprojected State Hub consistency projection regenerates current coordinates from canonical source and records its source revision.
Repository-native workplans, AGENTS.md, catalog/manifest files, and other source files containing the repository slug target-repo handoff The target repository migration workplan updates intentional live references and retains historical ones. State Hub verifies the committed source revision.

Dashboard, events, edge, and external systems

Surface Treatment Owner and required behavior
Dashboard repository list and /repos/[slug] route alias-resolved Dashboard accepts an old route, identifies alias use, and links/redirects to the canonical route without losing the UUID.
Dashboard domain, inbox, interface-change, TPSC, SBOM, service, and capability queries alias-resolved Dashboard passes either slug to the API and renders returned canonical identity.
Existing org.statehub.repo.registered events historically retained Event Router retains payloads. Registration is not emitted for rename.
New org.statehub.repo.renamed and rename-phase events historically retained State Hub emits operation ID, repository UUID, Forge identity, old/new slug, phase, actor, expected commit, and evidence reference. Events contain no credential.
Edge outbox repo_slug metadata and literal request path/body historically retained Edge relay preserves queued envelopes. Central alias-aware handlers replay old-slug requests against the same UUID. Rename phase mutations are never edge-queueable and require central availability.
Edge read-cache keys containing old/new slug reprojected Edge relay treats them as separate keys, invalidates repository-scoped keys on phase events, and returns canonical identity. Stale old-slug cache content must not authorize a mutation.
Forgejo repository name, web/clone URL, redirects, Actions variables, hooks, branch protection, packages, releases, and deploy keys target-repo handoff Forgejo/HelixForge operator performs and verifies the rename. Redirects are compatibility evidence, not canonical configuration. Owner transfer remains out of scope.
Downstream Git remotes, submodules, module/dependency references, CI consumers, image/package references, deployments, policies, credentials, fabric declarations, and documentation target-repo handoff Each owning repository or platform component receives a target-workplan task. Product/runtime-name changes require explicit decisions rather than following the repository slug automatically.
Local working copies and automation checkout paths target-repo handoff Operator clones the canonical URL, registers the new path against the existing UUID, verifies it, and only then removes an old checkout under a separately confirmed cleanup step.

Operation and phase contract

Every rename has an immutable operation UUID and captures:

  • State Hub repository UUID;
  • Forge provider, instance, owner, and numeric repository ID;
  • old and proposed canonical slug, name, URLs, and registered paths;
  • default branch and expected head commit;
  • preflight expiry and evidence checksum;
  • actor, confirmation evidence, achieved phase, timestamps, and errors.

Each phase is a compare-and-set transition. A caller requests one phase, not an opaque multi-system transaction. Before changing anything, the phase handler observes actual State Hub and Forgejo state. If the requested result is already true for this operation, it returns success without repeating the side effect. If reality corresponds to a later or conflicting state, it stops and reports the observed facts.

Live rename phase transitions are Red-lane operations. They require central State Hub availability, an unexpired accepted preflight, explicit operator confirmation, and the operation UUID. They are excluded from edge queued-write allowlists.

Phase and interruption recovery

Phase Durable truth Allowed next phase Safe action after interruption
draft Intent exists; no identity assertion has been accepted. preflighted or cancel draft Run non-mutating preflight. It is always safe to abandon a draft.
preflighted Forge identity, old/new availability, branch/head, baselines, effects, and expiry are recorded; no rename has occurred. forge-renamed Re-read Forgejo and State Hub. If any compare value changed, expire the preflight and run it again. Otherwise request only the Forge phase.
forge-renamed Forgejo numeric ID now has the new name; State Hub may still expose the old canonical slug. statehub-rebound or rollback-preflight Verify Forge ID and commit. Prefer completing the State Hub rebind. If continuation is unsafe, first prove the old Forge slug is free, then enter rollback preflight. Do not register a new Hub repository.
statehub-rebound Existing Hub UUID has the new canonical slug and protected old alias; Forge identity matches. source-synced or rollback-preflight Verify both slugs resolve to one UUID. Regenerate State Hub-owned projections and sync repository source. Alias compatibility makes pausing here safe.
source-synced Target repository source and State Hub projections are committed at the recorded revision. consumers-verified or rollback-preflight Resume external handoffs from the recorded checklist. Do not assume source changes rolled out to consumers.
consumers-verified Required consumers, routes, artifacts, deployment references, and telemetry continuity checks have evidence. completed or rollback-preflight Re-run final invariant checks. Complete only if no required handoff remains open.
completed Invariants and required handoffs passed; old alias remains protected. none in this operation No automatic action. Optional checkout or compatibility cleanup is a separately approved target-repository task.
rollback-preflight Desired rollback point, old-slug availability, current IDs/commits, and irreversible handoffs are recorded. rolled-back or resume forward If the old slug is unavailable or an external change is unsafe to reverse, stop and resume forward. Otherwise reverse one phase at a time, Forgejo first when its name must change.
rolled-back Forgejo and State Hub canonical coordinates are restored and both observed slugs remain protected aliases; history is intact. new operation only Verify the original canonical route and consumers. Keep evidence and aliases; investigate before starting a new operation.

Failure states

Failure code Detection Mutation rule Recovery
forge-unreachable Forge identity or repository cannot be read. None. Restore connectivity and repeat the same phase. Never infer success from a redirect or local clone.
forge-identity-mismatch Provider, owner, or numeric ID differs from preflight. None. Stop for operator investigation; a new preflight cannot waive an identity mismatch without a separately reviewed migration.
source-moved Default branch or expected head commit differs. None. Expire preflight and generate a new one against the intended commit.
target-slug-conflict New slug is owned by another Forgejo or State Hub identity/alias. None. Choose another slug or resolve the conflicting ownership outside this operation.
old-slug-unavailable Rollback target is no longer free. No rollback mutation. Continue forward or resolve ownership explicitly; do not steal or delete an alias.
statehub-conflict Old/new slug, alias, or Forge identity resolves to multiple/different UUIDs. None. Repair identity data under a separate reviewed incident; preserve all evidence.
forge-renamed-hub-pending Forge ID has new name but State Hub remains canonical on old slug. Only statehub-rebound or rollback after preflight. Verify ID/commit, then resume rebind with the same operation ID.
hub-rebound-source-pending Hub canonical slug changed but repository projections/source are stale. No duplicate rebind. Resolve through either alias, then run source/projection sync for the recorded revision.
consumer-verification-failed One or more external handoffs lack evidence or health checks fail. Do not complete or clean up. Keep aliases and both checkout facts; repair the named consumer or enter rollback preflight.
edge-writes-pending Relevant outbox envelopes or unknown queued writes exist. Preflight fails closed. Replay/quarantine the outbox, verify idempotency against aliases, then preflight again.
operation-conflict Another active operation targets the same UUID, Forge ID, old slug, or new slug. None. Finish/cancel the owning operation; only one active rename may hold the identity lock.
evidence-drift Counts, checksums, paths, or source revision no longer match accepted evidence. None for the requested phase. Explain the drift and renew preflight; never silently update the accepted baseline.

Retry and rollback limits

  • An idempotency key is the operation UUID plus requested phase. Reuse of the key with different expectations is a conflict.
  • A completed phase cannot be overwritten by a retry. Reconciliation may append observed evidence and an error, but not erase prior evidence.
  • Rollback is a new sequence on the same operation record and must itself pass compare-and-set checks. It never deletes aliases, operation history, events, or historical values.
  • Before forge-renamed, rollback means abandoning an unexpired intent and has no external side effect.
  • After forge-renamed, automatic rollback is possible only while the old Forge slug is provably available and external effects can be reversed.
  • After source or consumer changes, rollback generates explicit handoffs for irreversible or independently owned effects. It must not claim that a Forge and Hub coordinate reversal also reversed packages, deployments, policies, caches, or product/runtime names.

Consequences

  • T02 must add verified Forge identity, protected repository aliases, and an operation journal without changing existing repository UUIDs.
  • T03 must expose UUID-addressed preflight and one-phase mutation APIs with the failure codes above. Slug convenience routes are read/resolution helpers only.
  • T04 and T05 must centralize alias resolution and prove that history, telemetry, work records, and queued-write behavior remain continuous.
  • T06 must generate target-repository workplans that separate repository, product, runtime, consumer, and cleanup decisions.
  • T07 and T08 may orchestrate and rehearse the contract, but the live flex-auth to access-engine operation remains gated by its adopted target workplan.
  • Protected aliases may accumulate. A future alias-retirement policy needs its own evidence and decision; this rename workflow does not delete them.

Rejected alternatives

Alternative Reason rejected
Delete and re-register under the new slug Creates a second repository identity and strands foreign-key history.
Update only managed_repos.slug Leaves routing, files, external projections, retries, and recovery undefined.
Mass-rewrite every old-slug string Destroys historical provenance and cannot safely update independently owned systems.
Use Forgejo redirects indefinitely Redirects do not update canonical remotes, routing, packages, policies, or deployment consumers.
Make repository and product/runtime rename one operation Couples independently risky decisions and makes rollback boundaries unknowable.
Queue live rename phases through the edge relay A disconnected multi-system identity mutation cannot prove current Forge identity, conflicts, or ordering.

References

  • STATE-WP-0085 — lineage-preserving repository rename workflow
  • CUST-ADR-007 — stable workplan identity and registrar authority
  • CUST-ADR-012 — Forge projection, source revision, and rebuildability
  • docs/architecture/state-hub_v0.1.md — State Hub system context