/** * The watch daemon notices when its own code changes, and comes back on the new * code instead of running the old one indefinitely. * * ── Why ────────────────────────────────────────────────────────────────── * A long-lived daemon holds whatever bundle it started with. Node has already * parsed it; replacing the file on disk changes nothing about the running * process. So a fix can be committed, built, installed and still not be in * effect — with no signal anywhere that the process is stale. * * That has caused three separate incidents in this codebase: * - the trufflehog /tmp leak kept accreting 34MB per sync for a day after the * fix shipped, because the daemon predated it; * - the cross-home union landed and the collector kept reading one home for * another day, stranding 694 records of a live session; * - the ledger ack fix, where the daemon had been restarted just BEFORE the * commit and so needed restarting a third time. * * `auto-update.ts` already handles the published-release path (fetch, verify, * `npm i -g`, restart) which is what real customers hit. This covers the other * case: the bundle changing underneath a running daemon — a linked dev build, a * manual `npm i -g`, or a package manager upgrading the CLI without touching the * service. * * ── How it comes back matters more than how it notices ─────────────────── * Exiting is only safe when something will start us again, and that differs: * * systemd Restart=on-failure → must exit NON-ZERO (a clean exit stays down) * launchd KeepAlive=true → restarts on any exit * schtasks /sc onlogon → NEVER restarts; exiting means down until the * next logon, which is worse than stale code, * so we re-exec ourselves instead * foreground (no supervisor) → exiting would just kill the user's process; * warn loudly and keep running * * Getting that wrong turns a staleness fix into an outage, which is why the exit * code is 75 (EX_TEMPFAIL) rather than 0: it satisfies the `on-failure` policy * that is already installed on every existing machine, so this works without * re-rendering anyone's unit file. */ /** Non-zero so systemd's `Restart=on-failure` picks it up. EX_TEMPFAIL. */ export declare const EXIT_CODE_CODE_CHANGED = 75; export interface CodeFingerprint { path: string; size: number; /** * SHA-256 of the bundle bytes. * * mtime is deliberately NOT part of the identity. It was, and it made this * guard fire on rebuilds that changed nothing: an `npm i -g` of the SAME * version rewrites the file, so the mtime moves while every byte stays equal. * The log read `changed on disk (731957 -> 731957 bytes)` 90 times in one * day, and each line was a real process exit — 90 restarts that picked up no * new code, on top of a daemon that was already crash-looping. * * The content hash answers the actual question. An identical rebuild is not a * change, and a same-size edit still is. */ hash: string; } /** * Identity of the bundle we are running. The build produces a single bundled * `watch.js` (engine included), so one file answers "did my code change?" — * no need to walk node_modules. * * This reads the bundle (~750 KB) rather than stat-ing it. The caller is a * 60-second liveness tick, so the cost is one hash per minute, and correctness * here is worth far more than the microseconds: a false positive exits the * process, and a false negative leaves a fixed daemon running broken code. */ export declare function codeFingerprint(entry?: string): CodeFingerprint | null; export declare function sameFingerprint(a: CodeFingerprint | null, b: CodeFingerprint | null): boolean; export type Supervisor = 'systemd' | 'launchd' | 'windows-task' | 'none'; /** * Who, if anyone, will restart us. systemd exports INVOCATION_ID (and * JOURNAL_STREAM) to its units; launchd exports XPC_SERVICE_NAME. Neither is * something a normal shell has, so their presence is a reliable "I am * supervised" signal. */ export declare function detectSupervisor(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): Supervisor; export type RestartAction = { kind: 'exit'; code: number; } | { kind: 'reexec'; } | { kind: 'warn'; }; /** What to do about a detected code change, given who is supervising. */ export declare function restartActionFor(sup: Supervisor): RestartAction; export interface SelfRestartOptions { /** Fingerprint captured at boot. */ boot: CodeFingerprint | null; log?: (m: string) => void; /** Test seam. */ now?: () => CodeFingerprint | null; supervisor?: Supervisor; /** Test seam — defaults to actually exiting. */ exit?: (code: number) => void; } /** * Check whether our bundle changed and act on it. Returns true when a restart * was triggered (so callers can stop scheduling work). * * Opt out with CHAT_RECALL_SELF_RESTART=0 — the escape hatch for anyone * debugging with a hot-reloading build who does not want the process bouncing. */ export declare function checkSelfRestart(opts: SelfRestartOptions): boolean; //# sourceMappingURL=self-restart.d.ts.map