4.8 KiB
Capability Spec Delta: api-host — Pipeline per-stage observability
Package: @suaveplan/api-host
Status: PROPOSED
ADDED Requirements
Requirement: Host emits a structured event at every §6 pipeline stage boundary
@suaveplan/api-host's RequestPipeline SHALL accept a pipelineEvents: PipelineEventsBus adapter slot on HostAdapters (from @suaveplan/api-types/pipeline-events). For every request processed, the pipeline MUST emit PipelineStageEvent { stage, outcome: "entered", requestId, at } on entry to each of the 11 stages defined in §6 of the foundation design doc, and MUST emit a matching completed / rejected / skipped event on exit.
Scenario: a successful request emits 22 events (entered + completed for each of 11 stages)
- WHEN a request that satisfies every stage's gates is processed
- THEN
pipelineEvents.subscribeobserves exactly 22 events:enteredfollowed bycompletedfor each of the 11 stages in §6 order
Scenario: a stage rejection halts subsequent stages
- WHEN the auth stage rejects a request (
outcome: "rejected") - THEN no subsequent stage emits
enteredfor that request id
Scenario: conditionally-skipped stages emit skipped, not silent omission
- WHEN an anonymous request bypasses idempotency-by-principal (no principal to key by)
- THEN the idempotency stage emits
{ stage: "idempotency", outcome: "skipped", requestId }(NOT silent omission)
Verified by: src/request-pipeline/request-pipeline.test.ts::"successful request emits entered+completed for all 11 stages in order" AND src/request-pipeline/request-pipeline.test.ts::"rejected stage halts subsequent stage emission" AND src/request-pipeline/request-pipeline.test.ts::"skipped stage emits skipped outcome explicitly"
Requirement: Default pipelineEvents is a zero-cost noop bus
When no pipelineEvents adapter is configured, the host MUST install a NoopPipelineEventsBus whose emit and subscribe methods are zero-cost (no allocation per call, no listener-list iteration). Production deployments that don't need per-stage observability incur no overhead.
Scenario: NoopBus emit allocates no objects
- WHEN a request flows through a host configured with NoopBus
- THEN
emitreturns immediately AND a microbench (bench:emit-noop) confirms zero allocation per call
Scenario: NoopBus subscribe is a no-op returning an unsubscribe noop
- WHEN a consumer calls
noopBus.subscribe(listener) - THEN the listener is never invoked AND the returned unsubscribe function is callable without effect
Verified by: src/pipeline-events-bus/noop-bus.test.ts::"emit on noop bus is a no-op" AND src/pipeline-events-bus/noop-bus.test.ts::"subscribe on noop bus returns a no-op unsubscribe" AND src/pipeline-events-bus/noop-bus.bench.ts::"emit-noop zero-allocation"
Requirement: createTestHost wires an InMemoryPipelineEventsBus by default
@suaveplan/api-host/testing.createTestHost() MUST default pipelineEvents to an InMemoryPipelineEventsBus (the canonical fixture). The bus is exposed on host.adapters.pipelineEvents for direct assertion in smoke tests.
Scenario: createTestHost exposes the bus for direct assertion
- WHEN a test calls
await createTestHost({ ... }) - THEN
host.adapters.pipelineEventsis anInMemoryPipelineEventsBusinstance whoseeventsarray can be inspected
Scenario: explicit override is honoured
- WHEN a test calls
createTestHost({ adapters: { pipelineEvents: customBus } }) - THEN
host.adapters.pipelineEvents === customBus
Verified by: src/testing/create-test-host/create-test-host.test.ts::"defaults pipelineEvents to InMemoryPipelineEventsBus" AND src/testing/create-test-host/create-test-host.test.ts::"explicit pipelineEvents override is honoured"
Requirement: Conformance harness runPipelineEventsConformance
The host SHALL ship runPipelineEventsConformance(host) from @suaveplan/api-host/testing/pipeline-events-conformance. The harness MUST assert four invariants:
- Per-stage emit — every of the 11 stages emits
enteredAND (completedORrejectedORskipped). - Order — stages emit in §6 order.
- Skip — conditionally-skipped stages emit
skipped(not silent omission). - Rejection — when a stage rejects, no subsequent stage emits
entered.
Scenario: harness rejects a host whose pipeline omits a stage
- WHEN
runPipelineEventsConformanceruns against a stub host whose pipeline skips audit emission silently - THEN the harness reports the "Per-stage emit" invariant failure pointing to the audit stage
Verified by: src/testing/pipeline-events-conformance/pipeline-events-conformance.test.ts::"harness rejects host with missing stage emission"