export interface LifecycleMarkerIo { read(path: string): string | null; write(path: string, contents: string): void; /** * Move a marker whose content this module cannot trust aside, preserving it * for inspection. Optional so an injected in-memory io (tests, the daemon's * own fakes) needs no filesystem behaviour; absent, a bad marker is simply * read as "no marker" exactly as before. */ quarantine?(path: string, reason: string): void; } export declare const realLifecycleMarkerIo: LifecycleMarkerIo; /** * Upper bound on the persisted failed-start counter. The counter only ever * needs to be compared against a small threshold, so an absurd value (a * hand-edited file, a corrupted write) is clamped rather than trusted. */ export declare const MAX_TRACKED_FAILED_STARTS = 32; /** * How recent a failed-start streak has to be to still count as "rapid". Boots * spread further apart than this start a fresh streak: three failures over * three weeks are three unrelated incidents, not a crash loop. */ export declare const DEFAULT_CRASH_LOOP_WINDOW_MS: number; export interface LifecycleMarker { readonly state: 'running' | 'clean-shutdown'; readonly at: number; readonly pid?: number | undefined; /** Consecutive start attempts that never reached a fully-started daemon. */ readonly failedStarts: number; /** When the current failed-start streak began, what bounds "rapid". */ readonly streakStartedAt?: number | undefined; /** When an automatic rollback last restored the kept previous binary; cleared by the next fully-started boot. */ readonly autoRollbackAt?: number | undefined; /** * The artifact version the CURRENT streak belongs to. A failed-start streak * accuses a specific build; when the build on disk changes (an update, a * rollback, a hand-run install) the accusation does not carry over to its * replacement, so the streak restarts rather than convicting a binary that * never failed. */ readonly version?: string | undefined; /** * The version an automatic rollback moved AWAY from, the build that crash * looped. Kept across boots so the self-update loop does not download, * verify, swap and restart into the exact release that just failed, over and * over, every check interval. Cleared once that version starts successfully. */ readonly rejectedVersion?: string | undefined; } /** * Upper bound on a persisted version string. A version is compared and printed, * never executed, but an unbounded string from a corrupted file has no business * being carried forward into a receipt or a log line. */ export declare const MAX_TRACKED_VERSION_LENGTH = 64; /** * The marker as it stands, or null when there is none / it does not survive * content validation. Exported so the self-update loop can read the version an * automatic rollback rejected without a second parser for the same file. */ export declare function readLifecycleMarker(markerPath: string, io?: LifecycleMarkerIo): LifecycleMarker | null; export interface StartupMarkerResult { /** True when the previous daemon exited without an orderly shutdown. */ readonly crashed: boolean; /** The previous marker, when one existed and parsed. */ readonly previous: LifecycleMarker | null; } export interface StartAttemptResult extends StartupMarkerResult { /** * How many PREVIOUS consecutive start attempts never reached a * fully-started daemon (this attempt is not counted in the number, it has * not failed yet). Zero on a healthy host. */ readonly failedStarts: number; /** When an automatic rollback last fired, if no healthy boot has cleared it since. */ readonly autoRollbackAt: number | undefined; } export interface MarkerCallOptions { io?: LifecycleMarkerIo; now?: () => number; pid?: number; /** * The running artifact's version. Scopes the failed-start streak to the build * it accuses: a marker left by a DIFFERENT version is another build's record, * and its failures must not be counted against this one. */ version?: string | undefined; } /** * Called as the FIRST thing daemon start() does, before anything that could * fail: records this boot as an unconfirmed start attempt and reports how many * consecutive attempts before it never reached a fully-started daemon. * * The clean-shutdown fields (`state`, `at`, `pid`) are carried through * untouched, so crash detection at fully-started still sees the PREVIOUS run's * state rather than this boot's own write. */ export declare function recordDaemonStartAttempt(markerPath: string, options?: MarkerCallOptions & { windowMs?: number; }): StartAttemptResult; /** * Called once the daemon is fully started (the server is accepting): stamps * this run as `running` and RESETS the failed-start streak, a boot that got * this far was not a failed start, and it re-arms the automatic rollback. * Returns whether the previous run ended in a crash (marker still `running`). * An unreadable/absent marker is honestly NOT a crash, first boots and * hand-deleted state must not fabricate a crash receipt. */ export declare function recordDaemonStart(markerPath: string, options?: MarkerCallOptions): StartupMarkerResult; /** Called on orderly shutdown: stamps the marker `clean-shutdown` and clears the failed-start streak. */ export declare function recordDaemonCleanShutdown(markerPath: string, options?: MarkerCallOptions): void; /** * Called immediately after an automatic rollback restored the kept previous * binary: clears the streak (the restored version gets a clean slate) and * stamps when the rollback fired, so a second automatic rollback is refused * until a fully-started boot re-arms it. Without that stamp a rollback, which * EXCHANGES the live file with its kept previous, would ping-pong between two * versions that both fail to start. */ export declare function recordDaemonAutoRollback(markerPath: string, options?: MarkerCallOptions & { /** The version being rolled AWAY from, the build that crash looped. */ rejectedVersion?: string | undefined; }): void; //# sourceMappingURL=lifecycle-marker.d.ts.map