# Spec delta: api-host ## MODIFIED Requirements ### Requirement: `./testing` subpath ships the PipelineRouter conformance harness The package SHALL declare `./testing` as a subpath export AND ship `runPipelineRouterConformance(factory)` plus the supporting `PipelineRouterConformanceFactory` and `PipelineRouteRegistration` types. The harness SHALL exercise the host's `PipelineRouter` contract against any adapter via a factory-supplied `(router, register, reload?)` triple, covering: register → match round-trip, `null` on miss, `statusMap` propagation, plugin ownership across reload, `:param` parsing, and collision detection. The placeholder `STAGE_2_RESERVED` sentinel SHALL remain exported for back-compat so existing detection logic (`Object.keys(mod).length > 0` style) continues to discriminate "is the harness wired?" from "is the subpath reserved?". The package's vite build SHALL emit `dist/testing/testing.js` AND `dist/testing/pipeline-router-conformance/pipeline-router-conformance.js` so the subpath actually resolves at runtime — these files were previously absent because the testing module was tree-shaken out of the single-entry build. #### Scenario: harness drives a reference PipelineRouter through every required case - **WHEN** a test calls `runPipelineRouterConformance({ label, build: () => ({ router, register, reload? }) })` - **THEN** `describe("PipelineRouter conformance — ${label}", ...)` registers six `it()` cases AND all of them pass against any adapter whose `register` populates a backing table consulted by `router.match` #### Scenario: harness asserts collision detection throws - **WHEN** the harness invokes `register({ plugin: "p", method: "GET", path: "/x" })` followed by `register({ plugin: "q", method: "GET", path: "/x" })` - **THEN** the second call SHALL throw — the harness uses `expect(...).toThrow()` without prescribing the error class so any adapter-specific typed error satisfies the contract #### Scenario: harness asserts plugin ownership survives reload - **WHEN** the factory exposes a `reload()` hook, the harness registers a route, calls `reload()`, then calls `router.match(...)` - **THEN** the matched route's `plugin` AND `params` survive the reload cycle **Verified by:** `src/testing/pipeline-router-conformance/pipeline-router-conformance.test.ts` AND (downstream) `@suaveplan/api-router-hono`'s `src/pipeline/pipeline.conformance.test.ts`