feat: generation-segment store — the D1+D3 packed-tier file format
First stage of the co-frozen D1+D3+repacking unit: the format core,
self-contained under _generations/segments/.
- seg-<firstGen,20pad>.bgs: append-once packs of consecutive
generations (magic BGS1; frame = u32 len + u32 crc32c + msgpack
[generation, timestamp, delta, records, flags]; flags reserves
compressed-payload evolution without a format break). Sealed
segments are immutable — fold refuses overlap with sealed ranges.
- seg-<firstGen>.idx: DERIVED sidecar (per-generation frame offsets +
per-id generation postings + checksums); lost/corrupt sidecars
rebuild from their segment loudly; a damaged segment (frame CRC
mismatch) fails loudly, never serves wrong bytes.
- manifest.json: the one discovery path — open() reads it and never
lists the packed backlog (the scan-wedge class's cure); refuses a
newer manifest version rather than serving partial history.
- D3 semantics: dropSegmentsBelow reclaims WHOLE segments at
boundaries only and bumps compactedBelow durably; archival-profile
enforcement stays with the caller per the co-freeze.
- D8 rider: digestThroughPacked(g) — deterministic crc32c chain over
sealed-segment checksums (+ frame-level prefix mid-segment),
O(segments), reopen-stable.
Also fixes a cross-adapter contract bug the suite caught: memory
storage's deleteObjectFromPath ignored the raw-bytes store, so
deleteRawObject on a raw-bytes path (fact-log or segment files)
silently no-op'd — deletes now match filesystem unlink semantics.
Six pins. Wiring into GenerationStore (two-tier reads, the repacker,
cold-open manifest path) lands with the rest of the unit before its
release; cortex's fact-record/stamp shapes reconcile the sidecar
keying when they post.
2026-07-19 15:14:27 -07:00
/ * *
* @module tests / unit / db / generation - segments
* @description The generation - segment store ( Stage - 2 D1 + D3 file format ) .
* Laws : ( 1 ) fold → read round - trips deltas and records byte - faithfully via
* sidecar point - reads ; ( 2 ) the manifest is the ONLY discovery path — reopen
* reads one file , never a listing ; ( 3 ) a lost / corrupt sidecar rebuilds from
* its segment loudly , a damaged SEGMENT fails loudly ( never silent wrong
* data ) ; ( 4 ) D3 reclaim drops whole segments only and bumps compactedBelow ;
* ( 5 ) the packed digest is deterministic across reopen ; ( 6 ) immutability —
* fold refuses overlap with sealed ranges .
* /
import { describe , it , expect , beforeEach } from 'vitest'
import { MemoryStorage } from '../../../src/storage/adapters/memoryStorage.js'
import {
GenerationSegmentStore ,
SEGMENTS_PREFIX ,
type FoldGeneration
} from '../../../src/db/generationSegments.js'
const UUID = ( n : number ) : string = > ` 00000000-0000-4000-8000- ${ String ( n ) . padStart ( 12 , '0' ) } `
const gen = ( g : number , recordCount = 2 ) : FoldGeneration = > ( {
generation : g ,
timestamp : 1_700_000_000_000 + g ,
delta : { generation : g , nouns : [ UUID ( g ) ] , verbs : [ ] , bytes : 123 + g } ,
records : Array.from ( { length : recordCount } , ( _ , i ) = > ( {
kind : ( i % 2 === 0 ? 'noun' : 'verb' ) as 'noun' | 'verb' ,
id : UUID ( g * 100 + i ) ,
record : { metadata : { noun : 'document' , v : g } , vector : { v : [ g , i ] } }
} ) )
} )
describe ( 'db/GenerationSegmentStore — the D1+D3 packed tier' , ( ) = > {
let storage : MemoryStorage
let store : GenerationSegmentStore
beforeEach ( async ( ) = > {
storage = new MemoryStorage ( )
await storage . init ( )
store = new GenerationSegmentStore ( storage as any )
await store . open ( )
} )
it ( 'fold → read round-trips deltas and records via sidecar point-reads' , async ( ) = > {
const meta = await store . fold ( [ gen ( 1 ) , gen ( 2 ) , gen ( 3 ) ] )
expect ( meta ) . toMatchObject ( { firstGeneration : 1 , lastGeneration : 3 , frames : 3 } )
expect ( meta . checksum ) . toBeGreaterThan ( 0 )
expect ( store . hasGeneration ( 2 ) ) . toBe ( true )
expect ( store . hasGeneration ( 4 ) ) . toBe ( false )
const d2 = await store . readDelta ( 2 )
expect ( d2 ? . delta ) . toEqual ( { generation : 2 , nouns : [ UUID ( 2 ) ] , verbs : [ ] , bytes : 125 } )
expect ( d2 ? . timestamp ) . toBe ( 1 _700_000_000_002 )
const records = await store . readRecords ( 3 )
expect ( records ) . toHaveLength ( 2 )
expect ( records ! [ 0 ] ) . toEqual ( {
kind : 'noun' ,
id : UUID ( 300 ) ,
record : { metadata : { noun : 'document' , v : 3 } , vector : { v : [ 3 , 0 ] } }
} )
// Point read by id, both kinds.
expect ( await store . readRecord ( 3 , 'verb' , UUID ( 301 ) ) ) . toEqual ( {
metadata : { noun : 'document' , v : 3 } ,
vector : { v : [ 3 , 1 ] }
} )
expect ( await store . readRecord ( 3 , 'noun' , UUID ( 999 ) ) ) . toBeNull ( )
} )
it ( 'reopen discovers everything from the manifest alone — no listing' , async ( ) = > {
await store . fold ( [ gen ( 1 ) , gen ( 2 ) ] )
await store . fold ( [ gen ( 3 ) , gen ( 4 ) ] )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
expect ( reopened . segments ( ) ) . toHaveLength ( 2 )
expect ( reopened . hasGeneration ( 4 ) ) . toBe ( true )
expect ( ( await reopened . readDelta ( 1 ) ) ? . timestamp ) . toBe ( 1 _700_000_000_001 )
} )
it ( 'a lost sidecar rebuilds from its segment; a damaged segment fails LOUDLY' , async ( ) = > {
const meta = await store . fold ( [ gen ( 1 ) , gen ( 2 ) ] )
const idxPath = ` ${ SEGMENTS_PREFIX } /seg- ${ String ( 1 ) . padStart ( 20 , '0' ) } .idx `
await storage . deleteRawObject ( idxPath )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
// Rebuild path: still serves correct data.
expect ( ( await reopened . readRecords ( 2 ) ) ! ) . toHaveLength ( 2 )
// Now damage the SEGMENT itself: flip a payload byte → CRC mismatch, loud.
const segPath = ` ${ SEGMENTS_PREFIX } / ${ meta . file } `
const bytes = ( await storage . readRawBytes ( segPath ) ) !
bytes [ bytes . length - 3 ] ^= 0xff
await storage . writeRawBytes ( segPath , bytes )
const damaged = new GenerationSegmentStore ( storage as any )
await damaged . open ( )
; ( damaged as any ) . sidecars . clear ( )
await storage . deleteRawObject ( idxPath ) // force the sequential rebuild over damaged bytes
await expect ( damaged . readRecords ( 2 ) ) . rejects . toThrow ( /CRC mismatch|damaged/ )
} )
it ( 'D3 reclaim drops whole segments only and bumps compactedBelow' , async ( ) = > {
await store . fold ( [ gen ( 1 ) , gen ( 2 ) ] )
await store . fold ( [ gen ( 3 ) , gen ( 4 ) ] )
await store . fold ( [ gen ( 5 ) , gen ( 6 ) ] )
// Horizon mid-segment-2 (below 4): only segment 1 is FULLY below → drops.
const r1 = await store . dropSegmentsBelow ( 4 )
expect ( r1 ) . toEqual ( { dropped : 1 , compactedBelow : 3 } )
expect ( store . hasGeneration ( 1 ) ) . toBe ( false )
expect ( store . hasGeneration ( 3 ) ) . toBe ( true ) // partial segment survives whole
// Bytes actually gone.
expect ( await storage . readRawBytes ( ` ${ SEGMENTS_PREFIX } /seg- ${ String ( 1 ) . padStart ( 20 , '0' ) } .bgs ` ) ) . toBeNull ( )
// Horizon past everything: the rest drop; compactedBelow is durable.
const r2 = await store . dropSegmentsBelow ( 7 )
expect ( r2 . dropped ) . toBe ( 2 )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
expect ( reopened . compactedBelow ( ) ) . toBe ( 7 )
expect ( reopened . segments ( ) ) . toHaveLength ( 0 )
} )
it ( 'the packed digest is deterministic across reopen and changes with history' , async ( ) = > {
await store . fold ( [ gen ( 1 ) , gen ( 2 ) , gen ( 3 ) ] )
const atSeal = await store . digestThroughPacked ( 3 )
const midSegment = await store . digestThroughPacked ( 2 )
expect ( atSeal ) . not . toBeNull ( )
expect ( midSegment ) . not . toBeNull ( )
expect ( midSegment ) . not . toBe ( atSeal )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
expect ( await reopened . digestThroughPacked ( 3 ) ) . toBe ( atSeal )
expect ( await reopened . digestThroughPacked ( 2 ) ) . toBe ( midSegment )
await reopened . fold ( [ gen ( 4 ) ] )
expect ( await reopened . digestThroughPacked ( 4 ) ) . not . toBe ( atSeal )
} )
it ( 'sealed segments are immutable — fold refuses overlap, requires ascending input' , async ( ) = > {
await store . fold ( [ gen ( 1 ) , gen ( 2 ) ] )
await expect ( store . fold ( [ gen ( 2 ) , gen ( 3 ) ] ) ) . rejects . toThrow ( /overlaps the packed tier/ )
await expect ( store . fold ( [ gen ( 4 ) , gen ( 4 ) ] ) ) . rejects . toThrow ( /strictly ascending/ )
await expect ( store . fold ( [ ] ) ) . rejects . toThrow ( /at least one generation/ )
} )
fix(generations): a sealed segment may only declare the generations it holds
Diagnosis of the "packed history is damaged" narration that fires on every
run of the affected stores. It is a WRITER defect, and the reader's refusal
was the symptom rather than the cause.
A sealed segment declares one contiguous range [firstGeneration,
lastGeneration], and every reader treats that range as containment:
coveringSegment is an interval test, hasGeneration returns true for anything
inside it, and open() seeds committedRanges from it.
repackHistory handed fold() a SPARSE batch. Three filters punch holes in its
candidate list mid-run — a generation absent from committedRanges never
appears, one still in the pending buffer is skipped, one whose tx.json will
not read is skipped — and fold() then computed the range from the first and
last survivor, claiming every generation in between. The next open merged
that mis-declared range back into committedRanges, re-admitting the hole as
committed history, so the following auto-compaction pass asked the packed
tier for a frame that was never written and failed. Re-merged at every open,
which is why it repeated on every run.
Confirmed against a forensic fixture: generation directories 1..2503 present
except exactly one, 1416; and its fact-log segment already showed the tell —
seg-...1410.bfl declaring 1410..1940 (531 generations) while recording 530
facts.
Three changes:
- repackHistory folds each contiguous RUN as its own segment
(`contiguousRuns`), so ranges describe exactly what the segments contain.
- fold() REFUSES a non-contiguous batch, naming the gap and its width. The
density law is now mechanical, so no future caller can reintroduce it. A
refusal loses nothing: the generations stay live and readable.
- Stores already carrying the damage heal instead of wedging. A segment
whose declared span exceeds its frame count is SPARSE; `actualRanges()`
reads the real generation list from its sidecar so open() never re-admits
the holes, and readFrame reports such a hole as unpacked with a narration
naming the segment, rather than throwing. A DENSE segment missing a frame
is still loud damage — that one means the manifest and sidecar disagree.
Pins: nine unit cases (refusal and its message, honest ranges for separately
folded runs, a reconstructed pre-fix sparse segment serving its real frames
while reporting holes as unpacked, holes excluded from actualRanges, and the
dense-segment damage path still throwing) plus an end-to-end case that
deletes a generation directory and drives the real sequence — ordinary
close()-time repacking folds over the hole, then reopen and compact must both
complete. Verified red without the fix: the segment declared an
11-generation span while holding 10 frames.
(cherry picked from commit 9a888c37e9ebec5573cd7ebd0764396f3a424de3)
2026-08-31 09:13:42 -07:00
// ==========================================================================
// THE DENSITY LAW
// ==========================================================================
//
// A sealed segment declares a CONTIGUOUS range and every reader treats that
// range as containment. Folding a sparse batch therefore makes the segment
// claim generations it does not hold — and because `open()` merges declared
// ranges back into committedRanges, the hole is re-admitted as committed
// history and every later maintenance pass fails asking for a frame that was
// never written. That is the "generation N is inside sealed segment
// seg-....bgs's declared range but has no frame — packed history is damaged"
// narration seen on every run of the affected stores.
it ( 'fold REFUSES a batch with a hole — a dense range may not be declared over sparse input' , async ( ) = > {
await expect ( store . fold ( [ gen ( 1 ) , gen ( 2 ) , gen ( 4 ) ] ) ) . rejects . toThrow (
/not contiguous: 2 → 4 skips 1 generation/
)
// The refusal loses nothing: no segment was sealed, so the generations
// stay in the live tier and the next pass folds them correctly.
expect ( store . segments ( ) ) . toHaveLength ( 0 )
expect ( store . hasGeneration ( 1 ) ) . toBe ( false )
} )
it ( 'a wider gap names how many generations it would have swallowed' , async ( ) = > {
await expect ( store . fold ( [ gen ( 10 ) , gen ( 20 ) ] ) ) . rejects . toThrow (
/not contiguous: 10 → 20 skips 9 generation\(s\)/
)
} )
it ( 'two contiguous runs folded separately declare honest ranges' , async ( ) = > {
// What the caller now does instead of folding across the gap.
const a = await store . fold ( [ gen ( 1 ) , gen ( 2 ) , gen ( 3 ) ] )
const b = await store . fold ( [ gen ( 7 ) , gen ( 8 ) ] )
expect ( a ) . toMatchObject ( { firstGeneration : 1 , lastGeneration : 3 , frames : 3 } )
expect ( b ) . toMatchObject ( { firstGeneration : 7 , lastGeneration : 8 , frames : 2 } )
// The gap is honestly outside the packed tier.
for ( const g of [ 4 , 5 , 6 ] ) expect ( store . hasGeneration ( g ) ) . toBe ( false )
for ( const g of [ 1 , 2 , 3 , 7 , 8 ] ) expect ( store . hasGeneration ( g ) ) . toBe ( true )
expect ( await store . actualRanges ( ) ) . toEqual ( [
[ 1 , 3 ] ,
[ 7 , 8 ]
] )
} )
it ( 'actualRanges() is exact and I/O-free for dense segments' , async ( ) = > {
await store . fold ( [ gen ( 1 ) , gen ( 2 ) ] )
await store . fold ( [ gen ( 3 ) , gen ( 4 ) ] )
// Adjacent dense segments each contribute their declared range.
expect ( await store . actualRanges ( ) ) . toEqual ( [
[ 1 , 2 ] ,
[ 3 , 4 ]
] )
} )
// ---- pre-existing damage: a store sealed by the old writer ----------------
/ * *
* Seal a SPARSE segment the way the pre - fix writer did : write the bytes and
* sidecar for a contiguous run , then rewrite the manifest so the segment
* declares a wider range than the frames it holds . This reproduces on disk
* exactly what the affected stores carry , without needing the old code .
* /
const sealSparseSegment = async ( ) : Promise < void > = > {
await store . fold ( [ gen ( 1 ) , gen ( 2 ) , gen ( 3 ) ] )
const manifest = ( await storage . readRawObject ( ` ${ SEGMENTS_PREFIX } /manifest.json ` ) ) as any
// Declare 1..5 while holding frames for 1..3 — generations 4 and 5 become
// holes inside a sealed range.
manifest . segments [ 0 ] . lastGeneration = 5
await storage . writeRawObject ( ` ${ SEGMENTS_PREFIX } /manifest.json ` , manifest )
}
it ( 'a pre-existing sparse segment reports its holes as UNPACKED, not as damage' , async ( ) = > {
await sealSparseSegment ( )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
// The frames it really holds still serve, byte-faithfully.
expect ( ( await reopened . readDelta ( 2 ) ) ? . timestamp ) . toBe ( 1 _700_000_000_002 )
expect ( await reopened . readRecords ( 3 ) ) . toHaveLength ( 2 )
// The holes answer "not packed" instead of throwing. This is the fix for
// the wedge: the old reader threw here on EVERY maintenance pass.
expect ( await reopened . readDelta ( 4 ) ) . toBeNull ( )
expect ( await reopened . readRecords ( 5 ) ) . toBeNull ( )
} )
it ( 'actualRanges() excludes the holes so they are never re-admitted as committed' , async ( ) = > {
await sealSparseSegment ( )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
// Declared 1..5; actually holds 1..3. The store seeds committedRanges from
// THIS, so generations 4 and 5 never become committed history again.
expect ( await reopened . actualRanges ( ) ) . toEqual ( [ [ 1 , 3 ] ] )
} )
it ( 'a DENSE segment missing a frame is still loud damage' , async ( ) = > {
// The other side of the branch: when the manifest claims a complete span,
// a missing frame means the manifest and sidecar disagree — real damage,
// and it must not be quietly downgraded to "unpacked".
await store . fold ( [ gen ( 1 ) , gen ( 2 ) , gen ( 3 ) ] )
const idxPath = ` ${ SEGMENTS_PREFIX } /seg- ${ String ( 1 ) . padStart ( 20 , '0' ) } .idx `
const raw = ( await storage . readRawBytes ( idxPath ) ) !
const { decode , encode } = await import ( '@msgpack/msgpack' )
const idx = decode ( raw ) as any
// Drop generation 2's entry while the manifest still declares 3 frames.
idx . generations = idx . generations . filter ( ( [ g ] : [ number ] ) = > g !== 2 )
await storage . writeRawBytes ( idxPath , encode ( idx ) )
const reopened = new GenerationSegmentStore ( storage as any )
await reopened . open ( )
await expect ( reopened . readDelta ( 2 ) ) . rejects . toThrow (
/manifest and the sidecar disagree; packed history is damaged/
)
} )
feat: generation-segment store — the D1+D3 packed-tier file format
First stage of the co-frozen D1+D3+repacking unit: the format core,
self-contained under _generations/segments/.
- seg-<firstGen,20pad>.bgs: append-once packs of consecutive
generations (magic BGS1; frame = u32 len + u32 crc32c + msgpack
[generation, timestamp, delta, records, flags]; flags reserves
compressed-payload evolution without a format break). Sealed
segments are immutable — fold refuses overlap with sealed ranges.
- seg-<firstGen>.idx: DERIVED sidecar (per-generation frame offsets +
per-id generation postings + checksums); lost/corrupt sidecars
rebuild from their segment loudly; a damaged segment (frame CRC
mismatch) fails loudly, never serves wrong bytes.
- manifest.json: the one discovery path — open() reads it and never
lists the packed backlog (the scan-wedge class's cure); refuses a
newer manifest version rather than serving partial history.
- D3 semantics: dropSegmentsBelow reclaims WHOLE segments at
boundaries only and bumps compactedBelow durably; archival-profile
enforcement stays with the caller per the co-freeze.
- D8 rider: digestThroughPacked(g) — deterministic crc32c chain over
sealed-segment checksums (+ frame-level prefix mid-segment),
O(segments), reopen-stable.
Also fixes a cross-adapter contract bug the suite caught: memory
storage's deleteObjectFromPath ignored the raw-bytes store, so
deleteRawObject on a raw-bytes path (fact-log or segment files)
silently no-op'd — deletes now match filesystem unlink semantics.
Six pins. Wiring into GenerationStore (two-tier reads, the repacker,
cold-open manifest path) lands with the rest of the unit before its
release; cortex's fact-record/stamp shapes reconcile the sidecar
keying when they post.
2026-07-19 15:14:27 -07:00
} )