/** * Push one stream's journal (journal spec §2.8 "Persist before push", * "Upload", §2.9, §1.3 "Local clock ahead"; T12343 S4-2). * * {@link pushStream} runs, in order: * 1. the preconditions: `sync.push` on, the stream cut and its genesis * checkpoint stored (`genesis_pending` clear), so no segment ever * precedes the checkpoint it extends; * 2. the local-clock check: the server's date is recorded for the sealer's * clamp (`sync.server_clock`, read by `sealWallCeiling`); when this * device's clock runs more than `MAX_DRIFT` ahead of it, push pauses * (persisted as `sync.clock_ahead` for `cleo doctor`), so future-stamped * HLCs do not spread; local writes are never refused; * 3. the rollback check: when the server already holds a later segment from * this replica than the store has persisted, the store is behind (a * restore or a copy) and must rebind (§1.5, §2.9); nothing is sent; * 4. seal everything pending, then pack and persist segments * ({@link buildSegment}), advancing the device registry's high-water mark * after each persist (the store ahead of the registry is the safe * direction); * 5. upload the persisted segments, lowest `replicaSeq` first, one at a time, * always the exact persisted bytes; a duplicate counts as stored. * * The upload goes through a {@link SegmentUploader} port: the journal client * in production (`cloud/nexus-vault.ts`), a fake in tests. * * @task T12343 * @module store/sync/push */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; import type { ReplicaRegistry } from './replica-registry.js'; import { type SecretSeals } from './sealer.js'; import { type BuildSegmentOptions, type PersistedSegment, type SegmentSealer } from './segments.js'; /** `MAX_DRIFT` (§1.3, Q6): how far this device's clock may run ahead of the server's. */ export declare const MAX_DRIFT_MS: number; /** `_sync_meta` key set while push is paused for a clock ahead of the server's (`cleo doctor`). */ export declare const CLOCK_AHEAD_KEY = "sync.clock_ahead"; /** Uploads one persisted segment; resolves with the server's seq, `duplicate` when it was stored already. */ export type SegmentUploader = (segment: PersistedSegment) => Promise<{ readonly seq: number; readonly duplicate: boolean; }>; /** Options for {@link pushStream}. */ export interface PushStreamOptions { readonly scope: TableScope; readonly stream: string; /** This store's bound replica (the stream knows it by the same id). */ readonly replica: string; /** The merge key's project, or null on a home stream. */ readonly project: string | null; readonly sealer: SegmentSealer; /** Signs each packed transaction for the stream ({@link BuildSegmentOptions.signTxn}). */ readonly signTxn: BuildSegmentOptions['signTxn']; readonly upload: SegmentUploader; /** * The server's clock minus this device's, measured when its latest response * arrived (negative: this device runs ahead), or null when unknown. */ readonly serverOffsetMs: number | null; /** The server's last stored `replicaSeq` for this replica on the stream, or null when unknown or none. */ readonly serverLastReplicaSeq: number | null; /** The device registry, whose high-water mark advances after each persist. */ readonly registry?: ReplicaRegistry; /** Wall clock. @defaultValue Date.now */ readonly now?: () => number; readonly env?: NodeJS.ProcessEnv; /** Seal and push although the flags are unreleased (tests and staging only). Never set from user input. */ readonly allowUnreleased?: boolean; /** Override {@link MAX_DRIFT_MS} (tests). */ readonly maxDriftMs?: number; /** Phase A's sealed secrets for the sealer (§2.5, T13467); without them secret groups stay pending. */ readonly secrets?: SecretSeals; } /** * Why {@link pushStream} sent nothing, as a kind callers branch on (the * message in `refused` is for people and may be reworded): * - `schema-missing`: the sync schema is not installed; * - `push-off`: `sync.push` is off; * - `no-genesis`: this store has no genesis cut on the stream; * - `genesis-pending`: the genesis checkpoint is not stored yet; * - `store-behind`: the server holds a later segment of this replica; * - `reconcile-pending`: the store rebound at open and has not reconciled * against the stream yet (§1.5 N7, T13335). */ export type PushRefusal = 'schema-missing' | 'push-off' | 'no-genesis' | 'genesis-pending' | 'store-behind' | 'reconcile-pending'; /** What {@link pushStream} did. */ export interface PushStreamReport { readonly stream: string; /** Why nothing was sent, or null. */ readonly refused: string | null; /** The kind of `refused`, or null when nothing was refused. */ readonly refusedKind: PushRefusal | null; /** Push paused because this device's clock is ahead of the server's. */ readonly clockAhead: boolean; /** Transactions sealed, segments packed and persisted, segments the server stored (`duplicates` of them retries). */ readonly sealed: number; readonly built: number; readonly pushed: number; readonly duplicates: number; /** The server seq of the last segment stored, or null. */ readonly lastServerSeq: number | null; /** Groups the last seal pass held back, and why: nothing after one seals before it (§2.9). */ readonly held: ReadonlyArray<{ readonly firstSeq: number; readonly reason: string; }>; } /** * Seal, persist and upload one stream's pending journal (module docs). * Must run outside a transaction. An upload failure propagates after the * segments before it were recorded; the next run resends from the lowest * unpushed `replicaSeq`, with the same bytes. * * @param db - The store. * @param o - {@link PushStreamOptions}. * @returns What was sealed, persisted and stored, or why nothing was sent. */ export declare function pushStream(db: DatabaseSync, o: PushStreamOptions): Promise; //# sourceMappingURL=push.d.ts.map