← Choice Health 2026-07-10-state-program-debugger-implementation-plan.md raw .md

State Program Debugger Implementation Plan

Date: 2026-07-10
Depends on: 2026-07-10-state-program-debugger-evidence-design.md revision 2
Status: execution plan for implementation models; no packet implies production readiness

Purpose

Turn the design contract into small, independently reviewable work packets. This plan is intentionally explicit enough for lower-cost models to implement mechanical packets without deciding architecture as they go.

The first useful milestone is read-only recorded navigation for Codex, Claude, and the browser. Historical-code execution and state simulation come later and require stronger implementation and security review.

Non-Negotiable Rules

  1. The Markdown evidence design is authoritative when this plan is silent.
  2. Do not edit generated business flows to implement debugger behavior.
  3. Do not put raw PII, secrets, or unredacted caller audio into test fixtures.
  4. Do not add external tool writes, phone calls, texts, ticket mutations, or network access to simulation.
  5. Do not infer missing values, timestamps, writers, event ownership, or source revisions. Mark them unavailable.
  6. Keep recorded navigation and simulation separate in code and JSON.
  7. The browser, CLI, and API must call the same pure projection engine.
  8. Each packet lands with its focused tests before the next packet starts.
  9. Existing /api/debug/state_trace and /api/debug/session_facts behavior is preserved until the replacement client is proven.
  10. No production integration or OTA test occurs without the design gates and explicit human GO.
  11. Every operator-facing Markdown document has a rendered counterpart under docs/html/; docs/html/index.html links the current canonical pages and removes or relabels obsolete prototype links.

Documentation Lifecycle

Markdown under docs/design/ or docs/guide/ is canonical. After changing an operator-facing document, render its HTML counterpart with:

cd docs
../.venv/bin/python ../scripts/render_doc.py <source-relative-to-docs> \
  html/<output-name>.html

Every packet checks whether its user-visible behavior changes the evidence design, implementation plan, CLI/API guide, or operator workflow. When it does:

  1. update the canonical Markdown in the same commit;
  2. regenerate the complete HTML page, not a hand-maintained excerpt;
  3. add or update its link and summary in docs/html/index.html;
  4. remove, relabel, or redirect obsolete index entries so there is one obvious current starting point;
  5. run tests/test_docs_index.py, tests/test_docs_toc.py, and tests/test_docs_truth.py.

Do not delete mock pages while tests or review packets still depend on them. WP6 marks them prototype/obsolete after the hydrated page exists; WP10 removes their routes and files only after equivalent production tests pass.

Target Module Boundaries

File Ownership
riff/postcall/state_program_artifact.py normalize durable artifacts and flow metadata into riff.state_program_debugger/v1
riff/state_program_debugger.py I/O-free cursor projection, stepping, reverse, breakpoints, hashes, and later simulation commands
riff/session_slot_ledger.py seeded baseline, safe value representation, per-session fingerprints, and mutation helpers
scripts/state_program_debugger.py thin JSON CLI over the artifact builder and pure engine
riff/web_server.py thin authenticated/local HTTP adapter; no projection or simulation logic
web/state-program-debugger.js API client and rendering only
web/state-program-debugger.html production debugger surface; the mock remains a reference until replacement
docs/guide/state-program-debugger.md operator and AI instructions for build, CLI/API stepping, evidence labels, and safety
docs/html/state-program-debugger.html complete rendered operator guide
docs/html/index.html canonical discovery links; obsolete mock/debugger links removed or relabeled

Do not put hydrator joins in JavaScript or command semantics in HTTP handlers.

Shared Schemas

Seeded Slot Event

New event type:

{
  "type": "session_slots_seeded",
  "session_id": "v3:example",
  "slots": [
    {
      "slot": "caller_name",
      "safe_value": "<str len=5>",
      "availability": "redacted",
      "value_fingerprint": "hmac-sha256:...",
      "writer": "replicant_identify"
    }
  ],
  "snapshot_fingerprint": "sha256:...",
  "fingerprint_scheme": "session-hmac-sha256/v1"
}

The per-session HMAC key is random, lives only on SessionContext, and is not persisted. Fingerprints can be compared inside one session but cannot correlate caller values across sessions. safe_value uses the existing PII-safe slot representation. Internal routing enums may remain visible under the existing policy.

Every later slot_write adds old_fingerprint and new_fingerprint using the same session key. Dictionary-valued changes emit a safe shape plus fingerprint; they are not skipped. A boundary fingerprint is computed from sorted slot -> value_fingerprint pairs.

Execution Target

Every command that executes logic uses:

{
  "purpose": "recorded_reproduction|candidate_regression|controlled_isolation",
  "engine_revision": "git:<immutable-sha>",
  "flow_revision": "sha256:<immutable-flow-hash>",
  "runtime_lock": "sha256:<dependency-lock-hash>",
  "fixture_hash": "sha256:<capsule-fixtures>"
}

Read-only recorded navigation has no execution target because it executes no FSM, resolver, prompt, or tool code.

Work Packet Summary

Packet Outcome Depends on Suggested model
WP0 redacted failing fixture and schema tests none low-cost/mechanical
WP1 complete flow transition metadata WP0 low-cost/mechanical
WP2 strict visit framing and canonical recorded events WP0-WP1 medium
WP3 seeded baseline and complete mutation attribution WP0 medium; strong review
WP4 read-only artifact and cursor engine WP1-WP3 medium
WP5 CLI and local read-only API WP4 low-cost/mechanical
WP6 production read-only browser UI WP4-WP5 medium frontend
WP7 state capsule and candidate simulation WP4 strong model + security review
WP8 historical reproduction runner WP7 strongest model + human security review
WP9 bounded pattern runner and regression export WP7-WP8 strong model
WP10 promotion review and production integration all Fable/Opus adversarial review + human GO

Lower-cost models should not receive WP7-WP10 as unsupervised implementation tasks. They can implement fixtures, metadata extraction, adapters, and focused tests once the interfaces below are fixed.

Current Progress

WP0 - Redacted Boundary Fixture

Goal: create the smallest durable fixture that reproduces the original announce/menu attribution defect without caller PII.

Files:

Fixture facts:

Tests written first:

Do not: copy a production session directory into the repository.

WP1 - Flow Transition Metadata

Goal: make declared legal exits machine-readable.

Files:

Required state metadata:

{
  "transitions": [
    {
      "edge_id": "menu:1",
      "declaration_index": 1,
      "to": "cap_existing_request__list_tickets",
      "when": "slot_equals:_route:existing_request",
      "reason": "caller chose existing_request",
      "sets": [],
      "clears": []
    }
  ],
  "static_slot_references": []
}

edge_id is <state_id>:<zero-based declaration index> and is stable for a specific flow hash. Preserve declaration order. Never join by target alone.

static_slot_references is a list of {slot, source, detail} rows. Allowed sources in this packet are required_slot, must_include, locked_choice_slot, prompt_interpolation, required_tool_arg, edge_guard, edge_set, and edge_clear. Parse slot names only from structured fields, {{slots.<name>}} interpolation, and known guard DSL forms such as slot_equals: and slot_set:. For a custom registered guard whose reads cannot be derived from its authored string, emit {source: "custom_guard", slot: null, detail: <guard-name>}; do not inspect Python source or guess its reads. Runtime guard_evaluated.slots_read supplies the actual guard-read evidence later.

Definition of done: focused tests cover duplicate targets, sets, clears, reasons, static reference sources, a custom guard with unknown static reads, and a state with no transitions.

WP2 - Visit Framing and Canonical Events

Goal: build strict state visits without variable reconstruction yet.

Files:

Pure entry point:

build_state_program_artifact(
    *, manifest: dict, bus_events: list[dict], trace_events: list[dict],
    flow_metadata: dict, transcript_events: list[dict], audio_refs: list[dict]
) -> dict

No file reads occur inside this function. A separate thin loader reads session files and calls it.

Rules:

Definition of done: WP0 attribution tests pass; re-entry creates distinct visits; a post-exit event cannot appear in the prior visit; hydration is byte-identical on two runs.

WP3 - Seeded Baseline and Mutation Ledger

Goal: make entry/current/exit variable claims reconstructable.

Files:

API:

seed_session_slots(ctx, sources: list[tuple[str, dict]]) -> dict
slot_write_event(ctx, *, slot, old, new, kind, writer) -> dict | None
snapshot_fingerprint(ctx) -> str

The phone path passes separate sources for prior_call, caller_identity, replicant_identify, transport, and flow_default; it must not merge them before attribution. Keep the old constructor argument temporarily for test and caller compatibility, assigning it writer legacy_initial_slots.

Replace direct baseline slots.update and initial caller/called-number writes with seed_session_slots before the first state performs work. Do not treat slot_filled as mutation evidence. Replace the matched locked-choice session.slots.update(slot_updates) with per-key ledger writes before guard evaluation; keep the event's current turn semantics and add an explicit visit_id when available.

Definition of done:

WP4 - Read-Only Artifact and Cursor Engine

Goal: deliver Mode 1 for AI use without simulation.

Files:

Pure engine API:

open_debugger(artifact: dict) -> DebuggerState
apply_command(state: DebuggerState, command: dict) -> tuple[DebuggerState, dict]

Supported commands: list_visits, inspect_state, seek, step, reverse, continue_until, inspect_variables, inspect_exits, inspect_evidence, and fetch_audio_range metadata. The engine returns audio references; it does not play audio or read a WAV.

Every response includes schema, mode=recorded, old/new cursor, projection, evidence refs, and determinism hash. Unknown action, stale hash, unavailable event, and boundary overrun return typed errors without moving the cursor.

Definition of done: forward then reverse returns the original hash; Entry/Exit never change; CLI/API/UI fixture projections can be compared later.

WP5 - JSON CLI and Local Read-Only API

Goal: let Codex and Claude step without a browser.

Files:

CLI examples:

.venv/bin/python scripts/state_program_debugger.py build <session-dir> --out <artifact.json>
.venv/bin/python scripts/state_program_debugger.py command <artifact.json> \
  --json '{"action":"seek","visit_id":"menu#1"}'

The command prints one JSON object to stdout and diagnostics to stderr. It does not start a server, execute a model, or call a tool.

HTTP: add one read-only command endpoint under /api/debug/state_program. Validate session IDs with the existing strict alphabet. Bind/debug access is loopback-only by default. If the web server is exposed beyond loopback, require Authorization: Bearer $RIFF_DEBUG_API_TOKEN; when the token is unset, return 403. Do not add wildcard CORS or return raw slot values.

Definition of done: CLI and API return the same response and determinism hash for WP0; path traversal, stale hash, missing token, and unsupported simulation actions are rejected. Create the operator/AI Markdown guide, render its complete HTML counterpart, link it from docs/html/index.html, and pass the three docs tests listed in Documentation Lifecycle.

WP6 - Production Read-Only Browser

Goal: replace hardcoded mock data with Mode 1 artifact hydration.

Files:

Use the existing mock only as a visual reference. Remove hardcoded visits and browser speech from evidence claims. Render the dedicated conversation lane, state breadcrumb, boundary variables, legal exits, six evidence beats, and recorded/unavailable/redacted labels.

Definition of done: semantic browser tests assert frame ownership and immutable Entry/Exit; desktop/mobile Playwright screenshots have no overlap; the page and CLI report the same cursor hash. Update the operator guide and HTML. Make the hydrated page the primary debugger link in docs/html/index.html; relabel mock links as prototypes rather than leaving two apparently canonical debugger entries.

WP7 - State Capsule and Candidate Simulation

Goal: support safe counterfactual runs against an explicitly selected candidate engine and flow.

Status (2026-07-11): the call-time identity/snapshot prerequisite and riff.state_test_capsule/v1 export are implemented. CLI and session-service/API exports are byte-identical for the same cursor, and the browser exposes the same export action. Capsules fail closed on mutable-flow provenance, redacted inputs, uncorrelated tool previews, unaddressed audio, missing mutable context, and unfrozen execution policies. Candidate simulation, historical execution, pattern runs, and browser fork controls remain NO-GO until the no-network/no-write runner and its security gates land.

Files: chosen only after a strong-model design check of WP0-WP6. Extend the pure engine rather than adding simulation to HTTP or JavaScript.

Required behavior:

Definition of done: a candidate run cannot mutate the recorded artifact, filesystem fixtures, network, ticket store, or phone transport. Security review is mandatory.

WP8 - Historical Reproduction Runner

Goal: reproduce recorded Python behavior when the historical engine is available.

Resolve the recorded Git SHA and dependency lock from trusted local artifacts. Use an isolated detached worktree or immutable container with network disabled. Never fetch or execute an untrusted revision on demand from the API. If code, flow, lock, or fixture dependencies are absent, return HISTORICAL_RUNTIME_UNAVAILABLE; never use HEAD as fallback.

Compare four explicit targets when requested:

  1. recorded engine + recorded flow;
  2. candidate engine + recorded flow;
  3. recorded engine + candidate flow;
  4. candidate engine + candidate flow.

This packet requires strongest-model implementation, human security review, and tests proving unavailable historical code cannot fall through to current code.

WP9 - Pattern Runner and Regression Export

Goal: let an AI sweep bounded patterns against one state.

Input is JSONL with a schema, unique pattern ID, overlays, caller observations, selected source, DTMF/control, audio outcome, tool fixtures, and execution target. Reject unknown keys. Enforce branch, event, time, and model-budget caps.

Output is JSONL with exact exit edge, variable delta, simulated audio/model refs, invariant findings, resolved revisions, and comparison to the recorded branch. export_regression writes a redacted fixture plus a focused failing test template; it never edits production flows.

Definition of done: the property-menu matrix reproduces the recorded route, finds a transcript-selection counterexample, and exports a deterministic test that fails before the candidate fix and passes after it.

WP10 - Promotion

Run all focused suites, the full relevant regression suite, docs truth checks, security tests, Playwright desktop/mobile screenshots, and the Claude Fable re-review packet. Verify the thirteen design GO criteria and all seventeen acceptance gates.

Promotion states are separate:

Before PRODUCTION GO, audit every debugger entry in docs/html/index.html. Remove superseded entries and delete obsolete mock files/routes only when no test, review packet, or operator link references them. Re-render every changed Markdown page and run the documentation lifecycle tests.

Required Handoff From Every Implementation Model

Each packet result reports:

Do not report a higher promotion state than the packet can prove.