Repository navigation
Relabel repair, a duplicate check that reads the match list, ISO dates at intake, and two re-admission and approval fixes - #1514
Merged
Conversation
…t, fresh run ids for re-qualification - repair/relabel.py: when a save button is renamed, prove from the recorded identity hash that only its label changed (same control id, kind and form), build a candidate that changes only that label, re-qualify it on the test copy, ask a person once, then re-admit by lineage, or, for a control in the record check, return a fresh admission for the deployment to pin. - no_duplicate_candidate postcondition: an app's possible-duplicate list must have loaded and name no row with the run's value of any identity parameter (for example the last name or date of birth). It fails closed on an unreadable list, a list that hasn't loaded, or a missing value. The browser backend reads the rows from the DOM (element_texts). - readmission.requalify names each re-qualification's case runs afresh. A case run's authorization id is its run id, and a process refuses an id it has already used, so re-qualifying in the process that qualified the parent was always refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… save The switch check runs at the save, after the last point a person can be asked, so once the switch turned off every later item stopped unasked and was entered by hand. The gate now asks at that last point while the switch log reads off, as each_item does, when an approval store is wired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and the switched-off gate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…date before any step A referral that wrote the date of birth as MM/DD/YYYY was typed into the form and saved as the app's guess. The record check caught it afterwards, but a wrong chart landed and the problems_only switch turned off for the rest of the run. A run now refuses before any step when a ParamKind.DATE value is not a canonical ISO date, as hosted dispatch already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and records the conversion A date of birth arrives however the sender writes dates. Intake now converts it to ISO, with the original text and the conversion kept in the provenance file, when the order can be told: ISO already, a spelled-out month, YYYY/MM/DD, a numeric date with a part over 12 or equal parts, or a numeric date whose order is declared (--date-format DOB=MM/DD/YYYY). A numeric date that reads either way with no declared format (03/04/1970) and a date that doesn't exist now go to a person; intake used to read every numeric date as MM/DD/YYYY. The run's ISO check stays as the last guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the row shows A chart that shares only the date of birth (a married-name change) must stop the item. The rows showed the date in the app's own format, so a word match against the run's ISO date missed 'Apr 20, 1944' or '20.04.1944'. Each date in a row is now read the way intake reads dates and compared as a date; a row date that reads either way counts when either reading matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
abrichr
enabled auto-merge
October 24, 2026 23:33
This was referenced Oct 24, 2026
Duplicate check asks the system of record (FHIR/REST, SQL fallback) instead of the screen list
#1516
Draft
Merged
abrichr
added this pull request to the merge queue
Oct 25, 2026
abrichr
added a commit
that referenced
this pull request
Oct 25, 2026
…e only) Qualification fault injection, consequential delivery, effect checks and read-back, the API tier, acting by kind, settling and confirming move verbatim into mixins (107 units; verify_moved_code finds no edits). replayer.py goes from 18,300 to 10,926 lines. Left in place because open pull requests change them: identity checks (#1518, #1504), _resolve_step (#1504), postconditions and step evidence (#1516, #1514), the starting-state settle and qualification boundary (#1449), and the run and step drivers. Two methods that name Replayer stay too. Two tests patched module globals the moved code now reads from its new module, and patch them there as well: resolve (scroll readiness is in replay.acting, interstitials in replay.settling) and _DIALOG_WAIT_S (replay.acting). Checked: strict mypy on runtime/core, runtime/replay and replayer.py; package-wide mypy; both import contracts; 3,149 tests across the 146 files that import the replayer plus the real-browser e2e files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 4 tasks
abrichr
enabled auto-merge
October 25, 2026 05:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes that cut false stops and wrong writes and make recovery after an app change work under the production configuration (Regulated,
problems_only, durable, pinned). They were found and measured on local OpenEMR; the evidence is in OpenAdaptAI/OpenAdapt#1513, which is stacked on this PR.1. Relabel repair (
repair/relabel.py)A renamed save button stopped every item until a person re-taught the step. Self-repair refuses every failed identity check, because a renamed control usually can't be told from a different one.
propose()handles the one case that can be told apart:It refuses:
build()changes only that step's identity hash, intent and OCR text.readmit()then:A save button's identity is part of the record check, so
fixed_contract_refusalcorrectly refuses a lineage hop. In that casereadmit()returnsfresh_admission: the person's approval admits the certified copy, and the deployment re-pins. Qualified and managed runs stay refused.2.
no_duplicate_candidatepostconditionOpenEMR's duplicate check lists any chart that shares a first or last name or a date of birth. The recorded program required "No matches were found.", so it stopped on any listed chart. 9 of 15 such stops in the before run shared only a first name.
The new postcondition reads the listed rows from the DOM (
PlaywrightBackend.element_texts).lname,DOB).Apr 20, 1944,20.04.1944,04/20/1944, ISO) is read as intake reads dates, so a chart that shares only the date of birth stops the item, even under another last name. A row date that reads either way counts when either reading matches.Linear runs now keep their params for this check (
_run_params). Seedocs/DUPLICATE_CHECK.md.3. Two bugs the live runs found
readmission.requalifynow passes a freshrun_id_prefix. Replaying the same authorization is still refused (test_governed_authorization).problems_onlygate never asked a person. The switch check (P1) runs at the save, after the last point a person can be asked. The gate now asks there while the switch log reads off and an approval store is wired, aseach_itemdoes.4. Dates: converted at intake when the order can be told, refused before any step otherwise
A referral that wrote the date of birth as MM/DD/YYYY was typed into the form, and OpenEMR saved today's date. The record check caught it afterwards, but a wrong chart landed, and the
problems_onlyswitch turned off for the rest of the run.A run now refuses before any step when a
ParamKind.DATEvalue is not a canonical ISO date, as hosted dispatch already did (runner.inputs). The OpenEMR program declares DOB as a string; the soak'styped_doboption admits the version that declares it a date.So that this doesn't just turn a wrong save into a stop, intake now converts a date to ISO when its order can be told. It keeps the original text and the conversion in the provenance file (
raw_value,conversion). The order can be told when:YYYY/MM/DD;12/25/1970), or equal parts;--date-format DOB=MM/DD/YYYY).A numeric date that reads either way with no declared format (
03/04/1970), and a date that doesn't exist (1970-02-30), go to a person. Intake used to read every numeric date as MM/DD/YYYY, which silently swapped day and month for a DD/MM source. The run-time ISO check stays as the last guard.Measured (#1513, local OpenEMR, synthetic data, cost model x1)
afterrun.Tests
test_repair_relabel.py(10): proof; refusals (another control, another form, a field, unstructured, geometry, a step that may have written); the candidate changes only the label; a failed re-qualification asks nobody; a decline admits nothing; a fresh admission.test_no_duplicate_candidate.py(17): first-name-only hits proceed; a last name or a date of birth in any format stops; an ambiguous row date counts on either reading; unrelated or absent dates proceed; it fails closed.test_intake_dates.py(20):12/25/1970converts;03/04/1970halts without a declared format and converts with one (either order);1970-02-30and other invalid dates halt; the conversion is recorded as provenance; an ambiguous date needs a person.test_provenance.py's fixture date changed from the ambiguous03/07/1991to03/27/1991.test_program_ir_phase1.py(6): five malformed values refuse before anything is typed; an ISO date runs.test_approval_mode_replayer.py(switched off: approve then save; no answer, no save).test_repair_readmission.py.test_layout_drift::test_real_render[kw3-font_size_changed]fails the same way on main (local font rendering);🤖 Generated with Claude Code