feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/ * *
* @module db / factLog
* @description The generation FACT LOG — an append - only , CRC - framed record of
* every committed generation as an AFTER - IMAGE "fact" : what each touched
* entity / relationship BECAME ( or a body - less tombstone when it was removed ) .
* This is the dual - write half of the log - canonical transition : today the
* before - image history + canonical tree remain authoritative ; the fact log is
* appended at the same commit points and reconciled to committed truth at
* open , so consumers ( index heals , replays , scans ) can read one sequential ,
* self - verifying stream instead of walking the entity tree .
*
* # # Wire format ( frozen ; additive - only within a major )
*
* Fact ( msgpack , POSITIONAL array — the segment header ' s formatVersion
* governs the schema ) :
*
* fact : = [ generation :u64 , timestamp :u64 , ops , meta | nil , blobHashes | nil ]
* op : = [ kind :u8 ( 0 = noun , 1 = verb ) , id :bin16 ( raw uuid bytes ) ,
* record : [ metaLeg , vecLeg ] | nil ] // nil = TOMBSTONE
*
* Segment file ( ` _generations/facts/seg-<firstGeneration, zero-padded 20>.bfl ` ) :
*
* header : = magic "BFACTS\0\0" ( 8 B ) | formatVersion :u32 LE |
* firstGeneration :u64 LE | reserved 12 B ( ZEROED , verified )
* frame : = length :u32 LE | crc32c :u32 LE ( of payload ) | payload
*
* A fact is never split across segments ; a torn tail ( length overrun or CRC
* mismatch ) terminates that segment ' s scan — everything before it is intact .
* Zero - padded names make lexicographic order == generation order .
*
* # # Invariant
*
* After { @link FactLog . open } , the log contains EXACTLY the committed prefix :
* facts are appended BEFORE the commit point ( inside the same durability
* window ) , so a crash can only leave the log AHEAD of committed truth — open
* truncates any fact beyond the committed generation . Absent generation =
* never committed ; present = committed . A scan can never see an uncommitted
* fact .
*
* The manifest ( ` _generations/facts/manifest.json ` , JSON — forensics stay
* terminal - readable ) is the single source of truth for the segment SET ;
* rotation flips it atomically ( write - new → fsync → rename ) BEFORE the new
* tail ' s first byte exists , so no segment file is ever unaccounted for .
2026-08-10 10:55:11 -07:00
*
* # # Mixed - version logs ( the v2 live - write cutover )
*
* The segment header ' s ` formatVersion ` selects the decoder PER SEGMENT :
* v1 segments ( ops - shaped facts , the format above ) stay readable forever and
* are NEVER rewritten ; a NEW tail segment writes the v2 format
* ( ` src/db/factLogFormat.ts ` — record envelope , minted dense ints , genesis ,
* sector seals ) whenever the int minter is installed ( { @link FactLog . setIntMinter } —
* the brain wires it from the metadata index ' s id mapper right after init ) .
* A bare ` FactLog ` with no minter keeps writing v1 ( there is no authority
* that could reproduce int assignments , and 0 is never written ) . Cutover
* mechanics on an existing v1 log : an EMPTY v1 tail is re - headed to v2 in
* place ; a non - empty v1 tail is sealed by an immediate rotation and the new
* tail is v2 . Decoded v2 facts map back to the SAME { @link CommitFact } shape
* v1 consumers read ( noun / verb ops with ` {metadata, vector} | null ` records ) —
* the vector wrapper object is reconstructed from the record ' s metadata leg
* through the reserved - field hydration law ( see ` commitFactFromV2 ` ) .
*
* V2 tails additionally : write the ` log.genesis ` record ( id - space width 64 +
* the brain id , minted once into the manifest ' s additive ` brainId ` field ) as
* the first record of the FIRST fact of a brand - new log , and seal every
* ` sync() ` to the header - declared sector size with pad frames that are
* invisible to readers ( torn - page defense at group - commit boundaries ) .
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
* /
import { encode as defaultEncode , decode as defaultDecode } from '@msgpack/msgpack'
import { crc32c } from '../utils/crc32c.js'
import { prodLog } from '../utils/logger.js'
2026-08-10 10:55:11 -07:00
import {
FACT_LOG_FORMAT_V1 ,
FACT_LOG_FORMAT_V2 ,
DEFAULT_SEAL_SIZE ,
parseSegmentHeader ,
encodeSegmentHeaderV2 ,
encodeFactV2 ,
decodeFact as decodeFormatFact ,
decodeGroupV2 ,
encodePadFrame ,
minPadFrameBytes ,
type CommitFactV2 ,
type LogRecord ,
type EmbedPendingRecord ,
type EmbedLandedRecord ,
type BlobManifestRecord ,
type BootstrapBaselineRecord ,
type ProjectionNoteRecord
} from './factLogFormat.js'
import {
splitNounMetadataRecord
} from '../types/reservedFields.js'
import { NounType } from '../types/graphTypes.js'
import { v4 as uuidv4 } from '../universal/uuid.js'
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
// Swappable msgpack implementation — defaults to the JS codec; a native
// provider (registered via the plugin registry's 'msgpack' key) may replace
// it. Byte-compatibility is the contract (positional arrays, bin16 ids).
let msgpackEncode : ( value : unknown ) = > Uint8Array = defaultEncode
let msgpackDecode : ( bytes : Uint8Array ) = > unknown = defaultDecode
/** Replace the msgpack encode/decode implementation at runtime. */
export function setFactCodec ( impl : {
encode : ( value : unknown ) = > Uint8Array
decode : ( bytes : Uint8Array ) = > unknown
} ) : void {
msgpackEncode = impl . encode
msgpackDecode = impl . decode
}
/** Storage-root-relative home of the fact log. */
export const FACTS_PREFIX = '_generations/facts'
/** The facts manifest path (JSON). */
export const FACTS_MANIFEST_PATH = ` ${ FACTS_PREFIX } /manifest.json `
2026-08-10 10:55:11 -07:00
/ * *
* The v1 segment format version — the MANIFEST ' s formatVersion gate and the
* header value of v1 ( minter - less ) tails . NOT the live - write ceiling : new
* tails write ` FACT_LOG_FORMAT_V2 ` ( src / db / factLogFormat . ts ) whenever the
* int minter is installed ; both versions are read forever , per segment .
* /
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
export const FACTS_FORMAT_VERSION = 1
/** Rotation threshold: seal the tail segment once it exceeds this many bytes. */
const SEGMENT_ROTATE_BYTES = 8 * 1024 * 1024
/** Segment header: magic(8) + formatVersion(4) + firstGeneration(8) + reserved(12). */
const HEADER_BYTES = 32
const MAGIC = new Uint8Array ( [ 0x42 , 0x46 , 0x41 , 0x43 , 0x54 , 0x53 , 0x00 , 0x00 ] ) // "BFACTS\0\0"
/** Frame prefix: length(4) + crc32c(4). */
const FRAME_PREFIX_BYTES = 8
/** One write inside a fact: what the id became (or a tombstone). */
export interface FactOp {
kind : 'noun' | 'verb'
id : string
/** The AFTER-IMAGE legs, or `null` for a tombstone (the id was removed). */
record : { metadata : unknown | null ; vector : unknown | null } | null
}
2026-08-10 10:55:11 -07:00
/ * *
* V2 - native records beyond noun / verb ops that a fact may carry through the
* ENCODER ( types 6 / 7 / 8 / 9 / 10 of the v2 registry : embed markers , blob
* manifests , projection notes , bootstrap baselines ) . Encoder - ready by
* design ; nothing produces them yet — the deferred - embed sidecar and blob
* lifecycle remodel onto these records in a later leg .
* /
export type FactMarkerRecord =
| EmbedPendingRecord
| EmbedLandedRecord
| BlobManifestRecord
| ProjectionNoteRecord
| BootstrapBaselineRecord
/ * *
* Mints the dense integer handle for an entity / verb id at fact - append time —
* REQUIRED to be reproducible : a rebuilt id mapper must reproduce the same
* assignments exactly , so the only legal implementation delegates to the
* metadata index ' s id mapper ( ` getOrAssign ` ) . Returns a POSITIVE bigint ; a
* minter that cannot resolve its mapper throws — an int of 0 is never written .
* /
export type FactIntMinter = ( kind : 'noun' | 'verb' , id : string ) = > bigint
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/** One committed generation, as scanned back out of the log. */
export interface CommitFact {
generation : number
timestamp : number
ops : FactOp [ ]
meta? : Record < string , unknown >
blobHashes? : string [ ]
2026-08-10 10:55:11 -07:00
/ * *
* V2 - native marker records riding this fact ( see { @link FactMarkerRecord } ) .
* Optional and additive : absent on every v1 fact and on every fact the
* current writers produce ; requires a v2 tail to encode .
* /
records? : FactMarkerRecord [ ]
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
/** The telemetry a scan batch carries (frozen shape). */
export interface FactScanBatch {
facts : CommitFact [ ]
firstGeneration : number
lastGeneration : number
factCount : number
byteSize : number
segmentId : string
}
2026-07-19 14:54:36 -07:00
/ * *
* Liveness bound on a scan ' s FIRST batch ( Stage - 2 co - freeze , D1 contract ) :
* ` batches() ` must yield its first batch — or fail loudly — within this many
* ms of the first pull . A backlogged or damaged store may be SLOW , but it may
* never be SILENT : a consumer awaiting the first batch is otherwise
* indistinguishable from a wedge ( the exact failure shape a production heal
* hit against a generations - backlogged brain ) .
* /
export const SCANFACTS_FIRST_BATCH_MS = 10 _000
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/** The telemetry a scan OPEN returns (frozen shape). */
export interface FactScanHandle {
headGeneration : number
segmentCount : number
approxFactCount : number
2026-07-19 14:54:36 -07:00
/ * *
* Ordered batches ; a detected gap aborts LOUDLY , never a silent skip .
* Liveness contract : the FIRST batch resolves or rejects within
* { @link SCANFACTS_FIRST_BATCH_MS } of the first pull — never a silent hang .
* /
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
batches : ( ) = > AsyncGenerator < FactScanBatch >
/** Close telemetry — the invariant cross-check, valid after iteration ends. */
summary : ( ) = > { factsYielded : number ; segmentsRead : number }
}
/** Manifest entry for a sealed segment. */
interface SegmentEntry {
file : string
firstGeneration : number
lastGeneration : number
facts : number
bytes : number
}
/** The facts manifest (JSON on disk). */
interface FactsManifest {
formatVersion : number
segments : SegmentEntry [ ]
/** The append target. Its true content is established by scanning (crash tolerance). */
tailSegment : string | null
updatedAt : string
2026-08-10 10:55:11 -07:00
/ * *
* This brain ' s stable id ( additive , v2 cutover ) : minted as a uuid at the
* first v2 tail creation and never changed ; the ` log.genesis ` record
* carries it . Absent on logs that have never had a v2 tail .
* /
brainId? : string
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
/** The narrow byte-level storage surface the fact log rides. */
export interface FactLogStorage {
appendRawBytes ( path : string , bytes : Uint8Array ) : Promise < void >
readRawBytes ( path : string ) : Promise < Uint8Array | null >
writeRawBytes ( path : string , bytes : Uint8Array ) : Promise < void >
rawByteSize ( path : string ) : Promise < number | null >
readRawObject ( path : string ) : Promise < any | null >
writeRawObject ( path : string , data : any ) : Promise < void >
syncRawObjects ( paths : string [ ] ) : Promise < void >
deleteRawObject ( path : string ) : Promise < void >
}
/** True when the storage adapter exposes every primitive the fact log needs. */
export function storageSupportsFactLog ( storage : unknown ) : storage is FactLogStorage {
const s = storage as Record < string , unknown >
return (
typeof s . appendRawBytes === 'function' &&
typeof s . readRawBytes === 'function' &&
typeof s . writeRawBytes === 'function' &&
typeof s . rawByteSize === 'function'
)
}
/** uuid string → 16 raw bytes (bin16 on the wire). */
function uuidToBytes ( id : string ) : Uint8Array {
const hex = id . replace ( /-/g , '' )
if ( hex . length !== 32 ) {
// Non-uuid ids (legacy/natural keys) ride as UTF-8 with a length prefix
// marker impossible for uuids: we refuse instead — the write API has
// guaranteed uuid ids since 8.0, so anything else is a corruption signal.
throw new Error ( ` fact log: id is not a uuid: ${ id } ` )
}
const bytes = new Uint8Array ( 16 )
for ( let i = 0 ; i < 16 ; i ++ ) {
bytes [ i ] = parseInt ( hex . slice ( i * 2 , i * 2 + 2 ) , 16 )
}
return bytes
}
/** 16 raw bytes → canonical lowercase uuid string. */
function bytesToUuid ( bytes : Uint8Array ) : string {
let hex = ''
for ( let i = 0 ; i < 16 ; i ++ ) hex += bytes [ i ] . toString ( 16 ) . padStart ( 2 , '0' )
return ` ${ hex . slice ( 0 , 8 ) } - ${ hex . slice ( 8 , 12 ) } - ${ hex . slice ( 12 , 16 ) } - ${ hex . slice ( 16 , 20 ) } - ${ hex . slice ( 20 ) } `
}
/** Zero-padded segment filename: lexicographic order == generation order. */
function segmentFileName ( firstGeneration : number ) : string {
return ` seg- ${ String ( firstGeneration ) . padStart ( 20 , '0' ) } .bfl `
}
/** Build a segment header. Reserved bytes are ZEROED (and verified on open). */
function buildHeader ( firstGeneration : number ) : Uint8Array {
const header = new Uint8Array ( HEADER_BYTES )
header . set ( MAGIC , 0 )
const view = new DataView ( header . buffer )
view . setUint32 ( 8 , FACTS_FORMAT_VERSION , true )
view . setBigUint64 ( 12 , BigInt ( firstGeneration ) , true )
// bytes 20..31 stay zero (reserved)
return header
}
/** Encode one fact into a framed record (length + crc32c + msgpack payload). */
function encodeFrame ( fact : CommitFact ) : Uint8Array {
const payload = msgpackEncode ( [
fact . generation ,
fact . timestamp ,
fact . ops . map ( ( op ) = > [
op . kind === 'noun' ? 0 : 1 ,
uuidToBytes ( op . id ) ,
op . record === null ? null : [ op . record . metadata , op . record . vector ]
] ) ,
fact . meta ? ? null ,
fact . blobHashes && fact . blobHashes . length > 0 ? fact.blobHashes : null
] )
const frame = new Uint8Array ( FRAME_PREFIX_BYTES + payload . length )
const view = new DataView ( frame . buffer )
view . setUint32 ( 0 , payload . length , true )
view . setUint32 ( 4 , crc32c ( payload ) , true )
frame . set ( payload , FRAME_PREFIX_BYTES )
return frame
}
/** Decode one msgpack payload back into a CommitFact. */
function decodeFact ( payload : Uint8Array ) : CommitFact {
const raw = msgpackDecode ( payload ) as unknown [ ]
const [ generation , timestamp , ops , meta , blobHashes ] = raw as [
number ,
number ,
Array < [ number , Uint8Array , [ unknown , unknown ] | null ] > ,
Record < string , unknown > | null ,
string [ ] | null
]
return {
generation : Number ( generation ) ,
timestamp : Number ( timestamp ) ,
ops : ops.map ( ( [ kind , idBytes , record ] ) = > ( {
kind : kind === 0 ? ( 'noun' as const ) : ( 'verb' as const ) ,
id : bytesToUuid ( idBytes ) ,
record : record === null ? null : { metadata : record [ 0 ] ? ? null , vector : record [ 1 ] ? ? null }
} ) ) ,
. . . ( meta ? { meta } : { } ) ,
. . . ( blobHashes && blobHashes . length > 0 ? { blobHashes } : { } )
}
}
2026-08-10 10:55:11 -07:00
/ * *
* Deep - normalize a decoded v2 JSON position ( metadata legs , meta maps ,
* notes ) back to plain - JSON values : the v2 codec decodes msgpack int64 / uint64
* as ` bigint ` ( its u64 wire discipline ) , but canonical records are JSON — a
* metadata timestamp like ` createdAt: 1786… ` must come back as the NUMBER it
* was encoded from . Safe - range bigints narrow exactly ; anything beyond the
* safe - integer range in a JSON position refuses loudly ( it cannot have come
* from a JSON write ) .
* /
function normalizeWireJson ( value : unknown ) : unknown {
if ( typeof value === 'bigint' ) {
if ( value > BigInt ( Number . MAX_SAFE_INTEGER ) || value < - BigInt ( Number . MAX_SAFE_INTEGER ) ) {
throw new Error (
` fact log v2: decoded integer ${ value } exceeds the JS safe-integer range in a JSON position `
)
}
return Number ( value )
}
if ( Array . isArray ( value ) ) return value . map ( normalizeWireJson )
if ( value && typeof value === 'object' && ! ( value instanceof Uint8Array ) ) {
const out : Record < string , unknown > = { }
for ( const [ k , v ] of Object . entries ( value ) ) out [ k ] = normalizeWireJson ( v )
return out
}
return value
}
/ * *
* JSON - serialization equivalence for a v2 ENCODE - side JSON position : drop
* undefined - valued object keys and map undefined array elements to null —
* exactly what ` JSON.stringify ` does when canonical records are persisted .
* Commit facts are built from write - cache - WARM objects that may still carry
* undefined - valued engine keys ( ` service: undefined ` , … ) which the durable
* JSON never had ; msgpack would preserve them as nil ( the v1 capture ' s known
* wart ) , so the v2 capture — the future storage authority — sanitizes to the
* DURABLE truth instead .
* /
function toJsonSafe ( value : unknown ) : unknown {
if ( value === undefined ) return null
if ( Array . isArray ( value ) ) return value . map ( ( v ) = > ( v === undefined ? null : toJsonSafe ( v ) ) )
if ( value && typeof value === 'object' && ! ( value instanceof Uint8Array ) ) {
const out : Record < string , unknown > = { }
for ( const [ k , v ] of Object . entries ( value ) ) {
if ( v === undefined ) continue
out [ k ] = toJsonSafe ( v )
}
return out
}
return value
}
/ * * M i r r o r o f t h e s t o r a g e l a y e r ' s s t o r e d - t i m e s t a m p n o r m a l i z a t i o n , m i n u s i t s
* ` Date.now() ` fallback ( a DECODER must be deterministic — an unreadable
* timestamp is omitted , and the divergence surfaces via the oracle ) . * /
function reconstructTimestamp ( value : unknown ) : number | undefined {
if ( typeof value === 'number' && value > 0 ) return value
if (
value !== null &&
typeof value === 'object' &&
typeof ( value as { seconds? : unknown } ) . seconds === 'number'
) {
return ( value as { seconds : number } ) . seconds * 1000
}
return undefined
}
/ * *
* Rebuild a noun ' s canonical VECTOR - FILE wrapper from a v2 after - image —
* the read - side of the hydration law . Canonical noun vector files hold the
* denormalized enumerable entity ( ` {id, vector, connections, level, type,
* … reserved fields … , metadata } ` — the write path's composition); the v2
* record deliberately carries only the ENTITY state ( metadata leg + embedding
* floats ) , because connections / level are derived HNSW residue with their own
* rebuild paths ( empty in every 8 . x write ) and the denormalized top - level
* fields are projections of the metadata leg . This reconstruction applies
* the SAME split / hydrate law the storage layer uses
* ( ` splitNounMetadataRecord ` — the single source of truth in
* src / types / reservedFields . ts ; field map mirrors
* ` BaseStorage.hydrateNounWithMetadata ` , undefined keys omitted exactly as
* JSON serialization omits them ) , so in the no - drift case the reconstructed
* wrapper digests byte - equal to canonical . A drifted denormalized copy
* surfaces as an oracle ` state-differs ` — named , never silently absorbed .
* /
function reconstructNounWrapper (
id : string ,
metadataLeg : unknown ,
floats : number [ ]
) : Record < string , unknown > {
const { reserved , custom } = splitNounMetadataRecord (
( metadataLeg ? ? null ) as Record < string , unknown > | null
)
const wrapper : Record < string , unknown > = {
id ,
vector : floats ,
connections : { } ,
level : 0 ,
type : ( reserved . noun as string ) || NounType . Thing
}
if ( reserved . subtype !== undefined ) wrapper . subtype = reserved . subtype
if ( reserved . visibility !== undefined ) wrapper . visibility = reserved . visibility
const createdAt = reconstructTimestamp ( reserved . createdAt )
if ( createdAt !== undefined ) wrapper . createdAt = createdAt
const updatedAt = reconstructTimestamp ( reserved . updatedAt )
if ( updatedAt !== undefined ) wrapper . updatedAt = updatedAt
if ( reserved . confidence !== undefined ) wrapper . confidence = reserved . confidence
if ( reserved . weight !== undefined ) wrapper . weight = reserved . weight
if ( reserved . service !== undefined ) wrapper . service = reserved . service
if ( reserved . data !== undefined ) wrapper . data = reserved . data
if ( reserved . createdBy !== undefined ) wrapper . createdBy = reserved . createdBy
wrapper . _rev = typeof reserved . _rev === 'number' ? reserved._rev : 1
wrapper . metadata = custom
return wrapper
}
/ * * C o e r c e a c a n d i d a t e e m b e d d i n g t o ` n u m b e r [ ] ` : p l a i n a r r a y s p a s s t h r o u g h
* ( element - checked ) ; numeric typed arrays ( the JS HNSW rebuild path stores
* ` Float32Array ` vectors on the memory adapter ) widen via ` Array.from ` . * /
function floatsOf ( candidate : unknown , context : string ) : number [ ] | undefined {
if ( Array . isArray ( candidate ) ) {
for ( const el of candidate ) {
if ( typeof el !== 'number' ) {
throw new Error ( ` fact log v2: ${ context } vector carries a non-number element ` )
}
}
return candidate as number [ ]
}
if ( ArrayBuffer . isView ( candidate ) && ! ( candidate instanceof DataView ) ) {
return Array . from ( candidate as unknown as ArrayLike < number > )
}
return undefined
}
/ * * E x t r a c t t h e e m b e d d i n g f l o a t a r r a y f r o m a c a n o n i c a l v e c t o r v a l u e : a b a r e
* float array ( or numeric typed array ) passes through ; a wrapper object
* yields its ` vector ` floats ; ` null ` stays ` null ` ; anything else refuses
* loudly . * /
function embeddingLegOf ( value : unknown , context : string ) : number [ ] | null {
if ( value === null || value === undefined ) return null
const direct = floatsOf ( value , context )
if ( direct !== undefined ) return direct
if ( typeof value === 'object' ) {
const nested = floatsOf ( ( value as { vector? : unknown } ) . vector , context )
if ( nested !== undefined ) return nested
}
throw new Error (
` fact log v2: ${ context } has a canonical vector record with no float vector — ` +
` cannot encode its after-image `
)
}
/ * *
* Map one decoded v2 fact to the { @link CommitFact } shape every consumer
* already reads : noun / verb after - images and tombstones become ops ( vector
* wrappers reconstructed — see { @link reconstructNounWrapper } ) ; a
* ` batch.meta ` record becomes ` meta ` when the fact position carries none ;
* ` log.genesis ` is log - level metadata ( its width was verified at decode ) and
* is not an op ; marker records surface on the additive ` records ` field so
* nothing is silently dropped . Decoded JSON positions are normalized back
* from the codec ' s bigint discipline ( { @link normalizeWireJson } ) .
* /
function commitFactFromV2 ( f : CommitFactV2 ) : CommitFact {
const ops : FactOp [ ] = [ ]
const markers : FactMarkerRecord [ ] = [ ]
let batchMeta : Record < string , unknown > | undefined
for ( const r of f . records ) {
switch ( r . type ) {
case 'noun.afterImage' : {
const metadata = normalizeWireJson ( r . metadata ) ? ? null
let vector : unknown | null = null
if ( r . vectorLeg !== null ) {
if ( ! Array . isArray ( r . vectorLeg ) ) {
throw new Error (
` fact log v2: noun.afterImage ${ r . id } carries a vector ref — this reader ` +
` resolves inline vectors only (refs are a later leg); refusing `
)
}
vector = reconstructNounWrapper ( r . id , metadata , r . vectorLeg )
}
ops . push ( { kind : 'noun' , id : r.id , record : { metadata , vector } } )
break
}
case 'noun.tombstone' :
ops . push ( { kind : 'noun' , id : r.id , record : null } )
break
case 'verb.afterImage' : {
const metadata = normalizeWireJson ( r . metadata ) ? ? null
if ( r . vectorLeg !== null && ! Array . isArray ( r . vectorLeg ) ) {
throw new Error (
` fact log v2: verb.afterImage ${ r . id } carries a vector ref — this reader ` +
` resolves inline vectors only (refs are a later leg); refusing `
)
}
// The canonical verb vector-file wrapper: endpoints + verb name ride
// as first-class v2 wire fields precisely so this reconstruction is
// exact ({id, vector, connections:{}, verb, sourceId, targetId} —
// verbs carry no `level`).
const vector : Record < string , unknown > = {
id : r.id ,
vector : r.vectorLeg ? ? [ ] ,
connections : { } ,
verb : r.verb ,
sourceId : r.sourceId ,
targetId : r.targetId
}
ops . push ( { kind : 'verb' , id : r.id , record : { metadata , vector } } )
break
}
case 'verb.tombstone' :
ops . push ( { kind : 'verb' , id : r.id , record : null } )
break
case 'batch.meta' :
batchMeta = normalizeWireJson ( r . meta ) as Record < string , unknown >
break
case 'log.genesis' :
break // the log's birth certificate — log-level metadata, not an op
case 'projection.note' :
markers . push ( { . . . r , note : normalizeWireJson ( r . note ) as Record < string , unknown > } )
break
case 'bootstrap.baseline' :
markers . push ( { . . . r , metadata : normalizeWireJson ( r . metadata ) } )
break
default :
// embed.pending / embed.landed / blob.manifest carry no loose JSON maps.
markers . push ( r )
break
}
}
const meta = f . meta ? ( normalizeWireJson ( f . meta ) as Record < string , unknown > ) : batchMeta
return {
generation : f.generation ,
timestamp : f.timestamp ,
ops ,
. . . ( meta ? { meta } : { } ) ,
. . . ( f . blobHashes && f . blobHashes . length > 0 ? { blobHashes : f.blobHashes } : { } ) ,
. . . ( markers . length > 0 ? { records : markers } : { } )
}
}
/** One intact v2 frame's extent inside a segment (byte-slicing support). */
interface V2FrameExtent {
/** Byte offset just past this frame. */
end : number
/** The frame's generation (0 for pad filler). */
generation : number
/** True when the frame is a pad (invisible filler). */
isPad : boolean
}
/ * * W a l k a v 2 s e g m e n t ' s i n t a c t f r a m e s ( t o r n - t a i l t e r m i n a t e d ) , r e t u r n i n g e a c h
* frame ' s extent — the byte - level view truncation slices against , so kept
* frames are never re - encoded ( byte - immutability of CRC - covered frames ) . * /
function walkV2Frames ( bytes : Uint8Array ) : V2FrameExtent [ ] {
const view = new DataView ( bytes . buffer , bytes . byteOffset , bytes . byteLength )
const extents : V2FrameExtent [ ] = [ ]
let offset = HEADER_BYTES
while ( offset + FRAME_PREFIX_BYTES <= bytes . length ) {
const length = view . getUint32 ( offset , true )
const expectedCrc = view . getUint32 ( offset + 4 , true )
const start = offset + FRAME_PREFIX_BYTES
const end = start + length
if ( end > bytes . length ) break // torn tail
const payload = bytes . subarray ( start , end )
if ( crc32c ( payload ) !== expectedCrc ) break // torn tail
const fact = decodeFormatFact ( payload , FACT_LOG_FORMAT_V2 , {
expectedIdSpaceWidth : 64
} ) as CommitFactV2
extents . push ( { end , generation : fact.generation , isPad : fact.records.length === 0 } )
offset = end
}
return extents
}
/ * *
* The byte offset a v2 segment is cut at to keep exactly the facts with
* ` generation ≤ keepThrough ` : the end of the last kept FACT frame ( pads
* between kept facts sit inside the retained span ; pads after the cut are
* dropped and re - sealed at the next sync ) . When nothing is dropped the cut
* lands after the last intact frame — trailing pads retained , only a torn
* suffix ( if any ) removed .
* /
function v2CutOffset ( extents : V2FrameExtent [ ] , keepThrough : number ) : number {
let cut = HEADER_BYTES
let lastIntactEnd = HEADER_BYTES
for ( const e of extents ) {
lastIntactEnd = e . end
if ( e . isPad ) continue
if ( e . generation <= keepThrough ) {
cut = e . end
} else {
return cut // first beyond-keep fact: everything from here (pads included) goes
}
}
return lastIntactEnd
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/ * *
* Parse a segment ' s bytes : verify the header , then walk frames until the end
* or a torn tail ( length overrun / CRC mismatch ) , which terminates the walk —
2026-08-10 10:55:11 -07:00
* everything before it is intact . The header ' s formatVersion selects the
* decoder : the v1 walk below is byte - identical to the original v1 reader ;
* v2 segments decode through the reference codec ( ` decodeGroupV2 ` , pads
* invisible , id - space width verified at 64 — a disagreeing genesis throws
* the codec ' s typed ` GenesisWidthMismatchError ` ) . Returns the decoded facts
* plus the byte length of the VALID prefix ( header + intact frames ) , which
* reconciliation uses to cut a torn tail without re - encoding .
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
* /
function parseSegment (
file : string ,
bytes : Uint8Array
2026-08-10 10:55:11 -07:00
) : { facts : CommitFact [ ] ; validBytes : number ; formatVersion : number ; sealSize? : number } {
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
if ( bytes . length < HEADER_BYTES ) {
prodLog . warn ( ` [FactLog] segment ${ file } shorter than its header — treating as empty ` )
2026-08-10 10:55:11 -07:00
return { facts : [ ] , validBytes : 0 , formatVersion : 0 }
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
2026-08-10 10:55:11 -07:00
let header : { formatVersion : number ; sealSize? : number }
try {
header = parseSegmentHeader ( bytes . subarray ( 0 , HEADER_BYTES ) )
} catch ( err ) {
throw new Error ( ` fact log: segment ${ file } : ${ ( err as Error ) . message } ` )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
2026-08-10 10:55:11 -07:00
if ( header . formatVersion === FACT_LOG_FORMAT_V2 ) {
const group = decodeGroupV2 ( bytes . subarray ( HEADER_BYTES ) , { expectedIdSpaceWidth : 64 } )
return {
facts : group.facts.map ( commitFactFromV2 ) ,
validBytes : HEADER_BYTES + group . validBytes ,
formatVersion : FACT_LOG_FORMAT_V2 ,
sealSize : header.sealSize
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
}
2026-08-10 10:55:11 -07:00
// v1 walk — byte-identical to the original reader.
const view = new DataView ( bytes . buffer , bytes . byteOffset , bytes . byteLength )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
const facts : CommitFact [ ] = [ ]
let offset = HEADER_BYTES
while ( offset + FRAME_PREFIX_BYTES <= bytes . length ) {
const length = view . getUint32 ( offset , true )
const expectedCrc = view . getUint32 ( offset + 4 , true )
const start = offset + FRAME_PREFIX_BYTES
const end = start + length
if ( end > bytes . length ) break // torn tail: frame length overruns the file
const payload = bytes . subarray ( start , end )
if ( crc32c ( payload ) !== expectedCrc ) break // torn tail: payload CRC mismatch
facts . push ( decodeFact ( payload ) )
offset = end
}
2026-08-10 10:55:11 -07:00
return { facts , validBytes : offset , formatVersion : FACT_LOG_FORMAT_V1 }
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
/ * *
* The generation fact log . One instance per open store ; every method assumes
* the single - writer discipline the generation store already enforces ( calls
* arrive under its commit mutex ) .
* /
export class FactLog {
private readonly storage : FactLogStorage
2026-07-15 12:44:52 -07:00
/** Rotation threshold (bytes); tests may lower it to exercise rotation. */
private readonly rotateBytes : number
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
private manifest : FactsManifest = {
formatVersion : FACTS_FORMAT_VERSION ,
segments : [ ] ,
tailSegment : null ,
updatedAt : new Date ( 0 ) . toISOString ( )
}
/** Decoded facts of the TAIL segment (bounded by the rotation threshold). */
private tailFacts : CommitFact [ ] = [ ]
2026-08-10 10:55:11 -07:00
/ * * B y t e s i z e o f t h e t a i l s e g m e n t f i l e ( v a l i d p r e f i x , p a d s i n c l u d e d —
* pads count toward bytes but NEVER toward facts ) . * /
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
private tailBytes = 0
/** Highest generation in the log (0 = empty). */
private head = 0
/** Segment paths appended since the last sync (the fsync batch). */
private readonly dirtySegments = new Set < string > ( )
2026-08-10 10:55:11 -07:00
/** The TAIL segment's on-disk format version (selects the live encoder). */
private tailVersion : number = FACT_LOG_FORMAT_V1
/** The tail's sector-seal size (v2 tails; from its header on reopen). */
private tailSealSize : number = DEFAULT_SEAL_SIZE
/** The v2 int minter (see {@link FactIntMinter}); null = v1 live writes. */
private intMinter : FactIntMinter | null = null
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
2026-07-15 12:44:52 -07:00
constructor ( storage : FactLogStorage , options ? : { rotateBytes? : number } ) {
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
this . storage = storage
2026-07-15 12:44:52 -07:00
this . rotateBytes = options ? . rotateBytes ? ? SEGMENT_ROTATE_BYTES
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
2026-08-10 10:55:11 -07:00
/ * *
* Install the v2 int minter — the capability gate for v2 LIVE WRITES .
* With a minter installed , every NEW tail segment writes the v2 format and
* after - image records carry minted dense ints ; without one , live writes
* stay v1 ( no authority could reproduce int assignments , and 0 is never
* written ) . The brain wires this from the metadata index ' s id mapper right
* after the index is ready ; an existing v1 tail cuts over on the next
* append ( empty tail : re - headed in place ; non - empty : sealed by rotation ) .
* /
setIntMinter ( mint : FactIntMinter ) : void {
this . intMinter = mint
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/** The highest committed generation the log holds (0 = empty). */
headGeneration ( ) : number {
return this . head
}
/ * *
* Open the log and reconcile it to committed truth : read the manifest ,
* establish the tail ' s intact content ( torn - tail scan ) , then TRUNCATE any
* fact with ` generation > committedGeneration ` — those never committed ( a
* crash between fact - append and the commit point ) . After open , the log is
* exactly the committed prefix .
* /
fix(log): acked writes survive power loss; rejected writes never silently commit — the kill-matrix goes 11/11 with zero .fails debt
Two release-blocking findings from the durability kill-matrix, both fixed
in the owning layer:
1. LOG-AUTHORITY REPLAY AT OPEN: durable-at-ack fsynced the fact before
the ack, but open() truncated every fact above the manifest — after a
power loss that takes the un-fsynced tmp+rename canonical bytes, the
acked write's ONLY durable copy was discarded. Now: under 'log'
authority, open() REPLAYS intact facts above the manifest into
canonical (FactLog.peekFactsAbove — CRC-gated, order-sorted) and
advances the manifest to cover them; tree-authority brains keep the
truncate contract they were promised. Pinned end to end: the power-loss
row constructs the exact disk state (fsynced log, vanished canonical
rename) and the acked write lives.
2. NO SILENT COMMIT: commitSingleOp buffered the generation BEFORE the
fact append; an append failure (ENOSPC) rejected the caller but the
next flush durably committed the generation with NO fact — a permanent
silent log gap. Now the failure path un-buffers and returns the counter
reservation: nothing commits, the log stays gap-free, and the canonical
execute-residue orphan is the documented crash-equivalent.
Plus: the kill-matrix itself (11 rows — every commit-path fault point ×
reopen-as-crash recovery contract, at-ack variants, disk-full row; five
new zero-cost faultPoint sites), the log-authority pin suite (oracle
green/red/state-differs, flip refusal, switch survives reopen, 9/9), and
the group-commit covering pins (5/5).
Gates: unit 2002/2002 (152 files) · integration 785 · conformance 27/27.
2026-08-10 09:29:21 -07:00
/ * *
* Read ( without truncating ) every intact fact ABOVE a generation — the
* log - authority recovery surface : after a crash , facts beyond the
* manifest watermark that survived with valid CRCs are ACKED writes in
* durable - at - ack mode , and the owner REPLAYS them instead of letting
* open ( ) truncate them . Must be called BEFORE open ( ) ( it reads the raw
* segments directly ; the torn tail ' s invalid suffix is ignored exactly
* like open ( ) would ) .
* /
async peekFactsAbove ( committedGeneration : number ) : Promise < CommitFact [ ] > {
const stored = ( await this . storage . readRawObject ( FACTS_MANIFEST_PATH ) ) as FactsManifest | null
if ( ! stored || typeof stored !== 'object' || ! Array . isArray ( stored . segments ) ) return [ ]
if ( stored . formatVersion !== FACTS_FORMAT_VERSION ) return [ ]
const out : CommitFact [ ] = [ ]
const files = [ . . . stored . segments . map ( ( s ) = > s . file ) ]
if ( stored . tailSegment ) files . push ( stored . tailSegment )
for ( const file of files ) {
const bytes = await this . storage . readRawBytes ( ` ${ FACTS_PREFIX } / ${ file } ` )
if ( bytes === null ) continue
const { facts } = parseSegment ( file , bytes )
for ( const f of facts ) {
if ( f . generation > committedGeneration ) out . push ( f )
}
}
out . sort ( ( a , b ) = > a . generation - b . generation )
return out
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
async open ( committedGeneration : number ) : Promise < void > {
const stored = ( await this . storage . readRawObject ( FACTS_MANIFEST_PATH ) ) as FactsManifest | null
if ( stored && typeof stored === 'object' && Array . isArray ( stored . segments ) ) {
if ( stored . formatVersion !== FACTS_FORMAT_VERSION ) {
throw new Error (
` fact log: manifest formatVersion ${ stored . formatVersion } ; this build reads ${ FACTS_FORMAT_VERSION } `
)
}
this . manifest = stored
}
// Drop sealed segments that sit ENTIRELY beyond committed truth (a crash
// right after a rotation whose facts never committed), newest first.
while ( this . manifest . segments . length > 0 ) {
const last = this . manifest . segments [ this . manifest . segments . length - 1 ]
if ( last . firstGeneration > committedGeneration ) {
prodLog . warn (
` [FactLog] dropping sealed segment ${ last . file } (generations ${ last . firstGeneration } .. ` +
` ${ last . lastGeneration } never committed) `
)
await this . storage . deleteRawObject ( ` ${ FACTS_PREFIX } / ${ last . file } ` )
this . manifest . segments . pop ( )
await this . persistManifest ( )
} else if ( last . lastGeneration > committedGeneration ) {
// A sealed segment STRADDLING committed truth: cut it back.
await this . truncateSegmentTo ( last . file , committedGeneration )
const cut = await this . reloadSegmentEntry ( last . file )
this . manifest . segments [ this . manifest . segments . length - 1 ] = cut
await this . persistManifest ( )
break
} else {
break
}
}
// Establish the tail: scan its intact prefix, then truncate beyond
// committed truth (the common crash shape: buffered single-op facts whose
// counter never went durable).
if ( this . manifest . tailSegment ) {
const tailPath = ` ${ FACTS_PREFIX } / ${ this . manifest . tailSegment } `
const bytes = await this . storage . readRawBytes ( tailPath )
if ( bytes === null ) {
2026-08-10 10:55:11 -07:00
// Manifest named a tail whose first byte never landed — an empty
// tail. Its header (and format version) is established at the next
// append (see the tail-provisioning ladder there).
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
this . tailFacts = [ ]
this . tailBytes = 0
} else {
2026-08-10 10:55:11 -07:00
const parsed = parseSegment ( this . manifest . tailSegment , bytes )
const { facts , validBytes } = parsed
this . tailVersion =
parsed . formatVersion === FACT_LOG_FORMAT_V2 ? FACT_LOG_FORMAT_V2 : FACT_LOG_FORMAT_V1
this . tailSealSize = parsed . sealSize ? ? DEFAULT_SEAL_SIZE
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
const kept = facts . filter ( ( f ) = > f . generation <= committedGeneration )
if ( kept . length !== facts . length || validBytes !== bytes . length ) {
const dropped = facts . length - kept . length
if ( dropped > 0 ) {
prodLog . warn (
` [FactLog] truncating ${ dropped } uncommitted fact(s) beyond generation ` +
` ${ committedGeneration } from the tail (never committed) `
)
}
2026-08-10 10:55:11 -07:00
if ( this . tailVersion === FACT_LOG_FORMAT_V2 ) {
// V2: byte-slice at frame boundaries — CRC-covered frames are
// byte-immutable; a truncation never re-encodes what it keeps.
const cut = v2CutOffset ( walkV2Frames ( bytes ) , committedGeneration )
await this . storage . writeRawBytes ( tailPath , bytes . subarray ( 0 , cut ) )
this . tailFacts = kept
this . tailBytes = cut
} else {
await this . rewriteTail ( kept )
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
} else {
this . tailFacts = facts
this . tailBytes = validBytes
}
}
}
this . head = this . computeHead ( )
}
/ * *
* Append one committed generation ' s fact . NOT durable until { @link sync } —
* the caller batches durability at its commit barrier ( transact syncs in
* the same call ; Model - B group - commit syncs at flush ) .
2026-08-10 10:55:11 -07:00
*
* Tail provisioning ( in order ) : a missing tail starts one ; a named tail
* whose header never landed ( manifest - first crash ) gets its header now ; an
* existing V1 tail cuts over to v2 once the minter is installed ( empty :
* re - headed in place , non - empty : sealed by rotation — v1 segments are never
* rewritten ) ; a full tail rotates . The frame then encodes in the TAIL ' s
* format : v2 tails carry after - image records with minted ints ( and the
* genesis record on the very first fact of a brand - new log ) ; v1 tails keep
* the v1 wire format byte - identically .
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
* /
async append ( fact : CommitFact ) : Promise < void > {
if ( fact . generation <= this . head ) {
throw new Error (
` fact log: non-monotonic append (generation ${ fact . generation } ≤ head ${ this . head } ) `
)
}
if ( this . manifest . tailSegment === null ) {
await this . startTail ( fact . generation )
2026-08-10 10:55:11 -07:00
} else if ( this . tailBytes === 0 ) {
await this . reinitializeTailHeader ( )
} else if ( this . intMinter !== null && this . tailVersion === FACT_LOG_FORMAT_V1 ) {
if ( this . tailFacts . length === 0 && this . tailBytes <= HEADER_BYTES ) {
await this . upgradeEmptyTailToV2 ( )
} else {
await this . rotate ( fact . generation )
}
2026-07-15 12:44:52 -07:00
} else if ( this . tailBytes >= this . rotateBytes ) {
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
await this . rotate ( fact . generation )
}
2026-08-10 10:55:11 -07:00
let frame : Uint8Array
if ( this . tailVersion === FACT_LOG_FORMAT_V2 ) {
const records = this . buildV2Records ( fact )
if ( this . needsGenesis ( ) ) {
if ( this . ensureBrainId ( ) ) await this . persistManifest ( )
records . unshift ( this . genesisRecord ( ) )
}
frame = encodeFactV2 ( {
generation : fact.generation ,
timestamp : fact.timestamp ,
records ,
. . . ( fact . meta ? { meta : toJsonSafe ( fact . meta ) as Record < string , unknown > } : { } ) ,
. . . ( fact . blobHashes && fact . blobHashes . length > 0 ? { blobHashes : fact.blobHashes } : { } )
} )
} else {
if ( fact . records && fact . records . length > 0 ) {
throw new Error (
` fact log: marker records ( ${ fact . records . map ( ( r ) = > r . type ) . join ( ', ' ) } ) require a ` +
` v2 tail segment — this log's tail is v1 (no int minter installed); refusing rather ` +
` than silently dropping them `
)
}
frame = encodeFrame ( fact )
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
const tailPath = ` ${ FACTS_PREFIX } / ${ this . manifest . tailSegment } `
await this . storage . appendRawBytes ( tailPath , frame )
this . tailFacts . push ( fact )
this . tailBytes += frame . length
this . head = fact . generation
this . dirtySegments . add ( tailPath )
}
2026-08-10 10:55:11 -07:00
/ * *
* Fsync every segment appended since the last sync . SEALS AT SYNC : a v2
* tail is first padded to its sector - seal boundary ( one pad frame ,
* invisible to readers ; a gap smaller than the smallest constructible pad
* frame pads through one extra sector — the codec ' s rule ) , so every
* durability barrier leaves the tail sector - aligned : a torn page can only
* tear INSIDE the group being written , never a previously - sealed one .
* /
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
async sync ( ) : Promise < void > {
2026-08-10 10:55:11 -07:00
await this . padTailToSealBoundary ( )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
if ( this . dirtySegments . size === 0 ) return
const paths = [ . . . this . dirtySegments ]
this . dirtySegments . clear ( )
await this . storage . syncRawObjects ( paths )
}
feat(log): the guarded log-authority core — group-commit durable-at-ack, the per-brain switch, the verification oracle
The storage-authority adoption path, guarded shape: the canonical tree
stays authoritative by default ('tree'); a brain flips to 'log' only
through the verification oracle, and the flip is stored, per-brain,
checked at open only.
- FactLog.ensureSynced(): classic group commit — concurrent writers
append, then join ONE covering fsync (running + queued slots give the
covering guarantee: the sync a caller awaits always starts after its
append landed). Solo writer = immediate sync.
- GenerationStore.logDurability 'deferred' (default, byte-identical to
today: fact durability rides the group-commit flush, ack latency
unchanged) | 'at-ack' (log-authority mode: every single-op ack awaits a
covering log fsync — an acked write's fact survives power loss, by
contract). transact() was already durable-at-return in both modes.
- src/db/logAuthority.ts: the stored switch artifact
(_system/log-authority.json, absent = tree), readLogAuthority, and the
VERIFICATION ORACLE — replay the fact log, fold latest state per id
(digests, never bodies — memory-bounded), diff against the canonical
tree paged; verdict green iff every canonical row is exactly reproduced
AND the log claims nothing canonical denies. Divergences are NAMED by
class (pre-log-record → needs baseline backfill; state-differs;
log-live-canonical-absent; log-tombstone-canonical-present). The flip
REFUSES on red with the first divergence and the cure in the message.
- Brainy: authority read at open (log → durable-at-ack enabled);
logAuthority() / verifyLogAuthority() / adoptLogAuthority() public API.
Nothing flips by itself; nothing changes for existing brains.
2026-08-06 10:08:18 -07:00
// --- GROUP COMMIT ON THE LOG (durable-at-ack mode) ------------------------
// Classic group commit: concurrent writers append, then join ONE fsync
// whose completion releases every covered ack. Two slots — the running
// sync and at most one queued behind it — give the covering guarantee:
// an append followed by ensureSynced() is always covered, because the
// sync it awaits STARTS after the append landed (a running sync that
// may have snapshotted earlier is never joined; the queued one is).
private syncRunning : Promise < void > | null = null
private syncQueued : Promise < void > | null = null
/ * *
* Await a sync that covers every byte appended before this call . Many
* concurrent callers share one fsync ( solo caller = immediate sync ) . The
* durability contract of an acked write in log - durable mode : this promise
* resolving means the caller ' s frames survive power loss .
* /
async ensureSynced ( ) : Promise < void > {
if ( this . syncQueued ) {
// A sync that has NOT started yet exists — it will snapshot after our
// append, so it covers us.
return this . syncQueued
}
if ( this . syncRunning ) {
// The running sync may have snapshotted before our append — queue the
// next one behind it and join that.
const queued = this . syncRunning
. catch ( ( ) = > { } )
. then ( ( ) = > {
// Promote: the queued sync becomes the running one.
this . syncQueued = null
this . syncRunning = this . sync ( ) . finally ( ( ) = > {
this . syncRunning = null
} )
return this . syncRunning
} )
this . syncQueued = queued
return queued
}
this . syncRunning = this . sync ( ) . finally ( ( ) = > {
this . syncRunning = null
} )
return this . syncRunning
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/ * *
* Open a scan over committed facts . The scan runs against a MANIFEST
* SNAPSHOT ( sealed segments + the tail ' s decoded facts at open ) — exactly -
* once per fact , inclusive bounds , stable under concurrent appends . Gaps
* abort LOUDLY : a missing generation inside a segment ' s declared range is
* corruption , never silently skipped .
* /
scanFacts ( options ? : {
fromGeneration? : number
toGeneration? : number
kinds? : Array < 'noun' | 'verb' >
batchSize? : number
2026-07-19 14:54:36 -07:00
/** Test override for the first-batch liveness bound (default {@link SCANFACTS_FIRST_BATCH_MS}). */
firstBatchTimeoutMs? : number
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
} ) : FactScanHandle {
const from = options ? . fromGeneration ? ? 1
const to = options ? . toGeneration ? ? this . head
const kinds = options ? . kinds
const batchSize = Math . max ( 1 , options ? . batchSize ? ? 256 )
// Snapshot: the segment list + tail content as of NOW.
const segments = this . manifest . segments . filter (
( s ) = > s . lastGeneration >= from && s . firstGeneration <= to
)
const tailSnapshot = this . tailFacts . filter ( ( f ) = > f . generation >= from && f . generation <= to )
const tailId = this . manifest . tailSegment ? ? 'tail'
const approxFactCount =
segments . reduce ( ( sum , s ) = > sum + s . facts , 0 ) + tailSnapshot . length
let factsYielded = 0
let segmentsRead = 0
const storage = this . storage
async function * batches ( this : void ) : AsyncGenerator < FactScanBatch > {
let expectedNext = 0 // gap detection: generations are monotonic, not necessarily dense
const emit = ( facts : CommitFact [ ] , segmentId : string , byteSize : number ) : FactScanBatch = > ( {
facts ,
firstGeneration : facts [ 0 ] . generation ,
lastGeneration : facts [ facts . length - 1 ] . generation ,
factCount : facts.length ,
byteSize ,
segmentId
} )
const filterOps = ( fact : CommitFact ) : CommitFact = >
kinds
? { . . . fact , ops : fact.ops.filter ( ( op ) = > kinds . includes ( op . kind ) ) }
: fact
for ( const entry of segments ) {
const bytes = await storage . readRawBytes ( ` ${ FACTS_PREFIX } / ${ entry . file } ` )
if ( bytes === null ) {
throw new Error (
` fact log: sealed segment ${ entry . file } is MISSING — the log is damaged; aborting scan `
)
}
const { facts } = parseSegment ( entry . file , bytes )
segmentsRead ++
const inRange = facts . filter ( ( f ) = > f . generation >= from && f . generation <= to )
for ( const f of inRange ) {
if ( f . generation <= expectedNext - 1 ) {
throw new Error ( ` fact log: out-of-order fact ${ f . generation } in ${ entry . file } — aborting scan ` )
}
expectedNext = f . generation + 1
}
for ( let i = 0 ; i < inRange . length ; i += batchSize ) {
const slice = inRange . slice ( i , i + batchSize ) . map ( filterOps )
if ( slice . length === 0 ) continue
factsYielded += slice . length
yield emit ( slice , entry . file , slice . reduce ( ( n , f ) = > n + encodeFrame ( f ) . length , 0 ) )
}
}
if ( tailSnapshot . length > 0 ) {
segmentsRead ++
for ( const f of tailSnapshot ) {
if ( f . generation <= expectedNext - 1 ) {
throw new Error ( ` fact log: out-of-order fact ${ f . generation } in the tail — aborting scan ` )
}
expectedNext = f . generation + 1
}
for ( let i = 0 ; i < tailSnapshot . length ; i += batchSize ) {
const slice = tailSnapshot . slice ( i , i + batchSize ) . map ( filterOps )
factsYielded += slice . length
yield emit ( slice , tailId , slice . reduce ( ( n , f ) = > n + encodeFrame ( f ) . length , 0 ) )
}
}
}
2026-07-19 14:54:36 -07:00
// Liveness wrapper: the FIRST pull races the contract deadline. Only the
// first — the bound is time-to-first-batch (proof the producer is alive),
// not per-batch pacing; and it runs only while a pull is actually pending,
// so consumer think-time between pulls never counts against the producer.
const firstBatchTimeoutMs = options ? . firstBatchTimeoutMs ? ? SCANFACTS_FIRST_BATCH_MS
async function * batchesWithLiveness ( this : void ) : AsyncGenerator < FactScanBatch > {
const inner = batches ( )
let timer : NodeJS.Timeout | undefined
try {
const deadline = new Promise < never > ( ( _ , reject ) = > {
timer = setTimeout (
( ) = >
reject (
new Error (
` fact log: scanFacts produced no first batch within ${ firstBatchTimeoutMs } ms ` +
` (liveness contract) — the store is wedged or unreadably slow; aborting scan LOUDLY ` +
` instead of hanging the consumer. `
)
) ,
firstBatchTimeoutMs
)
timer . unref ? . ( )
} )
const first = await Promise . race ( [ inner . next ( ) , deadline ] )
if ( first . done ) return
yield first . value
} finally {
clearTimeout ( timer )
}
yield * inner
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
return {
headGeneration : this.head ,
segmentCount : segments.length + ( tailSnapshot . length > 0 ? 1 : 0 ) ,
approxFactCount ,
2026-07-19 14:54:36 -07:00
batches : batchesWithLiveness ,
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
summary : ( ) = > ( { factsYielded , segmentsRead } )
}
}
/ * *
* The mmap fast path ( capability handoff ) : the immutable sealed segment
* files covering ` fromGeneration ` , in order . The TAIL is deliberately NOT
* included — it is append - mutable ; consumers read it via { @link scanFacts } .
* /
segmentPaths ( options ? : { fromGeneration? : number } ) : string [ ] {
const from = options ? . fromGeneration ? ? 1
return this . manifest . segments
. filter ( ( s ) = > s . lastGeneration >= from )
. map ( ( s ) = > ` ${ FACTS_PREFIX } / ${ s . file } ` )
}
/ * *
* Drop every fact with ` generation > keepThrough ` — the in - session abort
* compensation : a transact appends its fact BEFORE the commit point , so a
* real ( non - crash ) abort after the append must take the fact back out . The
* dropped facts can only live in the TAIL ( they were just appended ) ; the
* rewrite is atomic and bounded by the rotation threshold .
* /
async dropAbove ( keepThrough : number ) : Promise < void > {
if ( this . head <= keepThrough ) return
const kept = this . tailFacts . filter ( ( f ) = > f . generation <= keepThrough )
if ( kept . length === this . tailFacts . length ) {
throw new Error (
` fact log: dropAbove( ${ keepThrough } ) found no droppable facts in the tail ` +
` (head ${ this . head } ) — the fact to drop was already sealed; the log needs reopen `
)
}
2026-08-10 10:55:11 -07:00
if ( this . tailVersion === FACT_LOG_FORMAT_V2 ) {
// V2: byte-slice at frame boundaries (kept frames stay byte-identical;
// pads between kept facts are retained inside the prefix, trailing pads
// go and the next sync re-seals). The dropped frames may be unsynced —
// readRawBytes is read-after-write coherent over the append path.
const file = this . manifest . tailSegment
if ( ! file ) return
const tailPath = ` ${ FACTS_PREFIX } / ${ file } `
const bytes = await this . storage . readRawBytes ( tailPath )
if ( bytes === null ) {
throw new Error ( ` fact log: dropAbove( ${ keepThrough } ) cannot read the tail segment ${ file } ` )
}
const cut = v2CutOffset ( walkV2Frames ( bytes ) , keepThrough )
await this . storage . writeRawBytes ( tailPath , bytes . subarray ( 0 , cut ) )
this . tailFacts = kept
this . tailBytes = cut
} else {
await this . rewriteTail ( kept )
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
this . head = this . computeHead ( )
}
// -- internals -------------------------------------------------------------
private computeHead ( ) : number {
if ( this . tailFacts . length > 0 ) return this . tailFacts [ this . tailFacts . length - 1 ] . generation
const sealed = this . manifest . segments
if ( sealed . length > 0 ) return sealed [ sealed . length - 1 ] . lastGeneration
return 0
}
2026-08-10 10:55:11 -07:00
/** The header bytes for a NEW tail: v2 whenever the minter is installed. */
private newTailHeader ( firstGeneration : number ) : Uint8Array {
return this . intMinter !== null
? encodeSegmentHeaderV2 ( firstGeneration , DEFAULT_SEAL_SIZE )
: buildHeader ( firstGeneration )
}
/** Record the just-created tail's format in memory (mirrors its header). */
private noteFreshTail ( ) : void {
this . tailVersion = this . intMinter !== null ? FACT_LOG_FORMAT_V2 : FACT_LOG_FORMAT_V1
this . tailSealSize = DEFAULT_SEAL_SIZE
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
/** Create the very first tail segment (manifest-first, then header bytes). */
private async startTail ( firstGeneration : number ) : Promise < void > {
const file = segmentFileName ( firstGeneration )
this . manifest . tailSegment = file
2026-08-10 10:55:11 -07:00
if ( this . intMinter !== null ) this . ensureBrainId ( )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
await this . persistManifest ( )
2026-08-10 10:55:11 -07:00
await this . storage . appendRawBytes ( ` ${ FACTS_PREFIX } / ${ file } ` , this . newTailHeader ( firstGeneration ) )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
this . tailFacts = [ ]
this . tailBytes = HEADER_BYTES
2026-08-10 10:55:11 -07:00
this . noteFreshTail ( )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
/ * *
* Seal the tail into the manifest and start a new one . Manifest - first : the
* flip both seals the old tail AND names the new one atomically , so no
2026-08-10 10:55:11 -07:00
* segment file ever exists unaccounted for . The NEW tail ' s format follows
* the minter gate ( { @link newTailHeader } ) — this is also the v1 → v2 cutover
* seam for a non - empty v1 tail ( sealed as - is , never rewritten ) .
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
* /
private async rotate ( nextGeneration : number ) : Promise < void > {
const sealedFile = this . manifest . tailSegment
if ( ! sealedFile ) return
2026-08-10 10:55:11 -07:00
// Seal what the tail actually holds (sync() also sector-seals a v2 tail).
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
await this . sync ( ) // sealed segments are always fully durable
const entry : SegmentEntry = {
file : sealedFile ,
firstGeneration : this.tailFacts [ 0 ] ? . generation ? ? nextGeneration ,
lastGeneration : this.tailFacts [ this . tailFacts . length - 1 ] ? . generation ? ? nextGeneration - 1 ,
facts : this.tailFacts.length ,
bytes : this.tailBytes
}
const newFile = segmentFileName ( nextGeneration )
this . manifest . segments . push ( entry )
this . manifest . tailSegment = newFile
2026-08-10 10:55:11 -07:00
if ( this . intMinter !== null ) this . ensureBrainId ( )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
await this . persistManifest ( )
2026-08-10 10:55:11 -07:00
await this . storage . appendRawBytes ( ` ${ FACTS_PREFIX } / ${ newFile } ` , this . newTailHeader ( nextGeneration ) )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
this . tailFacts = [ ]
this . tailBytes = HEADER_BYTES
2026-08-10 10:55:11 -07:00
this . noteFreshTail ( )
}
/ * *
* The v1 → v2 cutover for an EMPTY v1 tail : re - head it in place ( nothing but
* the 32 - byte header exists , so no v1 frame is ever rewritten ) . Also the
* cheapest cutover shape : brand - new brains whose first tail predates the
* minter installation converge here on their first post - install append .
* /
private async upgradeEmptyTailToV2 ( ) : Promise < void > {
const file = this . manifest . tailSegment
if ( ! file ) return
if ( this . ensureBrainId ( ) ) await this . persistManifest ( )
const first = this . segmentFirstGenerationFromName ( file )
const path = ` ${ FACTS_PREFIX } / ${ file } `
await this . storage . writeRawBytes ( path , encodeSegmentHeaderV2 ( first , DEFAULT_SEAL_SIZE ) )
this . tailBytes = HEADER_BYTES
this . tailVersion = FACT_LOG_FORMAT_V2
this . tailSealSize = DEFAULT_SEAL_SIZE
this . dirtySegments . add ( path )
}
/ * *
* A manifest - named tail whose header never landed ( crash between the
* manifest flip and the first header byte — previously this appended
* frames into a headerless file the next open could not parse ) : write the
* header now , in the CURRENT format gate .
* /
private async reinitializeTailHeader ( ) : Promise < void > {
const file = this . manifest . tailSegment
if ( ! file ) return
if ( this . intMinter !== null && this . ensureBrainId ( ) ) await this . persistManifest ( )
const first = this . segmentFirstGenerationFromName ( file )
const path = ` ${ FACTS_PREFIX } / ${ file } `
await this . storage . writeRawBytes ( path , this . newTailHeader ( first ) )
this . tailBytes = HEADER_BYTES
this . noteFreshTail ( )
this . dirtySegments . add ( path )
}
/ * * T r u e w h e n t h e N E X T a p p e n d e d f a c t i s t h e f i r s t f a c t o f a b r a n d - n e w v 2
* log — the one that must open with the log . genesis record . * /
private needsGenesis ( ) : boolean {
return (
this . tailVersion === FACT_LOG_FORMAT_V2 &&
this . manifest . segments . length === 0 &&
this . tailFacts . length === 0
)
}
/** Mint the brain id into the manifest if absent; true when it changed. */
private ensureBrainId ( ) : boolean {
if ( this . manifest . brainId ) return false
this . manifest . brainId = uuidv4 ( )
return true
}
/ * * T h e l o g ' s b i r t h c e r t i f i c a t e ( i d - s p a c e w i d t h 6 4 — t h e o n l y w i d t h t h i s
* writer mints ; a reader expecting another width refuses at decode ) . * /
private genesisRecord ( ) : LogRecord {
const brainId = this . manifest . brainId
if ( ! brainId ) {
throw new Error (
'fact log v2: genesis requires a brainId in the facts manifest — invariant violated'
)
}
return { type : 'log.genesis' , idSpaceWidth : 64 , brainId , createdAt : Date.now ( ) }
}
/ * *
* Convert one CommitFact ' s ops ( + optional marker records ) to v2 wire
* records , MINTING ints at append time : entity / verb ints come from the
* injected minter ( the metadata index ' s id mapper — the one authority a
* rebuild reproduces exactly ) . Verb endpoints and the verb name ride as
* first - class wire fields , lifted from the canonical verb vector wrapper .
* Every refusal here is loud — an after - image without a mintable int , a
* verb without endpoints , or a vector record without floats fails the
* WRITE , never writes a 0 .
* /
private buildV2Records ( fact : CommitFact ) : LogRecord [ ] {
const mint = ( kind : 'noun' | 'verb' , id : string ) : bigint = > {
if ( this . intMinter === null ) {
throw new Error (
` fact log v2: no int minter is installed — cannot mint the ${ kind } int for ${ id } ; ` +
` refusing to write a v2 after-image (an int of 0 is never written) `
)
}
const minted = this . intMinter ( kind , id )
if ( typeof minted !== 'bigint' || minted <= 0 n ) {
throw new Error (
` fact log v2: the int minter returned ${ String ( minted ) } for ${ kind } ${ id } — ` +
` minted ints are positive bigints; refusing to write `
)
}
return minted
}
const records : LogRecord [ ] = [ ]
for ( const op of fact . ops ) {
if ( op . kind === 'noun' ) {
if ( op . record === null ) {
records . push ( { type : 'noun.tombstone' , id : op.id } )
continue
}
records . push ( {
type : 'noun.afterImage' ,
id : op.id ,
entityInt : mint ( 'noun' , op . id ) ,
metadata : toJsonSafe ( op . record . metadata ? ? null ) ,
vectorLeg : embeddingLegOf ( op . record . vector , ` noun ${ op . id } ` )
} )
} else {
if ( op . record === null ) {
records . push ( { type : 'verb.tombstone' , id : op.id } )
continue
}
const wrapper = op . record . vector as Record < string , unknown > | null
const verbName = wrapper ? . verb
const sourceId = wrapper ? . sourceId
const targetId = wrapper ? . targetId
if (
typeof verbName !== 'string' ||
typeof sourceId !== 'string' ||
typeof targetId !== 'string'
) {
throw new Error (
` fact log v2: verb ${ op . id } has no canonical endpoints (verb/sourceId/targetId ` +
` live in its vector record, which is missing or torn) — refusing to write an ` +
` after-image that could not be replayed `
)
}
const floats = floatsOf ( wrapper ? . vector , ` verb ${ op . id } ` ) ? ? [ ]
records . push ( {
type : 'verb.afterImage' ,
id : op.id ,
verbInt : mint ( 'verb' , op . id ) ,
metadata : toJsonSafe ( op . record . metadata ? ? null ) ,
vectorLeg : floats ,
verb : verbName ,
sourceId ,
sourceInt : mint ( 'noun' , sourceId ) ,
targetId ,
targetInt : mint ( 'noun' , targetId )
} )
}
}
for ( const marker of fact . records ? ? [ ] ) records . push ( marker )
return records
}
/ * *
* Pad a v2 tail to its next sector - seal boundary with ONE pad frame —
* called from { @link sync } so alignment holds at every durability barrier .
* Pads count toward { @link tailBytes } but never toward facts ( they are
* invisible to every reader ) ; a gap smaller than the smallest constructible
* pad frame pads through one extra sector ( the codec ' s rule ) . No - op for v1
* tails , empty tails , and already - aligned tails .
* /
private async padTailToSealBoundary ( ) : Promise < void > {
if ( this . tailVersion !== FACT_LOG_FORMAT_V2 ) return
const file = this . manifest . tailSegment
if ( ! file || this . tailBytes <= HEADER_BYTES ) return
const remainder = this . tailBytes % this . tailSealSize
if ( remainder === 0 ) return
let padBytes = this . tailSealSize - remainder
if ( padBytes < minPadFrameBytes ( ) ) padBytes += this . tailSealSize
const tailPath = ` ${ FACTS_PREFIX } / ${ file } `
await this . storage . appendRawBytes ( tailPath , encodePadFrame ( padBytes ) )
this . tailBytes += padBytes
this . dirtySegments . add ( tailPath )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
}
/** Atomically persist the manifest (write-new → fsync → rename downstream). */
private async persistManifest ( ) : Promise < void > {
this . manifest . updatedAt = new Date ( ) . toISOString ( )
await this . storage . writeRawObject ( FACTS_MANIFEST_PATH , this . manifest )
await this . storage . syncRawObjects ( [ FACTS_MANIFEST_PATH ] )
}
/** Rewrite the tail segment to hold exactly `facts` (atomic replace). */
private async rewriteTail ( facts : CommitFact [ ] ) : Promise < void > {
const file = this . manifest . tailSegment
if ( ! file ) return
const first = facts [ 0 ] ? . generation ? ? this . segmentFirstGenerationFromName ( file )
const parts : Uint8Array [ ] = [ buildHeader ( first ) ]
for ( const f of facts ) parts . push ( encodeFrame ( f ) )
const total = parts . reduce ( ( n , p ) = > n + p . length , 0 )
const merged = new Uint8Array ( total )
let offset = 0
for ( const p of parts ) {
merged . set ( p , offset )
offset += p . length
}
await this . storage . writeRawBytes ( ` ${ FACTS_PREFIX } / ${ file } ` , merged )
this . tailFacts = facts
this . tailBytes = total
}
2026-08-10 10:55:11 -07:00
/ * * C u t a S E A L E D s e g m e n t b a c k t o ` c o m m i t t e d G e n e r a t i o n ` ( a t o m i c r e p l a c e ) .
* v2 segments byte - slice at frame boundaries ( kept frames — pads
* included — are never re - encoded ) ; the v1 re - encode path is unchanged . * /
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
private async truncateSegmentTo ( file : string , committedGeneration : number ) : Promise < void > {
const path = ` ${ FACTS_PREFIX } / ${ file } `
const bytes = await this . storage . readRawBytes ( path )
if ( bytes === null ) return
2026-08-10 10:55:11 -07:00
const { facts , formatVersion } = parseSegment ( file , bytes )
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
const kept = facts . filter ( ( f ) = > f . generation <= committedGeneration )
prodLog . warn (
` [FactLog] truncating sealed segment ${ file } to generation ${ committedGeneration } ` +
` ( ${ facts . length - kept . length } uncommitted fact(s) dropped) `
)
2026-08-10 10:55:11 -07:00
if ( formatVersion === FACT_LOG_FORMAT_V2 ) {
const cut = v2CutOffset ( walkV2Frames ( bytes ) , committedGeneration )
await this . storage . writeRawBytes ( path , bytes . subarray ( 0 , cut ) )
return
}
feat: generation fact log — after-image commit records, dual-written at every commit point
Every committed generation now also appends a FACT — an after-image
commit record (what each touched entity/relationship became, or a
body-less tombstone for a removal) — to an append-only, crc32c-framed
segment log under _generations/facts/. The before-image history and the
canonical tree remain authoritative; the fact log gives consumers ONE
sequential, self-verifying stream (index heals, incremental replays)
in place of a per-entity directory walk.
- Wire format: positional msgpack facts [generation, timestamp, ops,
meta, blobHashes]; op = [kind u8, id bin16, record | nil tombstone];
32-byte segment header (magic, formatVersion, firstGeneration,
zeroed+verified reserved); length+crc32c frame per fact; zero-padded
segment names so lexicographic order == generation order; JSON
manifest with an atomic rename flip, manifest-first rotation.
- Commit protocol: facts append+fsync BEFORE the commit point inside
the existing durability window, so a crash can only leave the log
AHEAD of committed truth — open() truncates back (torn tails detected
by CRC). Absent generation = never committed; a scan can never see an
uncommitted fact. transact() facts are durable-on-return; single-op
facts ride the group-commit flush exactly like buffered history. A
fact-append failure fails the write, loudly — a silent gap would be a
lie a later replay discovers.
- New public surface: brain.scanFacts() (sequential batches with heal
telemetry: head/segments/approx up front, per-batch generation range
+ bytes + segment id, loud abort on gaps, summary cross-check) and
brain.factSegmentPaths() (immutable sealed segments for zero-copy
consumers; the mutable tail excluded). Exported types CommitFact,
FactOp, FactScanBatch, FactScanHandle.
- Storage: optional binary raw-byte primitives (appendRawBytes,
readRawBytes, writeRawBytes, rawByteSize) on StorageAdapter —
feature-detected; filesystem + memory adapters implement them; an
adapter without them hosts no fact log. Fact segments are byte-copied
(never hard-linked) into snapshots. The _generations/facts/ namespace
is registered as a protected family (rebuildable: false): no sweeper
or GC may delete under it.
- New crc32c (Castagnoli) utility with RFC known-answer tests.
2026-07-15 10:49:02 -07:00
const first = kept [ 0 ] ? . generation ? ? this . segmentFirstGenerationFromName ( file )
const parts : Uint8Array [ ] = [ buildHeader ( first ) ]
for ( const f of kept ) parts . push ( encodeFrame ( f ) )
const total = parts . reduce ( ( n , p ) = > n + p . length , 0 )
const merged = new Uint8Array ( total )
let offset = 0
for ( const p of parts ) {
merged . set ( p , offset )
offset += p . length
}
await this . storage . writeRawBytes ( path , merged )
}
/** Re-derive a sealed segment's manifest entry from its actual bytes. */
private async reloadSegmentEntry ( file : string ) : Promise < SegmentEntry > {
const bytes = await this . storage . readRawBytes ( ` ${ FACTS_PREFIX } / ${ file } ` )
const { facts , validBytes } = bytes
? parseSegment ( file , bytes )
: { facts : [ ] as CommitFact [ ] , validBytes : 0 }
return {
file ,
firstGeneration : facts [ 0 ] ? . generation ? ? this . segmentFirstGenerationFromName ( file ) ,
lastGeneration : facts [ facts . length - 1 ] ? . generation ? ? 0 ,
facts : facts.length ,
bytes : validBytes
}
}
/** Parse the zero-padded firstGeneration back out of a segment filename. */
private segmentFirstGenerationFromName ( file : string ) : number {
const match = /^seg-(\d{20})\.bfl$/ . exec ( file )
return match ? Number ( match [ 1 ] ) : 0
}
}