feat(log): v2 is the LIVE write format — envelope records with minted ints, genesis, sector seals; v1 readable forever

The cutover: new tail segments write format v2 (per-record [type, version,
cipherFlag, keyId] envelope; noun/verb after-images carry dense ints
MINTED AT APPEND from the id mapper — a rebuilt mapper reproduces
assignments exactly; log.genesis opens every new log with the id-space
width + a minted brain id; sync() seals to the header-declared sector
boundary with reader-invisible pad frames). Existing v1 segments are
never rewritten — per-segment decoder dispatch reads both formats and v2
facts map to the exact CommitFact shape all consumers already read.
Cutover on a live v1 log: an empty v1 tail re-heads in place; a non-empty
one is sealed by rotation, byte-identical. Records reserve the encryption
fields (cipherFlag 0 / keyId nil are the only legal values; anything else
refuses typed naming the needed newer reader) — crypto-ready with no
future bump on the compat surface. Empty-records facts are legal (an
all-deduped batch is a real generation — v1 semantics preserved; the
refusal there tore a column-store flush mid-commit in the full suite, the
consistency guard caught it loudly, and the root is fixed).

Golden byte vectors pinned for the second (native) reader implementation.
Pins: cutover 5/5 · codec 54 · kill-matrix stays 11/11.
This commit is contained in:
David Snelling 2026-08-10 10:55:11 -07:00
parent 73eb88d481
commit 26c6025158
6 changed files with 1372 additions and 135 deletions

View file

@ -46,7 +46,13 @@ import type {
TxLogEntry
} from './types.js'
import { readLogAuthority } from './logAuthority.js'
import { FactLog, storageSupportsFactLog, type CommitFact, type FactOp } from './factLog.js'
import {
FactLog,
storageSupportsFactLog,
type CommitFact,
type FactOp,
type FactIntMinter
} from './factLog.js'
import { GenerationSegmentStore, type FoldGeneration } from './generationSegments.js'
import { crc32c } from '../utils/crc32c.js'
@ -182,6 +188,22 @@ export class GenerationStore {
this.logDurability = mode
}
/**
* The fact log's v2 int minter injected by the OWNER (brainy wires the
* metadata index's id mapper here right after the index is ready), because
* this store cannot know the mapper. With the minter installed, new fact
* segments write the v2 format and after-image records carry minted dense
* ints reproducible by an id-mapper rebuild. Survives reopen: `open()`
* re-installs it on the fresh {@link FactLog} instance.
*/
private intMinter: FactIntMinter | null = null
/** Install the fact log's v2 int minter (see {@link intMinter}). */
setIntMinter(mint: FactIntMinter): void {
this.intMinter = mint
this.factLog?.setIntMinter(mint)
}
/** Latest reserved/observed generation (≥ {@link committed}). */
private counter = 0
/** Committed-transaction watermark (manifest generation). */
@ -493,6 +515,7 @@ export class GenerationStore {
// hosts no fact log (readers fall back to canonical enumeration).
if (storageSupportsFactLog(this.storage)) {
this.factLog = new FactLog(this.storage)
if (this.intMinter) this.factLog.setIntMinter(this.intMinter)
// LOG-AUTHORITY REPLAY (durable-at-ack's recovery half): when this
// brain's stored authority is the log, an intact fact ABOVE the
// manifest is an ACKED write whose canonical bytes may not have