/** * Durable post-commit bookkeeping for a V2 uploader. * * A server commit is not the point at which the local cursor may move. This * module first makes the server result and its self-delta durable in one WAL * record, then lets the ordered delta consumer acknowledge that delta later. * Keeping those transitions separate is what makes a generation that changes * while an older upload is in flight rebase instead of becoming a conflict. */ import { StateStore, type StateStoreRecord } from "./state-store.js"; export interface UploaderFinalizationOptions { rootDir: string; scopeId: string; maxRecordsBeforeCompaction?: number; } export interface DirtyGeneration { path: string; generation: number; sha256: `sha256:${string}`; /** The exact canonical version on which this generation was prepared. */ baseVersionId: string | null; } export interface LocalAttempt extends DirtyGeneration { attemptId: string; } /** * The immutable local identity and recovery bytes selected before `prepare`. * Keeping this in the v3 journal makes a lost prepare response replayable with * the same idempotency key rather than allocating a second generation. */ export interface PlannedMutation extends DirtyGeneration { clientMutationId: string; operation: "upsert" | "delete"; membershipRevision: string; scopeRevision: string; bytes?: { filePath: string; size: number; sha256: `sha256:${string}`; mode: number; }; } export interface CommittedMutation { attemptId: string; path: string; generation: number; sha256: `sha256:${string}`; versionId: string | null; deltaId: string; } export interface SelfDeltaDrain { /** Durable cursor before this ordered page was applied. */ cursorBefore: string | null; /** Cursor emitted by the ordered delta response after this delta. */ cursorAfter: string; /** The contiguous delta IDs consumed by this drain, ending with deltaId. */ contiguousDeltaIds: readonly string[]; } export interface PendingSelfDelta { attemptId: string; path: string; generation: number; versionId: string | null; consumed: boolean; } export interface UploaderPathState { generation: number; sha256: `sha256:${string}`; baseVersionId: string | null; synchronizedVersionId: string | null; pendingAttemptId?: string; plannedMutation?: PlannedMutation; attempts: Record; } export interface UploaderFinalizationState { cursor: string | null; paths: Record; finalizedAttempts: Record; selfDeltas: Record; /** Commit order is the only ordering information available before U53. */ selfDeltaOrder: string[]; } export type FinalizationOutcome = "rebased-current-generation" | "rebased-later-generation" | "already-finalized"; export interface FinalizationResult { outcome: FinalizationOutcome; rebasedGeneration: number; cursor: string | null; } export declare function initialUploaderFinalizationState(): UploaderFinalizationState; /** Opens the state-store generation used exclusively by uploader bookkeeping. */ export declare function openUploaderFinalizationStore(options: UploaderFinalizationOptions): StateStore; /** * The public writer-side finalization seam. Its calls intentionally append * exactly one frame per logical transition; StateStore fdatasyncs that frame * before this method returns. */ export declare class UploaderFinalizer { private readonly store; constructor(store: StateStore); getState(): UploaderFinalizationState; recordDirty(input: DirtyGeneration): void; recordAttempt(input: LocalAttempt): void; /** Persist the replay identity and durable source before any prepare I/O. */ recordMutationPlan(input: PlannedMutation): void; /** * Atomically records the logical server result and the self-delta marker. * It never changes the scope cursor: only consumeSelfDelta may do that. */ finalize(input: CommittedMutation): FinalizationResult; /** * Called only by the ordered delta drain after it has durably handled a * contiguous self delta. Calling finalize can therefore never jump cursor. */ consumeSelfDelta(deltaId: string, drain: SelfDeltaDrain): void; } export declare function reduceUploaderFinalization(state: UploaderFinalizationState, record: StateStoreRecord): UploaderFinalizationState; //# sourceMappingURL=uploader-finalization.d.ts.map