Files
2026-05-27 23:29:53 +01:00

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.subscribe observes exactly 22 events: entered followed by completed for 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 entered for 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 emit returns 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.pipelineEvents is an InMemoryPipelineEventsBus instance whose events array 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:

  1. Per-stage emit — every of the 11 stages emits entered AND (completed OR rejected OR skipped).
  2. Order — stages emit in §6 order.
  3. Skip — conditionally-skipped stages emit skipped (not silent omission).
  4. Rejection — when a stage rejects, no subsequent stage emits entered.

Scenario: harness rejects a host whose pipeline omits a stage

  • WHEN runPipelineEventsConformance runs 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"