import { type ProcessIdentityReader } from "@claudexor/core"; import { type DaemonLeaseOwner, type DaemonLeaseOwnerCapability, type DaemonWriterLeaseStatus } from "./writer-lease.js"; export type DaemonTerminationOutcome = /** The daemon released its lease or its pid is gone — confirmed dead. */ { outcome: "exited"; detail: string; } /** The graceful window lapsed; an identity-VERIFIED SIGKILL brought it down. */ | { outcome: "killed"; detail: string; } /** Still alive at the deadline (or unkillable without identity proof). */ | { outcome: "still_alive"; detail: string; }; export interface AwaitDaemonTerminationOptions { /** Total confirmation budget (default 20s: the daemon's own W-C8 ladder * self-exits within its 15s stop deadline + 2s drain sweep + slack). */ deadlineMs?: number; /** Graceful window before the SIGKILL escalation (default 17s). */ killAfterMs?: number; /** Whether this caller has authority to escalate to SIGKILL (default false). * Runtime * replacement grants that authority only after an explicit fenced admission * receipt; an ambiguous RPC failure may observe death but never cause it. */ allowSigkill?: boolean; /** Exact daemon process instance selected before the stop RPC. Without this * the waiter could pin a same-build successor that acquired the lease while * the admission response was in flight. */ expectedOwner?: DaemonLeaseOwner; /** Runtime replacement refuses a successor observed during this termination * proof. The separately-adjudicated post-return/pointer-swap gap is outside * this waiter's observation window. */ requireNoSuccessor?: boolean; pollMs?: number; } export interface DaemonTerminationDeps { identity?: ProcessIdentityReader; kill?: (pid: number, signal: NodeJS.Signals) => void; isAlive?: (pid: number) => boolean; sleep?: (ms: number) => Promise; now?: () => number; } /** * Strict writer-lease authority used by termination. This is deliberately a * separate argument rather than new optional members on DaemonTerminationDeps: * downstream dependency objects may already use these names for unrelated * private or runtime state. */ export interface DaemonTerminationLeaseAuthority { inspect(socketPath: string): DaemonWriterLeaseStatus; classify(owner: DaemonLeaseOwner): DaemonLeaseOwnerCapability; } /** * Await the CONFIRMED death of the daemon owning `socketPath`'s writer lease * (W3.5): "stop requested" is not "stopped" — a disposer that removes state * under a still-live daemon manufactures orphans. * * The target is PINNED at entry (`terminateAndWait(exactIdentity, deadline)`): * the lease owner is snapshotted once and every later observation is judged * against THAT owner. Re-reading the lease each poll would follow whoever * currently holds it, so a replacement daemon started during the confirmation * window (the app auto-starts one) could be waited on — and SIGKILLed — in * place of the process we were asked to stop. * * Confirmed death = the pinned owner released its lease, or the canonical * classifier proves its pid missing, recycled, or a Linux zombie. A takeover * alone does not prove that the old owner exited. Past the graceful window a * SIGKILL is sent ONLY when an explicitly supplied pinned birth identity still * matches the process in that same iteration; without that proof this fails * closed to an honest `still_alive`. */ export declare function awaitDaemonTermination(socketPath: string, options?: AwaitDaemonTerminationOptions, deps?: DaemonTerminationDeps, leaseAuthority?: DaemonTerminationLeaseAuthority): Promise; //# sourceMappingURL=terminate.d.ts.map