RIFF · call replay & layered atlas · use-case-first spec · 2026-07-07 · TDD twin: tests/test_call_replay.py

Call Replay & Layered Atlas — Use Cases

One command turns any call — live, archived, or simulated — into a self-contained page a stranger can read. The success criterion for everything below: answer "why did this call do that?" in five minutes without opening flow YAML.

The four lanes. Every replay page renders the same time-aligned lanes: HEAR (what the caller heard/said), PATH (FSM states), SLOTS (the slot_write ledger: who set/erased what), VERDICTS (per-decision transition arbitration: winner + shadowed edges). Command: .venv/bin/python scripts/call_replay.py <call-id | session-dir | transcript.json> [--out page.html]
Input & ordering contract (Codex #1/#2 — the testable rules):

Case R1 — "Why did this call do that?" TR1

  1. The operator has a call id (from the phone log, companion page, or postcall dir).
  2. call_replay.py <call-id> finds the session artifacts under data/sessions/ and writes one self-contained HTML file.
  3. The page shows the four lanes in time order; every FSM state the call visited appears in PATH, in visit order; every slot_write appears in SLOTS with its writer.

Pass: page exists, self-contained (no external fetches), lanes present, states in order. Never: a lane silently empty — a lane with no data says "not recorded on this call" out loud.

Case R2 — Pre-lane archived calls degrade honestly TR2

  1. The operator replays a call recorded BEFORE the telemetry lanes existed (e.g. the 2026-07-07 incident calls).
  2. The page renders HEAR + PATH from what exists, and marks SLOTS / VERDICTS "not recorded on this call".

Pass: old artifacts still render; missing lanes named. Never: an absent lane rendered as an empty (implying clean) lane.

Case R3 — Layered atlas: the flow that mutates TR3

  1. An engineer wants to see how one flow differs for guest / ANI-known / verified callers — without any call at all.
  2. call_replay.py --atlas flows/legal_intake.yaml renders the compiled graph with a layer toggle: each state is tagged with the trust layers it can serve (data-layer="guest known verified"), and the welcome gate zone (welcome_*) is visually marked as gate machinery.
  3. Toggling a layer dims states that can't occur on that layer (e.g. welcome_back never happens for a guest; welcome_first never for a known caller).

Pass: every state carries an explicit layer tag from guest | known | verified | shared | unattributed; gate zone marked; the welcome branches tagged to their layers (welcome_first→guest-entry, welcome_back→known, welcome_unlock_granted→verified-edge). Never: a state with no tag at allunattributed is allowed but must be visibly called out (it is a to-do, not an error; Codex #4).

Case R4 — Live calls carry the lanes automatically TR4

  1. A live call ends. At teardown the phone server dumps this session's bus events (slot_write, transition_verdict, and the rest) to data/sessions/<call-id>/bus_events.jsonl.
  2. Replaying that call id renders all four lanes with no extra flags.

Pass: the dump function writes only THIS session's events, appends nothing to other sessions' files, and is safe when the bus buffer has rolled (writes what remains + a truncation notice). Never: another session's events in the file; a teardown crash because telemetry failed (dump errors are logged, not raised).

Case R5 — Sim conversations replay the same way TR5

  1. An eval or phantom-caller conversation produced a transcript JSON with fact_events (the PR1 lane).
  2. call_replay.py path/to/transcript.json renders the same page: HEAR from the transcript turns, PATH from per-turn states, SLOTS from fact_events.
  3. This is the shift-left half: the same page that explains a live incident explains a failing eval — one literacy for both.

Pass: phantom/eval transcripts render; SLOTS lane populated from fact_events. Never: public slot VALUES anywhere in any mode's output (see the global PII rule above) — the ledger's redaction (<str:12>) must survive rendering verbatim.

Case R6 — The PAGE lane: follow-along vs voice, one clock TR6

  1. The companion page already reports its life to the server (view = node entered, resync_skip, menu_poll) — but those events died in the phone log. The server now persists them per call to companion_events.jsonl (wall-clock stamped) beside the other artifacts.
  2. Replay renders a fifth lane, PAGE: node enter/exit intervals (an enter ends the previous node — the operator's exact ask: "track when we entered a specific page and when we exited"), aligned to the call clock.
  3. Replay computes divergence windows: any span > 5s where the page's current node and the FSM's current state disagree while both are known is flagged in red with its duration — "the page showed menu for 41s while the voice walked the Riders Club" becomes one visible bar.

Pass: PAGE lane renders enter/exit intervals; divergence windows named with durations; a session without the file says "not recorded on this call". Never: page and voice each rendering plausibly with no comparison — the lanes exist to disagree loudly.

Test matrix (TDD: these exist and fail before the tool does)

idcaseasserts
TR1R1 session-dir replaypage written, self-contained, 4 lanes, states in visit order
TR1nR1 Nevera lane without data renders "not recorded on this call"
TR2R2 pre-lane artifactsrenders; SLOTS+VERDICTS marked not-recorded
TR3R3 atlas layersevery state layer-tagged; welcome_* gate-marked; branch↔layer mapping
TR4R4 teardown dumpsession-filtered file; other sessions excluded; errors logged not raised
TR5R5 sim transcriptfact_events → SLOTS lane; PII redactions verbatim; no raw public values

Contract discipline: implementation cites the case it satisfies; a behavior change without a case change here is a bug by definition. Sibling contract: Session Facts — end-flow behavior.