/** * The kill switch — one durable STOP for unattended work (ISS-0178). * * WHAT IT STOPS, AND WHAT IT DELIBERATELY DOES NOT. The metaphor is a machine * tool's big red button: it halts the automation, it does not power down the * panel. Unattended work — the agentic queue replaying remote writes, the dojo * experiment runner spawning a script — refuses while stopped. Interactive * calls keep working on purpose, because the first thing anyone does after * hitting stop is ask what went wrong, and that takes sentinel, oracle and this * module itself. * * THE SAFETY INVARIANT. You can never stop yourself out of resuming. That holds * BY CONSTRUCTION rather than by an exception list: nothing in this file gates * itself, and enforcement lives at the call sites of the work being stopped. * There is no path where `resume` consults the flag it is trying to clear. * * WHY A FILE, AND WHY THIS FILE. State lives at ~/.decibel/killswitch.json: * * - machine-global, because a button on a desk is machine-global. Stopping * "the project you happen to be in" is not what a red button means. * - a file, so it survives a daemon restart, a crash, and a reboot. A stop * that forgets itself is worse than no stop, because it teaches people the * switch is unreliable. * - plain JSON with no schema tricks, so a process that is not this one — a * shell script, a microcontroller, a future physical button — can read and * write it without linking anything. * * WHAT IT IS NOT. It is not a security control. Anything that can write to * ~/.decibel can clear it. It is an operational brake for work you started, in * the same spirit as the daemon's own PID file. */ /** Machine-global, deliberately — see the header. */ export declare const KILL_SWITCH_PATH: string; export interface KillSwitchState { /** The only field a reader strictly needs. */ stopped: boolean; /** Why, when stated. A button has no keyboard, so this is optional by design. */ reason?: string; /** Who pressed it: an agent id, a person, or 'button' for the physical one. */ actor?: string; /** ISO timestamp of the stop that is currently in force. */ stopped_at?: string; /** Free-form origin, e.g. 'mcp', 'http', 'device'. Useful when a stop surprises someone. */ source?: string; } /** * Read the current state, treating every failure as RUNNING. * * FAIL-OPEN IS THE RIGHT CHOICE HERE and it is worth saying why, because the * opposite is defensible and wrong for this case. A corrupt or unreadable * killswitch file would, under fail-closed, halt all unattended work on the * machine with no way to diagnose it except editing the very file that is * broken — a brake that jams on is worse than one that does not engage. The * stop is an operational convenience, not a safety interlock, and it says so in * the header. * * Note this is the one place the module is deliberately quiet: a missing file * is the overwhelmingly common case (nobody has ever pressed stop) and must not * be noise. */ export declare function readKillSwitch(): KillSwitchState; /** True when unattended work should refuse. */ export declare function isStopped(): boolean; /** * Engage the stop. Idempotent: a second press reports the FIRST stop rather * than overwriting it, because a button will be pressed twice and the original * reason and time are the ones worth keeping. * * @returns the state now in force, and whether this call is what engaged it */ export declare function engageStop(opts?: { reason?: string; actor?: string; source?: string; }): { state: KillSwitchState; already: boolean; }; /** * Release the stop. Idempotent, and never gated — see the safety invariant. * * @returns the state that was cleared, when there was one */ export declare function releaseStop(): { released: KillSwitchState | null; }; /** * Thrown at the gate. Carries the stop's own reason and time so the refusal can * say WHY work stopped rather than merely that it did — the difference between * a legible brake and a mystery. */ export declare class KillSwitchEngagedError extends Error { readonly code = "KILL_SWITCH_ENGAGED"; readonly state: KillSwitchState; constructor(operation: string, state: KillSwitchState); } /** * The gate. Call at the top of anything that runs without someone watching. * * Takes the operation name so the refusal names what it refused — "agentic * queue replay" is actionable, "operation failed" is not. */ export declare function assertRunnable(operation: string): void; /** * Non-throwing form of {@link assertRunnable}, for call sites whose contract is * to RETURN an error rather than throw one. * * Both forms exist deliberately. `assertRunnable` suits functions that already * throw (agentQueueSync throws for unconfigured Supabase, so a throw is in * keeping); this suits functions that declare `Promise`, * where throwing would be a lie about the signature and would behave * differently when called directly than when called through a handler that * catches. That divergence between the direct and dispatched path is a bug * shape this repo has been bitten by more than once. */ export declare function checkRunnable(operation: string): KillSwitchEngagedError | null; //# sourceMappingURL=killSwitch.d.ts.map