/** * Auto-update rollout jitter. * * A commit landing on the tracked ref is detected by every node within one poll * interval, so without jitter the whole fleet builds + restarts in one narrow * window — a synchronized bootstrap storm (the trigger behind the 2026-07-10 * beacon OOM incident, where all 4 cores auto-updated to 10.0.6 within ~6 min * and hit the O(store) sync fallback lane at once). * * Poll-phase jitter does NOT fix this: detection is bounded by the interval * regardless of phase. The effective lever is a per-node random HOLD-OFF * between *detecting* an available update and *applying* it (build + restart) — * a staggered rollout that spreads the fleet's restarts across the jitter * window so only a few nodes bootstrap at any moment. * * Pure + deterministic (rng injectable) so it is unit-tested without the daemon. */ export declare const UPDATE_JITTER_ENV = "DKG_UPDATE_JITTER_MINUTES"; /** * Resolve the rollout-jitter window in milliseconds. * * Precedence: env `DKG_UPDATE_JITTER_MINUTES` > resolved config * `updateJitterMinutes` > fallback = the poll interval (so the window * self-scales with cadence). `0` disables. Clamped to [0, 12h]. */ export declare function resolveUpdateJitterMs(configuredMinutes: number | undefined, checkIntervalMinutes: number, env?: NodeJS.ProcessEnv): number; /** * A random hold-off in [0, jitterMs). Returns 0 when jitter is disabled or the * window is non-positive. `rng` returns a float in [0, 1) (defaults to * Math.random) and is injectable for deterministic tests. */ export declare function pickUpdateHoldoffMs(jitterMs: number, rng?: () => number): number; export type UpdateHoldoffDecision = 'proceed' | 'abort-shutdown'; export interface AwaitUpdateHoldoffDeps { /** Resolved jitter window in ms (from `resolveUpdateJitterMs`). */ jitterMs: number; /** True once the daemon has begun shutting down. */ isShuttingDown: () => boolean; /** Invoked once, with the chosen hold-off ms, when a non-zero wait is about * to start — lets the caller emit a mode-specific log line. */ onHold?: (holdMs: number) => void; /** Injectable for deterministic tests. */ rng?: () => number; /** Injectable for deterministic tests (default: an unref'd setTimeout). */ sleep?: (ms: number) => Promise; } /** * Wait out the per-node rollout hold-off, then report whether to proceed with * applying the update. Returns `'proceed'` after the (possibly zero) hold-off, * or `'abort-shutdown'` if the daemon began shutting down during the wait — in * which case the caller must NOT apply (the update is re-detected on next boot). * * The ordering (pick → optional log → sleep → re-check shutdown) is the exact * sequence both auto-update paths depend on; extracting it here makes the * shutdown-bail unit-testable rather than only eyeballed in the daemon loop. */ export declare function awaitUpdateHoldoff(deps: AwaitUpdateHoldoffDeps): Promise; /** Daemon-wide config for the rollout gate — stable across polling ticks. */ export interface UpdateHoldoffGateConfig { /** Resolved jitter window in ms (from resolveUpdateJitterMs). */ jitterMs: number; /** True once the daemon has begun shutting down. */ isShuttingDown: () => boolean; /** Toggle the daemon's user-visible "is updating" flag. */ setUpdating: (updating: boolean) => void; log: (msg: string) => void; /** Injectable for deterministic tests. */ rng?: () => number; sleep?: (ms: number) => Promise; } /** Per-rollout, mode-specific behaviour injected into a gate run. */ export interface UpdateHoldoffStep { /** Emit the mode-specific "holding Ns before applying" line (detected target). */ onHold: (holdMs: number) => void; /** * Re-confirm — AFTER the hold-off — that there is still a target to apply, and * return the CURRENT one. The jitter delay means the target detected before * the wait may have been withdrawn (dist-tag rolled back, ref moved) or the * node may have caught up; returning null skips the apply so a superseded / * withdrawn release is never installed. A refreshed target (e.g. a newer * version published during the wait) is applied in place of the stale one. */ revalidate: () => Promise; /** Apply the revalidated target (owns its own post-apply restart/log). */ apply: (target: T) => Promise; /** Logged when the run is aborted because the daemon is shutting down. */ shutdownMessage: string; /** Logged when revalidate() reports no current target (withdrawn / caught up). */ supersededMessage: string; } export interface UpdateHoldoffGate { /** Run one rollout attempt for a detected update. Concurrent calls while a * run is in flight are no-ops (single-flight across polling ticks). */ run(step: UpdateHoldoffStep): Promise; } /** * The single auto-update rollout gate shared by the git and npm daemon paths. * A factory so it OWNS its single-flight state (the `pending` flag) instead of * making callers allocate and thread a mutable object — create it ONCE, at the * daemon scope, and call `.run(step)` on every polling tick. Each run: * * single-flight guard -> hold-off (jitter) -> abort if shutting down * -> REVALIDATE the target -> abort if shutting down (revalidate is async) * -> set isUpdating -> apply -> clear isUpdating * * The second shutdown check matters: revalidate() is a network call, so SIGTERM * can arrive while it runs; without the re-check the gate would start a * build/install after shutdown cleanup has begun. */ export declare function createUpdateHoldoffGate(config: UpdateHoldoffGateConfig): UpdateHoldoffGate; //# sourceMappingURL=auto-update-jitter.d.ts.map