/** * 파일 단위 advisory lock — read-modify-write race 방지 * * 문제: * - injection cache를 여러 hook(solution-injector / pre-tool-use / backfill)이 * read → modify → write 패턴으로 갱신하지만 락이 없어서 last-writer-wins. * - rename atomicity는 찢어진 JSON만 막을 뿐, 동시 mutator의 변경을 보존하지 못한다. * * 해결: * - O_EXCL로 `${target}.lock` 파일 생성 → exclusive lock. * - withFileLock() 안에서 호출되는 fn은 fresh re-read 후 mutate해야 함. * - lock holder가 죽어 stale lock이 남으면 mtime + PID 검증으로 안전 회수. * - lock 파일에 randomBytes token 기록, release 시 token 일치할 때만 unlink * → cascade lock loss 방지 (H4+H7 fix). * * 외부 의존성 없음. 다중 OS 호환 (POSIX + Windows). * * Windows 한계: * - file-lock 자체는 동작하지만, 같은 process의 다른 fd가 lock 파일을 read하지 * 못하게 막지는 않는다 (advisory lock). * - lock 파일 mode 0o600은 POSIX에서만 의미. Windows는 ACL 기반. */ export interface FileLockOptions { /** 락 획득 최대 대기 시간 (ms). 초과 시 throw. */ timeoutMs?: number; /** 이만큼 오래된 lock 파일은 stale로 간주하고 강제 회수 (ms). */ staleMs?: number; } /** * file-lock 자체 결함 (Sentinel). * caller try/catch가 lock 결함과 fn 실패를 구분할 수 있도록 별도 클래스. */ export declare class FileLockError extends Error { readonly cause: NodeJS.ErrnoException; readonly lockPath: string; constructor(cause: NodeJS.ErrnoException, lockPath: string); } /** * 락을 획득한 후 fn을 실행하고, 끝나면 락을 해제한다. * * fn은 동기 또는 async 모두 지원. 예외가 발생해도 락은 항상 해제된다. * * release 시 lock token을 검증해 자기가 만든 lock만 unlink한다 (H4+H7 fix). * * 사용 예: * ```ts * await withFileLock(cachePath, () => { * const fresh = readCacheFromDisk(cachePath); // lock 안에서 fresh re-read * const merged = mergeWithUpdates(fresh); * atomicWriteJSON(cachePath, merged, { mode: 0o600 }); * }); * ``` */ export declare function withFileLock(targetPath: string, fn: () => T | Promise, options?: FileLockOptions): Promise; /** 동기 버전 — async fn을 받지 않음. setTimeout 대신 짧은 spin으로 대기. */ export declare function withFileLockSync(targetPath: string, fn: () => T, options?: FileLockOptions): T;