feat: structural (DOM/UIA) action rung — vision-first, not vision-only#69
Merged
Conversation
Make structural (DOM/accessibility) evidence a first-class ACTION rung — the deterministic top of the resolution ladder — not just an identity signal. On structure-bearing backends the runtime re-finds the recorded target as a DOM/UIA element and acts on its center deterministically, falling back to the visual ladder (template/ocr/geometry/grounder) only where structure is absent (pixel-only substrates: RDP/Citrix/canvas). Two external reviews + the desktop benchmark converge here: UIA execution 21/21 vs compiled visual replay 6/21. Ladder: API → tool/MCP → [structural DOM/UIA] → template → template_global → ocr → geometry → grounder(VLM) → human. `structural` is rung 0, above `ocr`, so an irreversible step may act on it (strongest evidence). The visual rungs are unchanged — the fallback floor for pixel-only substrates. - ir: StructuralLocator (selector / role+name / UIA AutomationId) on Anchor.structural; StructuralHandle; "structural" added to Rung. - backend: optional StructuralActionBackend protocol (structural_locator_at + locate_structural). - resolver: structural rung first; falls through unchanged on miss/pixel-only. - playwright/windows backends: DOM (#id / role+name, with an occlusion hit-test) and UIA (AutomationId / role+name) locate. - recorder/compiler: capture the locator at record time; keep the visual anchor. - replayer: structural resolution flows through the SAME click path, so the identity gate + risk gate still fire; exempt from healing (deterministic locate ≠ stale template). New use_structural flag (default True) lets the visual-floor characterization suites exercise the pixel-only path. Availability measured in benchmark/structural_action (21/21 vs 6/21). Identity gate proven to still abort a sibling on a structurally-resolved point. Occlusion safe-halt preserved. New coverage in tests/test_structural_rung.py and tests/e2e/test_structural_action.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
…n-rung # Conflicts: # openadapt_flow/runtime/replayer.py
…n-rung # Conflicts: # openadapt_flow/recorder.py
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.
Summary
Makes structural (DOM/accessibility) evidence a first-class ACTION rung — the deterministic top of the resolution ladder — instead of only an identity signal. On structure-bearing backends the runtime now re-finds the recorded target as a DOM/UIA element and acts on its center deterministically; it falls back to the visual ladder (template → ocr → geometry → grounder) only where structure is absent (pixel-only substrates: RDP/Citrix/canvas).
This is the thesis refinement from "vision-only" to "deterministic compiled automation with visual FALLBACK". Two external reviews + the literature converge here; the desktop benchmark showed UIA execution 21/21 vs compiled visual replay 6/21 under render drift.
Capability ladder (as implemented)
API → tool/MCP → **DOM/UIA (structural)** → template → template_global → ocr → geometry → grounder(VLM) → human(API/tool-MCP are future placeholders.)
structuralis the new rung 0, tried FIRST when the backend implementsStructuralActionBackendand the anchor carries aStructuralLocator. It sits ABOVEocr, so the irreversible-risk gate lets an irreversible step act on it (strongest evidence). Rungs 1–5 are unchanged — the visual FALLBACK floor for pixel-only substrates (the healthcare/Citrix wedge).What changed
ir.py— newStructuralLocator(selector / role+name / UIA AutomationId) captured onAnchor.structural; newStructuralHandle;"structural"added toRung.backend.py— new optionalStructuralActionBackendprotocol:structural_locator_at(record) +locate_structural(replay).resolver.py— structural rung runs FIRST; on miss/exception/pixel-only it falls through to the visual ladder unchanged.is_below_ocr("structural") == False.playwright_backend.py— DOM locate via stable#id/ role+name, with an occlusion hit-test (Playwright actionability): a covered element (modal / click-shield) returns None so the occlusion-safe-halt invariant is preserved.windows_backend.py— UIAControlFromPointlocator capture +AutomationId/role+name locate via the WAA execute channel.recorder.py/compiler/compile.py— capture the locator at record time and store it on the anchor (visual anchor kept as fallback).replayer.py— structural resolution flows through the SAME click path, so the identity gate + risk gate still fire on it; structural is exempt from healing (deterministic locate ≠ stale template). Newuse_structuralflag (default True) lets the visual-floor characterization suites exercise the pixel-only path deliberately.Availability improvement (measured)
benchmark/structural_action/reproduces the framing on a real DOM with the real ladder: structural 21/21 vs visual 6/21 (under drift: 15/15 vs 0/15). Numbers are measured, never hardcoded.Safety — identity/risk gating intact
verify_target_identity; a sibling/glyph-collapse mismatch still aborts (tests/test_structural_rung.py::test_structural_resolution_still_faces_identity_gate). Structure makes identity stronger (exact element), never bypasses it.tests/e2e/test_structural_action.py).Tests
tests/test_structural_rung.py— resolver mechanics, risk gate, identity gate on structural, Windows UIA (faked WAA), Playwright DOM round-trip, availability probe.tests/e2e/test_structural_action.py— default happy path resolves all steps viastructural(0 heals, 0 model calls); floor-vs-structural rung contrast; no-wrong-write under drift.use_structural=False).🤖 Generated with Claude Code
https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ