# `retry-policies` _(no submodules)_ ## Purpose Owns the retry-budget contract for the two automatic-recovery paths in the lifecycle FSM: `start_failed` (3 attempts with exponential-backoff + full jitter) and `crashed` (3 restarts within a rolling 60-second window that resets after 120 seconds of healthy operation). Backed by `@suaveplan/retry`'s `createExponentialBackoff` so the delay maths match the rest of the codebase. Throws `LIFECYCLE.RETRY_BUDGET_EXHAUSTED` when either budget is depleted. ## Features - `RETRY_POLICIES` frozen registry exposing `.startFailed` and `.crashed` policy bundles with the configured `delay` function. - `START_FAILED_DEFAULTS` and `CRASHED_DEFAULTS` as named constants for callers that need the raw numeric defaults (e.g. admin UI tooltips). - `StartFailedTracker` — per-plugin attempt counter; `recordFailure()` throws once `maxAttempts` is reached, `markStarted()` resets. - `CrashBudgetTracker` — per-plugin rolling-window crash counter; honours `crashRestartWindowMs` to drop expired entries and `markHealthy()` for the cooldown reset. - Injectable `Clock` on `CrashBudgetTracker` so tests can drive the rolling window deterministically without real time. ## Usage: Basic ```ts import { CrashBudgetTracker, RETRY_POLICIES, StartFailedTracker, } from "@suaveplan/api-lifecycle/retry-policies"; const startTracker = new StartFailedTracker("billing"); try { await plugin.start(); startTracker.markStarted(); } catch (err) { startTracker.recordFailure(); // throws once budget exhausted } ``` ## Usage: Advanced Custom policy for a host that wants 5 start attempts and a tighter crash window, while keeping the default delay function: ```ts import { type CrashedPolicy, type StartFailedPolicy, RETRY_POLICIES, } from "@suaveplan/api-lifecycle/retry-policies"; const startPolicy: StartFailedPolicy = { ...RETRY_POLICIES.startFailed, maxAttempts: 5, }; const crashPolicy: CrashedPolicy = { ...RETRY_POLICIES.crashed, crashRestartMax: 5, crashRestartWindowMs: 30_000, }; ``` Deterministic testing — feed the tracker a fake clock to step through the rolling window in zero wall-clock time: ```ts import { CrashBudgetTracker, RETRY_POLICIES } from "@suaveplan/api-lifecycle/retry-policies"; let now = 0; const tracker = new CrashBudgetTracker("billing", RETRY_POLICIES.crashed, () => now); tracker.recordCrash(); now += RETRY_POLICIES.crashed.crashRestartWindowMs + 1; tracker.recordCrash(); // old crash dropped — counts as first ``` ## API Reference - `RETRY_POLICIES` — frozen `{ startFailed, crashed }`. - `START_FAILED_DEFAULTS`, `CRASHED_DEFAULTS` — raw constants. - `class StartFailedTracker` — `recordFailure`, `markStarted`, `count`. - `class CrashBudgetTracker` — `recordCrash`, `markHealthy`, `count`. - `type StartFailedPolicy`, `type CrashedPolicy`, `type Clock`. ## Implementation Notes The crash tracker stores timestamps in an array — when the window grows past 3 a `LIFECYCLE.RETRY_BUDGET_EXHAUSTED` throw fires. The filter-on-each -record approach is fine for the small numbers involved (max 3 + one overflow); a ring buffer is overkill here. The trackers are deliberately not coupled to the FSM factory — the host driver wires them together so a custom driver can substitute its own budget policy without changing the state-machine wiring.