CODE-048: a seal inside one rev mints a duplicate rev and a chain hole

Now
OPEN
Sev
HIGH
Rep
///bee
Ask
gritzko
Rev
///bee#8cb69a0
See
BEE-061
See
LITE-044

Input

Context

index/index.js:1185:Wt re-put guard tests t.blob === bhl && t.commit === chl, but index/index.js:442:Wt idxWriter.put auto-commits every ix.mem rows (256 on the incremental open), so a seal can fall between one rev's BLOB and CMMT put. A run killed there leaves top.commit null; the next run re-derives the same (path, blob, commit), the guard misses, and a SECOND rev is minted for it. Confirmed end-to-end on a fixture: BLOB revs 0,1,2,3, CMMT revs 0,1,3 — a permanent hole at rev 2. index/index.js:525:Wt lastRev assumes a dense chain and stops at the hole, so view/list.js:52:cn's dir fuse freezes on a stale commit and loadDir mints duplicates on top. Found in the 2026-09-18 index review.

Goals

Repro: plant one torn BLOB row, bring up, show the duplicate rev and the hole. Guard on the blob plus an explicit revValAt(phl, t.rev, K_CMMT) probe, and write one rev's rows under a single batch slot so a seal never splits a rev.

Constraints

No index format change; the sealed-prefix contract (LITE-006:55:2d) stays.

WIP

Design decisions

A torn rev is finished, not re-minted: the CMMT row rules.

  1. idxWriter grew open/close, a hold count the auto-seal respects; emit writes one rev's rows between the two, so a seal lands BETWEEN revs and can no longer split one.
  2. The re-put guard keeps its cheap top test and adds an exact revValAt(phl, t.rev, K_CMMT) probe when that top carries the blob but no commit: a row on disk means the rev is whole, a miss means an older seal split it.
  3. Such a rev is FINISHED at its own rev — the CMMT, PARS and B2P rows the dead run never reached — not a second rev one up. BLOB and FSEG are NOT re-put: a seal persists a prefix (LITE-006:55:2d), so a BLOB row on disk proves FSEG landed.
  4. emitDir needs none of it: a dir rev IS one row, already atomic w.r.t. the batch.

TODOs

Blockers and bummers

Outcome