Files
skills/docs/api-stack/api/api-lifecycle/src/retry-policies/retry-policies.md
2026-05-29 08:20:56 +01:00

3.4 KiB

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

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:

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:

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.