export declare const LOCKED_JSON_STORE_TIMEOUT_MS: number; /** * Sentinel a `mutate` callback returns to skip the write (idempotent no-op). * The skip decision runs against the value read inside the SAME held lock as * the potential write, so it cannot race a concurrent writer that changed the * file between read and write. */ export declare const SKIP_WRITE: unique symbol; export interface LockedJsonStoreOptions { /** The JSON file this store owns. */ path: string; /** * Sibling lock file serializing every read-modify-write on `path`. Explicit * (not derived from `path`) because both consumers use an established lock * filename that is not `.lock`. */ lockPath: string; /** * Map the raw on-disk JSON value to the domain value handed to callers. * Receives `undefined` when the file is absent — return the initial value * there. May throw on corrupt/invalid content (the error propagates to the * caller; nothing is written). */ parse: (raw: unknown | undefined) => T; /** * Validate a value about to be persisted; throw to abort the write (the lock * is still released, the file is untouched). Runs on every write — never on * a {@link SKIP_WRITE} no-op. */ validate?: (next: T) => void; /** * Degrade-not-throw for a corrupt file (CP-NODE-5's recorded design for the * expected-submission set): a read that fails because the file exists but * does not PARSE resolves as if the file were absent — `parse` receives * `undefined`, so the caller's absent-path (rebuild / merge-from-nothing / * skip) IS the degrade. Infrastructure IO errors still propagate, as does a * `parse` that throws on its own judgment of the content. Off by default: the * loud-fail stores (analyzer policy, remediate state) keep their refusal. */ tolerateCorruptRead?: boolean; } export interface LockedJsonStore { /** * Lockless read: parse the current on-disk value (or the parse-supplied * initial value when the file is absent). Use {@link LockedJsonStore.mutate} * for any read-modify-write that requires TOCTOU safety. */ read: () => Promise; /** * TOCTOU-safe read-modify-write: acquires the file lock ONCE, reads + parses * the current value, passes it to `fn`, and atomically writes the returned * value (shared `writeJsonFile`: temp + atomic rename) before releasing the * lock. No other holder can interleave between the read and the write. * Returning {@link SKIP_WRITE} skips the write and resolves with the value * that was read. No caller adds backoff/retry of its own; that lives solely * in the shared lock. */ mutate: (fn: (current: T) => T | typeof SKIP_WRITE | Promise) => Promise; /** * Write `next` unconditionally under the lock, WITHOUT reading first (so a * corrupt on-disk value cannot block recovery). Prefer * {@link LockedJsonStore.mutate} for transitions; use this only when the * caller holds an external guarantee that no concurrent writer's update * could be lost. */ replace: (next: T) => Promise; } /** * A JSON file guarded by the shared {@link withFileLock}: read-under-lock → * domain parse/validate → atomic write, with the below-stale lock timeout * derived in one place. Owns only what its consumers share (the analyzer-policy * store, the remediate state store, and — with * {@link LockedJsonStoreOptions.tolerateCorruptRead} — the audit expected- * submission set, whose corrupt read degrades to absent per CP-NODE-5); * domain validation and public API shape stay with the thin adapters. */ export declare function createLockedJsonStore(options: LockedJsonStoreOptions): LockedJsonStore; /** * The sibling lock path for one store/ledger file: `.lock`, named off the * file's own stem so the lock is visibly the lock FOR that file rather than an * independently invented name. Consumers that already hold an established lock * name declare `lockPath` explicitly instead (see {@link LockedJsonStoreOptions}); * this is for the ones that own their file outright — the submission ledger, * which appends under exactly this derivation. */ export declare function siblingLockPath(targetPath: string): string; //# sourceMappingURL=lockedJsonStore.d.ts.map