/**
 * Server Supervisor — manages the full Instar server as a child process.
 *
 * Starts, monitors, and auto-restarts the server. Reports health status
 * back to the lifeline so it can inform users via Telegram.
 *
 * The supervisor spawns the server in a tmux session (same as `instar server start`)
 * and monitors it via health checks.
 *
 * RESTART ARCHITECTURE (v0.9.63):
 * The server NEVER restarts itself. When the AutoUpdater installs an update,
 * it writes a `restart-requested.json` flag. The supervisor detects this flag
 * during its health check polling and performs a graceful restart. This eliminates
 * the entire category of self-restart bugs (PATH mismatch, launchd confusion,
 * binary resolution failures, restart loops).
 */
import { EventEmitter } from 'node:events';
export interface SupervisorEvents {
    serverUp: [];
    serverDown: [reason: string];
    serverRestarting: [attempt: number];
    circuitBroken: [totalFailures: number, lastCrashOutput: string];
    debugRestartRequested: [request: {
        fixDescription: string;
        requestedBy: string;
    }];
    debugRestartSkipped: [info: {
        fixDescription: string;
        reason: string;
    }];
    /** Emitted when the server recovers after a planned update restart.
     *  The lifeline should self-restart to pick up new code from the shadow install. */
    updateApplied: [targetVersion: string];
}
/**
 * The signed payload a Remediator sends to the supervisor to request a
 * planned restart. The HMAC covers the canonical serialization of
 * `{requestId, runbookId, attemptId, blastRadius, requestedAt, monotonicTs,
 * handshakeVersion}` using the Remediator's per-call capability leaf key
 * (RemediationKeyVault.deriveLeafKey('capability', runbookId)).
 */
export interface RestartRequestedPayload {
    requestId: string;
    runbookId: string;
    attemptId: string;
    blastRadius: 'process' | 'machine' | 'fleet';
    /** Wall-clock ms (Date.now()). Used for staleness rejection. */
    requestedAt: number;
    /** `process.hrtime.bigint()` at request issuance. Informational. */
    monotonicTs: bigint;
    /** Handshake protocol version the Remediator is speaking. */
    handshakeVersion: number;
    /** HMAC-SHA256 over canonical payload using the Remediator's capability leaf. */
    hmac: Buffer;
}
/** Reply the supervisor returns from `handleRestartRequested`. */
export interface RestartRequestedReply {
    requestId: string;
    accepted: boolean;
    /** Machine-readable reason code, e.g. `'accepted'`, `'invalid-hmac'`,
     *  `'stale'`, `'blast-radius-out-of-scope'`, `'handshake-version-mismatch'`. */
    reason: string;
    /** Supervisor's handshake protocol version (informational). */
    supervisorHandshakeVersion: number;
    /** Supervisor's build id (informational; A15 lag check). */
    supervisorBuildId: string;
}
/**
 * Lightweight structural type compatible with F-8 Remediator's
 * `RemediationContext`. Imported as a type-only reference; runtime
 * decoupling lets us avoid a hard dependency from `src/lifeline/*` onto
 * `src/remediation/*` (the legacy spawn-time `preflightSelfHeal()` path
 * must keep working even if remediation files are absent).
 */
export interface SupervisorRemediatorInvocationContext {
    attemptId: string;
    runbookId: string;
    abortSignal: AbortSignal;
    /** `process.hrtime.bigint()` issued + expectedRuntimeMs converted to ns. */
    monotonicDeadline: bigint;
    /** §A3 capability-token HMAC — present on Tier-2 dispatched ctxs. */
    hmac?: Buffer;
    /** Wall-clock expiry, mirrors `RemediationContext.expiresAt`. */
    expiresAt?: number;
}
/**
 * Optional keyVault dependency for `invokeFromRemediator` §A3 / §A23
 * verification. Structural so `src/lifeline/*` doesn't pull in
 * `src/remediation/*` at module load.
 */
export interface SupervisorInvocationContextKeyVault {
    deriveLeafKey(context: 'capability', scopeId: string): Buffer;
}
export interface SupervisorRemediatorExecutionResult {
    outcome: 'success' | 'failure';
    details: Record<string, unknown>;
}
/** The Remediator instance interface the supervisor calls back into. */
export interface RegisteredRemediator {
    /** Called by supervisor after a planned restart completes. */
    onRestartComplete: (req: {
        requestId: string;
    }) => void;
    /** Returns the leaf key the Remediator used to sign restart-requested. */
    getCapabilityLeafKey: () => Buffer;
}
export declare class ServerSupervisor extends EventEmitter {
    private projectDir;
    private projectName;
    private port;
    private tmuxPath;
    private serverSessionName;
    private healthCheckInterval;
    private lastHealthCheckAt;
    private readonly sleepWakeGapMs;
    private restartAttempts;
    private maxRestartAttempts;
    private restartBackoffMs;
    private isRunning;
    private lastHealthy;
    private startupGraceMs;
    private spawnedAt;
    private firstSpawnedAt;
    private readonly graceCeilingMultiplier;
    private retryCooldownMs;
    private maxRetriesExhaustedAt;
    private consecutiveFailures;
    private readonly unhealthyThreshold;
    private consecutiveBindFailures;
    private readonly bindFailureEscalationThreshold;
    private readonly processAliveThreshold;
    private readonly maxLoadRatio;
    private readonly starvationRestartThreshold;
    private readonly loadRatioProvider;
    private stateDir;
    private maintenanceWaitStartedAt;
    private maintenanceWaitMs;
    private pendingUpdateVersion;
    private slept;
    private totalFailures;
    private totalFailureWindowStart;
    private readonly circuitBreakerThreshold;
    private readonly circuitBreakerWindowMs;
    private circuitBroken;
    private circuitBreakerTrippedAt;
    private circuitBreakerRetryCount;
    private readonly circuitBreakerRetryIntervalMs;
    private readonly maxCircuitBreakerRetries;
    private readonly slowRetryIntervalMs;
    private slowRetryStartedAt;
    /**
     * Eternal Sentinel condition 4 ("No Unbounded Loops", P19): never-give-up
     * must not mean never-tell-anyone. Fires the one-per-episode 'sentinelStalled'
     * escalation after a sustained-failure threshold; the retrying continues.
     */
    private readonly sentinelEscalation;
    private lastCrashOutput;
    private doctorSessionSecret;
    private sleepWakeDetector;
    private wakeTransitionUntil;
    private readonly wakeTransitionMs;
    /** Current handshake protocol version. Bump on any wire-format change. */
    static readonly HANDSHAKE_PROTOCOL_VERSION = 1;
    /** Max staleness of a restart-requested payload before it's rejected. */
    static readonly RESTART_REQUEST_MAX_AGE_MS: number;
    private registeredRemediator;
    /** Pending restart requests awaiting completion notification, keyed by requestId. */
    private pendingRemediatorRequests;
    /** Build id surfaced via handshake. Tests inject; production reads package.json. */
    private supervisorBuildId;
    constructor(options: {
        projectDir: string;
        projectName: string;
        port: number;
        stateDir?: string;
        /** How long to wait for server recovery during a planned restart before alerting. Default: 5 minutes. */
        maintenanceWaitMinutes?: number;
        /** How long to wait after spawning before starting health checks. Default: 180 seconds (3 minutes). */
        startupGraceSeconds?: number;
        /** Injectable CPU-load-ratio source (loadavg[0]/cpuCount) for tests. Default: cpuLoadRatio. */
        loadRatioProvider?: () => number;
        /** Sustained-failure threshold before the one-per-episode slow-retry escalation. Default: 12h. */
        slowRetryEscalateAfterMs?: number;
    });
    /**
     * Start the server and begin monitoring.
     */
    start(): Promise<boolean>;
    /**
     * Stop the server and monitoring.
     */
    /**
     * Operator-explicit wake from agent hard-sleep. Clears the `slept` state + the
     * slept-marker so a manual `/lifeline restart` (or `/restart`) actually brings the
     * server back up — without this, startHealthChecks() re-reads the marker and the
     * supervisor immediately re-enters `slept`, leaving an un-monitored server and no
     * in-band recovery. Distinct from a fleet-watchdog auto-bounce (which intentionally
     * stays asleep via the boot-marker); this is reached only from an explicit command.
     */
    wakeFromSleep(): void;
    stop(): Promise<void>;
    /**
     * Check if the server is currently healthy.
     */
    get healthy(): boolean;
    /**
     * Get supervisor status.
     */
    getStatus(): {
        running: boolean;
        healthy: boolean;
        restartAttempts: number;
        lastHealthy: number;
        serverSession: string;
        coolingDown: boolean;
        cooldownRemainingMs: number;
        circuitBroken: boolean;
        totalFailures: number;
        lastCrashOutput: string;
        circuitBreakerRetryCount: number;
        maxCircuitBreakerRetries: number;
        inMaintenanceWait: boolean;
        maintenanceWaitElapsedMs: number;
        inWakeTransition: boolean;
        wakeTransitionRemainingMs: number;
    };
    /**
     * Reset the circuit breaker — allows restart attempts to resume.
     * Call this after fixing the underlying issue (e.g., via /lifeline restart).
     */
    resetCircuitBreaker(): void;
    /**
     * Set the HMAC secret for validating doctor session restart requests.
     * Called by TelegramLifeline when a doctor session is spawned.
     */
    setDoctorSessionSecret(secret: string): void;
    /**
     * Gracefully restart the server: capture output, kill tmux session,
     * clean up child processes, then spawn fresh.
     *
     * Used by: restart-request handling (auto-update), /lifeline restart command.
     */
    performGracefulRestart(reason: string): Promise<boolean>;
    /**
     * Remediator-orchestrated entry point for the preflight self-heal.
     *
     * Wraps the existing private `preflightSelfHeal()` (six in-line heal steps:
     * shadow-install reinstall, node-symlink repair, stuck-git-rebase abort,
     * better-sqlite3 ABI rebuild, stale lifeline-lock cleanup, settings.json
     * merge-conflict repair) and exposes it as the W-2 `supervisor-preflight`
     * runbook's `surfaceCallable`.
     *
     * SELF-HEALING-REMEDIATOR-V2-SPEC §A34 mandates the runbook is a SINGLE
     * runbook composing all six heal steps rather than six separate runbooks —
     * the verify step asserts the durable lifeline state (not per-step liveness)
     * AFTER all six attempt their fix, mirroring how the in-line path is wired
     * into `spawnServer()`.
     *
     * §A3 capability-token enforcement. When `keyVault` is wired AND `ctx.hmac`
     * is present, the HMAC is verified at entry. An invalid ctx returns an
     * error result and does NOT run the preflight side-effects — falling back
     * to the legacy in-line path is the supervisor's spawn-time concern, not
     * this Remediator path's. This is fail-closed by design.
     *
     * Honours `ctx.abortSignal` at the boundary; the existing preflight body
     * itself is synchronous-leaning and not interruptible mid-step. Surfaces a
     * pre-step abort check so an already-aborted ctx returns immediately.
     *
     * The legacy `private preflightSelfHeal()` stays unchanged. The in-line
     * `spawnServer()` path keeps calling it directly — both entry points share
     * the same body, satisfying the §A2 lock-bound co-existence invariant at
     * the process level (and the MachineLock prevents two simultaneous
     * supervisor-preflight runs across processes).
     */
    invokeFromRemediator(ctx: SupervisorRemediatorInvocationContext, keyVault?: SupervisorInvocationContextKeyVault): Promise<SupervisorRemediatorExecutionResult>;
    /**
     * Run preflight checks and attempt to fix broken prerequisites.
     * Returns a summary of what was healed (empty string if nothing needed fixing).
     */
    private preflightSelfHeal;
    /**
     * Find a working npm binary. Checks common locations.
     */
    private findNpmPath;
    /**
     * Find a working node binary. Checks common locations.
     */
    private findNodePath;
    private spawnServer;
    private isServerSessionAlive;
    private startHealthChecks;
    /**
     * One health-check tick — extracted from the setInterval callback so a single
     * tick can be driven directly in unit tests (Fix A/B/C, supervisor-respawn-
     * guarantee) and so the wiring-integrity test can assert the loop probes
     * isServerSessionAlive() before honoring any startup-grace early-return.
     */
    private runHealthTick;
    private stopHealthChecks;
    private checkHealth;
    /**
     * Verify the server actually answers /health, retrying a few times so a
     * momentarily-stalled-but-healthy server is not needlessly respawned.
     *
     * Used by start() to distinguish a genuinely-running server (lifeline
     * self-restart while the server stays up → no-op) from a dying/stale tmux
     * session that merely still registers with `has-session` right after a
     * `kickstart -k` (→ must respawn). A live server answers on the first probe;
     * a dead/dying one fails every attempt. Bias: prefer respawning a possibly
     * stalled server over leaving a dead one running invisibly (the P1 bug).
     */
    private verifyServerResponding;
    /** Stop the server tmux session and enter `slept`. Honors sleep-requested.json. */
    private checkSleepRequest;
    /** Respawn the server on a wake-request. Called from the health loop ONLY while slept. */
    private checkWakeRequest;
    private writeSleptMarker;
    private clearSleptMarker;
    /** True when a slept-marker is on disk — read at boot so a reboot stays asleep. */
    private sleptMarkerPresent;
    /**
     * Check if the server (AutoUpdater) has requested a restart.
     * Called during the health check loop. If a valid request exists,
     * initiate a graceful restart of the server tmux session.
     */
    private checkRestartRequest;
    /**
     * Check if a doctor session has requested a restart via HMAC-signed file.
     * Called during the health check loop alongside checkRestartRequest().
     */
    private checkDebugRestartRequest;
    /**
     * Validate HMAC on a debug restart request using the doctor session secret.
     */
    private validateRestartHmac;
    /**
     * Register a Remediator instance for the handshake. Idempotent; calling
     * twice with the same instance is a no-op. Calling with a different
     * instance replaces the previous registration (last-writer-wins is
     * fine because there is only ever one Remediator per process).
     */
    registerRemediator(remediator: RegisteredRemediator): void;
    /** Current handshake protocol version (instance accessor). */
    getHandshakeProtocolVersion(): number;
    /** Build id used for the A15 partial-upgrade lag check. */
    getSupervisorBuildId(): string;
    /** Tests inject; production callers should not need to set this. */
    setSupervisorBuildId(buildId: string): void;
    /**
     * Receive a restart-requested from a Remediator. Verifies, in order:
     *   1. A Remediator is registered.
     *   2. Required fields are present and well-typed.
     *   3. `handshakeVersion` matches the supervisor's (A15 rule).
     *   4. `requestedAt` is within RESTART_REQUEST_MAX_AGE_MS of now.
     *   5. `blastRadius` is in {process, machine}. Tier-2 refuses 'fleet'.
     *   6. The HMAC verifies against the canonical payload using the
     *      Remediator's capability leaf key.
     *
     * On accept, the request is tracked under `pendingRemediatorRequests`
     * and a planned graceful restart is initiated. The Remediator's
     * `onRestartComplete` callback fires once the supervisor sees the
     * restart cycle finish (next healthy tick after a serverRestarting).
     */
    handleRestartRequested(payload: RestartRequestedPayload): Promise<RestartRequestedReply>;
    /**
     * Notify any pending Remediator restart-requested entries that the
     * server is healthy again. Called from the health-check loop after a
     * serverRestarting → healthy transition. Safe to call repeatedly; each
     * entry is removed after its callback fires so subsequent ticks are
     * no-ops.
     */
    private notifyPendingRemediatorRequestsOnHealthy;
    /**
     * Write the supervisor-handshake state file so a freshly-spawned
     * Remediator (post-restart, post-crash, cross-process) can read the
     * supervisor's protocol version + build id without an in-process
     * handle. Best-effort; failure is logged and ignored — the
     * Remediator's A15 fallback then trips, which is the correct fail-safe
     * shape.
     */
    private writeSupervisorHandshakeFile;
    /** Test helper — surface pending-request count for assertions. */
    getPendingRemediatorRequestCount(): number;
    /** Test helper — simulate a healthy tick driving completion notifications. */
    triggerHealthyTickForTests(): void;
    /**
     * Decide what to do about a server that has failed `>= unhealthyThreshold`
     * consecutive health checks. Shared by the health-check loop's two failure
     * paths (unhealthy /health response, and a thrown check).
     *
     *   - Process dead → restart immediately.
     *   - Process alive but unresponsive, below the (load-lenient) threshold →
     *     keep waiting.
     *   - Process alive, threshold reached, but the box is CPU-starved → DEFER the
     *     restart (restarting a starved server only drops the in-flight message
     *     and loops) until starvation eases or the hard cap forces it.
     *   - Process alive, threshold reached, not starved → restart.
     */
    private evaluateUnhealthyServer;
    /**
     * True when we should hold off restarting an alive-but-unresponsive server
     * because the machine is CPU-starved — but only up to the hard cap. Past the
     * cap we restart regardless (the server may be genuinely hung, not starved).
     */
    private deferRestartForCpuStarvation;
    private handleUnhealthy;
    /**
     * Capture crash output from multiple sources:
     * 1. tmux pane capture (last 50 lines of terminal output)
     * 2. stderr crash log file (tee'd from server process)
     */
    private captureCrashOutput;
    /**
     * Kill child processes (cloudflared, etc.) that were spawned by the server
     * but will become orphans when the tmux session is killed.
     */
    private cleanupChildProcesses;
    /**
     * Check for the legacy update-restart.json flag (written by old AutoUpdater versions).
     * New versions write restart-requested.json instead, handled by checkRestartRequest().
     */
    private isLegacyPlannedRestart;
    private clearLegacyRestartFlag;
    /**
     * Check if a planned restart is in progress.
     *
     * Two sources of truth (covers both race scenarios):
     * 1. Internal state: set by checkRestartRequest() when it sees plannedRestart: true
     * 2. Planned-exit marker: written by ForegroundRestartWatcher before process.exit()
     *    when it consumed the restart-requested.json before us
     *
     * Auto-expires after maintenanceWaitMs (default 5 min). If the server doesn't
     * come back within the window, fall back to normal alerting.
     */
    private isPendingPlannedRestart;
    /**
     * Clean up the planned-exit marker written by ForegroundRestartWatcher.
     */
    private clearPlannedExitMarker;
}
/**
 * Information about a discovered better-sqlite3 install copy.
 */
export interface BetterSqlite3Copy {
    /** Absolute path to the package directory (contains package.json). */
    packageDir: string;
    /** Absolute path to the compiled .node binary. */
    binaryPath: string;
    /** Absolute path to use as `--prefix` when invoking npm rebuild. */
    prefixDir: string;
}
/**
 * Scan a node_modules tree for every copy of better-sqlite3 that has a
 * compiled binary. Bounded depth (5) and bounded count (5) to guarantee
 * termination on pathological trees.
 *
 * Why this exists: npm does not always hoist `better-sqlite3` to the top of
 * `shadow-install/node_modules/`. A common shape is the package nested under
 * `instar/node_modules/better-sqlite3/...`. The previous preflight only
 * checked the hoisted path and silently missed the nested copy that was
 * actually being loaded — root cause of the Inspec 2026-04-29 silent
 * crash-loop.
 *
 * Exported for testing.
 */
export declare function findBetterSqlite3Copies(nodeModulesRoot: string): BetterSqlite3Copy[];
/**
 * Canonical, deterministic serialization of a restart-requested payload
 * EXCLUDING the `hmac` field, used as the HMAC input on both sides of
 * the handshake. Field order is fixed and lengths are length-prefixed
 * so a malicious crafter cannot shift bytes across field boundaries.
 *
 * Format (network byte order, big-endian):
 *   "instar-f6-restart-v1\0"          // 21-byte version tag
 *   uint32 version                    // 4 bytes — handshakeVersion
 *   uint8  blastRadiusTag             // 1 = process, 2 = machine, 3 = fleet
 *   uint64 requestedAt (BE)           // wall-clock ms
 *   uint64 monotonicTs (BE)           // hrtime ns
 *   uint32 requestIdLen + requestId   // utf-8
 *   uint32 runbookIdLen + runbookId   // utf-8
 *   uint32 attemptIdLen + attemptId   // utf-8
 *
 * The tag byte for `blastRadius` ensures unknown future values cannot
 * be silently re-cast to a known value at canonicalization time.
 */
export declare function canonicalRestartRequestedBody(payload: RestartRequestedPayload): Buffer;
//# sourceMappingURL=ServerSupervisor.d.ts.map