# Repocard — the code-description layer

## Purpose

Describe a git repository perfectly, in two documents, so that another
machine can rebuild exactly what the user sees — and so that a checkout,
rebase, or merge travels the rails as one card, never as thousands of file
events. This layer never syncs, never watches, never decides truth for
anyone else: it captures, it applies, it verifies.

## Axioms

1. **Two documents, one truth.** The *card* is the committed repository:
   HEAD, every local branch, every tag, and a bundle holding the objects.
   The *checkpoint* is everything uncommitted riding on top: the staged
   patch, the unstaged patch, and untracked file contents. Card + checkpoint
   rebuild the user's exact view.
2. **Identity is content-derived.** `cardId` hashes HEAD + branches + tags;
   `checkpointId` hashes base commit + patches + untracked hashes. Equal ids
   are silence — the echo-suppression rule of this layer. Bundle bytes never
   enter identity (git's packing is not byte-deterministic); the bundle
   carries its own checksum so corrupt data is rejected on apply.
3. **Busy is honest.** A repo mid-merge, mid-rebase, mid-cherry-pick holds
   index states no patch can represent. `capture` says `busy` and nothing
   else; the operation's end fires its own doorbell. This is why a rebase is
   one final card instead of a thousand events. Bisect is deliberately NOT
   busy: it is a mode, not an unfinished operation — it can run for hours,
   and every checkout it makes must be captured like any other.
4. **Apply proves itself.** After materializing, the ids are re-derived from
   the result and must equal the ids that arrived. Only then is the state
   "applied". There is no separate notion of verification — reproduction is
   the proof.
5. **Machine-local stays local.** Absolute paths, credentials, reflogs,
   hooks, stashes, and ignored files never travel. Remote URLs ride the card
   as seed metadata (a rebuilt repo should know its origin) but stay outside
   `cardId` — machines may legitimately reach the same origin differently.

## Shape

- `git.js` — the one place this layer runs git. Plain subprocesses, no
  libraries, Buffers in and out.
- `capture.js` — repo → `{ card, checkpoint, cardId, checkpointId, stateId }`
  or `{ busy }`. Reads git, writes nothing.
- `apply.js` — `materialize(dir, card, checkpoint)`: blank directory or
  existing replica in, exact repo state out. Same code for a first apply and
  a checkpoint-only refresh — refs, HEAD, and worktree are forced to the
  card's truth, then the checkpoint lands on top.
- `follow.js` — the follower: raw repo doorbells in, at most one honest
  semantic event out (`card.changed` / `checkpoint.changed` / silence).
- `index.js` — the api: `capture`, `verify`, `apply` (materialize + the
  axiom-4 proof), and `createFollower`.

Fidelity is git-level, not filesystem-level: the executable bit travels,
arbitrary permissions do not; commit timestamps travel (they are committed
history), machine timestamps do not; ignored files never travel.

## The follower

The observer answers "something happened in this repo territory"; the
follower answers "what fact, if any, should travel". Its axiom: **ids
identify settled states — and a settled state is the only completion git
ever announces.** Every change the two documents describe settles as one
of exactly two facts: the committed history moved (cardId) or the work on
top of it moved (checkpointId). History-moving operations — commit,
merge, rebase, checkout — end by moving a ref atomically; worktree
commands — add, restore, clean, an editor saving — end the way any edit
does, leaving files that settle into a new checkpointId. Ids never claim
a *command* finished: an unowned writer that pauses mid-run yields two
true states, not one. Operation boundaries exist only where someone owns
the writer — git's busy markers for its own operations, `command()` for
amalgm's — and no event-driven observer can invent one for a writer
nobody owns. So the follower reads where both ids are once the ground is
still. Card + checkpoint is the whole vocabulary; everything else is
read-safety plumbing for an outside reader git gives no bell, no atomic
glance, and no record of untracked files:

- **The torn-read guard** — a state travels only when two consecutive
  captures agree on its identity: no torn, id-inconsistent snapshot ever
  travels, and ignored churn — which never moves the ids — cannot starve
  detection (`capMs` keeps trying, the pair keeps it honest).
- **Busy markers** — git saying "I am between two cards; there is no
  coherent state to photograph yet"; their removal rings.
- **Locks with a liveness check** — a lock held by a living process is git
  mid-write: wait, and report it (`status().blocked`); its deletion rings.
  An unheld lock is litter from a crash and blocks nothing — proven: a
  stale lock stops neither editors nor `git clean` nor `update-ref`, so
  honoring it would be permanent blindness, not safety. The holder check
  is a single shot when a capture attempt meets a lock — never a poll.
- **`command(fn)` is an optimization, not an authority** — an amalgm-run
  command batches its noise into one capture at its declared end (nesting
  collapses to the outermost end; failure still ends). Correctness never
  depends on it: the ids' movement is the completion signal for the
  commands amalgm never sees.

Equal ids are silence, and `known` advances only on successful delivery —
a failed emit leaves the state undelivered and re-offered. The follower
retries itself whenever it witnessed the miss firsthand (a disagreeing
pair, a failed capture or emit); it waits on the doorbell only for endings
that are filesystem events; and consumer moments (`flush()` — a session
opening, an app regaining focus) are the pull valve that turns a lost
doorbell's "delayed" into "delivered" without polling. Named residual: a
lock-free writer that stalls across both reads looks like stillness — the
next doorbell or flush corrects the ids but cannot unsend the event.
