/** * @module db/familyStamp * @description The generalized FAMILY STAMP — one JSON shape that declares, * for any derived projection, WHICH source state it reflects and HOW to verify * it is whole. The entity tree (canonical current-state files) carries the * first brainy-side stamp; native index families carry the same shape. One * verifier reads both member modes: * * - `enumerated` — bounded families: exact byte size per member file, * verified at open. * - `rollup` — unbounded families (the entity tree: millions of files): * the verified surface is a small set of rollup invariants (entity/ * relationship counts) plus `sourceGeneration`. * * `sourceGeneration` is the generation of the source-of-truth log this * projection reflects — open-time coherence becomes a COMPARISON (stamp vs * log head), not a walk: * * - equal + invariants hold → coherent, serve. * - behind → the projection missed the tail (crash between commit and stamp); * for the Stage-1 tree this is benign by construction (the tree is written * BY the commit), so the stamp refreshes; a DERIVED projection would replay * the gap instead. * - invariants FAIL at equal generation → genuine incoherence: loud, and the * repair ritual (`repairIndex()`, whose recount rebuilds the rollups from a * canonical walk) heals it. * * Stamps are JSON on purpose — every incident gets debugged by reading a * stamp in a terminal. */ /** Storage-root-relative directory holding family stamps. */ export const FAMILY_STAMPS_PREFIX = '_system/family-stamps' /** The entity tree's stamp path. */ export const ENTITY_TREE_STAMP_PATH = `${FAMILY_STAMPS_PREFIX}/entity-tree.json` /** One enumerated member: a file and its exact expected byte size. */ export interface EnumeratedMember { path: string bytes: number } /** The stamp's verified surface, in one of the two member modes. */ export type StampMembers = | { mode: 'enumerated'; files: EnumeratedMember[] } | { mode: 'rollup'; invariants: Record } /** The generalized family stamp (one shape, one verifier, both engines). */ export interface FamilyStamp { /** Which projection this stamps (e.g. `'entity-tree'`). */ family: string /** Monotonic per-family stamp generation — bumps on every committed stamp. */ generation: number /** ISO timestamp of the stamp write. */ committedAt: string /** The source-of-truth generation this projection reflects. */ sourceGeneration: number /** The verified surface. */ members: StampMembers } /** The verdict of an open-time stamp verification. */ export type StampVerdict = | { state: 'coherent' } | { state: 'absent' } // legacy store — first stamp writes at the next flush | { state: 'behind'; stampSource: number; head: number } | { state: 'incoherent'; failures: string[] } | { state: 'unverifiable'; reason: string } // a FAULT reading the stamp — never conflated with absence /** The narrow storage surface stamps ride (JSON objects + fsync). */ export interface StampStorage { readRawObject(path: string): Promise writeRawObject(path: string, data: any): Promise syncRawObjects(paths: string[]): Promise } /** Read a family's stamp; `null` when none was ever written. */ export async function readFamilyStamp( storage: StampStorage, path: string ): Promise { const stored = (await storage.readRawObject(path)) as FamilyStamp | null if (!stored || typeof stored !== 'object' || typeof stored.family !== 'string') return null return stored } /** Write a family's stamp durably (atomic object write + fsync). */ export async function writeFamilyStamp( storage: StampStorage, path: string, stamp: Omit & { generation?: number } ): Promise { const prior = await readFamilyStamp(storage, path) const full: FamilyStamp = { ...stamp, generation: (prior?.generation ?? 0) + 1, committedAt: new Date().toISOString() } await storage.writeRawObject(path, full) await storage.syncRawObjects([path]) } /** * The ONE verifier, both member modes. `actual` supplies the observed rollup * values (rollup mode) or file sizes (enumerated mode, keyed by path); * `head` is the source-of-truth generation now. */ export function verifyFamilyStamp( stamp: FamilyStamp | null, head: number, actual: Record ): StampVerdict { if (stamp === null) return { state: 'absent' } if (stamp.sourceGeneration > head) { // A stamp AHEAD of the log claims state that never committed — the // projection was stamped against truth that a crash rolled back. return { state: 'incoherent', failures: [`sourceGeneration ${stamp.sourceGeneration} is ahead of the log head ${head}`] } } if (stamp.sourceGeneration < head) { return { state: 'behind', stampSource: stamp.sourceGeneration, head } } const failures: string[] = [] if (stamp.members.mode === 'rollup') { for (const [name, expected] of Object.entries(stamp.members.invariants)) { const observed = actual[name] if (observed === undefined) { failures.push(`rollup invariant '${name}' has no observed value`) } else if (observed !== expected) { failures.push(`rollup invariant '${name}': stamped ${expected}, observed ${observed}`) } } } else { for (const member of stamp.members.files) { const observed = actual[member.path] if (observed === undefined) { failures.push(`member '${member.path}' is missing`) } else if (observed !== member.bytes) { failures.push(`member '${member.path}': stamped ${member.bytes} bytes, observed ${observed}`) } } } return failures.length > 0 ? { state: 'incoherent', failures } : { state: 'coherent' } }