Repository navigation
Shadow mode: check an agent's writes against the system of record, never blocking one - #1678
Conversation
…ures, refused writes are UNCERTAIN, declared only unchecked episodes, FHIR tests)
… check; sql: verifier.recheck), late REFUTED in its own report section; CTO summary by kind
… timing, CAPABILITIES and CHANGELOG
# Conflicts: # packages/flow/CHANGELOG.md
…elf and never excuses a write to it; unexplained later writes and writes during a recheck give UNCERTAIN; final verdicts in counts; late UNCERTAIN and pending rechecks shown; stricter kinds; docs state the recheck settings measured)
…orrect confirmed); SQL reasons grouped by kind
|
Adversarial review, recorded for the verifier path (each round by an independent reviewer that was given the code, not my conclusions). Round 1 (proxy, episodes, verdict mapping). Blocker: a malformed request (a raw non-ASCII target, a bad chunk size) raised outside any try. The write was dropped and never logged, and its episode showed as PENDING, which the report didn't render. Should-fix items:
All fixed, with tests ( Round 2 (the recheck, report counts, CLI, evidence). No code blockers, and every number in SHADOW_RESULTS.md matched the JSON. One finding would have been a blocker while the doc still claimed it: the FHIR recheck didn't compare the confirmed record itself, so a record moved to another patient after the check came back CONFIRMED. The other should-fix items:
All fixed, with tests. The window semantics are now stated in SHADOW_MODE.md. Open after round 2, documented as limits:
|
…losed record, a deleted record and a moved record whatever else it sees; unknown later writes counted per write; pending rechecks shown on the Confirmed tile; an unschedulable SQL recheck says so)
|
Round 3 (an independent check of round 2's fixes) found two blockers in the FHIR recheck:
It also found these smaller items:
All are fixed in c2bc10b, with a test for each. The reviewer's own probes now pass. Round 4 (only c2bc10b): approve, no blockers. One non-blocking finding is left for a follow-up, since only blockers get fixed after approval. A later 2xx write whose record the shadow can't name no longer downgrades a recheck REFUTED to UNCERTAIN: a |
…ry; drop copied contracts
Draft. The live Medplum run is pending: it waits for the lane-2 fixture lease.
openadapt shadowruns beside a vendor's own agent, against their own system of record. It is a loopback pass-through HTTP proxy. It checks each write the agent makes (CONFIRMED / REFUTED / UNCERTAIN) and writesreport.htmlandreport.json. It never blocks, edits or resends a write. The only delay is the read before the write, which is bounded byhold_msand measured per write.It has no verifier of its own:
--mode fhircallsensure_record.verify_ensure. The values come from the request body, read at each contract field's path.--mode sqlcallsverifier.begin_write/verify_write, with the whole-database frame and no ledger.Design and limits:
packages/flow/docs/SHADOW_MODE.md.The verifier change:
verify_ensure(..., declared=...)leaves out the resources of other writes the caller saw the agent make and judges each of them on its own. Every other change in the window still counts.Tests
tests/test_shadow.py: SQL mode end to end on SQLite behind an HTTP app. It covers a correct write, a wrong value, a duplicate, a side write, an unreadable store, a slow baseline, no route, a route that doesn't fit, a shadow failure, and the bytes passing through unchanged.tests/test_shadow_fhir.py: grouping writes into episodes, what each check is told, refused writes, writes made during a check, and a non-ASCII target.Review
Adversarial review, round 1: one blocker (a malformed request could be dropped without a trace) and nine should-fix items, all fixed in 4b-round-1. The summary will be posted as a comment.