/** * Advisory file locking for the local-files store (DEC-036). * * The store layout (DEC-031) was originally written under the assumption that * a single CLI/MCP process owned the `/.h2a/` tree at any given time. * In practice, multiple `h2a` invocations and an `h2a mcp-serve` process can * race on the same root. Append-only JSONL files (`registry/instances.jsonl`, * `negotiations//journal.jsonl`) survive this via the `PIPE_BUF` * atomicity of `appendFileSync` for sub-4KB lines, but every read-then-write * critical section (dup detection on `registerInstance`, hash-chain link on * `appendNegotiationEvent`, the whole `stabilizeNegotiation` transaction) * remains a race. * * This module implements **advisory** file locking via exclusive-create * sentinel files (`O_CREAT | O_EXCL` semantics through `openSync(path, "wx")`). * It is intentionally limited to **same-machine** coordination: a lock file * holding a foreign-host PID would be treated as live (we cannot * `process.kill(pid, 0)` across hosts). Cross-machine sharing of a store * remains out of scope (V2). * * Stale-lock recovery: if the lock file already exists, we parse its JSON * payload `{pid, hostname, startedAt}`. If `hostname` matches and the PID is * gone (`process.kill(pid, 0)` throws `ESRCH`), we reclaim the lock by * unlinking and retrying. Otherwise we poll up to `timeoutMs` and throw * `LockTimeoutError`. */ export interface LockOwner { readonly pid: number; readonly hostname: string; readonly startedAt: string; /** Optional protocol marker for a lock with a stronger caller invariant. */ readonly protocol?: string; /** Caller-issued generation for a fenced critical section. */ readonly fenceEpoch?: string; } export interface WithLockOptions { readonly timeoutMs?: number; readonly pollMs?: number; /** * Extra immutable identity carried by the lock owner record. Callers that * need a structural fence use this to bind the lock to a protocol + epoch. */ readonly ownerMetadata?: Readonly>; /** * Whether a dead same-host holder may be reclaimed. Destructive protocols * deliberately disable this: an ambiguous fence is a refusal, never a * reason to silently continue. */ readonly reclaimStale?: boolean; } export declare class LockTimeoutError extends Error { readonly lockPath: string; readonly lastSeenOwner?: LockOwner; constructor(lockPath: string, lastSeenOwner: LockOwner | undefined, timeoutMs: number); } export declare function withLock(lockPath: string, fn: () => T | Promise, options?: WithLockOptions): Promise; export declare function withLockSync(lockPath: string, fn: () => T, options?: WithLockOptions): T; //# sourceMappingURL=locks.d.ts.map