How a save is checked¶
A screen can show "Saved" when nothing was stored: an optimistic page, a
save that failed on the server, a duplicate submit. OpenAdapt therefore
never takes the screen's word for a save. A record check reads the record
back and returns confirmed, refuted, or indeterminate. Only
confirmed lets a run continue.
How strong a read-back is¶
The engine ranks every read-back by how independent it is of the screen that did the work:
| Rank | Read-back | Example |
|---|---|---|
| 1, strongest | The system of record, through its API, database, or files | REST, GraphQL, FHIR, read-only SQL, a file or report drop, an email capture folder |
| 2 | The app's record page in a separate, separately signed-in, read-only session | ui_readback |
| 3 | The same session navigates back to the record and reads it again | |
| 4, weakest | The screen that did the work still shows the value |
A rank-2 read is independent of the session that acted, but it still reads a screen. It can't tell a record the app stored from one the app only displays.
What each result needs¶
VERIFIED under Standard or Regulated. On the main branch and in
openadapt-flow 2.1.0, every save must be confirmed at rank 2 or stronger:
the system of record, or a separate-session ui_readback. The regulated
policy pack raises that to rank 1. Ranks 3 and 4 can never produce
VERIFIED. A pixel-only Citrix save with no rank-1 or rank-2 read ends in a
halt or RECONCILIATION_REQUIRED, whatever an operator or a banner says.
Certification through qualification. Qualification labels each read
independent_readback (rank 1), screen_readback (any read through the
app's screens, including ui_readback), or unchecked. To certify, the
normal case must be confirmed and every fault case must show nothing landed,
all at independent_readback. A workflow whose only read-back is
ui_readback can run the cases but can't be certified this way.
A Seal. OpenAdapt Cloud signs a Seal only when the run was confirmed by a system-of-record read.
What a record check catches¶
On 21 July 2026 we ran ten scenarios through the governed runtime against a local test system, 9 runs each, 90 runs in all. Nine scenarios were faults we injected into the save. In 72 runs the record ended up wrong: missing, partial, duplicated, on the wrong record, with other data overwritten, or with an extra write to another table. Three checks judged the same runs:
| Check | Wrong records it reported as saved |
|---|---|
| The success banner on the screen | 54 of 72 |
| A read of the record through the app's REST API | 9 of 72, all extra writes to a billing table that this read didn't cover |
| A read-only SQL query over every table a save could change | 0 of 72 |
Each check also stopped 9 of the 18 runs that saved correctly, because the reply to the save never reached the client. A record check catches what its read can see, so it should cover every table a save can change. Method and per-fault results.
How a save is approved¶
Checking a save and approving one are separate. The approval mode decides whether a run waits for an answer before each save:
each_item: the run waits before each save until a person approves the filled form in OpenAdapt Desktop. This is whatopenadapt flow runuses unless another mode is set.problems_only: only saves that fail a check go to a person. It isn't available to customers yet; until it is configured, every save still asks.not_gated: no pause. Runs that OpenAdapt Cloud dispatches use this, and every report records it.
An approval never replaces the record check. An approved save that the
record check can't confirm still doesn't end VERIFIED.
Why stopping is the default¶
Each check is three-valued, and both refuted and indeterminate stop the
run. An unreachable database, a timeout, or an ambiguous read is never
treated as success. The cost is that a run sometimes stops when the save was
fine; the limits page covers when that happens most. The
alternative, a run that reports success when the record is wrong, is the
failure OpenAdapt is built to prevent.