first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
# `retry-policies`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.