/**
 * Native per-monitor state-diff store.
 *
 * This is the one genuinely new primitive monitors add over routines: a
 * last-observed-*value* store. Routines persist per-*run* metadata but have no
 * last-seen value, so hand-built watchers (the RUSH-1107 SSL watcher) re-invented
 * state-diffing through a markdown memory file every time. This store kills that.
 *
 * Layout (sibling of the runs layout, atomic writes like writeRunMeta):
 *   ~/.agents/.history/monitors/<name>/state.json      # last-seen hash/value + fire bookkeeping
 *   ~/.agents/.history/monitors/<name>/fires/<id>/…    # fire history
 */
import { type RunMeta } from '../scheduling/routines.js';
import type { MonitorEvent } from './config.js';
/** Persisted last-seen state for one monitor. */
export interface MonitorState {
    monitorName: string;
    /** Hash of the last-seen de-dupe signature (see hasChanged). */
    lastHash: string;
    /** The last-seen raw observation (truncated for storage). */
    lastValue: string;
    /** RFC3339 timestamp of the last observation. */
    lastSeenAt: string;
    /** RFC3339 timestamp of the last fire, when the monitor has ever fired. */
    lastFiredAt?: string;
    /** Epoch-ms timestamps of recent fires, for the rate-limit / firehose guard. */
    fireTimes?: number[];
}
/**
 * Per-monitor liveness heartbeat, recorded on EVERY poll — fire or not, match or
 * not. This is deliberately a separate record from MonitorState: change-detection
 * state (lastHash/lastValue) is written only when a monitor fires or establishes
 * a baseline, so a monitor that polls steadily but never matches leaves no
 * change-detection trace. Without a heartbeat, `view` on such a monitor showed
 * `state: null` — indistinguishable from a monitor the engine never touched, the
 * exact confusion RUSH-2485 reports. The heartbeat makes "never checked" (no
 * record) visibly distinct from "checked N times, not matching" (recent record,
 * zero fires). Kept in its own file so it can never perturb the baseline logic in
 * decideFire/hasChanged.
 */
export interface MonitorLiveness {
    monitorName: string;
    /** RFC3339 timestamp of the last poll attempt. */
    lastCheckedAt: string;
    /** Total polls the engine has run against this monitor's source. */
    checkCount: number;
    /** The last poll's error (source produced nothing / threw), cleared on the next good poll. */
    lastError?: string;
    /** Consecutive failed polls; reset to 0 on any successful observation. Drives drought escalation. */
    consecutiveErrors: number;
    /** RFC3339 timestamp of the last drought notification, so the engine notifies once per drought. */
    droughtNotifiedAt?: string;
}
/** Per-monitor history root, with the (untrusted) name contained to one segment. */
export declare function getMonitorHistoryDir(name: string): string;
/** Directory holding a monitor's fire history. */
export declare function getMonitorFiresDir(name: string): string;
/** Read a monitor's last-seen state, or null if it has never been observed. */
export declare function readState(name: string): MonitorState | null;
/** Persist a monitor's state atomically (temp file + rename, like writeRunMeta). */
export declare function writeStateRaw(state: MonitorState): void;
/**
 * Record a new observation as the monitor's last-seen state, preserving fire
 * bookkeeping. Truncates the stored raw value so a firehose can't bloat disk.
 */
export declare function writeState(name: string, value: string, dedupeKey?: string, extra?: Partial<Pick<MonitorState, 'lastFiredAt' | 'fireTimes'>>): MonitorState;
/**
 * The de-dupe signature for an observation. When `dedupeKey` is set, the
 * signature is the first regex match of dedupeKey against the observation (so
 * "the same event" is same matched token); otherwise it is the full observation.
 * An unmatched dedupeKey falls back to the full observation.
 */
export declare function dedupeSignature(observation: string, dedupeKey?: string): string;
/**
 * True when `observation`'s de-dupe signature differs from the monitor's
 * last-seen signature (or the monitor has never been observed). Pure read — the
 * caller persists the new value via writeState only on a real fire.
 */
export declare function hasChanged(name: string, observation: string, dedupeKey?: string): boolean;
/** Read a monitor's liveness heartbeat, or null if the engine has never polled it. */
export declare function readLiveness(name: string): MonitorLiveness | null;
/**
 * Record one poll attempt as the monitor's liveness heartbeat — the single call
 * the engine makes on every evaluation, regardless of fire/match. A successful
 * observation clears `lastError`/`consecutiveErrors` and any drought flag; a
 * failed one (source threw or produced nothing) records the error and increments
 * the consecutive-failure counter the drought escalation reads. Written
 * atomically (temp + rename) like writeStateRaw, and never touches state.json, so
 * change-detection is untouched.
 */
export declare function recordCheck(name: string, checkedAt: string, error?: string): MonitorLiveness;
/** Stamp the drought-notified marker so the engine notifies at most once per drought. */
export declare function markDroughtNotified(name: string, at: string): void;
/**
 * Append a fire timestamp and return the pruned window (fires within `windowMs`).
 * The engine uses the returned length to decide whether the rate limit tripped.
 */
export declare function recordFireTime(name: string, now: number, windowMs: number): number[];
/** Write a fire record to fires/<id>/event.json and return the fire id. */
export declare function writeFireRecord(event: MonitorEvent, meta?: Record<string, unknown>): string;
/** A single fire history entry (as read back from disk). */
export interface FireRecord extends MonitorEvent {
    runId?: string;
    action?: string;
    ok?: boolean;
    error?: string;
    /**
     * The dispatched run's status AT FIRE TIME, best-effort (RUSH-2690).
     * `dispatchAction` (lib/monitors/dispatch.ts) only sees a synchronous
     * snapshot: `executeJobDetached` writes `status: 'running'` before spawning
     * and returns immediately — the real outcome (`completed`/`failed`/`timeout`)
     * lands later, asynchronously, in `executeJobDetachedClaimed`'s own
     * `settle()` on child exit/error (lib/daemon/runner.ts). So a fire recorded
     * `runStatusAtFire: 'running'` had its `ok` frozen before the run actually
     * finished — that is the signal a future reconciliation pass (a daemon tick
     * that revisits `running`-at-fire records and patches `ok` for real) would
     * scan for. Never used to gate `ok` itself; `resolveFireOutcome` re-reads the
     * run fresh on every call instead of trusting this snapshot.
     */
    runStatusAtFire?: RunMeta['status'];
    /**
     * The action's postcondition command, snapshotted at fire time with `{event}`
     * already interpolated (PHNX-2842). `resolveFireOutcome` runs this once the
     * dispatched run has settled `completed`.
     */
    postcondition?: string;
    /** Result of the postcondition check, persisted after the first evaluation. */
    postconditionOk?: boolean;
    /** stderr/stdout snippet when `postconditionOk` is false. */
    postconditionError?: string;
}
/** List a monitor's fire history, chronologically ascending. */
export declare function listFires(name: string): FireRecord[];
/** The reconciled outcome of one fire, resolved against the run's live status. */
export interface ReconciledFireOutcome {
    /** True fire outcome, correcting the frozen `ok` against the run's CURRENT status. */
    ok: boolean;
    /** The run's live terminal status, when a runId is present and resolvable. */
    runStatus?: RunMeta['status'];
    /**
     * Present when a `completed` run had a postcondition to assert (PHNX-2842).
     * `met` = the command exited 0; `none` = ran but the intended effect did not
     * happen (the fire must not read as `ok`).
     */
    effect?: 'met' | 'none';
    /** Why the fire is not ok, when the postcondition failed. */
    error?: string;
}
/**
 * Run a fire's postcondition command. Exit 0 means the intended effect happened;
 * anything else (nonzero, timeout, spawn error, empty command) is "no effect".
 * Real `/bin/sh -c` (or `cmd /c`) — the same seam command sources use.
 */
export declare function evaluatePostcondition(command: string): {
    ok: boolean;
    error?: string;
};
/**
 * Reconcile a fire's frozen `ok` against its dispatched run's REAL, current
 * status — the render-time fix for RUSH-2690 — and, when the run has settled
 * `completed`, against a declared postcondition (PHNX-2842).
 *
 * `writeFireRecord` (this module) persists `ok` once, at fire time, from
 * `dispatchAction`'s synchronous return. For a `run`/`routine` action that
 * return is a snapshot: `executeJobDetached` writes `status: 'running'` before
 * spawning and hands that back immediately, so `dispatchAction`'s negative
 * check (`skipped`/`blocked`/`failed`) never sees the async outcome that lands
 * later via `settle()` — a run that goes on to fail, time out, or otherwise
 * never produce output still reads `ok: true` in `agents monitors runs`
 * forever, while `agents monitors logs` (which reads the run record fresh)
 * shows the real status. Re-reading the run here, at DISPLAY time, closes that
 * gap without touching the write path or the frozen historical record on disk.
 *
 * A fire with no `runId` (a `notify`/`webhook-out` action, or a `run`/`routine`
 * dispatch that never got a runId at all) has nothing to reconcile against —
 * its frozen `ok` is the only signal and is returned as-is.
 *
 * `completed` is not success by itself. An agent that exits 0 without doing
 * the job (merge-on-green that never merged) used to record `ok` because
 * `OK_RUN_STATUSES` treated `completed` as healthy. When the fire carries a
 * `postcondition` command, this function runs it once the run has settled and
 * returns `ok: false, effect: 'none'` when it fails — distinguishing "ran but
 * no effect" from a working fire. The result is persisted on the fire record
 * so later listings do not re-exec the command.
 */
export declare function resolveFireOutcome(jobName: string, fire: FireRecord): ReconciledFireOutcome;
