2026-07-12 08:54:14 -07:00
/ * *
* @module tests / integration / transact - durability - barrier
* @description Regression for a consumer - reported durability gap : a committed
* ` transact() ` reported success while its canonical entity writes were still
* only in the page cache ( tmp + rename , not yet fsync ' d ) , whereas the generation
* counter / manifest WERE fsync ' d — so a hard kill could leave the counter ahead
* of the persisted entity bytes ( "phantom progress" for any generation - based
* consumer resuming from the counter ) .
*
* Fix : ` commitTransaction ` opens a write barrier before running the planned
* operations and flushes it ( fsync of every canonical write + the parent dir of
* every canonical delete ) BEFORE advancing the counter and writing the manifest .
* These tests assert the ordering property directly by recording the storage ' s
* ` syncRawObjects ` calls : the entity writes are fsync ' d in an earlier call than
* the manifest , and a precommit - rejected batch opens no barrier and advances
* nothing .
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import { Brainy } from '../../src/brainy.js'
import { NounType , VerbType } from '../../src/types/graphTypes.js'
import { MemoryStorage } from '../../src/storage/adapters/memoryStorage.js'
const MANIFEST_REL = '_system/manifest.json'
describe ( 'transact durability barrier — entity writes fsync before the counter advances' , ( ) = > {
let dir : string
let brain : any
let syncCalls : string [ ] [ ]
let beginCount : number
let flushCount : number
beforeEach ( async ( ) = > {
process . env . BRAINY_DETERMINISTIC_EMBEDDINGS = 'true'
dir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'brainy-durability-' ) )
brain = new Brainy ( {
requireSubtype : false ,
storage : { type : 'filesystem' , path : dir } ,
dimensions : 384 ,
silent : true
} )
await brain . init ( )
feat(log): log authority is the fleet default — adopt-at-open, oracle-gated; plus the power-cut throw-site cures and the loud torn-record contract
THE DEFAULT FLIP (ruled on proven evidence — at-ack survived 301/301
acked-writes-through-power-cut in block-layer fault injection; deferred
tree authority demonstrably loses flush-covered acks): a brain with NO
stored authority artifact now ADOPTS LOG AUTHORITY AT OPEN. The oracle
gates the flip exactly as the guarded adoption path always did — curable
divergences baseline-backfilled, the flip lands ONLY on a green verdict —
and a brain that cannot verify STAYS tree-authoritative loudly, with the
refusal recorded on the switch artifact so subsequent opens are cheap.
config logAuthority: 'defer' is the explicit documented opt-out (no
automatic adoption; declared flush-window loss; adoptLogAuthority() flips
later). A stored artifact always wins. RELEASES.md carries the posture.
Two standing .fails debt pins FLIP TO HOLDING under the default: the
at-ack crash-survival gap and the ack-at-log durability target — both now
permanent asserted truths, not aspirations.
POWER-CUT THROW SITES (fault-injection findings, brainy-alone config):
- A manifest-listed-but-unloadable column segment QUARANTINES at
discovery (loud once, counted always, quarantinedSegments() exposed for
the heal) and the field serves its remaining segments DEGRADED — never
a raw throw killing every query on the field. Real storage faults still
propagate untouched.
- Torn generation artifacts (NaN/garbage in manifest or counter) DISCARD
with narration at the store's open and recovery re-derives — plus a
defensive finite-integer guard at the init consumer. Never a RangeError
killing an open.
THE LOUD TORN-RECORD CONTRACT: an existing-but-unparseable stored record
now surfaces as a typed, counted TornRecordError on every entity-read
surface (including fifteen previously-blind per-item batch catches);
ENOENT stays clean-absent; artifact readers with designed absent-recovery
keep null-tolerance behind the loud floor. Disk corruption can no longer
read as silent data invisibility.
Suite migration: the default's pins inverted deliberately, generation
baselines made relative, quarantine-contract pins rewritten to the ruled
behavior.
Gates: tsc 0 · unit 2065/2065 (159 files) · integration 826 (93 files) ·
conformance 31/31 · kill-matrix 15/15 · torn-open guards 2/2.
2026-08-11 08:37:38 -07:00
// Drain the pending tier BEFORE instrumenting: the adopt-at-open fleet
// default re-commits the init-time baseline as a buffered single-op
// generation, and transact() flushes buffered single-ops first — that
// flush's manifest sync would otherwise be recorded ahead of the
// transact's own commit point and break the first-index ordering pins.
await brain . flush ( )
2026-07-12 08:54:14 -07:00
// Instrument the real filesystem storage: record every fsync batch in order,
// and count barrier open/flush, delegating to the originals.
syncCalls = [ ]
beginCount = 0
flushCount = 0
const storage = brain [ 'storage' ]
const origSync = storage . syncRawObjects . bind ( storage )
storage . syncRawObjects = async ( paths : string [ ] ) = > {
syncCalls . push ( [ . . . paths ] )
return origSync ( paths )
}
const origBegin = storage . beginWriteBarrier . bind ( storage )
storage . beginWriteBarrier = ( ) = > {
beginCount ++
return origBegin ( )
}
const origFlush = storage . flushWriteBarrier . bind ( storage )
storage . flushWriteBarrier = async ( ) = > {
flushCount ++
return origFlush ( )
}
} )
afterEach ( async ( ) = > {
await brain . close ( )
fs . rmSync ( dir , { recursive : true , force : true } )
} )
/** Index of the first sync call whose paths satisfy `pred` (or -1). */
const findSync = ( pred : ( p : string ) = > boolean ) : number = >
syncCalls . findIndex ( ( paths ) = > paths . some ( pred ) )
it ( 'a committed transact fsyncs the entity write BEFORE the manifest' , async ( ) = > {
const id = '00000000-0000-4000-8000-00000000ab01'
await brain . transact ( [ { op : 'add' , id , data : 'durable entity' , type : NounType . Thing } ] )
// The barrier opened and flushed exactly once.
expect ( beginCount ) . toBe ( 1 )
expect ( flushCount ) . toBe ( 1 )
// The entity's canonical metadata write was fsync'd…
const entitySyncIdx = findSync ( ( p ) = > p . includes ( ` / ${ id } / ` ) && p . includes ( 'entities/nouns' ) )
expect ( entitySyncIdx ) . toBeGreaterThanOrEqual ( 0 )
// …and the manifest (the commit point) was fsync'd in a LATER call.
const manifestSyncIdx = findSync ( ( p ) = > p === MANIFEST_REL )
expect ( manifestSyncIdx ) . toBeGreaterThanOrEqual ( 0 )
expect ( entitySyncIdx ) . toBeLessThan ( manifestSyncIdx )
} )
it ( 'a multi-op transact (add + relate) fsyncs both endpoints and the edge before the manifest' , async ( ) = > {
const a = '00000000-0000-4000-8000-00000000ab02'
const b = '00000000-0000-4000-8000-00000000ab03'
await brain . transact ( [
{ op : 'add' , id : a , data : 'A' , type : NounType . Thing } ,
{ op : 'add' , id : b , data : 'B' , type : NounType . Thing } ,
{ op : 'relate' , from : a , to : b , type : VerbType . Contains }
] )
expect ( flushCount ) . toBe ( 1 )
const aIdx = findSync ( ( p ) = > p . includes ( ` / ${ a } / ` ) )
const bIdx = findSync ( ( p ) = > p . includes ( ` / ${ b } / ` ) )
const manifestIdx = findSync ( ( p ) = > p === MANIFEST_REL )
expect ( aIdx ) . toBeGreaterThanOrEqual ( 0 )
expect ( bIdx ) . toBeGreaterThanOrEqual ( 0 )
expect ( manifestIdx ) . toBeGreaterThanOrEqual ( 0 )
expect ( Math . max ( aIdx , bIdx ) ) . toBeLessThan ( manifestIdx )
// Sanity: the data is actually there and traverses.
expect ( ( await brain . related ( a ) ) . map ( ( r : any ) = > r . to ) ) . toContain ( b )
} )
it ( 'a precommit-rejected batch opens no barrier and advances nothing' , async ( ) = > {
const anchor = await brain . add ( { id : '00000000-0000-4000-8000-00000000ab04' , data : 'anchor' , type : NounType . Thing } )
const genBefore = brain . generationStore . generation ( )
syncCalls . length = 0
beginCount = 0
flushCount = 0
const never = '00000000-0000-4000-8000-00000000ab05'
await expect (
brain . transact ( [
{ op : 'add' , id : never , data : 'never' , type : NounType . Thing } ,
{ op : 'update' , id : anchor , metadata : { poke : 1 } , ifRev : 999 }
] )
) . rejects . toMatchObject ( { name : 'RevisionConflictError' } )
// Precommit throws before the barrier opens (it lives just before execute):
// the barrier never opens, nothing is flushed, and the generation counter is
// unchanged. (A manifest sync CAN appear here from transact() flushing the
// anchor's previously-buffered single-op — that is the anchor's deferred
// durability, not this rejected batch committing; beginCount is the clean
// signal that this batch's commit path never reached execute.)
expect ( beginCount ) . toBe ( 0 )
expect ( flushCount ) . toBe ( 0 )
expect ( brain . generationStore . generation ( ) ) . toBe ( genBefore )
expect ( await brain . get ( never ) ) . toBeNull ( )
} )
it ( 'MemoryStorage does not implement the barrier (optional-chaining no-op)' , ( ) = > {
const mem = new MemoryStorage ( ) as any
// In-memory has no durability seam; the generation store treats the absent
// barrier methods as no-ops via optional chaining.
expect ( mem . beginWriteBarrier ) . toBeUndefined ( )
expect ( mem . flushWriteBarrier ) . toBeUndefined ( )
} )
} )