Skip to content

docs(adr-0011): narrow the offscreen rescue comment to the per-request surface policy - #2465

Open
thymikee wants to merge 3 commits into
mainfrom
claude/issue-2452-adr0011-offscreen-comment
Open

docs(adr-0011): narrow the offscreen rescue comment to the per-request surface policy#2465
thymikee wants to merge 3 commits into
mainfrom
claude/issue-2452-adr0011-offscreen-comment

Conversation

@thymikee

@thymikee thymikee commented Sep 10, 2026

Copy link
Copy Markdown
Member

Summary

Comment-only change to the offscreen cell in the ADR 0011 guarantee matrix
(packages/contracts/src/interaction-guarantees.ts, 1 file, +8/-6 lines). No behavior change.

The cell already documents the iOS live rescue (confirmOffscreenTargetVisible re-checks a
would-be refusal against a tree-independent read and retargets at the live rect). What it did not
record is how that read picks its surface, and how little it shares with the capture.

The rescue is its own runner request: confirmIosOffscreenTargetVisible
queryDirectIosSelector → the Apple runner's querySelector, with no bridge alternative, and in
the runner querySelector reaches executeOnMainPrepared only through
prepareActiveCommandContext. The bulk capture has a second route: createAppleSnapshotRoute
serves an eligible iOS simulator capture from the host AX bridge and only falls back to XCTest on
failure, a disabled generation, or target-resolution failure. So a bridge-served capture never
touches that seam, and the comment now limits the shared-policy claim to a runner-routed
capture. #2448 is what puts the system-surface case (the ASWebAuthenticationSession sign-in
sheet) back on the runner — the bridge would serve the occluded app tree — which is where the two
reads do share the seam.

The comment still does not claim the two requests observe the same surface instant: the probe
carries only the node's id/label plus the guard's own rootViewport, no captured surface
identity, so a surface that appears or dismisses between capture and rescue is undetected.
(Revision a4e639d2 overclaimed a same-surface guarantee; 2a52a4d3 overclaimed a universally
shared seam. Enforcing cross-request surface identity would be a behavior change, out of scope.)

Depends on #2448 for the system-surface clause; merge after it. Nothing #2448 touches is modified
here.

Closes #2452

Validation

Tested at e905700fff. pnpm format (oxfmt, repo-wide) produced no further diff; pnpm lint,
pnpm typecheck, and pnpm check:affected --run all pass — the latter covering format, lint,
typecheck, layering, fallow, build, and vitest related on the changed file (2 files, 12 tests:
the ADR 0011 honesty/completeness gate and the interaction contract coverage test). The changed
file was staged before the layering scan, which reads tracked files only.

No runtime validation applies: the diff is a source comment, so no device, simulator, or runner
path changes. The honesty gate only checks that referenced via symbols exist, and those are
untouched.

The offscreen guarantee's live rescue runs the runner's direct querySelector, which consumes the single activeApp that prepareActiveCommandContext resolved for the snapshot tree as well, including an in-place system surface. Record that the tree and the rescue read the same surface so the cell's rationale stays accurate.

Closes #2452
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.53 MB 4.53 MB 0 B
Package (unpacked) 4.53 MB 4.53 MB 0 B
Package (download) 1.34 MB 1.34 MB +1 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.6 ms 29.4 ms +0.8 ms
CLI --help 80.6 ms 80.8 ms +0.2 ms

@thymikee

Copy link
Copy Markdown
Member Author

The new same-surface guarantee is stronger than the implementation at a4e639d. Snapshot capture and the rescue query are separate runner requests; each resolves the foreground surface again, and the probe does not receive or compare the captured surface identity. A sign-in sheet can appear or dismiss between them. Please narrow the comment to the per-request selection policy, or enforce and test the cross-request identity before claiming it. This also remains dependent on #2448.

…t surface policy

The snapshot capture and the live rescue are separate runner requests, and each
calls prepareActiveCommandContext on its own, so only the selection policy is
shared, not the surface instant. State that, and say plainly that a surface
appearing or dismissing between the two requests is not detected.

Closes #2452
@thymikee thymikee changed the title docs(adr-0011): note the offscreen live rescue reads the tree's surface docs(adr-0011): narrow the offscreen rescue comment to the per-request surface policy Sep 10, 2026
@thymikee

Copy link
Copy Markdown
Member Author

You are right, and I took the narrowing option rather than enforcing cross-request identity.

Confirmed against the code first: executeSnapshotDispatchedOnce and executeOnMain each call
prepareActiveCommandContext independently
(apple/runner/.../RunnerTests+CommandExecution.swift:1339, :1462), and the rescue path
(confirmIosOffscreenTargetVisible -> queryDirectIosSelector -> queryAppleRuntimeSelector) sends
its own runner request carrying only session.appBundleId. No surface identity is captured, passed,
or compared. So the sheet-appears-in-the-gap case you describe is real.

Pushed 2a52a4d366. What the comment now claims:

What it deliberately no longer claims:

  • That tree and rescue read the same surface.
  • That a confirmation cannot come from a different app than the refusal was computed on.

And it now states the gap outright: this is not a same-instant guarantee, no captured surface
identity crosses the two requests, so a surface that appears or dismisses between them is
undetected. The diff is the 5 overclaiming lines replaced by 7 narrower ones; nothing else changed.

Enforcing and testing cross-request surface identity is a behavior change and a much larger one, so
I left it out of this comment-only PR rather than half-claiming it.

Still depends on #2448 merging first for the system-surface clause. PR body updated to match.

Gates on 2a52a4d366: pnpm check:affected --run green on all seven runnable checks (format, lint,
typecheck, layering, fallow, build, vitest related -> 2 files / 12 tests). pnpm format (oxfmt)
left no further diff. No device or simulator use.

@thymikee

Copy link
Copy Markdown
Member Author

The cross-request guarantee is correctly removed at 2a52a4d. One sentence still needs narrowing: ordinary simulator captures can use the AX bridge, so capture and rescue do not always both pass through prepareActiveCommandContext. Limit that statement to runner-routed captures, or remove it. The system-surface clause still depends on #2448 merging first.

An eligible iOS simulator snapshot is served by the host AX bridge
(packages/platform-apple/src/snapshot-route.ts), which never reaches the
runner's prepareActiveCommandContext. The rescue's direct querySelector always
does, so the two requests share that surface policy only when the capture is
runner-routed too - which is the case #2448 forces for the system surface.
Keep the unchanged statement that no surface identity crosses the two requests.
@thymikee

Copy link
Copy Markdown
Member Author

You are right, and I traced it rather than taking it on faith. Narrowed at e905700fff.

What the sentence now claims

// The live read is a SEPARATE runner request (the direct querySelector), so
// it shares the runner's prepareActiveCommandContext surface policy only with
// a RUNNER-ROUTED capture: eligible simulator captures go to the host AX
// bridge (packages/platform-apple/src/snapshot-route.ts), and a bridge-served
// capture never reaches that seam. #2448 puts the system-surface case (the
// web sign-in sheet) back on the runner, where both reads do share it. Either
// way this is not a same-instant guarantee — no captured surface identity
// crosses the two requests, so a surface that appears or dismisses between
// them is undetected.

Why each clause is true

  • The rescue is always runner-routed. confirmIosOffscreenTargetVisible (src/daemon/offscreen-target-probe.ts) → queryDirectIosSelectorqueryAppleRuntimeSelectorqueryAppleRunnerSelector, which is the single command: 'querySelector' builder. There is no bridge alternative on this path.
  • querySelector does pass through the seam. In the runner, a non-snapshot command goes executeDispatchedexecuteOnMainSafelyexecuteOnMain, and executeOnMain calls prepareActiveCommandContext before executeOnMainPrepared (where case .querySelector lives). snapshot reaches the same function via executeSnapshotDispatchedOnceprepareActiveCommandContextSafely. So runner-routed captures and the rescue genuinely do share it.
  • But the capture has a second route, which is your point. createAppleSnapshotRoute.capture tries the host AX bridge first whenever isEligible holds (apple + ios + simulator + appBundleId + not customActions + no preferredBackend) and only calls fallback — the XCTest path — on target-resolution failure, a disabled generation, an acquisition failure, or a presentation-invariant failure. A bridge-served capture never enters the runner at all, so "capture and rescue each resolve their surface through that seam" was false for the ordinary simulator case. Hence RUNNER-ROUTED.
  • Why the feat(ios): drive ASWebAuthenticationSession sign-in sheets in place (#2438) #2448 clause belongs on the narrowed form. feat(ios): drive ASWebAuthenticationSession sign-in sheets in place (#2438) #2448's simulator route takes the runner path while a system-surface host is live, precisely because the bridge would serve the occluded app tree. That is the case where a capture is runner-routed, so the narrowing makes the clause sharper rather than vestigial. It still depends on feat(ios): drive ASWebAuthenticationSession sign-in sheets in place (#2438) #2448 merging first — noted in the PR body, and merge order unchanged.
  • The unchanged honest part. The probe passes only {identifier, label} and the guard's own rootViewport; nothing carries a surface or generation identity across the two requests, so the "appears or dismisses between them is undetected" statement stands as written.

Gates at e905700fff: pnpm format (no further diff), pnpm lint, pnpm typecheck, pnpm check:affected --run — all pass (file staged before the layering scan). No device or simulator use; the diff is one comment.

@thymikee

Copy link
Copy Markdown
Member Author

The wording is accurate at e905700: it now limits the shared policy to runner-routed captures and explicitly states the cross-request gap. No remaining code findings. Merge after #2448; the Android home/recents smoke failure is unrelated to this comment-only change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(adr-0011): note offscreen live-rescue resolves the same surface as the tree

1 participant