/** * One cycle of watching, and the state that survives a restart. * * `--once` is the primitive: pull the window, keep it, evaluate the gates, * emit what crossed, save state. A cron entry runs exactly that, and so does * every test. The foreground loop is this function in a timer, so there is one * code path and no daemon-only behaviour that nobody exercises. * * **The state file is what makes a restart honest.** Without it a resumed * watcher re-alerts on yesterday's crossing (noise nobody reads) and implies * it was watching the whole time (a claim it cannot make). With it, the * crossing stays quiet and the unwatched stretch gets named once. */ import type { WatchCrossing } from '@trazum/core'; export declare const WATCH_STATE_FILE = ".trazum/watch.json"; export declare const WATCH_STATE_VERSION = 1; export interface WatchState { v: number; /** When the last cycle ran, so a long silence can be told from a first run. */ lastCycleMs: number; /** How far the measurements reached, for the coverage gap. */ lastCoveredToMs: number | null; /** Gate keys already alerted on, so a restart is not amnesia. */ fired: Record; } export declare function readWatchState(root: string): Promise; export declare function writeWatchState(root: string, state: WatchState): Promise; /** * Whether a webhook URL is one this tool will post to. * * **This is not the SSRF case and the difference matters.** `checkedEndpoint` * exists because a *request body* must never name a host: an anonymous caller * pointing a shared server at an internal address is somebody else's machine * reaching somewhere it was never meant to. Here the URL is in the operator's * own config, on their own machine, and pointing it at their own alerting * daemon on loopback is the ordinary case rather than the attack. * * So loopback is allowed and plain http is allowed *only* there, while two * rules stay absolute: no credentials embedded in the URL, because a URL ends * up in logs and shell history; and https everywhere else, because an alert * carries spend figures across a network. */ export type WebhookRejection = 'invalid-url' | 'credentials-in-url' | 'insecure-scheme'; export declare function checkWebhook(raw: string): { ok: true; url: URL; } | { ok: false; reason: WebhookRejection; }; /** * The alert payload. * * Figures and gate names, never prompt text — the store has never held any and * neither does this. Every crossing carries its own provenance, so a receiver * that fans these into a dashboard cannot lose track of what kind of number it * is holding. */ export interface WatchAlert { schemaVersion: 1; firedAtMs: number; crossings: WatchCrossing[]; } export declare function postWebhook(url: URL, alert: WatchAlert, fetchImpl?: typeof fetch): Promise<{ ok: boolean; status: number | null; error: string | null; }>; //# sourceMappingURL=watch-run.d.ts.map