feat(log): the guarded log-authority core — group-commit durable-at-ack, the per-brain switch, the verification oracle
All checks were successful
CI / Node 22 (push) Successful in 12m14s
CI / Node 24 (push) Successful in 12m7s
CI / Bun (latest) (push) Successful in 12m20s

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.
This commit is contained in:
David Snelling 2026-08-06 10:08:18 -07:00
parent 9fda6d9566
commit 6595309765
4 changed files with 409 additions and 3 deletions

View file

@ -134,6 +134,22 @@ export class GenerationStore {
*/
private factLog: FactLog | null = null
/**
* Fact-log durability mode. 'deferred' (default) = the fact becomes
* durable at the group-commit flush, together with the buffered history
* the pre-log-authority contract, zero added ack latency. 'at-ack' =
* every single-op ack awaits a covering log fsync (shared via the log's
* group commit) the log-authority contract: an acked write's fact
* survives power loss. Set by the owner from the stored authority switch
* at open; transact() is durable-at-return in BOTH modes (unchanged).
*/
private logDurability: 'deferred' | 'at-ack' = 'deferred'
/** Switch the fact-log durability mode (see {@link logDurability}). */
setLogDurability(mode: 'deferred' | 'at-ack'): void {
this.logDurability = mode
}
/** Latest reserved/observed generation (≥ {@link committed}). */
private counter = 0
/** Committed-transaction watermark (manifest generation). */
@ -1270,13 +1286,23 @@ export class GenerationStore {
// Fact log (dual-write): the acked write's AFTER-IMAGE fact, appended
// now (read back warm, under the mutex — group-commit means flush-time
// canonical only holds the LATEST state, so each generation's after-image
// exists only here). Durability rides the group-commit flush, exactly
// like the buffered before-image history: a crash before the flush loses
// the fact AND the generation together — never a torn state.
// exists only here).
//
// Durability is MODE-GOVERNED:
// - 'deferred' (default, the pre-log-authority behavior): durability
// rides the group-commit flush like the buffered history — a crash
// before the flush loses the fact AND the generation together, never
// a torn state.
// - 'at-ack' (log-authority mode): the ack awaits a covering fsync via
// the log's group-commit (many concurrent writers share ONE sync) —
// an acked write's fact survives power loss, by contract.
if (this.factLog) {
await this.factLog.append(
await this.buildCommitFact({ generation: gen, timestamp, nouns, verbs })
)
if (this.logDurability === 'at-ack') {
await this.factLog.ensureSynced()
}
}
this.schedulePendingFlush()
return { generation: gen, timestamp }