WS ContextVar token reset; case-insensitive traceparent [Bug-1158]

Story 1158Epic None8 files · +108/−13

PR #293 · pramaan-io/pramaan-functions · merged 2026-06-10

Why this matters

Two silent leaks in the WS observability path

Stale correlation ID in the next request; X-Ray span lost to lowercase

Both naive fixes pass tests — both die in prod.

The fix — Set the tag, reset the tag; read the header in any case it comes.

How it flows

sequenceDiagram
  autonumber
  participant req as WS request
  participant h as handler
  participant f as finally
  participant c as header clerk
  req->>h: set_correlation_id → token
  h->>h: work (may throw)
  h->>f: control falls through
  f-->>h: reset_correlation_id(token)
  req->>c: headers arrive mixed-case
  c->>c: get(): case-insensitive compare
  c->>c: keys(): original spelling preserved

src/shared/observability/correlation.py · lambda_wrapper.py

Methodin → outwhat it does
set_correlation_id(value) → Token[str | None]writes the request's ID into the ContextVar and returns the reset token
reset_correlation_id(token) → Noneconsumes that token in finally to wipe the slot and kill cross-invocation bleed
_CaseInsensitiveDictGetter(class)reads header keys case-insensitively at compare time without mutating the carrier dict
get(carrier, key) → list[str] | Nonereturns the trace value whether the caller sent Traceparent, TRACEPARENT, or any case

What changed, file by file

Vendored trace wrapper with case-insensitive getter for mixed-case propagator keys; TODO tracks deletion once upstream layers it in.

new classsignature
_CaseInsensitiveDictGetter(class)
get(carrier, key) → list[str] | None
keys(carrier) → list[str]

Adds set and reset helpers for the correlation ContextVar so the WS Lambda handler can return the token in finally.

new methodsignature
set_correlation_id(value) → Token[str | None]
reset_correlation_id(token) → None

Files changed (8)

File±
otel_wrapper.py ★+7 −3
runbooks/enable-observability-infra.md+4 −1
src/app_api/router_ws.py+4 −1
src/shared/observability/__init__.py+2
src/shared/observability/correlation.py ★+7 −3
src/shared/observability/lambda_wrapper.py ★+28 −5
tests/unit/shared/observability/test_lambda_wrapper.py+22
tests/unit/test_router_ws.py+34