/** * Lightweight filesystem lock for preventing concurrent mutations * to .totem/lessons/ and .lancedb/ directories. * * Uses a lockfile with PID + heartbeat timestamp + per-acquisition holder id. * The holder refreshes the timestamp every HEARTBEAT_INTERVAL_MS, so a stale * timestamp means many consecutive missed beats — a dead or wedged holder — * and the lock is reclaimed (#2564). Every deletion re-reads the file first * and removes only the exact lock it judged dead (TOCTOU narrowing, * lesson-b813e60b). */ interface LockData { pid: number; /** Last heartbeat (the acquisition instant until the first beat). */ timestamp: number; /** Acquisition instant — provenance only. Absent on pre-#2564 locks. */ createdAt?: number; /** * Opaque per-acquisition token; heartbeats and every deletion verify * against it. Absent on pre-#2564 locks. */ holderId?: string; } /** * Release handle returned by acquireLock: callable to release, plus a probe * for lost mutual exclusion. Callable-with-property keeps the shape * assignable to the pre-#2564 `() => void` return type. */ export interface LockRelease { (): void; /** True once this hold observably lost the lock to another process. One-way latch. */ isLost(): boolean; } /** * Acquisition tuning. The timing fields are test seams; `maxRetries` is also * a production surface — a caller with its own fallback (MCP add_lesson under * a long-held lock, #2564 leg MAJOR-R1) bounds its blocking instead of * waiting out the full ~255s default budget. */ export interface AcquireLockOptions { staleThresholdMs?: number; heartbeatIntervalMs?: number; maxRetries?: number; } /** * Delete the lock only if its content still matches the snapshot we judged * dead — a peer that already stole and rewrote the lock must not lose its * fresh lock to our stale judgment (lesson-b813e60b). A window between the * re-read and the unlink remains; this narrows it from the whole judgment * gap to a few instructions. Exported for direct contract tests — same-process * tests cannot interleave the synchronous judge→delete sequence. */ export declare function deleteLockIfUnchanged(filePath: string, snapshot: LockData): void; /** * Attempt to acquire the sync lock. * Returns a release handle on success, or throws after retries are exhausted. */ export declare function acquireLock(totemDir: string, onWarn?: (msg: string) => void, opts?: AcquireLockOptions): Promise; /** * Execute a function while holding the sync lock. * Automatically releases the lock when done (even on error). The callback * receives the release handle so long-running work can probe `isLost()` at * its own mutation boundaries (#2564); zero-arg callbacks are unaffected. */ export declare function withLock(totemDir: string, fn: (lock: LockRelease) => Promise, onWarn?: (msg: string) => void, opts?: AcquireLockOptions): Promise; export {}; //# sourceMappingURL=lock.d.ts.map