Structural action rung

July 13, 2026 · View on GitHub

Reproduces, on a real rendered DOM with the real resolution ladder, the desktop benchmark that reframed the thesis from "vision-only" to "deterministic compiled automation with visual FALLBACK": structural (DOM/UIA) execution scored 21/21 while compiled visual replay scored 6/21 under render drift.

What it measures

structural_action_probe.py (logic in openadapt_flow/validation/structural_action.py) renders a dense surface of n actionable targets, each with a stable DOM id (#open-pK) — what a browser recorder captures for the structural rung — plus a recorded visual anchor (template crop + OCR label). It then RE-RENDERS with drift: a deterministic subset is left untouched (steps whose surface did not change), the rest are MOVED, RELABELLED (OpenView) and RE-THEMED (dark, serif). The DOM id never changes. For each target it asks, on the drifted surface:

  • structuralbackend.locate_structural(locator): does it land inside the CORRECT element's box? (Deterministic, pixel-independent.)
  • visualresolve(anchor, drift_png, vision, structural=None): does the template/OCR/geometry ladder resolve to a point inside the CORRECT box? A drifted target's recorded crop no longer matches at its old position and either fails or matches a look-alike sibling — both are failures.

Numbers are MEASURED, never hardcoded.

Result (structural_action.json, n=21)

structural (DOM) rung : 21/21 acted correctly
visual ladder only    :  6/21 acted correctly
under drift (15 targets): structural 15/15 vs visual 0/15

Structural resolves every target whose id is still in the DOM; the visual ladder resolves only the non-drifted minority. The structural point flows through the SAME click path, so the pre-click identity gate and the irreversible risk gate still fire on it — structure makes identity STRONGER (an exact element), it never bypasses it.

Run it

python benchmark/structural_action/structural_action_probe.py 21

The in-suite assertion of the same win (structural resolves all, beats visual under drift, identity gate intact) lives in tests/test_structural_rung.py; the full record→compile→replay default-on path is in tests/e2e/test_structural_action.py.