5.6 KiB
Extract PipelineRouter Contract to api-router
Packages: @suaveplan/api-router, @suaveplan/api-host, @suaveplan/api-router-in-memory, @suaveplan/api-router-hono
Status: PROPOSED
Type: architectural-refactor
Why
@suaveplan/api-host owns the abstract PipelineRouter interface AND its conformance harness runPipelineRouterConformance. Every concrete router adapter that satisfies the §6 pipeline (currently api-router-in-memory and api-router-hono's pipeline facet, eventually every Stage-7 engine) must declare a build dep on @suaveplan/api-host to import the harness.
@suaveplan/api-host, in turn, declares a peerDependency + devDependency edge on @suaveplan/api-router-in-memory (the testing kit's default in-memory adapter, dynamically imported by createTestHost). Turborepo refuses to schedule the resulting cycle:
Cyclic dependency detected:
@suaveplan/api-host#build, @suaveplan/api-router-in-memory#build
bunx turbo test, bunx turbo lint typecheck test, and bunx turbo build repo-wide are all blocked. Per-package gates pass via direct bun run invocation, but the orchestrator-facing pre-publish gate documented in CLAUDE.md is unusable. This has been worked around in every recent agent dispatch — it is now blocking enough that fixing it correctly is a prerequisite to further stage work.
The root cause is misplaced ownership. Per openspec/AGENTS.md §16 (contract + conformance harness colocate with the contract package), PipelineRouter belongs in @suaveplan/api-router — the existing abstract router-contract package every adapter already depends on. api-host is a consumer of that contract, not the contract owner.
What changes
@suaveplan/api-router — contract owner (new subpaths)
- New feature folder
src/pipeline/:pipeline.types.tsexportingPipelineRoute,PipelineRouteOptions,PipelineRouteRegistration,PipelineRouter(relocated verbatim fromapi-host/src/request-pipeline/request-pipeline.types.ts).pipeline.md+index.tsbarrel (named exports, rule 10).pipeline.test.tscovering the type-shape invariants the harness asserts on.
- New feature folder
src/testing/pipeline-router-conformance/:pipeline-router-conformance.ts—runPipelineRouterConformance({ label, build })(relocated verbatim fromapi-host/src/testing/pipeline-router-conformance/).pipeline-router-conformance.md,pipeline-router-conformance.types.ts,pipeline-router-conformance.test.ts,index.ts.
package.jsonsubpaths added:./pipeline,./testing/pipeline-router-conformance. The existing./testingbarrel re-exports the harness for ergonomic single-subpath consumption.
@suaveplan/api-host — consumer (de-owned)
src/request-pipeline/request-pipeline.types.tsre-exports the 4 contract symbols from@suaveplan/api-router/pipeline(single source of truth) — every internal consumer keeps working without code changes.src/testing/pipeline-router-conformance/is deleted in favour of the relocated harness.src/testing/testing.tsre-exportsrunPipelineRouterConformancefrom@suaveplan/api-router/testing/pipeline-router-conformanceto preserve the back-compat path@suaveplan/api-host/testingalready in use.dependenciesadds@suaveplan/api-router.peerDependencies+peerDependenciesMeta+devDependencies: remove the@suaveplan/api-router-in-memoryentry. The dynamic-import pattern increateTestHostremains — no package-graph edge is needed for runtime resolution.
@suaveplan/api-router-in-memory — adapter
tests/conformance/api-host.test.tsrenamed →tests/conformance/pipeline-router.test.ts; import path updates to@suaveplan/api-router/testing/pipeline-router-conformance(or@suaveplan/api-router/testing).package.jsondependencies: replace@suaveplan/api-hostwith@suaveplan/api-router(already present — drop the api-host entry).vite.config.tsexternallist updates (@suaveplan/api-host→@suaveplan/api-router).- Docstring mentions of
@suaveplan/api-host'sPipelineRouterrewritten to@suaveplan/api-router/pipeline'sPipelineRouter.
@suaveplan/api-router-hono — adapter
src/pipeline/pipeline.conformance.test.tsimport path updates to@suaveplan/api-router/testing/pipeline-router-conformance.package.jsondependenciesswap if api-host was listed — replace with@suaveplan/api-router.
Cycle audit (acceptance criterion)
After the refactor, bunx turbo run build --dry=json | jq '...' MUST show:
@suaveplan/api-hostbuild edges:api-router,api-db-adapter,api-lifecycle,api-loader,api-manifest,api-secrets,api-types,error,events,opentelemetry,rate-limiter,retry. NOapi-router-in-memory.@suaveplan/api-router-in-memorybuild edges:api-router,api-types,error. NOapi-host.bunx turbo test --output-logs=errors-onlyexit code 0 (noCyclic dependency detectedwarning).
Back-compat
Every existing import path remains valid:
import { PipelineRouter } from "@suaveplan/api-host/request-pipeline"→ works (re-export).import { runPipelineRouterConformance } from "@suaveplan/api-host/testing"→ works (re-export).
New canonical imports:
import { PipelineRouter } from "@suaveplan/api-router/pipeline"(preferred).import { runPipelineRouterConformance } from "@suaveplan/api-router/testing/pipeline-router-conformance"(preferred).
Out of scope
- Adding a second
PipelineRouterimpl beyond what already exists. - Changing the harness's invariants.
- Stage-7 engine work — that's gate 2.7, not this change.