/** * Leader election via atomic lockfile on the graph directory. * * Exactly one process per `graphDir` owns `graph.lock` and is the "leader". * Other processes observe the lockfile and act as "followers" that will * connect to the leader over IPC (see Phase 2). * * Locking strategy: * - `openSync(path, 'wx')` is atomic on POSIX and Windows: exactly one * caller wins the create race. * - The winner writes metadata (pid, socketPath, startedAt, heartbeatAt, * version) and periodically refreshes `heartbeatAt`. * - Losers parse the file and decide if the lock is stale: * * `process.kill(pid, 0)` → dead pid is always stale, * * `now - heartbeatAt > staleMs` → stale even if pid survives, * * malformed/missing fields → stale. * - When stale, we unlink and retry acquire once. This is safe because the * unlink+create pair is still governed by the atomic `wx` — two racing * followers that both observe staleness will only let one create. */ export declare const LEADER_LOCK_VERSION = 1; export declare const LEADER_LOCK_FILENAME = "graph.lock"; /** Default heartbeat refresh interval (ms). */ export declare const DEFAULT_HEARTBEAT_MS = 5000; /** Default staleness threshold (ms). Heartbeat older than this is considered stale. */ export declare const DEFAULT_STALE_MS = 15000; export interface LeaderInfo { version: number; pid: number; socketPath: string; startedAt: number; heartbeatAt: number; } export interface AcquireOptions { /** Path of the IPC endpoint this process will listen on if it becomes leader. */ socketPath: string; /** Milliseconds between automatic heartbeat refreshes. */ heartbeatMs?: number; /** Milliseconds after which a lockfile without fresh heartbeat is stale. */ staleMs?: number; /** If true, do not install process exit handlers (useful for tests). */ skipExitHandlers?: boolean; /** Injectable clock for tests. */ now?: () => number; /** Injectable pid check for tests. Return true if pid is alive. */ isAlive?: (pid: number) => boolean; } export interface LeaderHandle { readonly role: 'leader'; readonly info: LeaderInfo; readonly lockPath: string; /** Rewrite the lockfile with a bumped heartbeat. Safe to call repeatedly. */ refresh(): void; /** Validate that the on-disk lockfile still belongs to this process. */ validateOwnership(): boolean; /** Stop heartbeat timer, unlink the lockfile if we still own it. */ release(): void; } export interface FollowerObservation { readonly role: 'follower'; readonly info: LeaderInfo; readonly lockPath: string; } export type AcquireResult = LeaderHandle | FollowerObservation; export declare function readLeaderInfo(graphDir: string): LeaderInfo | null; export declare function acquireLeader(graphDir: string, options: AcquireOptions): AcquireResult; //# sourceMappingURL=leader-lock.d.ts.map