/** * Shared session-file persistence for the local review writers * (`survey-review-console` and `survey-review-mcp`). * * Every write goes through {@link updateReviewSessionFile}, which takes one * exclusive lock file next to the session, re-reads the session inside the * lock, applies the caller's change, writes a uniquely named temp file and * renames it into place. Both writers must use this helper: a lock that only * one of them takes still races with the other (kontourai/survey#281). */ import type { ReviewQueueSessionState } from "./review-workbench/review-queue-session.js"; import type { ReviewSessionEvent } from "./review-resource.js"; export interface ReviewSessionFileContent { readonly session: unknown; readonly snapshot: ReviewQueueSessionState; readonly events: readonly ReviewSessionEvent[]; /** * The extraction import record (`ExtractionEnvelopeImport`) the snapshot's * items were built from, stored beside them so every reload path can check * the queue against it. Optional: a session without one whose items came * from an import is presented as an unverified queue. */ readonly extractionImport?: unknown; /** Presentation receipt after a producer consumed this session; further appends refuse. */ readonly producerApplication?: { readonly reference: string; readonly summary: string; readonly revision: string; }; } export interface ReviewSessionFileLockOptions { /** How long to wait for a busy lock before failing. Defaults to 10s. */ readonly timeoutMs?: number; /** A lock older than this is treated as abandoned. Defaults to 30s. */ readonly staleMs?: number; } export declare class ReviewSessionFileLockTimeoutError extends Error { constructor(lockPath: string, timeoutMs: number); } export declare function reviewSessionLockPath(sessionPath: string): string; export declare function readReviewSessionFile(sessionPath: string): Promise; /** * Opaque revision token for an event log: a digest of its serialized form. * An event count is not enough, because the log is regenerated from session * state and a changed decision keeps the count while changing the content. */ export declare function reviewSessionRevision(events: readonly ReviewSessionEvent[]): string; /** * Acquire the exclusive session lock. Resolves with a release function. * Exported so tests can hold the lock and observe that writers wait for it. */ export declare function acquireReviewSessionFileLock(sessionPath: string, options?: ReviewSessionFileLockOptions): Promise<() => Promise>; /** * Locked read-modify-write of a session file. `mutate` receives the content * read inside the lock and returns the content to write, or `undefined` to * leave the file untouched. Whatever `mutate` throws propagates after the lock * is released. */ export declare function updateReviewSessionFile(sessionPath: string, mutate: (current: T) => Promise<{ readonly next?: T; readonly result: R; }> | { readonly next?: T; readonly result: R; }, options?: ReviewSessionFileLockOptions): Promise; /** * The session name the stored log is recorded under: the name its events * already carry, else the stored ReviewSession's name, else `fallback` (the * writer's own default). Appended events are renamed into it so one log never mixes names. */ export declare function storedReviewSessionName(content: ReviewSessionFileContent, fallback?: string): string; /** * Append events to the stored log, renumbering them after the stored events * and renaming them into the stored session. The stored log is never * rewritten, so decision reversals and note changes stay on record. */ export declare function appendReviewSessionEvents(content: ReviewSessionFileContent, appended: readonly ReviewSessionEvent[]): ReviewSessionEvent[];