Files
2026-05-27 23:29:53 +01:00

75 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `@suaveplan/api-lifecycle` — plugin lifecycle FSM definition
**Package**: `@suaveplan/api-lifecycle`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 1 (Contracts) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
Design §4 specifies the plugin lifecycle as a finite-state machine with 13 states (`uninstalled`, `installing`, `installed`, `starting`, `started`, `rolled_back`, `crashed`, `stopping`, `stopped`, `uninstalling`, `install_failed`, `start_failed`, `uninstall_failed`) and a documented set of transitions. The host driver, the admin API status renderer, the hot-reload primitive, and operator tooling all need to agree on which transitions are legal — and a graph that lives in only one place. This package owns the FSM definition: the states (as a discriminated union), the transition function, the side-effect descriptors per transition (so the host can wire them), and the per-plugin mutex contract that guarantees at-most-one transition at a time. Reuses `@suaveplan/fsm` for the state-machine primitive — this package is the FSM *definition*, not a new FSM runtime.
Per design §4: "The FSM has a per-plugin mutex — at most one transition at a time. Concurrent triggers queue."
## What changes
Net-new package. Pure definition: states + transitions + side-effect descriptors + retry policies. No concrete plugin code; that lives in `api-host` (Stage 2).
- ADDED: `./states` — `PluginState` discriminated union (re-imported from `@suaveplan/api-types` for runtime introspection); state-shape descriptors.
- ADDED: `./transitions` — `PluginTransitions`: the legal `(from, to, trigger)` set as a frozen table. Trigger names: `install`, `installCompleted`, `installFailed`, `start`, `startCompleted`, `startFailed`, `rollbackTriggered`, `stop`, `stopCompleted`, `uninstall`, `uninstallCompleted`, `uninstallFailed`, `crashDetected`, `crashRestart`, `clearRollback`, `retryInstall`, `retryStart`, `forceUninstall`, `repair`.
- ADDED: `./fsm-factory` — `createPluginFsm({ initial, sideEffects, mutex })` returns an FSM bound to the genesis `@suaveplan/fsm` runtime, initialised with `PluginTransitions` and the side-effect bus from design §4's transition table.
- ADDED: `./side-effects` — descriptors for each transition's side effects (per §4 table): `installing → installed` emits `plugin.installed`, completes OTel span; `stopping → stopped` force-cancels stragglers, disposes capabilities reverse-registration order; etc. Descriptors are data; the host runtime executes them.
- ADDED: `./retry-policies` — `start_failed` auto-retry × 3 with `@suaveplan/retry` exponential backoff; `crashed` auto-restart × `crashRestartMax` (default 3); restart budget reset after `crashRestartCooldownMs` of healthy operation. Default values exported.
- ADDED: `./mutex-contract` — `PluginMutex` interface: at most one transition per plugin in flight; concurrent triggers queue (FIFO).
- ADDED: `./reinstall-criteria` — `requiresReinstall(prev, next): boolean` returning true when the new manifest bumps `database.schemaVersion`, declares new `scopes`/`events`/`jobs`/`secrets`, or changes `dependsOn` (per §4 reload semantics).
- ADDED: `./error-codes` — frozen `LIFECYCLE_CODES`: `ILLEGAL_TRANSITION`, `MUTEX_HELD`, `RETRY_BUDGET_EXHAUSTED`, `REINSTALL_REQUIRED_VERSION_DOWNGRADE`.
- ADDED: `./testing` — `runLifecycleConformance(factory)` reusable harness.
## Out of scope
- The host's lifecycle DRIVER (the code that actually CALLS `install`/`start`/`stop` hooks and reads side-effect descriptors) — lives in `api-host` (Stage 2).
- Plugin ledger persistence — `api-host`.
- Worker-tier `crashed` state restart implementation — `api-host` + `api-worker-host` (Stage 3).
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| State machine (13 states) | §4 (FSM ASCII diagram, lines 409-473) |
| Transition side-effects table | §4 (table, lines 475-484) |
| Reinstall criteria (`schemaVersion` bumped, new `scopes`/`events`/`jobs`/`secrets`, `dependsOn` changed) | §4 (line 486) |
| `start_failed` auto-retry × 3 + `rolled_back` path | §4 (failure recovery section, lines 491-493) |
| `crashed` auto-restart × `crashRestartMax`, cooldown | §4 (line 493) |
| Per-plugin mutex; concurrent triggers queue | §4 (final paragraph) |
| `VERSION_DOWNGRADE` failure | §4 (line 486) |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| Trigger naming (`installCompleted` etc.) | Mechanically derived from the §4 table; pinned here so the host and admin renderers agree. |
| `runLifecycleConformance` harness | Stage-5 deliverable (§13.4). |
## Verification approach
- `src/states/states.test.ts` — `PluginState` union matches design §4 exactly (no missing / extra states).
- `src/transitions/transitions.test.ts` — exhaustive transition table: every documented `(from, to)` pair is legal; an undocumented pair throws `LIFECYCLE.ILLEGAL_TRANSITION`.
- `src/fsm-factory/fsm-factory.test.ts` — `createPluginFsm` wires `@suaveplan/fsm` correctly; transition emits the right side-effect descriptors.
- `src/side-effects/side-effects.test.ts` — every documented side effect is in the table for the right transition (`installed → starting` mounts routes, etc.).
- `src/retry-policies/retry-policies.test.ts` — `start_failed` retried up to 3 times then parked; `crashed` restarted up to `crashRestartMax`; budget reset after cooldown.
- `src/mutex-contract/mutex-contract.test.ts` — concurrent triggers queue FIFO; second trigger waits until first releases mutex.
- `src/reinstall-criteria/reinstall-criteria.test.ts` — every documented trigger (`schemaVersion`, new scope/event/job/secret, `dependsOn` change) yields `true`; identical manifest yields `false`; version downgrade yields `REINSTALL_REQUIRED_VERSION_DOWNGRADE`.
- `tests/dep-graph.test.ts` — deps: `@suaveplan/api-types`, `@suaveplan/fsm`, `@suaveplan/retry`, `@suaveplan/error`, `@suaveplan/types`.
## Migration impact
None — additive only.
## Impact
### Affected dependents
- (none) — [x] no action needed (reason: net-new; first consumer is `api-host` in Stage 2)