fix: reject distinct ambiguous mutation targets - #1667
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Review: code is clean. The fail-closed structural policy, candidate publication/MCP pinning, and direct-iOS fallback align with ADRs 0010, 0011, 0012, and 0014; all CI checks are green.\n\nThis is not merge-ready yet. The required exact-head 39-flow replay/device evidence against the actual Element-14 motivating state is still missing, including both the ambiguous rejection and retry using a returned pinned ref. The reported synthetic gearshape.fill simulator run does not validate that motivating path. Please attach that replay/device evidence, then request re-review. |
388a007 to
01b3c74
Compare
|
Required exact-head evidence is now complete on
The earlier pre-rebase replay and the initial runner-handoff attempt are not claimed as exact-head evidence. This result is from the pushed rebased head. Ready for re-review once the refreshed GitHub checks complete. |
|
388a007 to
01b3c74
Compare
|
Re-reviewed exact head |
01b3c74 to
64d0819
Compare
|
Re-reviewed exact head |
64d0819 to
edc2c96
Compare
|
Final rebase is published at |
|
CI babysitting update: every check except iOS smoke is green. iOS smoke failed three times in the pre-existing hidden-keyboard XCTest area, never in selector ambiguity code: (1) partial text |
|
Correction to the prior note: #1676 identifies the same failing test but is not an acceptable unblock as written; its post-response polling weakens the production-route oracle (see the P1 review there). #1673 attempted the production fix but was closed with work continuing elsewhere. The important classification is unchanged: this PR does not touch |
|
P2 — scope raw-match rejection to mutations; it currently changes direct read behavior too.
Keep the new raw-match rule on mutation dispatches only, or give |
|
P2 fixed in
Coverage in
ADR 0011's 2026-08-07 amendment previously read as if the raw-match rule covered the whole direct XCTest path; it now says mutating dispatches only and states the read rule and why ( Honest limits on this head. This was authored in a Linux container: there is no Swift toolchain and no Xcode, so the change is not compiled or run locally — the iOS Smoke lane is the first build. There is no simulator here either, so no device evidence: the one-hittable + one-non-hittable read has not been observed end-to-end through Generated by Claude Code |
|
Code routing is corrected at 9e1ffb2, but not ready yet. The three new SelectorMatchPolicy Swift regressions are compile-only: ios.yml runs an explicit -only-testing list and none is included. They also test only the classifier, so deleting queryElement’s .preferHittableMatch wiring leaves them green. Add an executed route-level querySelector/get/is/wait regression with red-before proof (and include it in the targeted XCTest gate), then attach exact-head iOS evidence for the one-hittable + non-hittable duplicate read path. Mutation routes correctly retain fail-closed behavior. |
`RunnerTests+Interaction.findElement` applied the new fail-closed classification to `querySelector` as well as press/type, because the read call site takes the default `allowNonHittableFallback: false`. With one visible/hittable match and one non-hittable same-selector duplicate the query started returning AMBIGUOUS_MATCH where it previously selected the hittable element, and `queryDirectIosSelectorOrFallback` preserves that error for read callers — so `get`, `is`, and `wait` surfaced an error instead of their prior answer. `classifyDirectSelectorCandidates` now takes a `rawMatchPolicy`. Mutations keep `.rejectDistinctMatches` (the default, so no mutation call site changes); `queryElement` passes `.preferHittableMatch`, restoring the prior read rule: prefer the single hittable match, ambiguous only when hittable matches compete, and never adopt the Maestro coordinate fallback. The Maestro expected-point path is untouched. Covers the one-hittable + one-non-hittable read, competing hittable reads, and the non-hittable-only read. ADR 0011's amendment now states the scope.
9e1ffb2 to
2869f01
Compare
|
Addressed the remaining review blocker at exact head
The previously failing GitHub iOS smoke at |
|
Re-reviewed exact head 2869f01: code review clean. The querySelector regression now drives the production runner executor with one hittable and one non-hittable raw match, is explicitly executed in the targeted iOS XCTest workflow, and would fail before the read-only policy fix. Mutation tap/type routes retain fail-closed distinct-match rejection. Existing Element-14 mutation evidence remains probative because later changes are read-only/test-only. Local reproduction was blocked by the documented CoreSimulatorService/simdiskimaged host failure. Ready for human review. |
Summary
Fail mutating selectors closed when same-label matches belong to distinct accessibility subtrees, while preserving benign React Native wrapper chains by collapsing only a single ancestor-descendant chain that resolves to one actionable node.
The direct iOS path now counts raw exact matches before hittability and delegates ambiguous matches to the shared runtime. Rejections publish a bounded partial ref frame across CLI and MCP so an agent can immediately retry with a pinned
@ref.Root cause: the mutation policy used geometric disambiguation, so element-14 silently selected one of the distinct
Team Standuptargets. The #1597 candidate renderer had shipped, but this interaction path neither rejected nor published candidates.The scope crosses selector policy, runtime and direct-iOS guarantees, CLI/MCP recovery, contract coverage, ADRs, help, and changelog.
Validation
edc2c96c:VITEST_MAX_WORKERS=2 pnpm check:affected --runpassed all selected checks and 4,014 tests; fresh iOS and macOS XCTest builds succeeded. The worker cap only reduced host contention; no timeout or assertion was relaxed.64d08193(the selector patch is unchanged inedc2c96c; the final rebase only incorporates fix(test): clean Swift toolchain temporary directories #1664's Swift test temporary-directory cleanup): in-roompress 'label="Team Standup"'failed closed before mutation withAMBIGUOUS_MATCH, five distinct candidates, andrefsGeneration: 440518. Retryingpress @e13~s440518succeeded and opened the intended room-history screen.01b3c7478: the 39-flow React Navigation Maestro suite passed 39/39 in 1092.3s, each on one attempt with zero heals.64d08193was invalidated by host infrastructure: CoreSimulatorService/simdiskimaged crashed after 22 passing flows (connection invalid,BUILD INTERRUPTED), then the target shut down and the remaining flows failed atstopApp. This is not claimed as a product pass. Repeated read-onlysimctl listcalls continued to reproduce the host service crash, preventing a trustworthy final-head rerun without disrupting other simulator work.bench-golden*was booted or targeted. One disposable React Navigation simulator remains pending deletion once CoreSimulatorService stabilizes.