Repository navigation
Conversation
A form that creates a record (Trac's New Ticket, NetBox's Add Device)
shows no existing record next to its save button, so the save compiled
with no identity check and qualification refused it ("save step has no
armed identity check"). `record --identifier FIELD` didn't help: its pixel
crop pictures the demo's own value, so a run with a new value halted.
The record such a save commits is the one the run typed into the form.
The submit-binding observer now also reads a labelled control's form
relation, and the compiler gives a consequential labelled click that has
no other identity evidence, and is a button of the one form owning every
field typed since the last save, an Anchor.form_identity (counts and
tokens only). Before the click the replayer checks, in the DOM, that the
button's form holds exactly the fields this run typed into and that each
still holds the value the run left in it (the Playwright backend
snapshots each field after typing). A rewritten field, a missing field or
a button of another form halts before the click; a backend that can't
read the page leaves identity unreadable, which halts too.
Found by the onboarding-effort benchmark (#1447).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and portal saves Review cases for React apps: - A controlled input that reformats the value now halts. The Playwright backend records, after typing, whether the field holds exactly the typed text (a select: its chosen option's label), and the form check counts a field that didn't as changed. A field reformatted on every keystroke stops at the typed-value read-back; one reformatted on blur stops at the form check. - A submit button outside the <form> element is armed when its form= attribute names the form. One with no form owner (a portal that submits from script) gets no form identity and stays unarmed, and a program recorded with form= halts before the click on such a page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AI review (coordinator)Verdict: blocking. Auto-merge left off. Form identity proves that the typed fields hold the typed values. It proves nothing about the rest of the form: hidden record ids, untouched defaults, or the action URL. So it arms an existing record's edit form and a create form scoped to the wrong parent, and the save goes through. How I testedI wrote real headless Chromium probes on
Answers to the six questions:
There is also a smaller issue in Exact fixes
The parts that hold up well are the WeakMap snapshot (no value returns to Python), the dedupe and count check, the mixed-form refusal, and the fail-closed paths for an unreadable page or a remounted node. |
Create now carries a form identity (bug 17, #1543). The run gate then refused seven unarmed field clicks on Medplum's placeholder-only form (bug 21), so the teach reaches those fields by keyboard from armed clicks; all 10 clicks are armed. Certification still refuses two key steps that only move the focus and assert no postcondition (bug 22). Evidence in results/medplum-phase-a/lease-2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Tested this branch, merged with #1564, on Medplum 5.2.8's new-patient form (React and Mantine, synthetic data), from the second-installation work in #1548. What worked
Gaps and questions the form showed (observed at record and compile time only; no qualification case or replay has run yet)
Separately, certification still refuses the program for an unrelated reason: two key steps assert no postcondition, because they only move the focus. That's tracked as bug 22 in #1548. 🤖 Generated with Claude Code |
…s own submit Review of #1543: form identity checked only that the typed fields kept their typed values, so an edit form that looked like the create form, an address, method or record id a script changed or added, a hidden parent or an untouched select on another record, and a click handler that submitted another form all verified and submitted wrong data. - Fingerprint: the observer records, for a labelled submit button of the filled form, the submission's origin and path, its method, and the sorted name/type list of every control it would send (hidden ones included). FormIdentity keeps their SHA-256 and the run recomputes them right before the click; any difference halts. - Untouched values: every field the run didn't type into is kept as a salted digest (the identity-template scheme) and compared. Hidden tokens are exempt: named like one (csrf, token, nonce...), since their value changes every page load, or changed while the demonstration was typed. Their values are never kept. Passwords never leave the page. - Only a form's own submit button gets a form identity. Right before the click a one-shot capture-phase submit guard cancels any submit that isn't that form from that button, and the step halts. - A signal-quorum identity policy and a form identity must now both hold. Tests: the review's six holes halt before the click with nothing submitted, a per-session CSRF token and a hidden value that rotated during the demonstration still verify, and a keyed React remount is pinned as a known false halt (LIMITS.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed in 81d00f5, all five points:
I also ran your unmodified Local regression with one worker: 1646 passed, 0 failed. Auto-merge is still off for your re-check. |
`arg=self._fill_value_key` passes a per-backend page key (a uuid made at runtime) and matched generic-api-key. The line is gone since 81d00f5; the fingerprint pins commit, file, rule and line, so nothing else is relaxed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
gitleaks: the failure was a false positive in this PR's first commit. The Medplum questions, answered from how the code works. None of these were run on Medplum, which has only been recorded and compiled so far.
|
AI review (coordinator), re-review of 81d00f5Verdict: still blocking. Auto-merge left off. All six original holes now halt with nothing submitted, and the qualification-quorum ordering is fixed. The new mechanisms have four gaps that let a wrong-record save through with identity What I testedI used real headless Chromium with the PR's recorder, compiler, Round 1, re-run (14 probes): edit, action_swap, late_hidden, parent_wrong, select_wrong and click_other all halt now, with Round 2 (15 new probes). Each row records on one variant and replays on another.
Answers to your four questions:
Exact fixes
The probes are in the review worktree: |
The third onboarding blocker from #1447: a save on a create-new form compiled with no identity check, and qualification and
clinical-writerefuse an unarmed save. #1537 fixes the other two.The problem
A form that creates a record (Trac's New Ticket, NetBox's Add Device) shows no existing record next to its save button. The identity band around Create ticket holds only the button's own label, so the save compiled unarmed ("the only readable row text is the target's own label"). Qualification then refused it ("save step … has no armed identity check").
The documented remedy,
record --identifier FIELD, doesn't work here. I recorded a Trac-shaped form with--identifier field_summary, and the save was armed by a pixel crop of the summary field. Replay with a new summary then halted on an identity mismatch, because the crop pictures the demo's own value.The fix
The record a create-form save commits is the one the run typed into the form. So the demonstrated form, all of it, is the identity.
Recording. For a labelled submit button of the form that owns every field typed since the last save, the submit-binding observer (
submit_binding.py) records three things:Compile. The step gets
Anchor.form_identity(FormIdentity), which holds only:form_submit;Hidden tokens are never compared, and their values are never kept. Two kinds are exempt: ones named like a token (
csrf,token,nonce, …), whose value changes on every page load (Trac's__FORM_TOKENis one), and hidden values that changed while the demonstration was typed. Password values never leave the page.Replay. The new top identity tier (mode
form) checks, in the DOM, right before the click, that:Anything else is a
mismatchand halts. A backend withoutform_identity_atleaves identityunreadable, which halts.During the click. A one-shot capture-phase
submitguard cancels any submit that is not that form from that button (for example a click handler'srequestSubmit()on another form), and the step halts.Signal quorum. A signal-quorum identity policy and a form identity must both hold.
Policy and reports.
is_identity_armed, the replayer's identity gates, the armed-step count and the fixup pass all acceptform_identity.Tests (real headless Chromium)
tests/test_create_form_identity.pyhas 17 cases. Each records a Trac-shaped New Ticket form with the interactive recorder, compiles it with the real compiler, and replays it with the realReplayer.Verifies and creates the right ticket:
form=;Halts before the click, nothing submitted:
id=42,/ticket/42);form=recording;The reviewer's unmodified probe (
test_probe_1543.py), run against this commit: all six holes halt with nothing submitted, and the controls (plain,parent_ok,select_ok,search_present) verify and submit.Regression, run locally with one worker: the submit-binding, interactive-recorder, recorder, id-less locator, create-form, policy, identity, qualification (without the long gate campaign), replayer, compiler and fixup suites. 1646 passed, 11 skipped, 0 failed. ruff and mypy are clean.
Limits
form.submit()orfetchsends no submit event, so the guard doesn't see it. LIMITS.md says so.Merge note
origin/main(with #1537) merged in cleanly, with no conflicts.🤖 Generated with Claude Code