Story 1437: ws_pusher continues the run trace

This Story closes the last server-side hop between the clerk worker and browser delivery. The clerk worker now injects W3C traceparent/tracestate into the Lambda invoke payload for ws_pusher. The pusher extracts that carrier before calling API Gateway post_to_connection, so the approved functions.ws_push.deliver CLIENT span joins the run trace.

Why

Without this carrier, X-Ray can show the worker and the WebSocket pusher as unrelated work. The product event still has run_id, but run_id is business truth, not trace context. pramaan-docs ADR-124 requires async server-side boundaries to carry traceparent explicitly.

Boundary model

clerk_worker runner
  -> inject active W3C trace context
    -> clerk_worker handler Lambda invoke payload
      -> ws_pusher extracts traceparent / tracestate
        -> functions.ws_push.deliver CLIENT span
          -> API Gateway post_to_connection
            -> browser frame remains run_id-only

What changed

SurfaceChange
src/clerk_worker/runner.py _FramePusher injects the active trace context at the server-side push boundary and passes it separately from the browser frame.
src/clerk_worker/handler.py _push serializes traceparent and optional tracestate as top-level fields on the ws_pusher Lambda payload.
src/ws_pusher/handler.py The pusher extracts the payload carrier and wraps run frames in manual_span(ManualSpanName.WS_PUSH_DELIVER) before post_to_connection.

Browser frame contract

{
  "v": 1,
  "type": "run.done",
  "run_id": "...",
  "payload": {}
}

The browser frame does not receive traceparent, tracestate, trace_id, or span_id. Browser receipt stays run_id-correlation-only for v1.

Semconv check

Standard keys emittedCustom PRAMAAN keys emitted
None added by this manual span. Transport/library telemetry remains T1 auto-instrumentation owned by ADOT. run_id, event.type
No stale standard keys such as http.method or http.status_code are introduced. No baggage is written; firm_id is not sent to the pusher payload or browser frame.

Verification

uv run pytest tests/unit/ws_pusher/test_handler.py \
  tests/unit/clerk_worker/test_handler.py \
  tests/unit/clerk_worker/test_runner.py \
  tests/unit/shared/observability/test_manual_span.py -q

uv run ruff check src/clerk_worker/handler.py src/clerk_worker/runner.py \
  src/ws_pusher/handler.py tests/unit/clerk_worker/test_handler.py \
  tests/unit/clerk_worker/test_runner.py tests/unit/ws_pusher/test_handler.py \
  tests/unit/shared/observability/test_manual_span.py

bash .github/scripts/observability-guardrail-check.sh
git diff --check