/** * The genesis cut: where a stream's pushed history begins (journal spec * §2.11 §10, §3.5 Rule 2; T12343 S4-1a). * * Before genesis nothing leaves the device: every effect is carried by the * stream's genesis checkpoint instead of by segments. {@link cutGenesis} * checks the store is fit to push (the step-0 preconditions, T13032 AC2), * seals what is pending, then in ONE `BEGIN IMMEDIATE` with no frame open: * - records `genesis_cut:` and `genesis_source_seq:` (the * highest capture seq whose effect the checkpoint carries; equal by * construction, since every capture at or below it is sealed first); * - folds every sealed, unsegmented transaction (`state = 'folded'`): it is * in the checkpoint and is never sent; * - initialises row meta for every row that has none (§1.2 genesis HLC); * - sets `undo_enabled`, raises `min_writer_version` and turns `sync.push` * on, so every pushed transaction has undo (C1); * - leaves `genesis_pending:`, which the push keeps until the * genesis checkpoint is stored (S4-1b), so no segment ever precedes it. * * Draining first is what makes "every capture at or below the cut is * folded" hold without folding unsealed captures, whose rows would keep * stale meta and ledger counts: the cut transaction refuses (and retries * the drain) if a capture is still live. * * @task T12343 * @module store/sync/genesis */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; import { type StreamCursor } from './pull.js'; /** `_sync_meta` key prefix of a stream's genesis cut (read by `streamStarted`, T13217). */ export declare const GENESIS_CUT_KEY_PREFIX = "genesis_cut:"; /** `_sync_meta` key prefix of a stream's genesis source seq (equal to the cut). */ export declare const GENESIS_SOURCE_SEQ_KEY_PREFIX = "genesis_source_seq:"; /** `_sync_meta` key prefix set at the cut and cleared once the genesis checkpoint is stored. */ export declare const GENESIS_PENDING_KEY_PREFIX = "genesis_pending:"; /** `_sync_meta` key prefix: the highest `local_seq` a stream's cut folded (so a raced cut can be undone). */ export declare const GENESIS_FOLDED_UPTO_KEY_PREFIX = "genesis_folded_upto:"; /** `_sync_meta` key prefix: the lowest `local_seq` a stream's cut folded (its range, never another cut's). */ export declare const GENESIS_FOLDED_FROM_KEY_PREFIX = "genesis_folded_from:"; /** `_sync_meta` key the capture triggers' undo `WHEN` checks (§3.5 Rule 2). */ export declare const UNDO_ENABLED_KEY = "undo_enabled"; /** Options for {@link cutGenesis}. */ export interface GenesisCutOptions { readonly scope: TableScope; /** The stream whose pushed history starts here (`project:` or `home:`). */ readonly stream: string; /** Wall clock in ms (the genesis HLC of baselined rows). @defaultValue Date.now */ readonly now?: () => number; /** Environment for the sync kill switches. @defaultValue process.env */ readonly env?: NodeJS.ProcessEnv; /** Enable the unreleased `sync.seal` / `sync.push` (tests only). Never set from user input. */ readonly allowUnreleased?: boolean; } /** What {@link cutGenesis} did. */ export interface GenesisCutReport { readonly stream: string; /** Why nothing was cut, or null. */ readonly refused: string | null; /** The stream was already cut: nothing changed. */ readonly already: boolean; /** `genesis_cut` (= `genesis_source_seq`): the highest capture seq the checkpoint carries. */ readonly cut: number | null; /** Transactions the pre-cut drain sealed. */ readonly sealed: number; /** Sealed, unsegmented transactions folded into genesis. */ readonly folded: number; /** Rows given genesis row meta, per table (tables with none are absent). */ readonly baselined: Readonly>; /** * A cut committed by an earlier run that never finished its snapshot (it * crashed) was snapshotted now, at that same cut (T13301). */ readonly resumed: boolean; } /** * A stream's genesis cut, or undefined before it. Read-only. * * @param db - The store. * @param stream - The stream. */ export declare function genesisCutOf(db: DatabaseSync, stream: string): number | undefined; /** * Whether a stream is cut but its genesis checkpoint is not yet stored: the * push sends no segment until it is. Read-only. * * @param db - The store. * @param stream - The stream. */ export declare function genesisPending(db: DatabaseSync, stream: string): boolean; /** * Why the store cannot reach genesis, or null when it can (the step-0 * preconditions of `cleo sync enable push`, T13032 AC2). Read-only. * * - the sync schema is installed, capture is on and its triggers match the * current schema, and the owned guard triggers are sound; * - the sealer may run (`sync.seal` on, released or explicitly allowed); * - a replica is bound; * - identity follows the current recipe, and every minted row has its uid * and birth_fp (otherwise the sealer leaves its groups pending); * - no table is suspect and no capture is quarantined (the repair diff * runs first: genesis must not freeze drift into the checkpoint). * * @param db - The store. * @param opts - Scope, environment, and whether unreleased flags are allowed. */ export declare function genesisPreconditions(db: DatabaseSync, opts: Pick): string | null; /** * Record a stream's genesis cut and turn push on (§2.11 §10; module docs). * Must run outside a transaction (it seals, then opens its own * `BEGIN IMMEDIATE`). A refusal changes nothing beyond the drain's seals. * * @param db - The store. * @param opts - {@link GenesisCutOptions}. * @returns What was cut, or why not. */ export declare function cutGenesis(db: DatabaseSync, opts: GenesisCutOptions): GenesisCutReport; /** A write reached the store between the cut and the end of its snapshot; the cut was undone. */ export declare class GenesisRacedError extends Error { readonly code: string; } /** * Another process took the genesis marker over during the snapshot: the * pending cut, and any bundle saved for it, are the new holder's (T13390). */ export declare class GenesisTakenOverError extends GenesisRacedError { readonly code = "E_SYNC_GENESIS_MARKER_LOST"; } /** * {@link cutGenesis}, then the genesis checkpoint's bundle snapshotted at * the committed cut (§2.11 §10; T13296). The cut commits FIRST, so the * bundle carries everything it wrote: genesis row meta, the genesis keys, * `undo_enabled`, `sync.push` and the folded transactions. A device restored * from it therefore has full row meta and never pushes a folded transaction. * * Nothing may write between the cut and the end of the snapshot. For the * whole run the store carries a `genesis` marker ({@link writeRestoreMarker}): * other processes wait at their store open, and every writer, in this * process too, waits at the write chokepoint (`assertExodusWriteSafe`: the * task accessor's write transactions, session creation, `insertIdempotent`, * `upsertIdempotent`) and refuses with `E_STORE_GENESIS` if the snapshot * outlasts its wait (T13297). The snapshot itself writes nothing. A writer * that bypasses the chokepoint is caught after the snapshot (the capture * position moved past the cut): the cut is undone and * {@link GenesisRacedError} is thrown, so no bundle holding a post-cut write * is ever pushed. A failing snapshot undoes the cut too, and so does a * marker that went stale during the snapshot with nobody taking it over * ({@link GenesisRacedError}, `E_SYNC_GENESIS_MARKER_LOST`). When another * process took the stale marker over, it owns the pending cut: this run * aborts with the same error and never undoes the cut. The marker is always * released, unless another process now holds it. * * A cut that an earlier run committed but never snapshotted (a crash, kill * or sleep mid-export; `genesis_pending` still set) is resumed (T13301): with * no capture since, the snapshot runs at that cut (`resumed`); otherwise the * stale cut is undone and the store is cut again. A stored cut * (`genesis_pending` cleared) is `already`, and no snapshot runs. * * @param db - The store. * @param opts - {@link GenesisCutOptions}, plus the store file the marker guards. * @param snapshot - Export the store as the checkpoint bundle; receives the cut. Must not write. * @returns What was cut, or why not. * @throws {GenesisRacedError} When a write reached the store during the snapshot. */ export declare function cutGenesisWithSnapshot(db: DatabaseSync, opts: GenesisCutOptions & { readonly dbPath: string; }, snapshot: (cut: number) => Promise): Promise; /** * Finish a stream's genesis once its checkpoint is stored (S4-1b), in one * `BEGIN IMMEDIATE`: raise the persisted replicaSeq high-water mark to the * replica's last segment on the stream (the vault's delta segments spend this * replica's seqs, so the first journal segment follows them), then clear * `genesis_pending:`, which lets the push send segments. * * @param db - The store, outside a transaction. * @param o - Stream, replica, the replica's last replicaSeq on the stream (null: none yet) and the time. */ export declare function completeGenesis(db: DatabaseSync, o: { readonly stream: string; readonly replica: string; readonly replicaSeqFloor: number | null; readonly nowIso: string; }): void; /** * A stream's genesis cut read from a store file with a read-only open, or * undefined (no cut, no store, or no journal): the vault reads it before a * push without binding or migrating anything. * * @param dbPath - The `cleo.db` file. * @param stream - The stream. */ export declare function readGenesisCut(dbPath: string, stream: string): Promise; /** What {@link joinStream} did. */ export interface JoinStreamReport { readonly stream: string; /** Why the store did not join, or null. */ readonly refused: string | null; /** The join point: the capture position this store's pushed history starts after. */ readonly cut: number | null; } /** * Join a stream another device already started (T13312; journal spec * §2.11 §10, §3.5 Rule 2): the store holds exactly the journal checkpoint it * restored, with that checkpoint's row meta, and from here on pushes its own * writes and pulls the stream's after it. No genesis, no fold: the checkpoint * is the store's base. * * Every sync-set row must carry meta that matches it (the restore carried * the checkpoint's merge state, and nothing changed since); a row without * meta, or one whose content left its meta behind, refuses the join, and so * does a write captured after the check (seen inside the cut's transaction). * A refusal leaves capture as it found it. Then, in ONE `BEGIN IMMEDIATE` * that also turns sealing on: the restore's * suspect marks are cleared (the check just proved the rows clean), the * ledgers set, `genesis_cut:` records the join point (the stream has * started here: T13217's meta-less-row rule applies from now), `undo_enabled`, * `sync.push` and `sync.pull` are set, and the pull position is seeded from * the checkpoint. * * `merge` (T13466): a store that never synced with the stream keeps its own * rows. The row check and the baseline are skipped, and the join records a * copy reconcile due instead (`sync.reconcile_due`, push paused): the * reconcile against the checkpoint pulled to head adopts the stream's rows * and emits the store's own as its first transactions (`reconcile-copy.ts`). * * @param db - The store, outside a transaction, bound to its own replica. * @param o - Scope, stream, the checkpoint's pull position, and the unreleased opt-in. * @returns The join point, or why not. */ export declare function joinStream(db: DatabaseSync, o: { readonly scope: TableScope; readonly stream: string; readonly cursor: StreamCursor; readonly now?: () => number; readonly allowUnreleased?: boolean; /** Keep this never-synced store's own rows: reconcile them in instead of refusing (T13466). */ readonly merge?: boolean; }): JoinStreamReport; //# sourceMappingURL=genesis.d.ts.map