# `request-pipeline-outcomes` — driver config + success/failure result builders ## Purpose This module ties the bare emitters and validators together for the orchestrator. It exposes `DriverConfig` (the frozen per-pipeline settings bundle), `OutcomeArgs` (the common argument shape carried through every gate-fail branch), `runRequestValidation` (a thin adapter that parses the URL query, short-circuits on streaming / opt-out routes, and emits the dedicated `api.validation.failures_total{where=request}` counter), and the `successResult` / `failResult` builders that translate an outcome into a fully-shaped `PipelineResult`. These helpers were extracted from `request-pipeline.ts` during the file-size refactor. Behaviour is byte-for-byte identical to the pre-split implementation; the only change is that the shared argument bundle is now reified as a type so each branch can use `...spread` instead of re-listing eight parameters. ## Features - `DriverConfig` freezes the constructor inputs once and is threaded through every helper, so step-level functions see the same `boundary`, `now`, `instruments`, `defaultTenant`, and `responseValidation` mode. - `runRequestValidation` is the only piece that knows about the request URL's query — the orchestrator does not parse it directly anymore. - `successResult` always increments `api.requests_total{outcome=success}` and the duration histogram before returning. - `failResult` increments the same counter/histogram with `outcome=failure`; the audit envelope is only emitted when a route matched (the 404 path skips audit per spec). - `OutcomeArgs` is exported so the orchestrator can declare a single `carry` variable and reuse it across every fail branch. ## Usage: Basic ```ts import { type DriverConfig, failResult, runRequestValidation, successResult, } from "@suaveplan/api-host/request-pipeline-outcomes"; const validation = await runRequestValidation(config, { request, route, rootSpan }); if (validation.kind === "fail") { return failResult(config, { ...carry, failure: validation.failure }); } return successResult(config, { ...carry, response: handlerResponse }); ``` ## Usage: Advanced `DriverConfig` decouples the orchestrator from the `RequestPipeline` class — tests can construct a config object directly and call `runPipeline` to verify behaviour without instantiating the full class: ```ts const config: DriverConfig = { options, instruments, boundary: new ErrorBoundary({ instruments, logger }), now: () => 1000, idempotencyTtlMs: 60_000, responseValidation: "enforce", defaultTenant: null, }; ``` `OutcomeArgs.route` is nullable so the 404 path can reuse the same `failResult` shape. Tests that assert "no audit envelope on 404" rely on this branch — supplying a non-null route would silently emit an audit entry. ## API Reference ```ts interface DriverConfig { options: RequestPipelineOptions; instruments: HostInstruments; boundary: ErrorBoundary; now: () => number; idempotencyTtlMs: number; responseValidation: "enforce" | "warn"; defaultTenant: TenantContext | null; } interface OutcomeArgs { request: Request; requestId: string; rootSpan: OtelSpanLike & BoundarySpan; startedAt: number; route: PipelineRoute | null; principal: Principal | null; tenantCtx: TenantContext | null; } function runRequestValidation( config: DriverConfig, args: { request: Request; route: PipelineRoute; rootSpan: OtelSpanLike & BoundarySpan } ): Promise; function successResult(config: DriverConfig, args: OutcomeArgs & { response: Response }): Promise; function failResult(config: DriverConfig, args: OutcomeArgs & { failure: BoundaryFailure }): Promise; ``` ## Implementation Notes `runRequestValidation` is the ONLY function that derives `parsedQuery` from the request URL. Centralising it ensures all three early-validation outcomes (streaming-skip, opt-out, schema-pass) return the same query record shape — without that, the handler ctx could see `{}` on one path and the parsed record on another. `successResult` calls `withRequestHeaders` exactly once, deliberately. The original implementation also called it once and the post-extracted form preserves that — re-cloning the response would burn an unnecessary `Headers` allocation per request. `failResult` always emits the `api.requests_total{outcome=failure}` counter, including on the 404 path. That contract is depended on by the `api.errors_by_step` dashboard the host's STATUS docs reference; removing it (e.g. as an optimisation for the no-route case) would silently break the metric.