Skip to content

spike(flow): compile the OpenEMR referral save to HTTP requests (draft, do not merge) - #1261

Closed
abrichr wants to merge 4 commits into
mainfrom
claude/api-tier-spike-8494a8
Closed

abrichr wants to merge 4 commits into
mainfrom
claude/api-tier-spike-8494a8

Conversation

@abrichr

@abrichr abrichr commented Oct 18, 2026

Copy link
Copy Markdown
Member

Engineering spike, not a feature. Please don't merge. Synthetic patients on a local OpenEMR 8.0.0.3 test copy; no public claims.

Write-up: packages/flow/benchmark/openemr_api_tier/README.md

Question

Can the recorded referral's save step be compiled into the HTTP requests the browser made, replayed without a browser using a fresh sign-in and the run's values, and still pass the same independent record check? How much faster is it?

Result (20 runs per arm, interleaved, same engine 8d0758b, same SQL record check)

Browser program (replay-dom bundle) API step
Correct 19 of 20 20 of 20
New referrals saved 12/12 17 of 18 18 of 18
r6 possible duplicate, no new chart 2 of 2 2 of 2
Median per run (correct new referrals) 23.9 s 2.14 s

The median run was 11.2 times faster (bootstrap 95% CI 10.8–12.1), 21.8 s less per run. The browser miss was a fail-closed stop with nothing saved: the identity check on the final Confirm click saw unexpected text near the button.

How it works

  • capture_har.py replays the published bundle once with HAR on. The save is one form-encoded POST to new_comprehensive_save.php that needs the session cookie and a per-session csrf_token_form.
  • formtier.py explains all 105 body fields: 11 recorded params, 1 select chosen by label, 1 CSRF token, 92 form defaults. If a field can't be explained, it refuses to compile. It emits a one-step bundle that uses the engine's existing ApiBinding (on_unavailable="halt") with the GUI save step's effect contract.
  • SessionFormActuator implements the engine's API actuator protocol. It signs in fresh, re-reads the form (CSRF, defaults, drift and client-side rule checks), runs the duplicate check, and posts the save once. It runs through the unchanged Replayer with a backend that raises on any GUI call.

Where a recorded request is not enough (results/probes.json)

  • The server rejects a stale or missing CSRF token, so the step reads a fresh one every run.
  • The server does not run the duplicate check. If the check is skipped, r6 is saved as a second chart, so the step runs the check itself.
  • The server does not enforce required fields set in the layout. The step refuses when the form's rules change.
  • The same save sent twice writes two charts. The step never retries after the POST begins.

The write-up covers where this can't apply (Citrix/RDP, rules that live in the page's script, SSO/MFA sign-in, multi-step server state) and what a real feature would need.

Review notes

  • api_bundle/session_form.json is an OpenEMR-specific compiled form binding for the public test copy. I judged it on the public side of source-policy.yaml because it is synthetic, and the OpenEMR fixture and REST arm are already public. Please confirm, or I'll move it private.
  • Not committed: the raw HAR (it holds the session cookie and the test copy's sign-in password; results/har_summary.json is the redacted version) and the GUI bundle (it stays in the referral study's scratch area).

🤖 Generated with Claude Code

abrichr and others added 4 commits October 18, 2026 12:26
…requests

Engineering spike, synthetic data only, not a feature.

- capture_har.py: replay the replay-dom referral bundle once with HAR on
- formtier.py / compile_api.py: explain every save-body field (recorded
  param, select label, session CSRF token, form default) or refuse; emit a
  one-step bundle whose ApiBinding (on_unavailable=halt) keeps the GUI save
  step's effect contract
- SessionFormActuator: fresh sign-in, fresh form read (CSRF, defaults,
  drift and client-rule checks), duplicate check, one save POST; runs
  through the unchanged Replayer with a backend that refuses GUI calls
- bench.py: 20 browser vs 20 API runs, interleaved, same record check
- probes.py: CSRF, skipped duplicate check, double submit, no session,
  form and rule drift
- results/: runs.csv, per-run JSON, figures.json, probes.json, redacted
  HAR summary

Browser 19/20 correct, median 23.9 s; API 20/20, median 2.14 s (11.2x).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- record --capture-network (web): NetworkCapture keeps the page's
  requests in network.jsonl; headers, secrets, session tokens and echoed
  cookie values are withheld
- compile proposes a web_form ApiBinding on the save when every field of
  its request is explained (param, select label, session value, form
  default); reads whose responses carry the GUI's pre-save text become
  pre-checks; api_proposal.json says why when nothing is proposed
- api show / propose / admit: a person admits the API step at a terminal;
  the admission is bound to the exact binding and copies the save's
  record check
- WebFormActuator sends through the replaying browser's own session,
  re-reads the form (CSRF, defaults, drift, client rules), runs the
  pre-checks, posts once; refusals before the post fall back to GUI
- Replayer runs an admitted API step in place of its GUI steps in plain
  linear runs (not under each_item/problems_only approval, governed,
  durable or resumed runs) and reports api_folded / api_fallback
- docs/API_TIER.md; tests/test_api_tier_web_form.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-8494a8

# Conflicts:
#	packages/flow/openadapt_flow/interactive_recorder.py
#	packages/flow/openadapt_flow/recorder.py
#	packages/flow/openadapt_flow/runtime/replayer.py
@abrichr

abrichr commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Superseded by #1269: the spike and the feature, rebuilt as one commit on main so gitleaks' history scan passes (a made-up token in a test on this branch tripped generic-api-key). The branch is kept.

@abrichr abrichr closed this Oct 18, 2026
@abrichr
abrichr deleted the claude/api-tier-spike-8494a8 branch October 18, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant