/**
 * LifelineDriftPromoter — turns the server's "your lifeline is N patches behind"
 * info-signal into a self-action by the lifeline.
 *
 * Background:
 *   The lifeline / server version handshake (see versionHandshake.ts) accepts a
 *   forward when MAJOR.MINOR match, but reports an info-level degradation when
 *   PATCH diff exceeds the policy threshold (10). The historical message is
 *   "Lifeline hasn't restarted in a while; consider manual kick" — a
 *   recommendation-to-user that nothing acts on.
 *
 *   The right structural response is for the lifeline to restart itself at the
 *   next safe moment, picking up the matching shadow-install code. This class
 *   owns that lifecycle: detect → defer-until-clean → request restart →
 *   record marker for post-restart user notice.
 *
 * Signal-vs-authority (feedback_signal_vs_authority):
 *   The server's handshake is the SIGNAL ("here is the observed drift, in this
 *   number"). The promoter is the gate with full context — it decides whether
 *   to act, and when. It never restarts blindly: it requires drift ≥ a higher
 *   threshold than the info signal AND a clean-window predicate. The actual
 *   exit goes through the existing RestartOrchestrator so quiesce/persist
 *   semantics are preserved.
 *
 * Own-the-lifecycle (feedback_own_the_lifecycle_pattern):
 *   detect (noteDrift) → defer (start, tick) → verify (isCleanWindow) →
 *   request (deps.requestSelfRestart) → finalize (deps.recordPendingNotice
 *   writes a marker file for the post-restart Telegram note).
 */
export interface LifelineDriftPromoterDeps {
    /**
     * Whether the lifeline is currently in a clean state to be restarted:
     *   - No in-flight Telegram forwards
     *   - No unanswered user message younger than the lifeline's quiet-window
     *
     * The promoter polls this on each tick; restart fires the first time it
     * returns true after drift was observed.
     */
    isCleanWindow: () => boolean;
    /**
     * Initiates the actual self-restart via the existing RestartOrchestrator
     * (reason: 'drift-auto-promote', bucket: 'versionSkew'). The orchestrator
     * handles quiesce + persist + exit, and launchd respawns the process.
     *
     * Must be called at most once per LifelineDriftPromoter instance — after
     * the first call, the promoter transitions to 'fired' and the timer
     * stops. Re-entry from a subsequent boot is a new instance.
     */
    requestSelfRestart: (reason: string) => Promise<void>;
    /**
     * Persists a marker that, on the next boot, lets the lifeline send a
     * one-shot user-facing note like:
     *   "Lifeline self-restarted: was N patches behind, now in sync at vX.Y.Z."
     *
     * Called BEFORE requestSelfRestart so the marker survives even if the
     * restart races the in-process logger.
     */
    recordPendingNotice: (info: {
        observedDiff: number;
        observedAt: string;
        reason: string;
    }) => void;
    /** Optional structured log channel. Default: console.log. */
    log?: (msg: string) => void;
    /** Test clock. Default: Date.now. */
    now?: () => number;
}
export interface LifelineDriftPromoterConfig {
    /**
     * Minimum observed PATCH diff to trigger auto-promote. The server's info-
     * signal threshold is 10 (PATCH_INFO_THRESHOLD); the auto-promote default
     * is higher (20) so we don't churn on every minor drift, only on the
     * "significantly stale" case that produces the silent-skew degradations
     * we saw on Luna 2026-05-22.
     */
    threshold?: number;
    /** Tick cadence for clean-window polling. Default 30_000 (30s). */
    pollIntervalMs?: number;
    /**
     * Hard cap: even if a clean window never appears, force the restart after
     * this much time. Default 60 * 60_000 (1 hour). Set to 0 to disable cap.
     */
    maxDeferMs?: number;
    /** Master switch. Default true. */
    enabled?: boolean;
}
export type DriftPromoterState = {
    kind: 'idle';
} | {
    kind: 'pending';
    observedDiff: number;
    observedAt: number;
} | {
    kind: 'fired';
    observedDiff: number;
    firedAt: number;
} | {
    kind: 'disabled';
};
export declare class LifelineDriftPromoter {
    private readonly config;
    private readonly deps;
    private readonly log;
    private readonly now;
    private state;
    private tickTimer;
    private firingInProgress;
    constructor(deps: LifelineDriftPromoterDeps, config?: LifelineDriftPromoterConfig);
    /**
     * Record an observation of patch drift, typically from a successful
     * forward whose response carried `X-Instar-Lifeline-Patch-Drift`.
     *
     * Side effects:
     *   - If disabled: noop.
     *   - If diff < threshold: noop (the info-signal alone isn't enough).
     *   - If already fired: noop.
     *   - First time over threshold: transitions to 'pending' and starts the tick.
     *   - Already pending: updates observedDiff to the max.
     */
    noteDrift(patchDiff: number, observedAtMs?: number): void;
    /**
     * Start the tick timer manually (otherwise noteDrift starts it on first
     * trigger). Used by tests and by code that wants to install the timer
     * eagerly.
     */
    start(): void;
    /** Stop any pending tick timer. Idempotent. */
    stop(): void;
    /** Test helper — exposes internal state without forcing a tick. */
    _getState(): DriftPromoterState;
    /** Test helper — manually trigger one tick. */
    _tickForTesting(): Promise<void>;
    private startTicking;
    /**
     * Attempt to fire the restart if conditions are met. Idempotent under
     * concurrent calls — a re-entry while a fire is in progress is a noop.
     */
    private tryFire;
}
/** File name (relative to stateDir) used to survive the restart with a
 *  user-facing notice payload. The lifeline reads + deletes this on boot. */
export declare const DRIFT_RESTART_PENDING_NOTICE_FILE = "lifeline-drift-restart-pending.json";
//# sourceMappingURL=LifelineDriftPromoter.d.ts.map