Story 1374: Measure What Users Actually Get From the Atlas Rail
Bug-1372 hung Step-5 ("thinking" forever) for ~15% of runs, and a human found it after a marathon. Direct-invoke, unit tests, and CI were all green the entire time. Nothing was wrong with the code paths anyone tested β what was broken was delivery: the terminal frame that settles the UI never reached the reconnected browser, and nothing measured that. This Story makes the rail report, per run, whether the user actually got their terminal frame β and pages on the first one who doesn't.
Why this matters (the catastrophe avoided)
A smoke detector that only tests itself is not a smoke detector. The old observability watched components: the brain completed, the worker invoked, the run persisted succeeded in the DB. Every one of those was green during Bug-1372. The thing that was on fire β "did a terminal frame reach a live socket?" β was the one thing no signal watched. So the fix is not another synthetic probe (that is a backstop); the fix is to instrument the real traffic at the exact seam where delivery succeeds or fails, define an SLO on it, and alarm when real users stop getting frames.
π°οΈ Satellite: where a run is born and where it must land
WS $default (app_api) Clerk worker (this repo) Browser
βββββββββββββββββββ ββββββββββββββββββββββββ βββββββ
stamps launched_at_ms β invokes brain, streams, β run.done lands
on the launch command pushes frames to the socket on a LIVE socket
ββ on 410 Gone, re-resolves = DELIVERED
the live socket (Bug-1372) ββ (or it hangs)
The clock starts when the client hits the WS edge and stops when a live socket accepts the terminal frame. That span β launch to delivered β is the latency the user feels. Component time (brain completion) is not; a slow rail can hide behind a fast brain.
βοΈ Airplane: the seam already knew the answer β we just never asked it
The _FramePusher (built in #356 for the Bug-1372 recovery) already returns whether a terminal frame reached a live socket: True from the launch connection or a re-resolved live one, and it falls through when no live connection exists. That fall-through is the hang. We taught the pusher to remember two facts:
| Flag | Set when | Reads as |
|---|---|---|
terminal_seen | a run.done/run.failed was reached and a push attempted | terminal truth exists in the DB |
terminal_delivered | that frame was accepted by a live socket | the user is unstuck |
terminal_delivered_at_ms | the instant of that accept (wall_clock()) | the "delivered" timestamp for latency |
The three combine into one outcome the SLO reads:
result | Condition | Meaning |
|---|---|---|
delivered | terminal_delivered | a terminal frame reached a live socket (even a delivered run.failed β the user is unstuck) |
hung | terminal_seen and not terminal_delivered | terminal in DB, no live socket got it β the exact Bug-1372 signature |
error | neither | the run never reached a terminal event at all (stream/rail failure) |
π¬ Microscopic: two lines that stop the metric from ever lying
A delivery metric is only trustworthy if it always fires and measures the right instant. Two deliberate choices in src/clerk_worker/runner.py:
1. Emitted in a finally β a hard hang can never read as "no traffic."
try:
body = _invoke_brain(command, deps)
outcome = await _consume_stream(body, command, deps, pusher)
except Exception as exc:
await _fail_run(deps, ..., pusher=pusher) # brain/infra failure β run.failed truth
outcome = "failed"
finally:
_emit_run_delivery(pusher, launched_at_ms=launched_at_ms, ...) # ALWAYS one outcome
Whatever happens inside the launch β success, brain failure, the Bug-1345 mid-stream stall, an unexpected raise β exactly one atlas_run outcome is emitted. If the run never reached terminal, it emits error, never silence. The only escape is a Lambda hard SIGKILL, and the new pramaan-clerk-worker-errors alarm backstops that.
2. The latency clock stops at DELIVERED, not at the DB write.
async def push(self, frame_type, payload):
delivered = await self._deliver(frame_type, payload) # True iff a LIVE socket accepted it
if frame_type in self._RUN_TERMINAL:
self.terminal_seen = True
if delivered and not self.terminal_delivered:
self.terminal_delivered = True
self.terminal_delivered_at_ms = self._deps.wall_clock() * 1000.0
time_to_terminal_frame_ms = terminal_delivered_at_ms β launched_at_ms. Because the timestamp is captured the moment _deliver returns True β a live socket accepted the frame β the latency is launch-to-delivered, not brain-completion. A run that completes in the DB but never reaches the user contributes zero latency samples and one hung count, exactly as it should.
From metric to pager
The worker emits EMF: AtlasRunDelivery{result} (count) and AtlasRunTimeToTerminalFrameMs (delivered runs only). provision-rail-delivery-alarms.yml turns them into SLO alarms on the existing pramaan-bridge-alarms topic (pramaan-docs ADR-028):
| Alarm | Pages when |
|---|---|
pramaan-atlas-rail-hung-frame | any result=hung β the Bug-1372 class, fires on the first affected user |
pramaan-atlas-rail-delivery-slo | delivered / total < 99% over 15m |
pramaan-atlas-rail-delivery-latency | p95 launchβdelivered > 60s |
pramaan-atlas-rail-push-gone-rate | 410-Gone churn climbing (leading indicator) |
One structured line per run, clerk.run_delivery: <result> keyed by correlation_id + run_id (pramaan-docs ADR-104), makes a stall a one-query root cause.
The backstop, kept in its place
src/rail_canary/ is a secondary probe: it runs one forced mid-run reconnect against the live rail (the exact Bug-1372 trigger) to cover zero-traffic windows and guard the recovery path. It mints a real Clerk session JWT so the live authorizer verifies it like any client β never special-cased. The real-traffic SLO above is the headline; the canary only catches what zero traffic would hide.
Scope discipline
launched_at_ms is added to the AgentCommand queue envelope (not an HTTP route), so make openapi snapshots are untouched. No RLS, no schema migration. The instrumentation lives only at the delivery seam in the clerk worker; tracing stays on the single ADR-104 ADOT path β no second tracing library added.
atlas_run outcome per run in a finally β delivered / hung / error with launch-to-delivered latency β so a terminal frame that never reaches a live socket pages immediately instead of hanging a user in silence.