3.7 KiB
Context
Every genesis package is required to ship @opentelemetry/api instrumentation (counters, histograms, spans) on its public entrypoints, with a no-op fallback when no SDK is registered (openspec/specs/testing/spec.md "Observability" requirement; CLAUDE.md Quality gates). The audit at /mnt/s/web/genesis/docs/superpowers/specs/2026-04-25-test-suite-audit.md §1 and §4 records two related defects: there is no fixture that exercises the export path of that instrumentation, and the test runner itself emits no telemetry — so flaky-test triage has nothing to correlate against. This change addresses both at once because the runner-side requirement is meaningless without a collector to receive its export, and the collector fixture is hard to justify without a real producer driving it.
Goals / Non-Goals
Goals:
- A
ServiceFixtureplugin forotel/opentelemetry-collector-contribwhose lifecycle, env-bridge, and health contract match the conventions established by the existingtesting-integration-*plugins (CLAUDE.md package list). - A runner reporter at
@suaveplan/testing/reporters/otelthat wraps eachit/testin a span, records assertion failures as span events, and serialises the full span tree of failed tests to disk + Garage S3. - Deterministic span ids and timestamps under test (the reporter accepts
ClockandIdGeneratorfrom@suaveplan/typesso OTel-of-OTel-tests is itself reproducible). - Zero behaviour change when
OTEL_EXPORTER_OTLP_ENDPOINTis unset.
Non-Goals:
- Replacing the per-package
@opentelemetry/apiinstrumentation. The reporter wraps tests, not source code. - Shipping a Jaeger / Tempo / Honeycomb backend — the collector fixture supports
logging,memory,jaeger, andfileexporters; downstream backend choice is a consumer concern. - Sampling. Every test span is exported; tail-sampling is the consumer's job in their own collector config.
Decisions
- Memory exporter is the assertion path. The
getSpans({ filter })helper queries the collector's in-memory exporter rather than parsingloggingexporter stdout — assertion-grade reliability is required. - Trace sink is opt-in. The reporter serialises failure traces unconditionally to
playwright-report/traces/<spec>.json(zero infra), but only uploads whenTESTING_TRACE_SINK_URLis set. This avoids surprise S3 traffic from local runs. - Reporter ships in
@suaveplan/testing(not in the new plugin) so that runner-side OTel works without taking the testcontainers dep — testing-infrastructure rule (CLAUDE.mdrule 8) forbids@suaveplan/*runtime deps in@suaveplan/testingbut not external@opentelemetry/*packages, which are already permitted. - Config synthesis vs
configFile. Most consumers want "OTLP HTTP receiver + memory exporter"; the plugin synthesises that case from the typed config object. Power users passconfigFileand the plugin mounts it verbatim — no template language. - Image pin. Tag
0.<minor>initially (matches existing plugin practice);tasks.mdtracks the digest-pin migration as a follow-up so this change does not block on a registry lookup.
Risks / Trade-offs
- Collector startup latency can dominate fast unit-test suites. Mitigated by
skipIfUnavailable()falling through when the fixture cannot start (consistent with othertesting-integration-*plugins) and by recommending nightly-only export in CI. - Memory-exporter retention is bounded; long suites can drop early spans. The plugin documents the bound and exposes a
flushSpans()method that pages through the memory exporter at suite teardown. - Failure-trace upload introduces a network dependency on red runs. Mitigated by best-effort upload (failures logged, not thrown) and by the env-var gate.