Skip to content

Route selector-resolution structural stages (occlusion, off-screen, promotion, poll) into typed policy #1656

Description

@thymikee

Split out of #1630 (see PR #1649 review).

Context

PR #1649 landed SELECTOR_RESOLUTION_POLICIES covering the ambiguity contract and the rect requirement — both consumed by resolveSelectorChainWithPolicy and pinned behaviorally.

An earlier revision of that matrix also carried four structural columns — occlusion, offscreenGuard, promotion, poll — describing which pipeline stages each caller runs. They were removed before merge because nothing consumed them: changing any of them left behavior and the tests green, so they were unverifiable claims that read as truth. (A source-sniffing test "verified" them by grepping caller files for marker strings, which is not behavioral coverage and stayed green when a row was disconnected entirely.) The suite now fails if such a field reappears without enforcement.

What remains

The stages themselves are still per-caller pipeline code:

stage today
occlusion isSnapshotNodeInteractionBlocked in resolution.ts (3 sites) + find.ts; is/get/wait never consult it
off-screen guard throwIfOffscreenInteractionTarget in resolution.ts, with the iOS rescue probe
hittable-ancestor promotion resolveActionableTouchResolution in core/interaction-targeting.ts
poll budget createWaitPolling in wait-polling.ts, wait only

Routing these into the policy would make the whole pipeline shape declarative rather than half-declared.

Constraints

  • Each stage must be consumed by the resolution interface, not merely declared — the point of this issue is to avoid re-introducing unverifiable columns.
  • Each must be pinned by a test that fails when its row changes (fixture-tree behavior, not source sniffing).
  • Semantics must not change: is/get attrs still fail closed, wait still skips occlusion and off-screen, acting paths keep promotion.
  • Maestro stays out (ADR 0015).

Acceptance criteria

  • Structural stages consumed through the resolution interface, not caller-local branching
  • Every declared field behaviorally pinned; flipping it changes an assertion
  • The "declare only what is enforced" guard extended to the new fields
  • No semantic change to any existing caller

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions