import { type ProcessIdentity } from "./processes.js"; export declare const SERVER_LOCK_SCHEMA: "pi-workflows.server-lock.v1"; /** Grace between the graceful stop request and the forced stop. */ export declare const SERVER_STOP_GRACE_MS = 5000; /** One lock record names the server process and fences a reused process ID. */ export type ServerLockRecord = ProcessIdentity & { serverId: string; }; export type RecordedServerStop = { pid: number; serverId: string; /** True when the process ignored the graceful request and a forced stop followed. */ forced: boolean; /** True when the recorded process no longer holds its start identity. */ exited: boolean; }; export declare function serverDirectoryPath(databasePath: string): string; export declare function serverLockPath(databasePath: string): string; export declare function isServerLockRecord(value: unknown): value is ServerLockRecord; /** Read the recorded server, or return undefined for a missing or unusable lock file. */ export declare function readServerLock(lockPath: string): ServerLockRecord | undefined; export declare function writeServerLock(lockPath: string, record: ServerLockRecord): void; /** One bounded socket probe: does a server accept connections here and answer? */ export type ServerLockProbe = (socketPath: string, timeoutMs: number) => Promise; /** * Ask the socket whether a server is really serving. Serving means the connect * succeeds and the server's hello arrives within the timeout; a connect error or * a silent window means the holder is alive but not serving, as during shutdown * or while frozen. The probe socket is always destroyed. */ export declare function probeServerServing(socketPath: string, timeoutMs: number): Promise; /** * Take the exclusive server lock for a starting server. A lock whose recorded * process is gone, or whose process is alive but provably not serving, is stale * and is removed. A holder that answers on the socket keeps the lock, and the * caller must not start. Fencing against a second live server stays with the * epoch claim, not with this file: a probe misfire costs one wasted start, and * the loser exits when its claim fails. * * The probe awaits, so the lock record can change while it runs. After a * not-serving verdict the record is re-read, and the lock is only removed when * it still names the same silent holder; a record another starter already * replaced makes this starter re-read and converge instead of clobbering the * newer takeover. Creation stays exclusive (`wx`), so a racing creator loses * and re-reads. Concurrent starters can therefore only displace a holder that * is provably not serving, and the epoch claim fences whichever of them wins. * * The returned `displaced` record is the live holder this start replaced, so a * starter whose claim is then fenced can restore the holder's lock file * instead of leaving the serving holder without one. */ export declare function acquireServerLock(lockPath: string, record: { pid: number; startIdentity: string; serverId: string; }, options: { socketPath: string; probeTimeoutMs?: number; probe?: ServerLockProbe; }): Promise<{ displaced: ServerLockRecord | undefined; }>; /** * Stop the server named by the lock file when the recorded process still has its start * identity. A client whose package version differs from the running server cannot send a * stop request, so the lock file is the only path to that stale process. A record whose * process is gone, or whose process ID now belongs to another process, stops nothing. */ export declare function stopRecordedServer(lockPath: string, options?: { graceMs?: number; }): Promise;