/** * Verdict IPC — CLIENT side (thin hook -> resident daemon). * * Split from the server half (`verdictIpc.ts`) on purpose: the client is on * the slim hook entry's module graph, loaded on EVERY IDE hook event, so it * may import Node built-ins ONLY. The server half runs inside the resident * daemon where module weight is irrelevant. `scripts/test-slim-hook.js` * enforces the graph budget. * * Fallback contract: ANY failure — pipe missing, handshake error, slow or * busy daemon (response cap ~2.5s) — makes the hook silently fall back to its * full local evaluation, identical to before this feature existed. Local * re-evaluation after a partial daemon evaluation is safe: verdict * computation is read-mostly (worst case a duplicate telemetry event), and * enforcement is never weakened by evaluating twice. The cap guarantees a * busy daemon (blocking schtasks call, GC pause) can never make a hook * SLOWER than the pre-IPC status quo. * * Security: named pipes are reachable by other local users by default, so the * server requires a per-boot random token stored under ~/.fullcourtdefense * (user-profile ACL). A client that cannot read the token cannot get verdicts. * The protocol is line-delimited JSON — one request line, one response line. */ export declare const VERDICT_IPC_PROTOCOL_VERSION = 1; export interface VerdictRequest { v: number; token: string; /** HookArgs as parsed by the CLI entry (all-string values). */ args: Record; /** Raw stdin payload exactly as the IDE piped it (BOM already stripped). */ stdin: string; /** * Client's `Date.now()` at hook start, so the daemon-written telemetry event * carries the wall time the DEVELOPER waited (process spawn + pipe + verdict), * not just the daemon's own evaluation slice. Optional on purpose: during a * staged rollout an old client omits it (daemon falls back to its own clock) * and an old daemon ignores it — same-machine clock, so no skew. */ t0?: number; /** * Liveness ping (watchdog / startup pid-reuse check): the server replies * `{ pong: true }` without evaluating anything. Watchdog and daemon always * ship in the same build (one install, one scheduled task target), so no * cross-version compatibility concern. */ ping?: boolean; } export interface VerdictResponse { v: number; /** Present on evaluation success. */ stdout?: string; stderr?: string; exitCode?: number; /** Present when the daemon could not evaluate — client falls back locally. */ error?: string; /** Reply to a liveness ping. */ pong?: boolean; } /** Result of a client attempt. Anything but 'verdict' → evaluate locally. */ export interface VerdictClientResult { outcome: 'verdict' | 'unavailable'; stdout?: string; stderr?: string; exitCode?: number; detail?: string; } export declare function verdictStateDir(): string; export declare function verdictTokenFile(): string; export declare function verdictPipePath(): string; /** * Ask the resident daemon for a verdict. Bounded hard: connect budget ~250ms * (a missing pipe fails in <1ms with ENOENT — the common daemon-down case * costs nothing), response capped at ~2.5s. A verdict that legitimately needs * longer than that (synchronous approval wait, no-cache gate call) is exactly * the case where the IPC saves nothing — the local evaluation path handles it * with its own bounded waits. Never throws. * * Burst behavior: an agent firing many commands at once (docker compose + * exec + build in flight together) means many hooks connecting to the ONE * pipe simultaneously — some connects lose the race (EBUSY / connect * timeout) while the daemon is demonstrably alive (token present). Those * get ONE short jittered retry; hard failures (ENOENT stale pipe, refused) * still fall back to local evaluation instantly. */ export declare function requestDaemonVerdict(args: Record, stdin: string, responseTimeoutMs?: number, clientStartedAt?: number): Promise; /** * Liveness probe result for the verdict endpoint: * - 'responsive' — something answered on the pipe (any parseable reply, * including 'unauthorized': the server is alive). * - 'unresponsive' — the endpoint EXISTS (connect succeeded, or the pipe is * busy) but nothing answered within the budget. On * Windows a named pipe is a kernel object freed when its * owner dies, so an existing-but-silent endpoint means a * live-but-wedged owner. * - 'no-endpoint' — nothing is serving the pipe/socket at all. */ export type VerdictServerProbe = 'responsive' | 'unresponsive' | 'no-endpoint'; /** * Probe the verdict server without evaluating anything. Used by the watchdog * (hung-daemon detection) and by daemon startup (pid-reuse detection). Never * throws; bounded by `timeoutMs`. */ export declare function probeVerdictServer(timeoutMs?: number): Promise;