Repository navigation
fix(approval): a desktop approval rereads the app's window, not the whole screen - #1778
Conversation
replayer.run clears the verified windows first, then main's at-rest handling. The window-forgetting test sets the checkpoint key that main's run now reads, since it builds the Replayer without __init__.
|
Review (Grok), head 24ab796: BLOCK I read the diff and the CI logs for this head. The approach is sound: reading only the app window, in OCR reading order, with the window frame in the digest, does fix the false
Not checked: I did not run the real-OCR window tests locally or try it on a Windows host. This verdict rests on the diff and the CI logs. I will re-review when a new head is pushed. |
# Conflicts: # packages/flow/CHANGELOG.md
Independent adversarial review: BLOCKBLOCK remains at 8b36670. The two code blockers from Grok's earlier review are still present after the main merge.
I checked the current source and exact-head hosted logs; I did not rerun unchanged probes or native Windows tests. This is a generic safety-ordering defect, with no per-app engine change requested. Held-out real-app evidence is not established by this review. Review by Codex. |
Replayer.run cleared the backend's captured windows before the draft and fail-closed checks, so a refused run touched the backend. The clear now runs in _run_unsealed_steps next to the qualification guard reset, after every refusal check. Narrow the resumed-run capture set to non-None for mypy.
|
Parked (coordinator, 2026-11-09). We're focusing on verification at the system of record (wire, database and API read-back, zero-config verifiers generated from captured traffic, and the agent cache) instead of hardening screen-reading heuristics such as OCR, pixels, fonts and UI label guards. This PR stays as a draft and gets no further review rounds unless it fixes a case where OpenAdapt takes an unsafe action. If so, comment with that case and it will be reprioritized. The branch is kept. |
…, row dates in their own module
|
Finished and ready at 4294954.
|
|
New head 17d8379 (after 0b3c85c, which rebuilt llms-full.txt for the changelog line). Guard: |
|
Cross-vendor review relayed from Grok (internal inbox, 2026-11-11). Round 2: APPROVE at 0b3c85c. The P1 is fixed, because |
…p reports without failing the job #1778 is on main, so its files take main's version. What remains is this PR's own work: the hosted governed Windows test, the move of _pipeline_safe_verifier to runtime/effects/pipeline_safety.py (replayer budget lowered to 20095), and the evidence and workflow wiring. The governed run is a continue-on-error step with a 10-minute timeout, after the no-skips check, so a stop before the save prints halts and form_state_probe without failing windows replay. The changelog line duplicated #1778's and is dropped; llms-full.txt is rebuilt.
…l, a hold and a resume) (#1804) * fix(approval): a desktop approval rereads the item's field rows, not the whole screen (#1697) * Read an approval's form in the app's window, in reading order * Format * Read the whole screen when an app window reaches past the frame; forget captures per run * A resumed run names the approval window from the earlier steps' points * Run the approval-window tests on hosted Windows too * test: a governed Windows run on a hosted runner, Standard with an approval, a hold and a resume (#1697) * Prove the hold and the person's approval; keep the approver key out of the run * The run reads the approver roster; only the person holds the passphrase * test: record which form-state read the held approval and its resume used The hosted resume halts with form_changed_after_approval. On failure the governed test now recomputes the request's digest from its own frame and from the resumed save's frame, over the whole screen and over the app window, and records the window lookup under the earlier steps' points. The approval request files join the uploaded evidence. * fix: clear captured windows after the run's refusal checks Main's draft and fail-closed tests require that a refused run never touches the backend. The run now forgets earlier captures only after the refusal checks, as #1778 does, and the form-window tests match #1778's: a refused run keeps the captures and sends nothing. * test: say why the approved governed run stopped On a nonzero exit the hosted test now records each halted step's category and error, and recomputes the request's form-state digest from the save's frame, ahead of the output tail in the assertion message. * test: print why a resumed governed run stopped The first non-gating run passed the approval and the hold but the resume halted, and the message showed neither the halt reason nor the whole probe. The resume now records halts too, and both phases print halts and form_state_probe in full before asserting. * ci: gate the Windows governed run on its record writes The governed step stays continue-on-error for the #2189 false stop. A new if: always() step reads the evidence the test now records (the SQLite rows each phase left, its outcome and approval) and fails the job on a sibling or wrong-record write, a held run that wrote, or a target write without an approved VERIFIED report. CAPABILITIES says the governed run is non-gating and drops the RDP-session claim, whose evidence is not public.
Problem
On a desktop run, approval mode compares the form after the person approves it with the form they saw. Without a DOM, that comparison was an OCR digest of the whole screen. On the hosted Windows governed run (#1697), the taskbar clock changed between the approval and the save, so every signed approval ended
HALTED (each_item: form_changed_after_approval).Change
window-bounds(POST /window/bounds, capabilitywindow_bounds_v1), returns the visible frame of a window, given its hwnd and pid. It answersNonewhen the window is gone, has changed process, or is hidden.approval_form_region(exclude_step)returns that frame for the one window the run's verified steps were in, apart from the save step. It answersNonewhen no step was verified, when steps were seen in two windows, or when the agent's answer is doubtful.None, which the approval path treats as a refusal.Review round 1 (the row-band version) asked for changes: fields with no click point, wrapped lines, the patient banner and scrolled fields fell outside the rows, and sorted words hid swapped values. Reading the whole app window, in reading order, addresses each of these.
Tests
tests/test_approval_form_window.pyuses real OCR on rendered frames:None;tests/test_win_agent_server.pycovers the agent's window-bounds read.Part of #1697.
Review record (independent adversarial reviewer)
run()now clears them.