MEASURED on a real store: the ALL-visibility ledger read 14,231 nouns against
14,056 identity records and 72,729 verbs against 72,679 — exactly that store's
25 noun and 50 verb SCAR directories. Two copies of the same archive derived
different numbers (14,231 and 14,081), because each had been persisted at a
different moment under the old rule that counted one entity per id DIRECTORY.
A downstream index heal subtracted against that denominator and reported
remaining work that did not exist.
The scan already applies the right predicate — one entity per IDENTITY RECORD
(the metadata content leg), shared with pruneOrphanedEntities so the two agree
by construction. What was missing is that a ledger persisted under the old rule
was only FLAGGED suspect and then went on serving its wrong numbers for the
life of the store, waiting for an operator to run repairIndex.
- The ledger now derives itself honestly in the BACKGROUND after the open,
narrating start and finish with the correction it made. Background because
these scalars are denominators — no read is served from them — and because
walks exactly like these are how a 24,898-id store spent minutes of a
restart in silence. Observable via whenCountLedgerSettled(); nothing in the
read path waits on it.
- A derivation that raced a write refuses to stamp its number "exact": one
retry on a quiet store, then the ledger stays SUSPECT and says so, naming
repairIndex as the door that recounts under a barrier.
- The one derivation that CANNOT leave the foreground says why it cannot:
getNounCount()/getVerbCount() are served from it, and a background walk
would make a populated store answer "0 entities" — a wrong answer, not a
slow one. It narrates its start and its wall instead.
- counts.json is written temp+rename. A truncating write left a window —
measured at roughly 750ms after a flush or close — in which a concurrent
reader saw the file EMPTY; an unparseable ledger sends the next open down
the full-rescan path, so the cheapest file in the store was buying the most
expensive recovery.
- The writer lock's clean-close record is now consulted before the
same-process branch too: a restart reported "Re-acquiring writer lock ...
this is a bug" immediately after a clean close, sending an operator after a
leak that did not exist.
Pins: tests/integration/count-ledger-identity-record.test.ts (background
correction with scar and ghost fixtures, two copies of one archive agreeing,
counts.json never observed unparseable across 40 persists);
tests/integration/ledger-derivation-identity.test.ts updated to the new law —
the OPEN still never walks (proved by slowing the walk 1.2s and timing the
open), and the ledger heals behind it.