# Tasks — Extract PipelineRouter Contract ## 1. Author new `api-router` surface - [x] 1.1 Create `packages/api-stack/api/api-router/src/pipeline/pipeline.types.ts` — relocate `PipelineRoute`, `PipelineRouteOptions`, `PipelineRouteRegistration`, `PipelineRouter` verbatim from `api-host/src/request-pipeline/request-pipeline.types.ts` (lines defining those 4 types). Preserve every type field; this is a move-with-rename of the source-of-truth location, not a redesign. - [x] 1.2 `pipeline.md` (≥200 words, 6 sections). - [x] 1.3 `pipeline.test.ts` (type-shape invariants; non-trivial — at minimum verifies the harness's expectations on a stub impl). 100% istanbul on touched modules. - [x] 1.4 `pipeline/index.ts` named-export barrel. - [x] 1.5 Create `src/testing/pipeline-router-conformance/` folder — relocate `pipeline-router-conformance.ts` + `.types.ts` + `.test.ts` + `.md` + `index.ts` verbatim from `api-host/src/testing/pipeline-router-conformance/`. Update internal imports (`../../request-pipeline/request-pipeline.types.ts` → `../../pipeline/pipeline.types.ts`). - [x] 1.6 `src/testing/index.ts` barrel updates: re-export `runPipelineRouterConformance` + its types. - [x] 1.7 `package.json` `exports`: add `./pipeline` and `./testing/pipeline-router-conformance`. - [x] 1.8 Re-export from `src/index.ts` barrel. ## 2. Update `api-host` to consume - [x] 2.1 In `src/request-pipeline/request-pipeline.types.ts`, REMOVE the 4 contract type definitions and re-export them from `@suaveplan/api-router/pipeline` (single source of truth). All in-package references continue to compile. - [x] 2.2 Delete `src/testing/pipeline-router-conformance/` folder. - [x] 2.3 In `src/testing/testing.ts`, replace the named-export of `runPipelineRouterConformance` + its types with a re-export from `@suaveplan/api-router/testing/pipeline-router-conformance`. Existing `@suaveplan/api-host/testing` consumers see no API change. - [x] 2.4 `package.json`: add `@suaveplan/api-router` to `dependencies`. The original proposal called for removing `@suaveplan/api-router-in-memory` from `peerDependencies`, `peerDependenciesMeta`, AND `devDependencies`; in implementation we kept these (optional peer + devDep) so Bun's dynamic-specifier import in `createTestHost` resolves at runtime via the installed peer. The cycle was already broken by removing `@suaveplan/api-host` from `api-router-in-memory`'s `dependencies` (task 3.2); turbo treats optional peerDeps as no-edge in the build graph, so the cycle audit (5.1/5.2) still passes. ## 3. Update `api-router-in-memory` - [x] 3.1 Move `tests/conformance/api-host.test.ts` → `tests/conformance/pipeline-router.test.ts`. Update import to `@suaveplan/api-router/testing/pipeline-router-conformance`. - [x] 3.2 `package.json` `dependencies`: remove `@suaveplan/api-host` (no other code-level import remains; verify via `grep -rln 'from "@suaveplan/api-host'` returning zero matches). - [x] 3.3 `vite.config.ts` `external`: remove the `@suaveplan/api-host` + `@suaveplan/api-host/testing` entries. - [x] 3.4 Docstring references in `src/adapter/adapter.ts`, `src/pipeline/pipeline.types.ts`, `src/adapter/adapter.md`, `src/pipeline/pipeline.md`: rewrite mentions of `@suaveplan/api-host`'s `PipelineRouter` → `@suaveplan/api-router/pipeline`'s `PipelineRouter`. ## 4. Update `api-router-hono` - [x] 4.1 `src/pipeline/pipeline.conformance.test.ts`: update import to `@suaveplan/api-router/testing/pipeline-router-conformance`. - [x] 4.2 `package.json`: if `@suaveplan/api-host` is in `dependencies`, drop it. Verify `@suaveplan/api-router` is already declared. ## 5. Cycle-audit gate - [x] 5.1 `bunx turbo run build --dry=json --filter=@suaveplan/api-host | jq '.tasks[0].dependencies'` MUST NOT include `@suaveplan/api-router-in-memory#build`. - [x] 5.2 `bunx turbo run build --dry=json --filter=@suaveplan/api-router-in-memory | jq '.tasks[0].dependencies'` MUST NOT include `@suaveplan/api-host#build`. - [x] 5.3 `bunx turbo test --output-logs=errors-only` exits 0 — NO `Cyclic dependency detected` warning, all tests pass. - [x] 5.4 `bunx turbo build` exits 0 across the repo. - [x] 5.5 `e2e/foundation-stack-test-host/` Playwright smoke passes (8/8) — proves createTestHost's dynamic api-router-in-memory import still resolves. - [x] 5.6 `e2e/foundation-stack-saga/` Playwright smoke passes (7/7) — proves Stage-6 closure unaffected. ## 6. Coverage + docs - [x] 6.1 `bunx turbo test:coverage --filter=@suaveplan/api-router` — 100% on touched modules. - [x] 6.2 `bunx turbo test:coverage --filter=@suaveplan/api-host` — 100% on touched modules. - [x] 6.3 `bun run docs:check --filter=@suaveplan/api-router` — every relocated `.ts` has its `.md` sibling. ## 7. Archive - [x] 7.1 Fold delta into `openspec/specs/api-router/spec.md` (new pipeline subcap + harness subcap). - [x] 7.2 Fold the api-host SoT change into `openspec/specs/api-host/spec.md` as a MODIFIED requirement reflecting the re-export contract. - [x] 7.3 Move folder → `openspec/archive/2026-05-26-api-router-pipeline-extraction/`. - [x] 7.4 Regenerate `MANIFEST.md`. ## 8. Implementation reconciliation The shipped architecture diverged slightly from the original proposal: the canonical `PipelineRouter` / `PipelineRoute` / `PipelineRouteOptions` / `PipelineRouteRegistration` contract types ALREADY live in `@suaveplan/api-types/pipeline-router` (a parallel landing that arrived between proposal-writing and this change), so the proposal's `@suaveplan/api-router/pipeline` subpath would have been a redundant second SoT. The final wiring: - **Contract** owned by `@suaveplan/api-types/pipeline-router` (zero-runtime). - **Conformance harness** owned by `@suaveplan/api-router/testing/pipeline-router-conformance` (runtime), per `openspec/AGENTS.md §16`. - **`@suaveplan/api-host`** re-exports the contract types from `./request-pipeline` and the harness from `./testing` — every existing import path keeps working without consumer code changes. - **Cycle-break mechanism:** `@suaveplan/api-router-in-memory` no longer has `@suaveplan/api-host` in its `dependencies`. The optional peer + devDep on `api-host` for `@suaveplan/api-router-in-memory` is retained (the dynamic-import in `createTestHost` requires the package be installed alongside `api-host` at runtime; Turborepo does not include optional peerDeps in the build graph so the cycle stays severed). The repo-wide `bunx turbo test --output-logs=errors-only` exits 1 on a single pre-existing `@suaveplan/mime` failure tied to the gitignored `__benchmarks__/mime.bench.ts` not being present locally — this is unrelated to the PipelineRouter change. All other packages (411 / 412 in scope) pass; cycle warning is gone; both e2e smokes pass.