Skip to content

fix(ios): keep the baseline presentation when a tap corroboration has no request flags - #1646

Merged
thymikee merged 2 commits into
mainfrom
fix/ios-tap-corroboration-presentation
Aug 6, 2026
Merged

fix(ios): keep the baseline presentation when a tap corroboration has no request flags#1646
thymikee merged 2 commits into
mainfrom
fix/ios-tap-corroboration-presentation

Conversation

@thymikee

@thymikee thymikee commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

matchingCaptureFlags returned undefined whenever a raw daemon/JSON-RPC request omitted flags, discarding the baseline's depth, scope, and raw presentation. The corroboration probe then used default presentation, could not match a non-default baseline, and silently declined to rescue a tap that XCTest recorded as failed even when the tap landed.

This complements #1634: that PR pinned the probe's capture backend; this PR preserves the baseline presentation. Both conditions must match for corroboration to fail safely.

The affected production boundary is raw daemon/JSON-RPC (http-server.ts passes an omitted params.flags through). CLI, legacy batch, and structured batch always materialize a flags object, so they cannot produce flags: undefined.

Validation

  • Regression test proven red without the fix:

    × corroborates a tap when the request carries no flags and the baseline used a non-default scope
    AssertionError: expected false to be true
    Tests  1 failed | 11 passed (12)
    
  • The test omits the flags key entirely; using an empty object would exercise the already-working path.

  • Exact-runtime-head iPhone 17 Pro / Bluesky evidence reproduced the recorded-failure → matched-presentation corroboration path three times (attempts 8–10), with zero scope/backend mismatch diagnostics: fix(ios): keep the baseline presentation when a tap corroboration has no request flags #1646 (comment)

  • pnpm check:affected --run passed after rebasing onto current main (75 files, 430 tests).

Scope

Two files: the presentation-preservation fix and its regression test. The final follow-up commit only corrects the test's reachability comment after the batch-path claim was disproved during live verification.

@thymikee
thymikee force-pushed the fix/ios-tap-corroboration-presentation branch from 8122646 to e0d1fff Compare August 6, 2026 14:09
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.99 MB 1.99 MB +12 B
JS gzip 635.8 kB 635.8 kB +7 B
npm tarball 769.2 kB 769.3 kB +15 B
npm unpacked 2.69 MB 2.69 MB +12 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 18.4 ms 18.7 ms +0.3 ms
CLI --help 48.5 ms 45.2 ms -3.3 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/interaction.js +12 B +7 B

@thymikee

thymikee commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Reviewed exact head e0d1fff0. No code finding: matchingCaptureFlags now reconstructs the decoded baseline presentation when the request omits flags, so the post-action capture uses the same depth/scope/raw key. The regression deliberately omits the key, uses non-default depth, reaches the real capture/state builder, and would fail pre-fix; #1634 supplies the complementary backend pin.

Not ready yet: this device-facing fix still lacks required exact-head live iOS evidence at the penalty/private-AX boundary. Please attach the Bluesky reproduction showing a matching-presentation baseline and successful corroboration after the recorded tap failure. Current completed checks are green, with required lanes still pending. No code blocker or conflict found.

@thymikee

thymikee commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Live iOS evidence — exact head e0d1fff0d

Reproduced on iPhone 17 Pro (6044A251), Bluesky signed in. 10 real device attempts, of which 8, 9 and 10 reproduced the target scenario identically; the earlier ones are described below rather than hidden.

Reaching the penalty boundary

5–6 fast scroll down 25 gestures on the Home feed drive the XCTest channel tree_backend_timeoutqueries_backend_timeout → private-AX recovery. The boundary was easy; a clean, attributable repro was not:

  • Attempt 1 — selector-based press corroborated, but selector resolution unconditionally re-captures a default-presentation snapshot before dispatch (resolveSelectorInteractionTargetcaptureInteractionSnapshot), silently clobbering the baseline. Default-vs-default proves nothing, so discarded.
  • Attempt 3 — over-scrolled; the runner crashed (ios_runner_session_invalidated) before corroboration's isLocalIosRunnerSession gate could engage.
  • Attempts 4–7 — once the runner has seen the timeout, it short-circuits to a fast private-AX path (reason: deferred) and taps stop failing. Needed open --relaunch to reset that per-bundle memory.
  • Attempts 8–10--relaunch, fresh deep scroll, then one batch combining an explicit non-default snapshot with a ref-targeted press (refs skip the pre-tap live capture, so they cannot clobber the baseline).

Attempt 10 — commands and literal output

open xyz.blueskyweb.app --relaunch --session evidence1646
scroll down 25 --duration-ms 120        # x6
batch --debug --json --session evidence1646 --steps \
  '[{"command":"snapshot","input":{"interactiveOnly":true,"depth":50}},
    {"command":"press","input":{"target":{"kind":"ref","ref":"@e44"}}}]'

Baseline — non-default presentation, private-AX backend:

"snapshotQuality": {"state":"recovered","backend":"private-ax","reasonCode":"deferred"},
"refsGeneration": 274845

The runner's raw failure:

{"level":"error","phase":"platform_command","command":"press",
 "data":{"error":"XCTest recorded a failure while executing tap; the action may not have been performed."}}

Corroboration engaging:

{"level":"warn","phase":"ios_tap_failure_corroborated","command":"press",
 "data":{"command":"press","beforeNodeCount":92,"afterNodeCount":52}}

grep -c "scope_mismatch\|backend_mismatch" over the request's ndjson → 0. The decisive negative signal holds.

Final response:

{"targetKind":"ref","ref":"e44","refLabel":"Open drawer menu",
 "resolution":{"source":"ref","phase":"pre-action","kind":"exact"},
 "warning":"XCTest reported the tap as failed, but a same-scope post-action accessibility capture changed; treating the tap as landed. Observe the current screen before issuing another tap."}

Same signature in attempts 8 and 9 (43abca4c385a5008, 0263cd0a5715ee77), so not a one-off.


Correction to this PR's own description

The evidence run turned up a factual error in what I wrote here, so I am correcting it rather than leaving it to be discovered.

I claimed the defect was reachable "via batch steps (cli/batch-steps.ts omits the flags key entirely when undefined) and the JSON-RPC boundary". The batch half is wrong. batch-steps.ts:89's ...(flags === undefined ? {} : { flags }) builds an intermediate LegacyCliBatchStep used for validation; that step is then converted by legacyStepToStructuredStep through cliFlagsFromBatchStep, which always materialises an object ({json:false, help:false, version:false, ...flags}). The modern {"command":…,"input":{…}} shape goes through projection.tsbuildRequestFlags, which likewise always returns an object.

So no CLI path — legacy batch, modern batch, or ordinary invocation — can produce flags: undefined. The only reachable production path is a raw daemon/JSON-RPC request that omits the key outright (http-server.ts:170 passes params.flags straight through), which is precisely what the shipped unit test exercises against the handler.

What that means for this evidence, stated plainly: the live run proves the end-to-end contract on-device at this HEAD — non-default baseline, recorded failure, presentation-matched corroboration, zero scope mismatch, success-with-warning. It does not isolate old-vs-new behaviour on the changed line, and via the CLI it cannot, because matchingCaptureFlags({}, presentation) takes the same branch before and after this commit. The unit test is what pins the regression; this run is what proves the surrounding path is real and works.

The defect and the fix are unaffected — the JSON-RPC boundary is a genuine production surface — but the reachability claim was broader than the code supports, and that was my error.

Cleanup

Sessions closed, daemon stopped. daemon stop left two orphaned runner processes (xcodebuild test-without-building + AgentDeviceRunnerUITests-Runner) which were terminated manually — flagging since plain daemon stop does not reap them after the fact. Simulator left booted and still signed in; bench-golden-bsky-latest untouched. Artifacts under /tmp/ad-evidence-1646b/.

@thymikee
thymikee marked this pull request as ready for review August 6, 2026 19:32
@thymikee

thymikee commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Reviewed exact head e0d1fff0d again, including the corrected reachability analysis and the exact-head Bluesky evidence. The implementation is sound and the device proof closes the prior live-evidence blocker, but the PR is not ready as recorded yet:

  • P2 — correct the remaining false reachability claim in the regression test. interaction-ios-tap-outcome.test.ts still says the missing-flags request mirrors “batch steps with no flags, JSON-RPC requests that omit the key.” The investigation established that every CLI/batch path materializes a flags object; only raw daemon/JSON-RPC can omit the key. The source comment now contradicts both the production route and the corrected PR description. Remove the batch claim so future reviewers do not infer nonexistent coverage.
  • P3 — reconcile the PR state/body with evidence already gathered. The body still says “draft until live evidence,” “No simulator run yet,” and that local gates are unreliable, while the exact-head evidence comment documents three clean reproductions and the complete CI rollup is green. Update that section and mark the PR ready for review.

No additional runtime/code finding. The test is non-vacuous: reverting matchingCaptureFlags to return undefined when request flags are absent makes the non-default presentation key mismatch and the success assertion fail.

… no request flags

matchingCaptureFlags dropped the baseline snapshot's scope/depth/raw whenever
the incoming request carried no flags, so the post-action corroboration
capture ran at the default presentation and could never match a non-default
baseline's presentationKey. The corroboration then silently declined to
engage, leaking the raw XCTEST_RECORDED_FAILURE it exists to eliminate.
@thymikee
thymikee force-pushed the fix/ios-tap-corroboration-presentation branch from e0d1fff to aae69fd Compare August 6, 2026 19:41
@thymikee

thymikee commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Re-reviewed at aae69fd00 after the fixup. The stale batch-path claim and draft/no-device-evidence wording are corrected; the regression test still targets the raw JSON-RPC boundary, and pnpm check:affected --run is green (75 files, 430 tests). The production diff is unchanged from the Bluesky/iPhone evidence head; the new head only rebases current main and corrects the test comment. No remaining code findings. Mergeable; CodeQL is queued with no confirmed failure.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 6, 2026
@thymikee
thymikee merged commit fd1ed57 into main Aug 6, 2026
4 checks passed
@thymikee
thymikee deleted the fix/ios-tap-corroboration-presentation branch August 6, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant