/**
 * Version-skew detection + lifeline-restart signal coordination.
 *
 * The signal file `state/lifeline-restart-requested.json` is written by THREE
 * independent channels (no single-point-of-failure):
 *
 *   1. AutoUpdater.requestRestart — when an apply crosses major.minor.
 *   2. Server /internal/telegram-forward 426 handler — direct evidence of skew.
 *   3. PostUpdateMigrator — one-time bootstrap nudge for currently-stuck lifelines.
 *
 * The signal is consumed by THREE independent readers:
 *
 *   1. TelegramLifeline tick loop — calls initiateRestart('plannedUpgrade').
 *   2. ServerSupervisor poll — force-restarts the lifeline if its tick hasn't
 *      acted within 60s of the signal write.
 *   3. (Future) v3 Remediator probe — absorbs both channels.
 *
 * Spec: docs/specs/auto-updater-lifeline-coordination.md
 */
export interface LifelineRestartSignal {
    requestedAt: string;
    requestedBy: 'auto-updater' | 'server-426' | 'post-update-migrator-bootstrap';
    reason: string;
    previousVersion: string;
    targetVersion: string;
    expiresAt: string;
}
/**
 * Predicate: does going from `prev` to `next` cross a major.minor boundary?
 *
 * Examples:
 *   crossesBreaking('1.1.0', '1.2.28') === true   // minor crossed
 *   crossesBreaking('1.2.0', '1.2.28') === false  // same major.minor
 *   crossesBreaking('1.2.28', '2.0.0') === true   // major crossed
 *   crossesBreaking('1.2.28', '1.2.28') === false // identity
 *
 * Malformed inputs are treated as breaking (fail safe — prefer over-signal
 * to under-signal, since the consequence of false-positive is a single
 * harmless lifeline restart, and the consequence of false-negative is the
 * exact incident class we're closing).
 */
export declare function crossesBreaking(prev: string | null | undefined, next: string | null | undefined): boolean;
export interface WriteSignalOpts {
    /** Project state directory (the `.instar` dir for the agent). */
    stateDir: string;
    /** Who is writing the signal — used for the audit trail. */
    requestedBy: LifelineRestartSignal['requestedBy'];
    /** Human-readable reason — picked up by the lifeline's restart log. */
    reason: string;
    previousVersion: string;
    targetVersion: string;
    /** Optional TTL in ms (default 1h). */
    ttlMs?: number;
    /** Optional clock for tests. */
    now?: number;
}
/**
 * Write the lifeline-restart signal atomically. Idempotent: if a non-expired
 * signal already exists for the SAME targetVersion, no-ops.
 *
 * Returns 'written' / 'skipped-fresh' / 'replaced-stale'.
 */
export declare function writeLifelineRestartSignal(opts: WriteSignalOpts): 'written' | 'skipped-fresh' | 'replaced-stale';
/**
 * Read the signal. Returns null if missing, malformed, or expired.
 * The reader is expected to delete the file as the first step of acting on it
 * (so a respawn doesn't re-fire on the same signal).
 */
export declare function readLifelineRestartSignal(stateDir: string, now?: number): LifelineRestartSignal | null;
/**
 * Delete the signal. Called by readers as the first step of acting on the
 * signal so a restart loop can't re-fire on the same write.
 */
export declare function clearLifelineRestartSignal(stateDir: string): void;
/** Absolute path of the signal file for a given state dir. */
export declare function lifelineRestartSignalPath(stateDir: string): string;
//# sourceMappingURL=version-skew.d.ts.map