docs: frame branch->refs migration as read-routing, not mirroring · Entire

docs: frame branch->refs migration as read-routing, not mirroring

8a9196c→main·

Soph·1w ago·1 file·+21 added/-11 removed

The git-refs backend doc originally presented "git-branch as a mirror of git-refs" as the safe rollout (a Parallel state) whose job was downgrade safety. That framing is wrong for this migration:

Rewrite the taxonomy note, config example, rollout table, and Migration section to state the real model (primary flip + read routing + version policy), drop the "v1 mirror push for downgrade safety" limitation, and reframe the ULID-on-branch guard: since git-branch is not a mirror of git-refs, a ULID reaching the branch write path is simply a bug to reject, not a topology-role-aware check. Mirroring remains documented as a general mechanism (e.g. non-git-backed targets), just not part of the branch->refs path.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Changes

1

33 unmodified lines  
34  
35  
36  
37  
37  
38  
39  
40  
103 unmodified lines  
144  
145  
146  
147  
148  
147  
148  
149  
150  
9 unmodified lines  
160  
161  
162  
164  
165  
166  
167  
168  
163  
164  
165  
166  
167  
168  
169  
170  
171  
8 unmodified lines  
180  
181  
182  
183  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
15 unmodified lines  
213  
214  
215  
205  
206  
216

33 unmodified lines

Only a git-backed backend can be the primary, because the lifecycle paths above operate through the repo and its refs; a non-git-backed backend has no such ref to drive them. The two built-in backends — `git-branch` and `git-refs` — are **both** git-backed and are registered directly in the built-in registry map. The `Register()` entry point is for non-git-backed (mirror-only) backends and is used in practice only by test-only backends, so a production binary can never select an unregistered one.

A **one-of-each-type** rule lets two distinct git-backed backends run in the same topology — specifically `git-refs` as primary with `git-branch` as a mirror, which is the safe rollout configuration (see [Rollout](#configuration-and-rollout)).
A **one-of-each-type** rule permits two distinct git-backed backends in the same topology. Note, though, that the branch→refs migration deliberately does **not** run `git-branch` as a mirror of `git-refs`. Cross-format compatibility comes from read routing plus the version policy — every reader (CLI, entire.io, entire-api) reads refs first and falls back to the branch — not from dual-writing the same checkpoint into both backends (see [Migration and coexistence](#migration-and-coexistence)). Mirroring stays available as a general mechanism, primarily for non-git-backed targets (e.g. a filesystem store).

## Ref layout and sharding

103 unmodified lines

```json
{
"checkpoints": {
    "primary": { "type": "git-refs" },
    "mirrors": [ { "type": "git-branch" } ]
    "primary": { "type": "git-refs" }
}
}

9 unmodified lines

Rollout states

State primary mirrors Behavior
Default git-branch — Legacy behavior; v1 branch only
Parallel git-refs [git-branch] Reads are authoritative from refs (bugs surface immediately), while v1 is still written for downgrade safety
Refs-only git-refs — Refs are the sole store
The switch is a primary flip, not a dual-write phase. There is no "run both backends in parallel" step — see Migration and coexistence for why read routing makes it unnecessary.
State primary Behavior
Default (today) git-branch Hex checkpoints on the v1 branch; unchanged legacy behavior
Refs-only git-refs New checkpoints are ULIDs written as per-checkpoint refs; pre-existing hex/v1 checkpoints stay readable via the read-routing fallback

Checkpoint version and policy

8 unmodified lines

Migration and coexistence

The read-routing rules above are what make a hex-on-branch repo and a ULID-in-refs repo the same repo: nothing needs to move for both formats to be readable. When checkpoints are migrated from the branch into refs, they are written under RefName(hexID) — i.e. hex-named refs — which is why a hex ID under a git-refs primary is looked up in refs first and only then falls back to the branch. The read-routing rules above are what make a hex-on-branch repo and a ULID-in-refs repo the same repo: nothing needs to move for both formats to be readable, so the branch→refs switch is a primary flip with no dual-write step.

Concretely, flipping the primary to git-refs means new checkpoints are ULIDs stored as per-checkpoint refs, while every checkpoint already written to the v1 branch stays exactly where it is and keeps resolving through the branch fallback. This works because every reader routes the same way — refs first (for both ID formats), branch fallback for the legacy format — not just the CLI but also entire.io and entire-api. So a repo can move to refs-only on the remote without keeping the v1 branch alive for any reader's benefit.

A mixed fleet is fine and needs no special handling:

This is why running git-branch as a mirror of git-refs is not part of the migration: it would dual-write every checkpoint into both backends to keep v1 populated, but no reader needs that — read routing already covers both formats, and the "old client can't read the new format" case is a feature, not something to paper over.

When checkpoints are actively migrated from the branch into refs (a path that is tooling-only today, not an official flow), they are written under RefName(hexID) — i.e. hex-named refs — which is why a hex ID under a git-refs primary is looked up in refs first and only then falls back to the branch.

Key files

15 unmodified lines