This repository has been archived on 2026-09-03. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
open-brainy/src/db/logAuthority.ts

342 lines
13 KiB
TypeScript
Raw Normal View History

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
/**
* @module db/logAuthority
* @description The per-brain LOG-AUTHORITY SWITCH and its verification
* oracle the guarded adoption path for log-canonical storage.
*
* Two storage authorities exist during the adoption window:
* - `'tree'` (the default, today's behavior): the canonical record tree is
* authoritative; the generation log is a complete dual-written journal.
* - `'log'`: the generation log is authoritative for this brain; single-op
* write acks await a covering log fsync (durable-at-ack), and derived
* state treats the log as ground truth.
*
* THE SWITCH IS PER BRAIN, STORED, CHECKED AT OPEN ONLY, and ONE-DIRECTIONAL
* unless explicitly reverted by an operator. A brain flips ONLY when its
* verification oracle is green: a full replay-and-diff of the log against
* the still-authoritative tree (the read-only witness). The oracle failing
* NAMES every divergence a brain with pre-log history (records the log
* never saw) reports them as `pre-log-record` mismatches and needs a
* baseline backfill before it can ever flip.
*
* Nothing in this module mutates data: the oracle is read-only; the flip
* writes ONE artifact. Reverting = rewriting the artifact to 'tree' (the
* tree remained authoritative-quality throughout the window by dual-write).
*/
import type { FactScanHandle } from './factLog.js'
import { prodLog } from '../utils/logger.js'
import { createHash } from 'crypto'
/** Storage-root-relative path of the authority switch artifact. */
export const LOG_AUTHORITY_PATH = '_system/log-authority.json'
/** The persisted shape of the authority switch. */
export interface LogAuthorityRecord {
/** Which store is authoritative for this brain. */
authority: 'tree' | 'log'
/** When the flip happened (ms epoch). Absent while authority = 'tree'. */
flippedAt?: number
/** The oracle verdict that justified the flip (summary, not the full report). */
oracle?: {
verifiedAt: number
generationsScanned: number
nounsChecked: number
verbsChecked: number
}
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
/**
* Recorded when an OPEN-TIME adoption attempt (the 10.0.0 fleet default)
* was refused the oracle could not go green. Keeps subsequent opens
* cheap; an operator re-runs adoptLogAuthority() after resolving it.
*/
adoptRefusal?: { at: number; reason: string }
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
}
/** The narrow storage surface this module needs. */
export interface LogAuthorityStorage {
readRawObject(path: string): Promise<unknown | null>
writeRawObject(path: string, data: unknown): Promise<void>
syncRawObjects(paths: string[]): Promise<void>
getNouns(opts: {
pagination: { limit: number; offset?: number; cursor?: string }
}): Promise<{ items: unknown[]; hasMore?: boolean; nextCursor?: string }>
getNounMetadata(id: string): Promise<unknown | null>
}
/** One divergence found by the oracle. */
export interface OracleMismatch {
id: string
kind: 'noun' | 'verb'
reason:
| 'pre-log-record' // canonical row the log never saw — needs baseline backfill
| 'state-differs' // latest log after-image ≠ canonical bytes
| 'log-live-canonical-absent' // log says live, canonical has no record
| 'log-tombstone-canonical-present' // log says deleted, canonical still has it
}
/** The oracle's full report. */
export interface OracleReport {
verdict: 'green' | 'red'
generationsScanned: number
nounsChecked: number
verbsChecked: number
matched: number
mismatches: OracleMismatch[]
/** Mismatch listing is capped; the counts above are always complete. */
mismatchListTruncated: boolean
}
const MISMATCH_LIST_CAP = 200
/** Read the stored authority (absent artifact = 'tree', the safe default). */
export async function readLogAuthority(
storage: Pick<LogAuthorityStorage, 'readRawObject'>
): Promise<LogAuthorityRecord> {
const raw = (await storage
.readRawObject(LOG_AUTHORITY_PATH)
.catch(() => null)) as LogAuthorityRecord | null
if (raw && (raw.authority === 'log' || raw.authority === 'tree')) return raw
return { authority: 'tree' }
}
feat(embedding): deferred-embed markers become log records — the sidecar recovery path is deleted The private recovery discipline, applied to its own machinery: pending- embed markers stop being sidecar files and become first-class log records riding the write's OWN commit fact — embed.pending lands in the same atomic append as its after-image (a marker can never be orphaned from its write, or vice versa; in durable-at-ack mode it shares the write's covering fsync — zero extra syncs), and the worker's landing commit rides embed.landed with the inline vector. Crash recovery is now a FOLD of the log (pending without a matching landed = recovered), skipped wholesale on brains with no v2 history; the one-time legacy bridge folds existing sidecar files in, migrates them as one fact, and deletes them — idempotent under a crash mid-bridge. No code path writes the sidecar again. Plus the ENTITY-TRUTH digest law, found by this train's own pins: canonical vector wrappers denormalize HNSW residue (connections + the randomly-assigned node level) that the log deliberately does not carry — the verification oracle digested it and would have reported false state-differs on ~any nonzero-level node (a ~15% flake in the cutover pin was the symptom). Both sides of every oracle comparison now normalize to entity truth (nounEntityTruth); index residue has its own rebuild path and is not entity state. Pins: embed-markers-in-log 5/5 (same-generation marker, landed+fold-to- zero, crash recovery via the log with the sidecar prefix EMPTY on disk, legacy bridge, VFS hung-embedder ack) · deferred-embedding 5/5 unchanged (the contract outlived its mechanism) · kill-matrix 11/11 · cutover 5/5 ×10 runs (flake dead) · unit 2031/2031.
2026-08-10 11:27:07 -07:00
/**
* Normalize a canonical noun record to its ENTITY TRUTH before diffing:
* the canonical vector-file wrapper denormalizes derived index residue
* (`connections` HNSW graph edges; `level` the node's random skip-list
* level) that the generation log deliberately does NOT carry (projections
* own their own rebuild paths). Digesting the residue would report false
* `state-differs` on ~any brain whose HNSW assigned a nonzero level. Both
* sides of every oracle comparison pass through this normalizer.
*/
export function nounEntityTruth(record: {
metadata: unknown
vector: unknown
}): { metadata: unknown; vector: unknown } {
const v = record.vector
if (v && typeof v === 'object' && !Array.isArray(v)) {
const { connections: _c, level: _l, ...entity } = v as Record<string, unknown>
return { metadata: record.metadata, vector: entity }
}
return { metadata: record.metadata, vector: v }
}
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
/**
* Stable content hash of a stored record for diffing key-sorted JSON so
* property order can never fake a divergence.
*/
export function recordDigest(record: unknown): string {
const stable = (v: unknown): unknown => {
if (Array.isArray(v)) return v.map(stable)
if (v && typeof v === 'object') {
const out: Record<string, unknown> = {}
for (const k of Object.keys(v as Record<string, unknown>).sort()) {
out[k] = stable((v as Record<string, unknown>)[k])
}
return out
}
return v
}
return createHash('sha256').update(JSON.stringify(stable(record))).digest('hex')
}
/**
* THE VERIFICATION ORACLE: replay the fact log's noun records and diff the
* final state per id against the canonical tree (the witness). Read-only;
* bounded memory (id {tombstoned, digest} digests, never bodies).
*
* Verdict law: 'green' iff EVERY canonical row's latest state is exactly
* reproduced by the log AND the log claims nothing canonical denies. A
* brain older than its log reports its unlogged rows as `pre-log-record`
* mismatches the named cure is a baseline backfill, never a silent pass.
*/
export async function runLogCompletenessOracle(args: {
storage: LogAuthorityStorage
scanFacts: () => FactScanHandle | null
/** Digest the canonical record the same way the log's after-image is digested. */
canonicalNounDigest: (id: string) => Promise<string | null>
/** Digest a log after-image record's payload. */
factRecordDigest: (record: unknown) => string
2026-08-10 10:55:11 -07:00
/**
* Verb legs (optional until every owner wires them): the canonical verb
* digest + the paged verb enumeration. When ABSENT, the oracle counts NO
* verbs and says so via verbsChecked = 0 an honest partial verdict,
* never a silent full-pass claim.
*/
canonicalVerbDigest?: (id: string) => Promise<string | null>
getVerbs?: (opts: {
pagination: { limit: number; offset?: number; cursor?: string }
}) => Promise<{ items: unknown[]; hasMore?: boolean; nextCursor?: string }>
/**
* Cap on the LISTED mismatches (counts are always complete). Defaults to
* the wire-friendly {@link MISMATCH_LIST_CAP}; the adoption backfill passes
* `Infinity` so ONE scan yields the ENTIRE curable set a production
* brain with a 12.7k-row pre-log baseline once advanced only 800 rows per
* adoption call because each pass could see (and cure) at most 200.
*/
mismatchListCap?: number
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
}): Promise<OracleReport> {
const listCap = args.mismatchListCap ?? MISMATCH_LIST_CAP
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
const report: OracleReport = {
verdict: 'red',
generationsScanned: 0,
nounsChecked: 0,
verbsChecked: 0,
matched: 0,
mismatches: [],
mismatchListTruncated: false
}
const addMismatch = (m: OracleMismatch): void => {
if (report.mismatches.length < listCap) report.mismatches.push(m)
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
else report.mismatchListTruncated = true
}
// Pass 1: fold the log — latest state per noun id (digest or tombstone).
const scan = args.scanFacts()
if (!scan) {
// No fact log on this store: nothing can be verified — red, loudly.
prodLog.warn('[logAuthority] oracle: this store has no fact log — cannot verify, verdict red')
return report
}
const logState = new Map<string, { tombstoned: boolean; digest: string | null }>()
2026-08-10 10:55:11 -07:00
const verbLogState = new Map<string, { tombstoned: boolean; digest: string | null }>()
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
for await (const batch of scan.batches()) {
for (const fact of batch.facts) {
report.generationsScanned++
for (const op of fact.ops) {
2026-08-10 10:55:11 -07:00
const state =
op.record === null
? { tombstoned: true, digest: null }
: { tombstoned: false, digest: args.factRecordDigest(op.record) }
if (op.kind === 'noun') logState.set(op.id, state)
else verbLogState.set(op.id, state)
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
}
}
}
// Pass 2: walk canonical (paged) and diff.
const seenCanonical = new Set<string>()
const PAGE = 500
let offset = 0
let cursor: string | undefined
for (;;) {
const page = await args.storage.getNouns({
pagination: cursor ? { limit: PAGE, cursor } : { limit: PAGE, offset }
})
for (const item of page.items) {
const id = (item as { id: string }).id
seenCanonical.add(id)
report.nounsChecked++
const inLog = logState.get(id)
if (!inLog) {
addMismatch({ id, kind: 'noun', reason: 'pre-log-record' })
continue
}
if (inLog.tombstoned) {
addMismatch({ id, kind: 'noun', reason: 'log-tombstone-canonical-present' })
continue
}
const canonicalDigest = await args.canonicalNounDigest(id)
if (canonicalDigest === null) {
addMismatch({ id, kind: 'noun', reason: 'pre-log-record' })
continue
}
if (canonicalDigest === inLog.digest) report.matched++
else addMismatch({ id, kind: 'noun', reason: 'state-differs' })
}
if (!page.hasMore || page.items.length === 0) break
if (page.nextCursor) cursor = page.nextCursor
else offset += page.items.length
}
// Pass 3: log-live ids canonical never showed us.
for (const [id, state] of logState) {
if (!state.tombstoned && !seenCanonical.has(id)) {
addMismatch({ id, kind: 'noun', reason: 'log-live-canonical-absent' })
}
}
2026-08-10 10:55:11 -07:00
// Verb passes — only when the owner wired the verb legs; otherwise the
// report says verbsChecked: 0, an honest partial scope, never a claim.
if (args.canonicalVerbDigest && args.getVerbs) {
const seenVerbs = new Set<string>()
let vOffset = 0
let vCursor: string | undefined
for (;;) {
const page = await args.getVerbs({
pagination: vCursor ? { limit: PAGE, cursor: vCursor } : { limit: PAGE, offset: vOffset }
})
for (const item of page.items) {
const id = (item as { id: string }).id
seenVerbs.add(id)
report.verbsChecked++
const inLog = verbLogState.get(id)
if (!inLog) {
addMismatch({ id, kind: 'verb', reason: 'pre-log-record' })
continue
}
if (inLog.tombstoned) {
addMismatch({ id, kind: 'verb', reason: 'log-tombstone-canonical-present' })
continue
}
const canonical = await args.canonicalVerbDigest(id)
if (canonical === null) {
addMismatch({ id, kind: 'verb', reason: 'pre-log-record' })
continue
}
if (canonical === inLog.digest) report.matched++
else addMismatch({ id, kind: 'verb', reason: 'state-differs' })
}
if (!page.hasMore || page.items.length === 0) break
if (page.nextCursor) vCursor = page.nextCursor
else vOffset += page.items.length
}
for (const [id, state] of verbLogState) {
if (!state.tombstoned && !seenVerbs.has(id)) {
addMismatch({ id, kind: 'verb', reason: 'log-live-canonical-absent' })
}
}
}
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
const totalMismatches =
report.mismatches.length + (report.mismatchListTruncated ? 1 : 0)
report.verdict = totalMismatches === 0 ? 'green' : 'red'
return report
}
/**
* Flip this brain's authority to the log REFUSES unless the supplied
* oracle report is green (the caller runs the oracle; the flip records its
* summary). Writes + fsyncs the switch artifact; the mode takes full effect
* at the NEXT open (checked-at-open-only law), except durable-at-ack which
* the owner may enable immediately.
*/
export async function flipToLogAuthority(
storage: Pick<LogAuthorityStorage, 'writeRawObject' | 'syncRawObjects'>,
oracle: OracleReport
): Promise<LogAuthorityRecord> {
if (oracle.verdict !== 'green') {
throw new Error(
`log-authority flip refused: the verification oracle is RED ` +
`(${oracle.mismatches.length}${oracle.mismatchListTruncated ? '+' : ''} mismatches; ` +
`first: ${oracle.mismatches[0] ? `${oracle.mismatches[0].reason} on ${oracle.mismatches[0].id}` : 'n/a'}). ` +
`A brain flips only on green — fix the divergences (pre-log records need a baseline backfill) and re-run.`
)
}
const record: LogAuthorityRecord = {
authority: 'log',
flippedAt: Date.now(),
oracle: {
verifiedAt: Date.now(),
generationsScanned: oracle.generationsScanned,
nounsChecked: oracle.nounsChecked,
verbsChecked: oracle.verbsChecked
}
}
await storage.writeRawObject(LOG_AUTHORITY_PATH, record)
await storage.syncRawObjects([LOG_AUTHORITY_PATH])
prodLog.info(
`[logAuthority] this brain's storage authority is now the generation log ` +
`(oracle green over ${oracle.nounsChecked} nouns / ${oracle.generationsScanned} generations)`
)
return record
}