# Spike #7 — BranchStore (git-KV) as the coarse/durable state substrate

> Date: 2026-06-02. Status: **PASS**.
> Throwaway code: `../spt-spikes/spike-07-branchstore` (outside the spt-core repo, not shipped).
> Validates ADR-0011. Design input for `REQ-STORE-1`.

## Question

ADR-0011 adopts a **git branch as a versioned key/value store** (`spt-store::BranchStore`) for coarse/durable/audited state (context, registry snapshot + distribution, daemon checkpoint), while keeping hot-path writes on fsync-append and queried state on SQLite. The boundary rests on one empirical claim: **a commit-per-write is cheap enough for coarse state and far too heavy for the hot path.** Is that true, and does the pattern actually deliver KV + history + atomic multi-key + crash-safe recovery as advertised?

## Method

One Rust binary (pure std) driving the `git` CLI as a BranchStore over a throwaway repo on branch `spt-state`:
- `put(key,val)` = write blob + one commit; `get` = `git show <rev>:<key>`; history = `git rev-list --count`; recovery = read the ref tip cold.
- Bench: `N=300` single-key snapshot commits (2 KB toy registry JSON), timing each commit.
- Correctness: read-back of the tip; one commit per write; an **atomic multi-key** commit (3 keys, verify all-present-at-tip / none-at-parent); a **cold recover** (fresh handle reads tip == last checkpoint).

Latency caveat baked into the method: the CLI is **~2 process spawns per commit**, so the numbers are an **upper bound** — an in-process `gix`/`git2` BranchStore avoids the spawns and is materially faster. The boundary conclusion is therefore conservative.

## Result

```
writes=300  blob=2048B  (Windows, git 2.43, CLI-driven)
commit-per-write (UPPER bound): avg 60.1 ms  p50 55.5  p95 58.0  max 424  → ~17 commits/s
checks: read-back OK · history=301 commits (300 writes + root) · atomic multi-key OK · cold-recover OK
```

All five behaviors held: versioned KV, one-commit-per-write history, atomic multi-key commit (all-or-nothing across the 3 keys), and crash-safe recovery = read the ref tip. The `max 424 ms` outlier is a periodic git/fs hiccup (packing), not the steady state.

## Key finding — the boundary is real with wide margin, even pessimistically

At the **CLI upper bound** of ~60 ms/commit:
- **Coarse/durable state is comfortable.** Context commits (one per commune), registry snapshots (on lifecycle events), and daemon checkpoints occur at ~1–100 writes/s; 60 ms/commit is trivially within budget, and history/diff/time-travel come free.
- **The hot path is ruled out by orders.** Per-keystroke PTY effects / per-message spool churn need thousands of writes/s; 60 ms/commit is ~10000× too slow. Confirms ADR-0011's rule to keep the B5 fsync-append journal and the SQLite spool off BranchStore.

Because the measured figure is a pessimistic CLI bound, an in-process library only **widens** the coarse margin and does not move the hot-path verdict. The boundary is empirically grounded.

## What this does NOT prove

- **Absolute latency of the real impl.** The shipping `BranchStore` will use an in-process git library (`gix` — pure-Rust, no C toolchain, preferred on Windows; or `git2`/libgit2, fabro's choice). Its true per-commit cost is expected to be **sub-ms to low-ms** for small trees — a quick `gix` re-measure would give the real number, but is unnecessary to lock the boundary.
- **Large-tree / deep-history scaling** (thousands of keys, very long history) and `gc`/pack cadence — measure when the registry/context sizes are realistic.
- **Concurrent multi-writer contention** — out of scope by design (single-writer-per-ref, ADR-0011).

## Follow-up — in-process `gix` (the true number)

The deferred re-measure is done: a second crate (`../spt-spikes/spike-07b-gix-branchstore`) implements the same BranchStore **in-process with `gix` 0.84.0** (no CLI, no libgit2) — `write_blob` + tree-editor `upsert` + `commit_as` (parent-CAS) + `rev_parse_single`/`lookup_entry_by_path` + `gix::open` for cold-recover.

```
writes=300  blob=2048B  (Windows, gix 0.84.0, in-process)
commit-per-write: avg 4.7 ms  p50 4.65  p95 5.2  max ~9  → ~125 commits/s
checks: read-back · atomic multi-key · history · cold-recover all OK
```

**~4.7 ms/commit — ~13× faster than the CLI bound (~60 ms),** and that figure already includes a *durable* commit (object writes + ref transaction + fsync) on Windows. Coarse/durable state is now comfortable by an even wider margin; the hot path stays ruled out (4.7 ms × thousands/s is still impossible). The boundary in ADR-0011 holds with room to spare.

**Provenance — produced by the attractor build-harness pilot.** This follow-up crate was written *by* the attractor dark-factory harness (pi → `openai-codex/gpt-5.5`), not by hand: `plan → approve-gate → implement → verify(cargo run, goal-gated) → approve-gate`, with the agent self-correcting its initial `gix` API errors inside the implement node (the explicit fixup node never fired). It is also the first datapoint on attractor as a build harness for spt-core (see the pilot evaluation) — verdict there: viable for bounded, single-machine, deterministically-verifiable tasks, exactly as scoped.

## Verdict

ADR-0011 closes **PASS**. The git-KV pattern delivers versioned KV + free history + atomic multi-key commits + crash-safe `commit=checkpoint / tip=resume`; the coarse-vs-hot boundary holds with wide margin at both the pessimistic CLI bound (~60 ms) and the true in-process number (~4.7 ms). Proceed with `spt-store::BranchStore` (in-process `gix`) as the M4 substrate for context / registry / checkpoint; keep hot paths on fsync-append + SQLite.
