# @suaveplan/api-saga - Saga orchestrator for the Suaveplan API plugin system. Drives manifest-declared multi-plugin workflows in declared order with reverse-order compensation on failure, per-step idempotency keyed by {sagaId, stepName}, persisted state across host restarts via the host's ledger DB (suaveplan_saga_instances + suaveplan_saga_steps), capability-gated cross-tenant dispatch, typed event-bus emission of every step + instance transition, and an in-memory MeterProvider-friendly OTel surface (saga.instance span, saga.step span, saga.compensate span, saga.instances_total counter, saga.step_duration_ms histogram, saga.compensations_total counter, saga.in_flight gauge). Exposes a SagaParticipantRegistry the host populates from plugin manifests' sagas.owns[] and sagas.participatesIn[] declarations, refuses install on duplicate owners or unknown participant wiring, and ships runSagaParticipantConformance on the ./testing subpath so every step + compensate handler authored against @suaveplan/api-types' SagaParticipant contract is pinned to the same invariants. Saga orchestrator for the Suaveplan API plugin system. Drives manifest-declared multi-plugin workflows in declared order with reverse-order compensation on failure, per-step idempotency keyed by `{sagaId, stepName}`, persisted state across host restarts via the host's ledger DB, a capability-gated cross-tenant guard, typed event-bus emission for every step + instance transition, and an OpenTelemetry instrument surface covering `saga.instances_total`, `saga.step_duration_ms`, `saga.compensations_total`, and `saga.in_flight`. API plugin system — see the umbrella change the design document and the spec at `api-saga/spec.md`. ## Documentation tree - [compensation](./src/compensation/compensation.md) — On step failure the saga orchestrator walks completed steps in reverse order and invokes each step's compensation handl… - [compensation](./src/compensation/compensation.md) — On step failure the saga orchestrator walks completed steps in reverse order and invokes each step's compensation handl… - [error-codes](./src/error-codes/error-codes.md) — Stable registry for every failure raised by `@suaveplan/api-saga`. The saga orchestrator surfaces ten distinct fault mo… - [error-codes](./src/error-codes/error-codes.md) — Stable registry for every failure raised by `@suaveplan/api-saga`. The saga orchestrator surfaces ten distinct fault mo… - [events](./src/events/events.md) — The saga orchestrator publishes every step + instance transition onto the host's `EventBus`. Sibling plug… - [events](./src/events/events.md) — The saga orchestrator publishes every step + instance transition onto the host's `EventBus`. Sibling plug… - [idempotency](./src/idempotency/idempotency.md) — Per design doc §9.1 the saga orchestrator MUST wrap every step invocation in an idempotency guard keyed by `{sagaId, st… - [idempotency](./src/idempotency/idempotency.md) — Per design doc §9.1 the saga orchestrator MUST wrap every step invocation in an idempotency guard keyed by `{sagaId, st… - [instruments](./src/instruments/instruments.md) — The saga orchestrator emits an OTel surface matching design doc §9.1: three spans (`saga.instance`, `saga.step`, `saga.… - [instruments](./src/instruments/instruments.md) — The saga orchestrator emits an OTel surface matching design doc §9.1: three spans (`saga.instance`, `saga.step`, `saga.… - [orchestrator](./src/orchestrator/orchestrator.md) — `createSagaOrchestrator(deps)` is the top-level factory plugin authors and host integrators consume. It binds together… - [compensate-runner](./src/orchestrator/compensate-runner.md) — `runFullCompensation(...)` is the orchestrator's per-instance compensation driver. It bridges the generic `compensation… - [orchestrator](./src/orchestrator/orchestrator.md) — `createSagaOrchestrator(deps)` is the top-level factory plugin authors and host integrators consume. It binds together… - [step-runner](./src/orchestrator/step-runner.md) — `runStep(...)` is the orchestrator's per-step engine. It bundles every cross-cutting concern that fires around an actio… - [persistence](./src/persistence/persistence.md) — `SagaPersistence` is the sole path through which the orchestrator mutates the host's saga tables (`suaveplan_saga_insta… - [persistence](./src/persistence/persistence.md) — `SagaPersistence` is the sole path through which the orchestrator mutates the host's saga tables (`suaveplan_saga_insta… - [recovery](./src/recovery/recovery.md) — When a host restarts, the saga orchestrator's in-memory state goes with it — but the saga itself is durable because eve… - [recovery](./src/recovery/recovery.md) — When a host restarts, the saga orchestrator's in-memory state goes with it — but the saga itself is durable because eve… - [registry](./src/registry/participant-registry.md) — The participant registry is the host-side compendium of every plugin that owns a saga and every plugin that participate… - [participant-registry](./src/registry/participant-registry.md) — The participant registry is the host-side compendium of every plugin that owns a saga and every plugin that participate… - [state](./src/state/state-machine.md) — The saga lifecycle has two coupled finite-state machines: the instance state (overall saga health) and the per-step sta… - [state-machine](./src/state/state-machine.md) — The saga lifecycle has two coupled finite-state machines: the instance state (overall saga health) and the per-step sta… - [tenant](./src/tenant/cross-tenant.md) — Design doc §9.1 (final paragraph) requires that a saga step whose target tenant differs from the saga initiator's tenan… - [cross-tenant](./src/tenant/cross-tenant.md) — Design doc §9.1 (final paragraph) requires that a saga step whose target tenant differs from the saga initiator's tenan… - [testing](./src/testing/testing.md) — `runSagaParticipantConformance(factory)` is the canonical contract suite every saga step + compensate handler in the mo… - [testing](./src/testing/testing.md) — `runSagaParticipantConformance(factory)` is the canonical contract suite every saga step + compensate handler in the mo… ## Modules - [orchestrator](./src/orchestrator/orchestrator.md) — `createSagaOrchestrator(deps)` factory + `SagaOrchestrator` public surface (`start`, `resume`, `cancel`, `listInstances`, `getInstance`). - [orchestrator/step-runner](./src/orchestrator/step-runner.md) — per-step engine. - [orchestrator/compensate-runner](./src/orchestrator/compensate-runner.md) — reverse-order compensation driver. - [registry/participant-registry](./src/registry/participant-registry.md) — owner vs participant registration; validates manifest wiring. - [persistence](./src/persistence/persistence.md) — state-machine-guarded CRUD against `suaveplan_saga_instances` + `suaveplan_saga_steps`. - [recovery](./src/recovery/recovery.md) — boot-time replay of non-terminal instances. - [idempotency](./src/idempotency/idempotency.md) — `@suaveplan/idempotency` wrapper keyed by `{sagaId, stepName}` with manual opt-out. - [events](./src/events/events.md) — typed `SagaEventMap` and Zod schemas for every emitted payload. - [compensation](./src/compensation/compensation.md) — generic reverse-walk retry runner. - [tenant/cross-tenant](./src/tenant/cross-tenant.md) — `saga.cross-tenant` capability gate. - [state/state-machine](./src/state/state-machine.md) — instance + step state machines. - [instruments](./src/instruments/instruments.md) — OTel surface. - [error-codes](./src/error-codes/error-codes.md) — 10 `SAGA.*` codes via `SagaError`. - [testing](./src/testing/testing.md) — `runSagaParticipantConformance` published on the `./testing` subpath. ## Owner vs participant A plugin **owns** a saga when its manifest declares `sagas.owns[]` — the entry pins the saga name and the ordered list of `{plugin, action, compensate}` steps. The owner is solely responsible for choosing the step sequence. A plugin **participates in** a saga when its manifest declares `sagas.participatesIn: ["owner-plugin/saga-name"]` AND it exports named `actions[name]` + `compensations[name]` handlers. The orchestrator dispatches step events into the participant's handlers via the `SagaParticipantRegistry`. Two plugins MAY NOT both own the same saga name — the registry refuses the second registration with `SAGA.DUPLICATE_OWNER`. A participant whose `actions[name]` is missing fails at install with `SAGA.UNKNOWN_PARTICIPANT`, before any saga can be started. ## Status `0.1.0` — bottom-up rebuild stage. Will graduate to `1.0.0` per the foundation umbrella's Stage 6 acceptance gate (`a multi-step saga in api-plugin-example runs end-to-end including a compensation path`).