import { ProcessGroupService, type ProcessGroupHandle } from "./process-group.js"; import { type KnownProcessIdentity, type ProcessIdentityReader } from "./process-identity.js"; /** * Process-group service for a lane that must supervise its child on Windows * too — today the interactive native-login runner and the daemon that watches * it. Off win32 this is the ordinary service. On win32 it opts into the two * Windows seams: the kernel birth-time identity reader (so a recycled pid is * never mistaken for the recorded process) and this module's `taskkill /T /F` * as the terminator. Every other consumer keeps the fail-closed default, where * win32 identity stays unprovable and no group signal is ever sent. */ export declare function processGroupServiceWithWindowsSupport(platform?: NodeJS.Platform): ProcessGroupService; export interface ProcessTreeNode { pid: number; ppid: number; pgid: number; } /** * Which whole-tree termination mechanism this host offers. POSIX hosts get the * identity-proven process-group ladder above; win32 has no process groups, no * `ps`, and no ESRCH group probe — its honest minimal tree kill is * `taskkill /PID /T /F` (Job Objects would need a native addon, declined * by owner proportionality). The strategy is decided ONCE here so callers * dispatch on a named mechanism, not on a platform string. */ export type KillTreeStrategy = "posix_process_group" | "windows_taskkill"; export declare function resolveKillTreeStrategy(platform?: string): KillTreeStrategy; /** * Typed D21 disclosure reason: Windows offers no process-group emptiness probe, * so a tree kill there can never PROVE whole-tree death (taskkill enumerates * the parent-child snapshot at kill time; a descendant whose intermediate * parent already exited is unreachable). The reap reports `unconfirmed` with * this reason instead of overclaiming `confirmed`. */ export declare const WINDOWS_TREE_DEATH_PROOF_UNAVAILABLE = "windows_no_process_group_death_proof"; export type WindowsKillTreeResult = { status: "killed"; pid: number; } | { status: "not_found"; pid: number; } | { status: "failed"; pid: number; detail: string; }; /** * `taskkill /PID /T /F` via an absolute System32 path (mirrors the pinned * `PATH` used for `ps` above — a poisoned PATH must not pick the killer). * `taskkill /T` reports one aggregate exit while members of the tree can exit * during its walk, so a non-zero exit cannot prove the root was absent before * the command. Root liveness owns that distinction; whole-tree death remains * separately unprovable on Windows and is disclosed by the reap owner below. */ export declare function killWindowsProcessTree(pid: number, run?: (cmd: string, args: string[]) => { status: number | null; stdout: string; stderr: string; }, probeAlive?: (pid: number) => boolean): WindowsKillTreeResult; export interface ProcessTreeReader { /** All live processes as {pid,ppid,pgid}; [] when unreadable (fail-closed). */ snapshot(): ProcessTreeNode[]; } /** * Read the live process table via `ps` (POSIX, present on both darwin and * linux). C locale, bounded time/buffer, no shell. Any failure yields [] — the * caller then falls back to the direct-group signal it already sends, and a * genuinely alive-but-invisible group surfaces as `unconfirmed`. */ export declare function readProcessTable(run?: (cmd: string, args: string[]) => { status: number | null; stdout: string; }): ProcessTreeNode[]; export declare const defaultProcessTreeReader: ProcessTreeReader; /** * Distinct process-group ids of `rootPid` and every transitive descendant, per * a tree snapshot. BFS over ppid so a grandchild that escaped into its own * pgid is still discovered — as long as the snapshot was taken before its * parent chain was torn down. */ export declare function descendantProcessGroupIds(rootPid: number, nodes: ProcessTreeNode[]): number[]; export interface CapturedProcessGroups { handles: ProcessGroupHandle[]; /** Live pgids whose leader identity could not be proven (fail-closed). */ unresolved: Array<{ pgid: number; reason: string; }>; } /** * Identity-proven handles for every process group in `rootPid`'s tree. A pgid * whose leader is gone/recycled/unreadable lands in `unresolved` — we never * signal an unproven group. */ export declare function captureProcessTreeGroups(rootPid: number, deps?: { tree?: ProcessTreeReader; groups?: ProcessGroupService; probeGroupAlive?: (pgid: number) => boolean; }): CapturedProcessGroups; export type ProcessTreeTerminationOutcome = /** Every owned process group was proven empty (ESRCH). */ { state: "confirmed"; pgids: number[]; } /** * At least one group was still alive (or unprovable) after the bounded * escalation ladder. `survivors` were proven-nonempty; `unresolved` could not * be identity-verified so were never signalled. */ | { state: "unconfirmed"; survivors: number[]; unresolved: Array<{ pgid: number; reason: string; }>; }; export interface ReapProcessTreeOptions { /** The direct child pid; its whole descendant tree is reaped. */ rootPid: number; /** * The root's ORIGINAL identity, captured while it was provably alive (round-4 * #2). The fixed-point rescan discovers descendants from the numeric `rootPid`; * if the child exits and its PID is reused mid-deadline, a rescan would capture * an UNRELATED replacement tree. Binding the root identity stops NEW-descendant * discovery the moment the root goes missing or its identity differs — already * captured groups keep being probed to death. Absent (legacy callers): numeric * behavior, no re-verification. */ rootIdentity?: KnownProcessIdentity; /** Reads live process identity for the root re-verification (default real). */ identity?: ProcessIdentityReader; /** Handles captured elsewhere (e.g. the direct child at spawn) to include. */ seedHandles?: ProcessGroupHandle[]; /** Cooperative signal first (default SIGTERM). */ cooperativeSignal?: NodeJS.Signals; /** Grace before SIGKILL escalation (default 1000ms). */ graceMs?: number; /** Overall bound before returning `unconfirmed` (default graceMs + 4000). */ deadlineMs?: number; /** Probe/re-scan cadence (default 100ms). */ probeIntervalMs?: number; groups?: ProcessGroupService; tree?: ProcessTreeReader; sleep?: (ms: number) => Promise; now?: () => number; /** Disclosed once per newly captured group (e.g. record for crash-GC). */ onCapture?: (handle: ProcessGroupHandle) => void; /** * Raw liveness probe for a pgid whose leader identity could NOT be proven * (never a kill — signal 0 only). ESRCH -> gone (returns false); anything * else -> keep waiting (fail-closed true). Lets a group we cannot safely * signal still clear once it actually dies, instead of pinning the deadline. */ probeGroupAlive?: (pgid: number) => boolean; /** Platform whose kill-tree strategy applies (default the live host). */ platform?: string; /** Injection seam for the win32 tree killer (deterministic tests). */ windowsKillTree?: (pid: number) => WindowsKillTreeResult; /** Raw single-PID liveness probe for the win32 path (signal 0 only). */ probePidAlive?: (pid: number) => boolean; } /** * Reap `rootPid`'s whole process tree and PROVE it dead. Cooperative signal -> * bounded grace -> SIGKILL, re-scanning for groups that fork/re-group during * the race (fixed point) until every group probes empty or the deadline lapses. * * The initial capture runs synchronously (before the first await) so callers * that invoke this the instant a cancel fires snapshot the tree while its ppid * chain is still intact. */ export declare function reapProcessTree(opts: ReapProcessTreeOptions): Promise; //# sourceMappingURL=process-tree.d.ts.map