Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
# Enterprise migration: @suaveplan/workflow
**Package**: `@suaveplan/workflow`
**Status**: PROPOSED
**Type**: new-package (migration from upstream)
**Date**: 2026-05-24
**Design**: ./design.md (deferred — covered inline)
**Spec delta**: ./specs/workflow/spec.md
## Why
Upstream `../suaveplan/packages/universal/workflow/` ships a tree-shakeable, zero-runtime-dep workflow / pipeline orchestration core with sequential + parallel step execution, conditional branching, saga compensation, retry strategies, timeouts, lifecycle hooks, and a fluent builder. It is consumed by higher-level packages (rules-engine, form-builder, multi-step business processes) and must land on genesis before any of those can migrate. The 43-file source has 100% test parity in upstream and we preserve that surface verbatim while bolting on the standard genesis additions: stable error codes via `defineCodes`, Zod schemas at the public boundary, OTel instruments, injectable `Clock` for deterministic timing.
## What changes
New package `@suaveplan/workflow` at `packages/foundation/flow/workflow/` (sibling to `fsm`, `job-scheduler`, `streams`). The public surface mirrors upstream exactly:
- ADDED: `createWorkflow<TData>(name, id?)` and `WorkflowBuilder<TData>` — fluent builder with `addStep`, `on`, `onError`, `withTimeout`, `buildConfig`, `build`, `run`.
- ADDED: `StepBuilder<TData>` — `withCompensation`, `withGuard`, `withRetry`, `withTimeout`, `build`.
- ADDED: `WorkflowEngine<TData>` and `createWorkflowEngine` — the orchestrator with `run`, `cancel`, `hooks` accessor.
- ADDED: `Step<TCtx, TResult>` and `createStep` — base unit with guard / retry / timeout / compensate.
- ADDED: `ParallelStep<TCtx>` / `createParallelStep` with `failFast` + `maxConcurrency`.
- ADDED: `ConditionalStep<TCtx>` / `createConditionalStep` with `onTrue` / `onFalse` branches.
- ADDED: `Saga<TCtx>` / `createSaga` — sequential steps with automatic reverse-order compensation.
- ADDED: `Compensator<TCtx>` / `createCompensator` — explicit compensation registry + `compensateAll` / `compensateSteps`.
- ADDED: `HookRegistry<TCtx>` / `createHookRegistry` + `buildStepPayload` — lifecycle-event dispatch.
- ADDED: `withRetry`, `createRetryWrapper`, `resolveRetryConfig`, `RetryPresets` — middleware-style retry.
- ADDED: `WorkflowContext<TData>` / `createContext` + `ContextSnapshot<TData>`.
- ADDED: utilities — `generateId`, `delay`, `withTimeout`, `calculateRetryDelay`, `now`.
- ADDED: constants — `DEFAULT_*` retry / timeout values.
- ADDED: types — `WorkflowStatus`, `StepStatus`, `StepExecutor`, `CompensationFn`, `GuardFn`, `RetryStrategy`, `RetryConfig`, `StepResult`, `StepDefinition`, `WorkflowResult`, `HookEvent`, `HookPayload`, `HookListener`, `WorkflowConfig`, plus `WorkflowTransitionData` / `WorkflowTransitionHookMap` / `WorkflowTransitioningHandler` for the data-pipeline hook.
- ADDED: `WORKFLOW_CODES` + `WorkflowError` via `@suaveplan/error`'s `defineCodes` — codes `WORKFLOW.STEP_FAILED`, `WORKFLOW.TIMEOUT`, `WORKFLOW.CANCELLED`, `WORKFLOW.COMPENSATION_FAILED`, `WORKFLOW.GUARD_REJECTED`, `WORKFLOW.RETRY_EXHAUSTED`.
Genesis-only additions on top of upstream:
- `WorkflowError` extends `SuaveplanError<WorkflowCode>` (upstream throws plain `Error`).
- Zod-validated `WorkflowConfig` schema at `buildConfig()` boundary.
- OTel counter `workflow.step.executed_total{outcome,step_id}`, counter `workflow.step.compensated_total{outcome,step_id}`, histogram `workflow.step.duration_ms{step_id}` — emitted from `Step.run` and `Compensator.compensateAll`.
- Injectable `Clock` (defaults to `Date.now`) on `Step`, `WorkflowEngine`, `Saga`, `Compensator`, `HookRegistry` — makes `now()`-driven timing deterministic in tests.
## Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
| Fluent builder (`createWorkflow`, `WorkflowBuilder`, `StepBuilder`) | ✅ | ✅ | |
| `WorkflowEngine.run` with `onError: stop/compensate/continue` | ✅ | ✅ | |
| `WorkflowEngine.cancel` mid-run | ✅ | ✅ | |
| `Step` with guard / retry / timeout / compensate | ✅ | ✅ | |
| `ParallelStep` with `failFast` + `maxConcurrency` batching | ✅ | ✅ | |
| `ConditionalStep` with `onTrue` / `onFalse` + skip when no false branch | ✅ | ✅ | |
| `Saga` orchestrator with reverse-order compensation | ✅ | ✅ | |
| `Compensator` with `compensateAll` + `compensateSteps` + `continueOnError` | ✅ | ✅ | |
| `HookRegistry` (`on`, `once`, `off`, `emit`, `listenerCount`) | ✅ | ✅ | |
| `buildStepPayload` helper | ✅ | ✅ | |
| `withRetry`, `createRetryWrapper`, `resolveRetryConfig`, `RetryPresets` | ✅ | ✅ | |
| `WorkflowContext` (`updateData`, `setData`, `addStepResult`, `setMeta`/`getMeta`, `snapshot`, `reset`) | ✅ | ✅ | |
| `calculateRetryDelay` fixed/linear/exponential | ✅ | ✅ | |
| `generateId`, `delay`, `withTimeout`, `now` utilities | ✅ | ✅ | |
| Constants (`DEFAULT_*`) | ✅ | ✅ | |
| Transition data-pipeline hook (`workflow:transitioning`) | ✅ | ✅ | |
| Subpath exports per feature module | ✅ | ✅ | |
| Plain `Error` throws | ✅ | ⬆️ | Replaced by `WorkflowError` + stable codes per genesis rule. |
| Zod schema validation at config boundary | 🚫 | ⬆️ | Genesis addition — `WorkflowConfig` is Zod-validated in `buildConfig`. |
| OTel counters + histogram | 🚫 | ⬆️ | Genesis addition — step execution + compensation instrumented. |
| Injectable `Clock` | 🚫 | ⬆️ | Genesis addition — defaults to `Date.now`, takes `Clock` from `@suaveplan/types`. |
## Out of scope
- Durable replay / persistence — upstream does not ship this; out of scope for the first cut and would be a separate sibling package (`workflow-store`) per the genesis modular pattern.
- Distributed execution — upstream is single-process; genesis preserves that boundary.
## Source mapping
This change derives from `../suaveplan/packages/universal/workflow/src/` (43 files). One genesis feature dir per upstream feature dir; file names preserved.
| Requirement | Source path |
|---|---|
| Step run / compensate / guard | `src/steps/step/step.ts` |
| Parallel step | `src/steps/parallel-step/parallel-step.ts` |
| Conditional step | `src/steps/conditional-step/conditional-step.ts` |
| Compensator | `src/compensation/compensator/compensator.ts` |
| Saga | `src/compensation/saga/saga.ts` |
| Workflow context | `src/core/context/context.ts` |
| Workflow builder | `src/core/workflow-builder/workflow-builder.ts` |
| Workflow engine | `src/core/workflow-engine/workflow-engine.ts` |
| Hook registry | `src/middleware/hook/hook.ts` |
| Retry middleware | `src/middleware/retry/retry.ts` |
| Utils + constants | `src/utils/utils.ts`, `src/constants/index.ts` |
| Common + hook types | `src/types/common.ts`, `src/types/workflow-hooks.ts` |
## Verification approach
Co-located `.test.ts` per feature directory under `src/`. Every requirement in `specs/workflow/spec.md` has a `**Verified by:**` line pointing at a concrete `it(...)` title in one of these files. 100% coverage gate via `bunx turbo test:coverage --filter=@suaveplan/workflow`.
## Impact
### Affected dependents
No genesis-side dependents yet (`bunx turbo ls --filter=...@suaveplan/workflow` returns empty — additive only). The package is a prerequisite for future migrations of rules-engine and form-builder.

View File

@@ -0,0 +1,286 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: workflow (enterprise-workflow)
**Package**: `@suaveplan/workflow`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
## ADDED Requirements
### Requirement: Fluent workflow builder
The package SHALL expose `createWorkflow<TData>(name, id?)` and a `WorkflowBuilder<TData>` class that fluently composes steps via `addStep(name, execute, id?) -> StepBuilder<TData>`, supports per-step configuration through `StepBuilder` (`withCompensation`, `withGuard`, `withRetry`, `withTimeout`), and emits a typed `WorkflowConfig<TData>` via `buildConfig()` or a ready-to-run `WorkflowEngine<TData>` via `build()`.
#### Scenario: builder produces a config with the configured steps and error strategy
- **WHEN** a caller invokes `createWorkflow("orders").addStep("validate", run).onError("compensate").buildConfig()`
- **THEN** the resulting `WorkflowConfig` has `steps.length === 1`, `onError === "compensate"`, and `name === "orders"`
#### Scenario: `StepBuilder.withRetry` applies defaults for omitted fields
- **WHEN** a caller invokes `addStep(...).withRetry({ maxAttempts: 5 }).build()`
- **THEN** the returned `StepDefinition.retry` has `maxAttempts: 5`, `strategy: "fixed"`, `delayMs: 1000`
**Verified by:** `src/core/workflow-builder/workflow-builder.test.ts::"builds a config with steps and error strategy"` AND `src/core/workflow-builder/workflow-builder.test.ts::"applies retry defaults for omitted fields"`
---
### Requirement: Sequential step execution with onError strategies
The `WorkflowEngine<TData>` SHALL execute steps sequentially in declaration order and SHALL honour the `onError` strategy on the first failed step: `"stop"` halts execution and returns a failed result; `"compensate"` halts execution, runs registered compensations in reverse order, and returns a failed result with status `"failed"`; `"continue"` records the failure and proceeds to the next step.
#### Scenario: onError stop short-circuits remaining steps
- **WHEN** a workflow with three steps fails on step two under `onError: "stop"`
- **THEN** step three is never invoked and the result `status === "failed"`
#### Scenario: onError compensate rolls back completed steps in reverse order
- **WHEN** a workflow has two steps with compensations and the second fails under `onError: "compensate"`
- **THEN** the first step's compensation runs exactly once and the result `status === "failed"`
#### Scenario: onError continue keeps executing after a failure
- **WHEN** a three-step workflow under `onError: "continue"` fails on step two
- **THEN** step three still executes and `result.steps` contains all three step results
**Verified by:** `src/core/workflow-engine/workflow-engine.test.ts::"halts on first failure when onError is stop"` AND `src/core/workflow-engine/workflow-engine.test.ts::"runs reverse-order compensation when onError is compensate"` AND `src/core/workflow-engine/workflow-engine.test.ts::"continues past a failure when onError is continue"`
---
### Requirement: Step retry, timeout, and guard
`Step<TCtx>` SHALL support optional `guard` (skips execution when the guard returns false), `retry` (re-executes on failure using fixed/linear/exponential strategy with `maxDelayMs` cap), `timeout` (rejects the in-flight execution after the configured milliseconds), and `compensate` (rolls back the side effect).
#### Scenario: guard returning false yields a skipped step
- **WHEN** `Step.shouldExecute(ctx)` returns false
- **THEN** the engine records the step result with `status === "skipped"` and never invokes `execute`
#### Scenario: retry escalates delay per strategy and caps at maxDelayMs
- **WHEN** a step with `strategy: "exponential", delayMs: 100, backoffMultiplier: 2, maxDelayMs: 250` retries
- **THEN** `calculateRetryDelay(3, ...)` returns `250` (capped)
#### Scenario: timeout rejects when the step exceeds the budget
- **WHEN** a step's `execute` resolves after 50ms but `timeout` is 10ms
- **THEN** `Step.run` returns `status: "failed"` and the error message contains "timed out"
**Verified by:** `src/steps/step/step.test.ts::"skips when guard returns false"` AND `src/utils/utils.test.ts::"caps exponential backoff at maxDelayMs"` AND `src/steps/step/step.test.ts::"fails with timeout error when execute exceeds budget"`
---
### Requirement: Parallel step execution with concurrency and fail-fast
`ParallelStep<TCtx>` SHALL execute its registered child steps concurrently, SHALL respect `maxConcurrency` (default `Infinity`) by batching when finite, SHALL support `failFast: true` to skip subsequent batches once any error is observed, and SHALL aggregate per-step `StepResult` plus a collected `errors` array.
#### Scenario: parallel run returns one result per child step
- **WHEN** a parallel step with three children runs and all succeed
- **THEN** `result.results.length === 3` and `result.status === "completed"`
#### Scenario: maxConcurrency=1 serialises execution into batches
- **WHEN** a parallel step with four children and `maxConcurrency: 1` runs
- **THEN** four sequential batches are produced (verified by recording start order)
#### Scenario: failFast halts subsequent batches after first error
- **WHEN** a parallel step with `maxConcurrency: 1, failFast: true` and four children sees child two fail
- **THEN** children three and four are not executed
**Verified by:** `src/steps/parallel-step/parallel-step.test.ts::"returns one result per child step"` AND `src/steps/parallel-step/parallel-step.test.ts::"serialises into batches when maxConcurrency is 1"` AND `src/steps/parallel-step/parallel-step.test.ts::"halts subsequent batches when failFast is true"`
---
### Requirement: Conditional step with true/false branches
`ConditionalStep<TCtx>` SHALL evaluate its `condition` against the context, run `onTrue` when the condition is truthy, run `onFalse` when falsy if present, and yield a skipped `StepResult` when falsy without a false branch. `compensate(ctx)` SHALL roll back only the branch that actually ran.
#### Scenario: truthy branch runs onTrue and records lastBranch
- **WHEN** the condition returns true
- **THEN** `lastBranch === "true"` and the result comes from the true-branch step
#### Scenario: falsy with no else yields a skipped result
- **WHEN** the condition returns false and `onFalse` is undefined
- **THEN** the result has `status === "skipped"` and `lastBranch === "skipped"`
**Verified by:** `src/steps/conditional-step/conditional-step.test.ts::"runs the true branch and records lastBranch"` AND `src/steps/conditional-step/conditional-step.test.ts::"skips when condition is false and no false branch is provided"`
---
### Requirement: Saga orchestration with reverse-order compensation
`Saga<TCtx>` SHALL execute its steps sequentially and on the first failure SHALL run registered compensations in reverse registration order via the internal `Compensator`. The result SHALL expose `stepResults`, `compensationRecords`, and a `WorkflowStatus` of `"completed"` or `"failed"`.
#### Scenario: failure mid-saga compensates earlier completed steps
- **WHEN** a three-step saga completes steps one and two, then step three fails
- **THEN** `compensationRecords` contains entries for steps two then one (reverse order) and `status === "failed"`
**Verified by:** `src/compensation/saga/saga.test.ts::"compensates earlier steps in reverse order on failure"`
---
### Requirement: Compensator registry
`Compensator<TCtx>` SHALL allow `register(stepId, fn)`, SHALL execute `compensateAll(ctx)` in reverse registration order, SHALL execute `compensateSteps(ctx, ids)` filtered to the given ids in reverse order, SHALL record `CompensationRecord` for every attempt (success or failure), and SHALL stop on first error when `continueOnError: false`.
#### Scenario: continueOnError defaults to true
- **WHEN** three compensations are registered and the second throws
- **THEN** all three records are present and the third still ran
#### Scenario: continueOnError false breaks on first failure
- **WHEN** three compensations are registered with `continueOnError: false` and the second throws
- **THEN** only two records are present
**Verified by:** `src/compensation/compensator/compensator.test.ts::"runs all compensations when continueOnError is true"` AND `src/compensation/compensator/compensator.test.ts::"stops at first failure when continueOnError is false"`
---
### Requirement: Hook registry lifecycle dispatch
`HookRegistry<TCtx>` SHALL register listeners for `HookEvent` topics, SHALL invoke listeners sequentially in registration order on `emit`, SHALL support `once` semantics (auto-unregister after the first invocation), and SHALL allow removal via the unregister callback returned from `on` or via `off(event?)`.
#### Scenario: listeners fire in registration order
- **WHEN** two listeners are registered for `"workflow:start"` and `emit` is called
- **THEN** they fire in registration order with the same payload `event` and `timestamp`
#### Scenario: once listener auto-unregisters
- **WHEN** a listener registered via `once("step:start", ...)` is emitted twice
- **THEN** the listener is invoked exactly once and `listenerCount("step:start") === 0`
**Verified by:** `src/middleware/hook/hook.test.ts::"invokes listeners in registration order"` AND `src/middleware/hook/hook.test.ts::"once listener auto-unregisters after first emit"`
---
### Requirement: Retry middleware (`withRetry`, `RetryPresets`)
The package SHALL expose `withRetry(fn, options)` that retries `fn` per the resolved `RetryConfig`, SHALL invoke `onRetry(error, attempt)` between attempts, SHALL respect `shouldRetry(error, attempt)` to short-circuit, and SHALL expose `RetryPresets` (`none`, `quick`, `standard`, `aggressive`) returning ready-to-use configs.
#### Scenario: withRetry exhausts attempts and throws the last error
- **WHEN** a function always throws and `withRetry` runs with `maxAttempts: 3`
- **THEN** the function is invoked exactly three times and the final rejection is the last thrown error
#### Scenario: shouldRetry false short-circuits
- **WHEN** `shouldRetry` returns false after attempt one
- **THEN** `withRetry` rejects immediately with that error and the function is invoked exactly once
**Verified by:** `src/middleware/retry/retry.test.ts::"exhausts attempts and rethrows the final error"` AND `src/middleware/retry/retry.test.ts::"short-circuits when shouldRetry returns false"`
---
### Requirement: Workflow context state container
`WorkflowContext<TData>` SHALL hold mutable workflow data and status, SHALL accumulate `StepResult` entries via `addStepResult`, SHALL store metadata via `setMeta`/`getMeta`, SHALL produce immutable snapshots via `snapshot()`, and SHALL be resettable to initial data via `reset(data)`.
#### Scenario: snapshot is a frozen point-in-time copy
- **WHEN** a context with one step result and one metadata key is snapshotted, then mutated
- **THEN** the snapshot reflects the original state and is not mutated by the post-snapshot changes
**Verified by:** `src/core/context/context.test.ts::"snapshot reflects state at the moment of capture"`
---
### Requirement: Cancellation
`WorkflowEngine<TData>.cancel()` SHALL mark the engine cancelled. When invoked between steps, the next loop iteration SHALL transition status to `"cancelled"`, emit `workflow:cancel`, and return a result with `status: "cancelled"`.
#### Scenario: cancel between steps short-circuits remaining work
- **WHEN** a workflow's first step calls `engine.cancel()` from within its `execute`
- **THEN** subsequent steps are not invoked and the result has `status: "cancelled"`
**Verified by:** `src/core/workflow-engine/workflow-engine.test.ts::"cancels between steps and returns cancelled status"`
---
### Requirement: Workflow-level timeout
The `WorkflowEngine` SHALL apply `config.timeout` (defaulting to `DEFAULT_TIMEOUT_MS`) over the entire `runSteps` pass and SHALL produce a failed result when the timeout elapses, with the error message naming the workflow.
#### Scenario: workflow timeout produces failed status
- **WHEN** a workflow with a 10ms timeout runs a step that takes 100ms
- **THEN** the result has `status: "failed"` and `error.message` includes the workflow name
**Verified by:** `src/core/workflow-engine/workflow-engine.test.ts::"fails when workflow timeout elapses"`
---
### Requirement: Transition data-pipeline hook
The engine SHALL expose `transitionHooks: Hooks<WorkflowTransitionHookMap<TData>>` and SHALL invoke `workflow:transitioning` between resolving `shouldExecute` and emitting `step:start`. Returned handlers may mutate the `context` field of the data, and the engine SHALL pass the resulting context to the step's `execute`.
#### Scenario: transition hook can rewrite the context handed to the step
- **WHEN** a `workflow:transitioning` handler returns `{ ..., context: rewritten }`
- **THEN** the next step receives `rewritten` as its execute argument
**Verified by:** `src/core/workflow-engine/workflow-engine-hooks.test.ts::"applies workflow transitioning hook context rewrite"`
---
### Requirement: Stable error codes via `defineCodes`
The package SHALL expose `WORKFLOW_CODES` registered via `@suaveplan/error`'s `defineCodes("WORKFLOW", [...])` and SHALL export a `WorkflowError` class extending `SuaveplanError<WorkflowCode>`. Codes are exactly `STEP_FAILED`, `TIMEOUT`, `CANCELLED`, `COMPENSATION_FAILED`, `GUARD_REJECTED`, `RETRY_EXHAUSTED`. Every code SHALL be reachable from a real production path.
#### Scenario: WorkflowError carries the stable code
- **WHEN** a workflow times out via `WorkflowEngine.run`
- **THEN** the surfaced error is a `WorkflowError` with `code === WORKFLOW_CODES.TIMEOUT`
**Verified by:** `src/errors/workflow-error.test.ts::"WorkflowError surfaces WORKFLOW.TIMEOUT when the workflow times out"`
---
### Requirement: OTel instrumentation
The package SHALL acquire a meter named `@suaveplan/workflow`, SHALL increment counter `workflow.step.executed_total{outcome,step_id}` on every step terminal state (`completed`, `failed`, `skipped`), SHALL increment counter `workflow.step.compensated_total{outcome,step_id}` on every compensation attempt (`compensated`, `compensation-failed`), and SHALL record histogram `workflow.step.duration_ms{step_id}` on every executed step.
#### Scenario: instruments are emitted from production paths
- **WHEN** a workflow runs one completed step and one failed step under `onError: "compensate"` with a registered compensation
- **THEN** the `executed_total` counter records `{outcome: "completed"}` once and `{outcome: "failed"}` once; the `compensated_total` counter records `{outcome: "compensated"}` at least once
**Verified by:** `src/otel/instruments.test.ts::"emits step counters and histogram from production paths"`
---
### Requirement: Injectable Clock
`Step`, `WorkflowEngine`, `Saga`, `Compensator`, `ConditionalStep`, and `ParallelStep` SHALL accept an optional `Clock` (from `@suaveplan/types`) defaulting to `Date.now`. All `now()` reads inside the package SHALL go through the injected clock so tests can pin timing deterministically.
#### Scenario: injected clock controls recorded timestamps
- **WHEN** a `Step` is constructed with a clock returning `1000`
- **THEN** a successful run records `startedAt === 1000` and `completedAt === 1000`
**Verified by:** `src/steps/step/step.test.ts::"uses the injected clock for timestamps"`
---
### Requirement: Subpath exports per feature
The package's `package.json` `exports` map SHALL provide a subpath per feature module so consumers can tree-shake unused features (`./core/workflow-engine/workflow-engine`, `./core/workflow-builder/workflow-builder`, `./core/context/context`, `./steps/step/step`, `./steps/parallel-step/parallel-step`, `./steps/conditional-step/conditional-step`, `./compensation/saga/saga`, `./compensation/compensator/compensator`, `./middleware/hook/hook`, `./middleware/retry/retry`, `./errors`, `./otel`).
#### Scenario: every feature module is independently importable
- **WHEN** dependency tooling enumerates `package.json` `exports`
- **THEN** at minimum twelve non-`.` subpaths are present (one per feature module + errors + otel)
**Verified by:** `tests/dep-graph.test.ts::"package exports map covers every feature module"`
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,71 @@
# Tasks — Enterprise migration: @suaveplan/workflow
## 0. Pre-flight (non-negotiable)
- [x] 0.1 Confirm clean working tree and account for all worktrees
- [x] 0.2 Confirm no stale `.claude/worktrees/` directories
- [x] 0.3 Read upstream `../suaveplan/packages/universal/workflow/` (43 files) end-to-end
## 1. Spec authoring
- [x] 1.1 Read upstream source + README
- [x] 1.2 Read sibling FSM / job-scheduler for genesis conventions
- [x] 1.3 Read `openspec/AGENTS.md` §0a / §0c / §13 / §14
- [x] 1.4 Draft `proposal.md` with parity checklist
- [x] 1.5 Draft `specs/workflow/spec.md` with `**Verified by:**` per requirement
## 2. Implementation
- [x] 2.1 Scaffold `packages/foundation/flow/workflow/` — `package.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`
- [x] 2.2 `src/errors/workflow-error.ts` — `WORKFLOW_CODES` via `defineCodes` + `WorkflowError`
- [x] 2.3 `src/constants/index.ts`
- [x] 2.4 `src/types/common.ts` + `src/types/workflow-hooks.ts`
- [x] 2.5 `src/utils/utils.ts` (+ test + md)
- [x] 2.6 `src/otel/instruments.ts` (+ md) — meter, counters, histogram
- [x] 2.7 `src/steps/step/step.ts` (+ test + md)
- [x] 2.8 `src/steps/parallel-step/parallel-step.ts` (+ test + md)
- [x] 2.9 `src/steps/conditional-step/conditional-step.ts` (+ test + md)
- [x] 2.10 `src/compensation/compensator/compensator.ts` (+ test + md)
- [x] 2.11 `src/compensation/saga/saga.ts` (+ test + md)
- [x] 2.12 `src/middleware/hook/hook.ts` (+ test + md)
- [x] 2.13 `src/middleware/retry/retry.ts` (+ test + md)
- [x] 2.14 `src/core/context/context.ts` (+ test + md)
- [x] 2.15 `src/core/workflow-builder/workflow-builder.ts` (+ test + md)
- [x] 2.16 `src/core/workflow-engine/workflow-engine.ts` (+ tests + md)
- [x] 2.17 `src/index.ts` — explicit named re-exports per §6a
## 3. Tests
- [x] 3.1 Co-located `.test.ts` per feature
- [x] 3.2 `tests/dep-graph.test.ts`
- [x] 3.3 Every `it(...)` has explicit `{ timeout: 5000 }` (or 30000 if async)
## 4. Documentation
- [x] 4.1 Sibling `.md` per implementation `.ts`
- [x] 4.2 `README.md` at root with Modules section
- [x] 4.3 Update CLAUDE.md flow listing
## 5. Pre-archive verification (5-gate triple)
- [x] 5.1 `bunx biome check packages/foundation/flow/workflow/src/` — zero errors
- [x] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/workflow` — green
- [x] 5.3 `bunx turbo test:coverage --filter=@suaveplan/workflow` — 100/100/100/100
- [x] 5.4 No banned patterns (TODO / FIXME / ts-ignore / .skip / .only / biome-ignore / direct vitest import) in `src/`
- [x] 5.5 `bunx turbo build --filter=@suaveplan/workflow` — green
## 6. Pre-archive 7-row checklist
- [x] 6.1 Generics threaded across `<TData>` / `<TCtx>`
- [x] 6.2 Zod schema wired at `buildConfig()` boundary
- [x] 6.3 Every code in `WORKFLOW_CODES` reachable from a real call site
- [x] 6.4 Strict-mode behaviors covered (`onError: stop/compensate/continue`)
- [x] 6.5 OTel counters + histogram emitted from production paths and asserted
- [x] 6.6 Lifecycle dedup tested (`HookRegistry.once`, idempotent unregister)
- [x] 6.7 No retroactive ticks
## 7. Archive
- [x] 7.1 Fold spec delta into `openspec/specs/workflow/spec.md`
- [x] 7.2 Move `openspec/changes/enterprise-workflow/` → `openspec/archive/2026-05-24-enterprise-workflow/`
- [x] 7.3 Refresh `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts`