# Capability Spec Delta: api-router — PipelineRouter contract ownership **Package**: `@suaveplan/api-router` **Status**: PROPOSED ## ADDED Requirements ### Requirement: Owns the canonical `PipelineRouter` contract `@suaveplan/api-router` SHALL export the canonical `PipelineRouter` interface and its companion types (`PipelineRoute`, `PipelineRouteOptions`, `PipelineRouteRegistration`) from the `./pipeline` subpath. Every concrete pipeline-router adapter (`api-router-in-memory`, the `HonoRouter` pipeline facet in `api-router-hono`, future Stage-7 engines) targets this contract. `@suaveplan/api-host` MUST consume these types via `@suaveplan/api-router/pipeline` rather than define them locally. #### Scenario: subpath exports the four contract types - **WHEN** `import type { PipelineRoute, PipelineRouteOptions, PipelineRouteRegistration, PipelineRouter } from "@suaveplan/api-router/pipeline"` is used - **THEN** all four symbols resolve AND match the prior shapes hosted in `@suaveplan/api-host/request-pipeline` #### Scenario: api-host re-exports preserve back-compat - **WHEN** existing code uses `import { PipelineRouter } from "@suaveplan/api-host/request-pipeline"` - **THEN** the symbol resolves to the same type identity as the new canonical location (verified by structural assignment in a type-level test) **Verified by:** `src/pipeline/pipeline.test.ts::"PipelineRouter contract exports the canonical surface"` AND `packages/api-stack/api/api-host/src/request-pipeline/request-pipeline.test.ts::"PipelineRouter re-export matches api-router source-of-truth"` --- ### Requirement: Ships the canonical `runPipelineRouterConformance` harness `@suaveplan/api-router` SHALL export `runPipelineRouterConformance({ label, build })` from `./testing/pipeline-router-conformance` and re-export it through `./testing` for ergonomic single-subpath consumption. The harness's invariants — route-match, plugin-ownership-preserved-across-reload, status-map propagation, missing-route returns null — MUST be preserved verbatim from the previous `@suaveplan/api-host/testing` location. #### Scenario: harness invariants unchanged from the api-host source location - **WHEN** an adapter that previously passed `runPipelineRouterConformance` from `@suaveplan/api-host/testing` re-targets the harness at `@suaveplan/api-router/testing/pipeline-router-conformance` - **THEN** the same scenarios pass with no signature or behaviour change #### Scenario: ergonomic `./testing` re-export - **WHEN** `import { runPipelineRouterConformance } from "@suaveplan/api-router/testing"` is used - **THEN** the symbol resolves AND matches the dedicated-subpath export by identity **Verified by:** `src/testing/pipeline-router-conformance/pipeline-router-conformance.test.ts::"invariants match prior api-host location"` AND `src/testing/index.test.ts::"./testing re-exports runPipelineRouterConformance"` --- ### Requirement: Cycle-free build graph After this change, the workspace build graph MUST NOT contain a cycle between `@suaveplan/api-host` and `@suaveplan/api-router-in-memory`. Turborepo's `bunx turbo run build` MUST complete without emitting `Cyclic dependency detected`. #### Scenario: turbo schedules build with no cycle warning - **WHEN** `bunx turbo run build --filter=@suaveplan/api-host` is run from a clean tree - **THEN** stderr contains no `Cyclic dependency detected` line AND the task graph builds successfully #### Scenario: repo-wide test gate runs end-to-end - **WHEN** `bunx turbo test --output-logs=errors-only` is run - **THEN** the exit code is 0 AND no package reports a cycle warning **Verified by:** CI assertion in `bun scripts/lint/check-no-cycles.ts` (new; greps `bunx turbo run build --dry=json` for any task whose `dependencies` form a cycle) AND the green-bar evidence captured in the change's archive `tasks.md`