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

11 KiB

Tasks — OpenTelemetry completion

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened.

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

Audit Note (2026-05-02)

Status: DEFERRED. packages/monitor/opentelemetry/src/ contains core (with propagation + sdk), errors, schemas, and utils (metrics-utils, resource, span-utils). None of the five proposed additions exist: no exporters/ directory (no InMemorySpanExporter, no ConsoleSpanExporter), no id-generator/ (no generateTraceId / generateSpanId / createDeterministicIdGenerator), no baggage/ (no W3CBaggagePropagator, no serializeBaggage / parseBaggage). The OTEL.EXPORT_FAILED / OTEL.INVALID_TRACE_ID / OTEL.INVALID_SPAN_ID error codes are also absent. 0% of work is done.

1. Spec authoring

  • 1.1 Read openspec/archive/2026-04-19-enterprise-opentelemetry/proposal.md end-to-end to confirm what was committed and what slipped
  • 1.2 Read openspec/AGENTS.md Sections 13 (spec-conformance audit) and 14 (source mapping for migrations); §14a (proposal must list affected dependents)
  • 1.3 Draft proposal.md from openspec/templates/proposal.md — include the ## Position on the legacy substrate rationale (out-of-scope items justified)
  • 1.4 Fill ## Source mapping with upstream line ranges for each ADDED requirement; every "no source line" requirement justified
  • 1.5 Draft specs/opentelemetry/spec.md from openspec/templates/spec-delta.md — five ADDED requirements: in-memory exporter, console exporter, id-generator, deterministic id-generator, baggage propagator
  • 1.6 Every requirement in the spec delta carries a **Verified by:** line naming concrete it(...) titles
  • 1.7 Draft ## Migration section in proposal.md listing legacy Tracer / Meter / error-bridge consumers and their replacement path
  • 1.8 bunx openspec change validate opentelemetry-completion exits with no errors

2. Implementation — exporters

  • 2.1 Add @opentelemetry/sdk-trace-base to packages/monitor/opentelemetry/package.json dependencies via catalog:observability (verify the catalog entry exists; add it if missing)
  • 2.2 Create packages/monitor/opentelemetry/src/exporters/memory/memory.ts implementing InMemorySpanExporter against the SpanExporter interface from @opentelemetry/sdk-trace-base
  • 2.3 Provide getFinishedSpans(), getSpansByName(name), reset(), shutdown(), forceFlush(), spanCount getter
  • 2.4 Make shutdown() idempotent; subsequent export() calls return ExportResultCode.FAILED with OpenTelemetryError(OTEL.EXPORT_FAILED, ...) as the error field
  • 2.5 Create packages/monitor/opentelemetry/src/exporters/console/console.ts implementing ConsoleSpanExporter and ConsoleSpanExporterOptions
  • 2.6 Default logFn to console.log; default pretty to true; render span kind, status code, ISO start/end times, duration, attributes, events, links, resource attributes
  • 2.7 Apply the same post-shutdown OTEL.EXPORT_FAILED rule to the console exporter
  • 2.8 Add OTEL.EXPORT_FAILED to packages/monitor/opentelemetry/src/errors/opentelemetry-error.ts OTEL_CODES

3. Implementation — id-generator

  • 3.1 Create packages/monitor/opentelemetry/src/id-generator/id-generator.ts exporting generateTraceId, generateSpanId, isValidTraceId, isValidSpanId
  • 3.2 Generation uses crypto.getRandomValues exclusively; throws OpenTelemetryError(OTEL.INVALID_TRACE_ID, ...) (or INVALID_SPAN_ID) if the host has no crypto.getRandomValues (genesis determinism rule — no Math.random() fallback)
  • 3.3 Re-roll on the all-zero value; cap at 8 attempts; if exceeded, throw OTEL.INVALID_TRACE_ID / INVALID_SPAN_ID
  • 3.4 Add INVALID_TRACE_ID / INVALID_SPAN_ID / TRACE_ID_HEX_LENGTH / SPAN_ID_HEX_LENGTH constants in packages/monitor/opentelemetry/src/constants/trace.ts
  • 3.5 Create packages/monitor/opentelemetry/src/id-generator/deterministic.ts exporting createDeterministicIdGenerator(rng: Rng): { generateTraceId: () => string; generateSpanId: () => string }
  • 3.6 Use the supplied Rng.bytes(n) to fill the trace / span ID; preserve the all-zero retry budget; route exhaustion through the same error codes
  • 3.7 Add OTEL.INVALID_TRACE_ID and OTEL.INVALID_SPAN_ID to OTEL_CODES

4. Implementation — baggage

  • 4.1 Create packages/monitor/opentelemetry/src/baggage/baggage.ts exporting W3CBaggagePropagator, serializeBaggage, parseBaggage, BAGGAGE_HEADER
  • 4.2 W3CBaggagePropagator implements TextMapPropagator from @opentelemetry/api; extract(context, carrier, getter), inject(context, carrier, setter), fields() returning ["baggage"]
  • 4.3 inject reads the active baggage via propagation.getBaggage(context); serialises with URI-encoding; calls setter.set(carrier, BAGGAGE_HEADER, value); no-op if the baggage is empty
  • 4.4 extract reads carrier[BAGGAGE_HEADER] (handling string-array carrier values); parses; sets via propagation.setBaggage(context, baggage)
  • 4.5 Honour MAX_BAGGAGE_ENTRIES / MAX_BAGGAGE_KEY_LENGTH / MAX_BAGGAGE_VALUE_LENGTH caps; entries past the cap are silently ignored (matches legacy and W3C tolerant-parser behaviour)
  • 4.6 Add the cap constants to packages/monitor/opentelemetry/src/constants/baggage.ts

5. Wiring

  • 5.1 Update packages/monitor/opentelemetry/src/index.ts to re-export every new symbol
  • 5.2 Update packages/monitor/opentelemetry/package.json exports map: add ./exporters, ./id-generator, ./baggage, ./constants subpath entries
  • 5.3 Add a top-level re-export of trace, metrics, context, propagation from @opentelemetry/api (Migration §1 — the convenience layer for consumers expecting the legacy substrate's classes)
  • 5.4 Update root README.md (packages/monitor/opentelemetry/README.md) ## Modules section to link the new co-located .md files

6. Tests

  • 6.1 src/exporters/memory/memory.test.ts — collect, query-by-name, reset, shutdown idempotency, post-shutdown export rejection, forceFlush
  • 6.2 src/exporters/memory/memory.spec-conformance.test.ts — pin the SpanExporter contract from @opentelemetry/sdk-trace-base (export signature, shutdown returns Promise, forceFlush returns Promise)
  • 6.3 src/exporters/console/console.test.ts — default logFn, custom logFn, pretty / compact rendering, post-shutdown rejection
  • 6.4 src/exporters/console/console.spec-conformance.test.ts — same SpanExporter contract pin
  • 6.5 src/id-generator/id-generator.test.ts — length, charset, non-zero invariant; isValidTraceId / isValidSpanId accept/reject
  • 6.6 src/id-generator/id-generator.spec-conformance.test.ts — fast-check property ∀ id: isValidTraceId(generateTraceId()) over 1000 samples
  • 6.7 src/id-generator/deterministic.test.ts — seeded Rng produces reproducible IDs across two calls
  • 6.8 src/id-generator/deterministic.spec-conformance.test.ts — supplied Rng that always yields zero bytes throws OTEL.INVALID_TRACE_ID after the 8-attempt budget
  • 6.9 src/baggage/baggage.test.ts — W3CBaggagePropagator round-trip extract/inject, URI-encoding round-trip for key with space=value;with semicolon
  • 6.10 src/baggage/baggage.spec-conformance.test.ts — MAX_BAGGAGE_ENTRIES cap silently drops the 181st entry; key longer than MAX_BAGGAGE_KEY_LENGTH is dropped; value longer than MAX_BAGGAGE_VALUE_LENGTH is dropped
  • 6.11 Every it(...) in 6.1-6.10 carries { timeout: 5000 }

7. Documentation

  • 7.1 Sibling .md for every implementation .ts (≥200 words, basic + advanced examples, full API reference)
  • 7.2 README links every co-located .md under ## Modules
  • 7.3 README ## Features enumerates the five new capabilities
  • 7.4 Update CLAUDE.md package listing for @suaveplan/opentelemetry to include the new exports

8. Pre-archive verification (the 5-gate triple)

  • 8.1 bun run check — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
  • 8.2 bunx turbo lint typecheck test --filter=@suaveplan/opentelemetry — green
  • 8.3 bunx turbo test:coverage --filter=@suaveplan/opentelemetry — 100/100/100/100
  • 8.4 bun scripts/openspec-conformance.ts @suaveplan/opentelemetry — exit 0, no error findings
  • 8.5 bunx turbo build --filter=@suaveplan/opentelemetry — green

9. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 9.1 Generics threaded; expectTypeOf assertions on every literal-type claim (e.g. OTEL_CODES keys)
  • 9.2 Zod schemas wired at every declared boundary (constructor options for both exporters; baggage cap config)
  • 9.3 Every error code in OTEL_CODES thrown by at least one production path (OTEL.EXPORT_FAILED, OTEL.INVALID_TRACE_ID, OTEL.INVALID_SPAN_ID — covered above)
  • 9.4 Strict-mode behaviors tested on and off (post-shutdown export, retry-budget exhaustion)
  • 9.5 OTel instruments emitted from production paths and asserted with the new InMemorySpanExporter (eat your own dogfood — withSpan test in src/utils/span-utils.test.ts adopts it)
  • 9.6 Lifecycle dedup tested where claimed (idempotent shutdown() on both exporters)
  • 9.7 No retroactive tick — every box above reflects work done in this change

10. Dependent sweep

  • 10.1 bunx turbo test --filter=...@suaveplan/opentelemetry — green; @suaveplan/events-metrics continues to pass
  • 10.2 Migrate any in-tree consumer that hand-rolled an in-memory span exporter to use the new InMemorySpanExporter. Run: grep -rln "InMemorySpanExporter\|SimpleSpanProcessor\|BatchSpanProcessor" packages/ src/ --include="*.ts" and verify no duplicate impl exists
  • 10.3 If any dependent currently mocks @opentelemetry/sdk-trace-base, replace with the real InMemorySpanExporter in a follow-up commit (per the genesis "no mocks" rule)

11. Archive

  • 11.1 Fold specs/opentelemetry/spec.md into openspec/specs/opentelemetry/spec.md
  • 11.2 Move openspec/changes/opentelemetry-completion/ → openspec/archive/<yyyy-mm-dd>-opentelemetry-completion/
  • 11.3 Refresh MANIFEST.md if package set or deps changed (this change adds @opentelemetry/sdk-trace-base as a dep)