6.1 KiB
@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—PluginStatediscriminated union (re-imported from@suaveplan/api-typesfor 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/fsmruntime, initialised withPluginTransitionsand the side-effect bus from design §4's transition table. - ADDED:
./side-effects— descriptors for each transition's side effects (per §4 table):installing → installedemitsplugin.installed, completes OTel span;stopping → stoppedforce-cancels stragglers, disposes capabilities reverse-registration order; etc. Descriptors are data; the host runtime executes them. - ADDED:
./retry-policies—start_failedauto-retry × 3 with@suaveplan/retryexponential backoff;crashedauto-restart ×crashRestartMax(default 3); restart budget reset aftercrashRestartCooldownMsof healthy operation. Default values exported. - ADDED:
./mutex-contract—PluginMutexinterface: at most one transition per plugin in flight; concurrent triggers queue (FIFO). - ADDED:
./reinstall-criteria—requiresReinstall(prev, next): booleanreturning true when the new manifest bumpsdatabase.schemaVersion, declares newscopes/events/jobs/secrets, or changesdependsOn(per §4 reload semantics). - ADDED:
./error-codes— frozenLIFECYCLE_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/stophooks and reads side-effect descriptors) — lives inapi-host(Stage 2). - Plugin ledger persistence —
api-host. - Worker-tier
crashedstate 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—PluginStateunion 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 throwsLIFECYCLE.ILLEGAL_TRANSITION.src/fsm-factory/fsm-factory.test.ts—createPluginFsmwires@suaveplan/fsmcorrectly; 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 → startingmounts routes, etc.).src/retry-policies/retry-policies.test.ts—start_failedretried up to 3 times then parked;crashedrestarted up tocrashRestartMax; 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,dependsOnchange) yieldstrue; identical manifest yieldsfalse; version downgrade yieldsREINSTALL_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-hostin Stage 2)