test(fold-checkpoint): the ARM-AT-FLIP pin arms its crash instead of racing the pending-flush timer
Some checks are pending
CI / Node 22 (push) Waiting to run
CI / Node 24 (push) Waiting to run
CI / Integration + conformance (Node 22) (push) Waiting to run
CI / Bun (latest) (push) Waiting to run

The pin wrote a post-flip row, abandoned the brain as crashed, and then
deleted the row's canonical bytes to prove the first post-flip boot folds
BOUNDED above the flip's stamp. Between the write's ack and the abandon sat
the store's 50ms pending-flush timer: on a loaded box (the plant lane) the
flush won, barrier-synced the row and advanced the checkpoint over it — and
the fold, correctly bounded, did not restore bytes the test had destroyed
after they were stamped durable. Green locally, red on the plant: the
engine was right, the pin was timing-dependent.

The crash is now armed at exactly singleop-after-fact-append: the fact is
appended and at-ack synced, no flush is ever scheduled, the stamp provably
still reads the flip's value when the pre-flip bytes are dropped, and the
post-flip row's bytes — which lived only in the pending tier's RAM — are
lost for real, not synthetically. The reopen must re-materialize it from
its fact and must not restore the pre-flip row.
This commit is contained in:
David Snelling 2026-08-24 09:58:45 -07:00
parent 116550eb16
commit 4176439ba3

View file

@ -23,6 +23,7 @@ import { Brainy } from '../../src/brainy.js'
import { NounType } from '../../src/types/graphTypes.js' import { NounType } from '../../src/types/graphTypes.js'
import { import {
abandonAsCrashed, abandonAsCrashed,
armCrash,
dropCanonicalNoun, dropCanonicalNoun,
makeTempDir, makeTempDir,
openBrain, openBrain,
@ -177,15 +178,33 @@ describe('fold-checkpoint bound — crash recovery folds (checkpoint, head], nev
expect(founded, 'checkpoint founded at flip').toBe(committedOf(brain)) expect(founded, 'checkpoint founded at flip').toBe(committedOf(brain))
// First post-flip boot, unclean (the production first-restart shape): // First post-flip boot, unclean (the production first-restart shape):
// a post-flip write above the checkpoint is restored FROM ITS AT-ACK FACT // a post-flip write above the checkpoint is restored FROM ITS AT-ACK FACT;
// (deliberately NOT flushed — a flush would barrier-sync it and advance // the pre-flip row (its baseline fact ≤ checkpoint, its bytes barrier-
// the stamp over it, making its loss synthetic); the pre-flip row (its // synced at the flip) is OUTSIDE the fold — vaporizing it synthetically
// baseline fact ≤ checkpoint, its bytes barrier-synced at the flip) is // proves the bound.
// OUTSIDE the fold — vaporizing it synthetically proves the bound. //
const postFlip = await brain.add({ data: 'post-flip write', type: NounType.Document, metadata: { era: 'log' } }) // THE CRASH IS ARMED, NOT RACED. The post-flip write "dies" at exactly
// `singleop-after-fact-append`: its fact is in the log and at-ack synced,
// and NO pending flush was ever scheduled — so the checkpoint provably
// still reads the flip's stamp when the bytes are dropped. The earlier
// shape (`add()` then abandon) raced the store's 50ms pending-flush
// timer: on a loaded box the flush won, barrier-synced the row, advanced
// the stamp over it — and the fold, CORRECTLY bounded, did not restore
// bytes the test had synthetically destroyed after they were stamped
// durable. The plant lane caught it; the engine was right, the pin was
// timing-dependent.
const postFlip = `post-flip-${Date.now().toString(36)}-0000-4000-8000-000000000000`
const arm = armCrash(brain, 'singleop-after-fact-append')
await expect(
brain.add({ id: postFlip, data: 'post-flip write', type: NounType.Document, metadata: { era: 'log' } })
).rejects.toThrow('simulated process crash at singleop-after-fact-append')
expect(arm.fired).toContain('singleop-after-fact-append')
expect(readCheckpoint(dir), 'the stamp did not move — nothing flushed after the flip').toBe(founded)
await abandonAsCrashed(liveBrains.pop()!) await abandonAsCrashed(liveBrains.pop()!)
// The post-flip row's canonical bytes lived only in the pending tier's
// RAM (written at flush, never reached) — the crash takes them for real;
// nothing to drop. Only the pre-flip row is vaporized synthetically.
dropCanonicalNoun(dir, preFlip) dropCanonicalNoun(dir, preFlip)
dropCanonicalNoun(dir, postFlip)
const reopened = await openBrain(dir, { logAuthority: 'adopt' }) const reopened = await openBrain(dir, { logAuthority: 'adopt' })
liveBrains.push(reopened) liveBrains.push(reopened)