Skip to content

fix(apple): refuse captures whose window or panel identity is unknown - #3761

Merged
thymikee merged 12 commits into
mainfrom
fix/adr-drift-apple-capture-identity
Oct 18, 2026
Merged

thymikee merged 12 commits into
mainfrom
fix/adr-drift-apple-capture-identity

Conversation

@thymikee

@thymikee thymikee commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Native macOS capture, scroll, and click fallback share one visible, nonempty layer-0 front-window selection and one on-screen window-list query. App-modal level-8 alerts and floating palettes are skipped. Capture refuses instead of substituting another window, with typed exclusion reasons and a runnable snapshot remedy. Each refusal builds its reason, hint, and front-window id in the Swift helper, so the TypeScript client forwards the details envelope without a lookup table. Screenshot and scroll report window-list-unreadable with a retry hint when the window-list query fails.
  • Foldable capture refuses with capture-panel-unidentified when neither panel power nor primary identity selects a panel. Help warns that an ambiguous primary panel may be dark.

10 files. Part of #3744. ADR 0031 and modal-panel eligibility remain outside this PR. Helper packaging (npm files and Package.swift test targets) is owned by a separate PR.

Validation

  • Head 6d40fbc0f2: pnpm check:affected --run --head 6d40fbc0f2 passed (exactCommit=true) (receipt run-ggnrE5).
  • At 6d40fbc0f2: Swift debug build and 16 helper + 6 input tests passed, including nil-versus-empty reads and per-refusal hint pins. 12 focused helper tests and 1338 capture/platform-apple tests passed. Format, lint, typecheck, layering, fallow, and docs checks passed in the receipt above.
  • Live native runs on this Mac (Screen Recording granted, helper rebuilt from this head, isolated session and state dir): TextEdit front-window captures succeeded (1172x976 and 780x636 PNGs); a menu-bar-only app after closing its windows refused with COMMAND_FAILED, details.reason: "window-not-found", and details.hint: "Open or unminimize a window of the app on the current Space, then retry the screenshot." reaching the CLI untouched. Sessions closed, daemon stopped, state dir removed.
  • Earlier-head live evidence also covers minimized-window refusal, overlays, diff, and front-only scrolling. Capture exclusions remain fixture-tested; no iOS 27.1 Duo run.
  • New-head CI: pending; Coverage, Integration, native/device lanes remain required.

View guided diff Turn on auto-fix

ADR 0031 decides that a native macOS app screenshot captures only the
app's front window through ScreenCaptureKit (#3188). When the front
window's WindowServer ID was missing from the shareable candidates, the
helper captured the app's largest window instead, so the image could
show a window the user was not looking at.

The helper now selects the front window by ID or refuses with a typed
reason: `window-not-found` when the app has no on-screen window,
`front-window-not-shareable` when ScreenCaptureKit does not offer the
front window. The shared outcomes fixture pins both reasons for Swift
and TypeScript, and the host adds a hint for each.

Evidence: the audit at bd4fd68 (AppWindowScreenshot.swift:21);
re-verified on 4c1c786. Part of #3744.
ADR 0025 decides that only CoreDevice facts identify a panel: the sole
lit panel, otherwise the `primary` panel, and never array position.
When no panel reported `primary` and power did not decide one lit
panel, the inventory captured the first lit or first integrated panel
in list order.

That inventory now names no panel, and capture-target resolution
refuses with COMMAND_FAILED, reason `capture-panel-unidentified`, and a
hint. The sole-lit-panel path and the primary-panel path are unchanged.
The foldable help topic states the refusal.

Evidence: the audit at bd4fd68 (display-inventory.ts:261);
re-verified on 4c1c786. Part of #3744.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 10 files

You’re at about 91% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-apple/src/os/macos/helper.test.ts Outdated
Comment thread src/commands/system/index.ts Outdated
Comment thread packages/platform-apple/src/os/macos/helper.ts Outdated
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.32 MB 5.33 MB +11.2 kB
Package (unpacked) 5.32 MB 5.33 MB +11.2 kB
Package (download) 1.60 MB 1.60 MB +2.8 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 19.4 ms 21.2 ms +1.9 ms
CLI --help 51.7 ms 52.6 ms +1.0 ms

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-18 10:06 UTC

@thymikee

Copy link
Copy Markdown
Member Author

The Swift change looks right, but the native-backend screenshot route has no live evidence yet, so it needs one run on a real host before merge (reviewed at dec7c9b).

AppWindowScreenshot.swift:19 changes the native-backend route for every macOS app capture. When there is no front window ID, or ScreenCaptureKit does not list the front ID, it now throws. It used to return the largest window. If a live app has a CGWindowList front window that SCShareableContent drops (for example a zero-area or transient layer-0 window), every screenshot of that app would now fail. No fixture can show whether that happens, and no live run reached this route because the author's host lacks Screen Recording permission. Could you run it on a host with Screen Recording granted? Set AGENT_DEVICE_MACOS_APP_BACKEND=native and open TextEdit. Run agent-device screenshot and show that the PNG holds the front window. Then minimize every TextEdit window and run screenshot again. It should fail with COMMAND_FAILED, reason window-not-found, and the hint. The minimized case is easy to provoke, unlike front-window-not-shareable. A Duo run is optional, since the changed branch is unit-proven and the lit-panel path is unchanged. Run one only on a host with the iOS 27.1 runtime.

I did not run the Swift or Vitest suites. The regression value of the Swift tests relies on the author's planted-fallback report, because frontCaptureWindow is new. The Duo refusal path is fixture-only, since a CoreDevice payload without a primary panel cannot be produced on a real host.

Smoke Tests failed in the iOS runner suite on RunnerTests.testSynthesizedReplacementLeavesAMaskedFieldUnverified (RunnerTests+SynthesizedTextEntryTests.swift:169), where the masked-field bullet count was 13 instead of 8. This PR changes nothing under apple/runner or the text-entry route, so the failure looks unrelated. No conflicts.

Of the open inline threads, the two docs-wording notes still apply: system/index.ts:281 (lit panel wording) and helper.ts:157-158 (retry hint wording). The helper.test.ts:247 thread does not apply: the PR leaves screenshot argv unchanged, and the existing argv test owns that shape. You can resolve it.

Before merge, this PR needs the live native-backend evidence above: a front-window PNG from on-screen TextEdit, and the window-not-found refusal with the hint when every TextEdit window is minimized.

Choose the first on-screen normal-layer app window with positive alpha and nonempty bounds. Keep interaction window selection unchanged and refuse if an eligible front window is absent from ScreenCaptureKit.

Review: #3761 (comment)
@thymikee

Copy link
Copy Markdown
Member Author

Follow-up head: f8e9688ac6.

  • fa3fe4f774: screenshot selection now rejects off-screen, nonzero-layer, fully transparent, and empty CGWindowList entries before matching the front ID in ScreenCaptureKit. A visible/nonempty front window omitted by ScreenCaptureKit still refuses; there is no substitution.
  • a77578f8ee, f8e9688ac6: capture-exclusion hint and ambiguous-primary-panel wording fixed.
  • Threads: 2 fixed, 1 not applicable and resolved per maintainer; 0 rejected/open.

pnpm check:affected --run --head f8e9688ac6 passed (exactCommit=true, receipt run-KhgbXp). Focused Vitest: 48 passed. Swift helper: 13 passed; release build and XCTest selection passed. Removing alpha/size filters in a disposable checkout made both added tests fail (11 passed); cleanup completed.

Previous-head CI: only iOS Smoke Tests failed, at the unchanged masked-field assertion (13 bullets vs 8). PR #3722 has the identical failure. That runner XCTest exercises neither changed capture route. Android/Linux/macOS smoke, Coverage, Integration, package/guards, docs/preview, CodeQL, Size, and cubic passed; the fixture-cache matrix job was skipped. New-head CI is pending.

Live check remains with maintainer: preflight is false; native TextEdit screenshot returned COMMAND_FAILED, permission: screen-recording, before dispatch. With permission granted, verify the front-window PNG and minimized-all-windows window-not-found plus hint. Isolated daemon stopped.

@thymikee

Copy link
Copy Markdown
Member Author

Live macOS evidence at f8e9688ac6 (native backend, Screen Recording granted, CGPreflightScreenCaptureAccess() == true).

Setup: AGENT_DEVICE_MACOS_APP_BACKEND=native, isolated --state-dir, TextEdit with three windows: live3761-front.txt (front), live3761-back.txt, and an existing Untitled. The helper that ran (~/.agent-device/macos-helper/current/agent-device-macos-helper, sha1 ce2f5974…) is byte-identical to pnpm build:macos-helper from this head.

  1. screenshot front.png → 1312×844 PNG of live3761-front.txt (title bar and text "FRONT WINDOW" visible), not another window.
  2. All three windows minimized through their AX minimize buttons, then screenshot → COMMAND_FAILED, details.reason: "window-not-found", hint "Open or unminimize a window of the app on the current Space, then retry the screenshot.", dispatched: "no". No PNG was written.
  3. One window restored: screenshot --overlay-refs annotates 4 refs on the front window; diff screenshot --baseline front.png reports 100 of 1,107,328 pixels changed, one 4×26 region at the text caret (the blinking cursor). Overlay and live diff use the front window.

Not run: a sheet or popover, a fullscreen Space, and the iOS 27.1 iPhone Duo (this host has no iOS 27.1 runtime). Cleanup: test documents closed, Untitled restored, session closed, daemon stopped.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed all three follow-up findings in separate commits: M1 dd5a35e488 shares front-window eligibility; L1 c326cafbfd defines it in help/docs; L2 0de4a1e2c8 distinguishes capture exclusions with a typed reason and non-retry hint. M2/ADR 0031 remain out of scope. No unresolved review threads.

At 0de4a1e2c8: 14 Swift tests passed, focused TypeScript/help/doc checks passed, CLI/native builds and XCTest selection passed. check:affected --run --head 0de4a1e2c8 passed with exactCommit=true, checkoutChanged=false (receipt run-CDMHCO). Disposable mutations fail the intended selection/exclusion tests; restoring fixes passes.

Live on this Mac with AGENT_DEVICE_MACOS_APP_BACKEND=native, worktree-built helper, session pr3761-r2, and worktree-local state: the 1312×844 PNG shows pr3761-r2-front.txt, not the back document. scroll down --pixels 300 reports ax-scroll-bar; the next PNG starts at FRONT line 23. AX text y changes 221→−80 only in the front document; back stays 192. Both created documents closed through close buttons; existing Untitled untouched; session/daemon stopped.

Prior-head CI is green; new-head CI is pending.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

You’re at about 96% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Share the on-screen window-list query while preserving nil versus empty point refusals. Pin layer-8 alert exclusion at the shared selection boundary.

Review: #3761 (comment)
@thymikee

Copy link
Copy Markdown
Member Author

Round 4 head: 5e16f816b5.

  • bb69e6a301: one on-screen query and shared frontWindow owner; nil-versus-empty point refusals preserved; level-8 alert fixture added.
  • 5e16f816b5: help/docs state layer-0 eligibility and skipped app-modal alerts; capture-exclusion hint gives runnable snapshot step.
  • Threads: 1 fixed and resolved (cubic); 0 rejected/open. All four round-3 low findings addressed. Issue comments, reviews, and inline threads reread.

Validation: exact-head affected gate passed (exactCommit=true, checkoutChanged=false, receipt run-3OxBK9); format, Swift debug/release builds, 15 helper + 6 input tests, 102 focused TS/help/doc tests, XCTest selection passed. Initial gate failed an unchanged config assertion because generated PID path test-run-12316 matched synthetic run-123; controlled reproduction fails identically on base 0de4a1e2c8 and head (1 failed/18 passed), then normal gate passed. Experiment checkout removed.

Live native TextEdit, rebuilt helper: 1312×844 PNG visibly captures pr3761-r4-front.txt, with Untitled behind it. Test document/session closed; isolated daemon stopped.

New-head CI pending; monitoring for up to ~40 minutes. No merge or approval.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files (changes from recent commits).

You’re at about 96% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift Outdated
Refuse screenshot and scroll with window-list-unreadable when the shared window-list read fails, and preserve a retry hint through the host helper client.

Review: #3761 (comment)
@thymikee

Copy link
Copy Markdown
Member Author

Round 5 head: 70acf8844c71e0de43730edda7c43e818ee70a8a (pushed).

Screenshot and scroll now refuse nil window-list reads with window-list-unreadable and a retry hint. Empty lists still mean no window. no-accessible-target was unsuitable because its native-interactor mapping reports unsupported backend and suggests XCTest. The new reason is pinned in the shared Swift/TypeScript fixture; no help/docs reason list needs extending.

Threads: 1 fixed and resolved (cubic); 0 rejected/open. Issue comments, submitted reviews, and inline threads reread; no new maintainer items. Follow-up self-review found no further issue; no separate reviewer used this round.

Validation: 16 helper + 6 input Swift tests, 26 focused TypeScript tests, format, and XCTest selection passed. pnpm check:affected --run --head 70acf8844c passed (exactCommit=true, checkoutChanged=false, receipt run-tnR25f). Optional live run skipped; earlier-head evidence remains attributed in the body.

Current-head CI is queued/in progress, with no result yet. Previous-head completed checks passed; iOS/macOS smoke were pending. PR is conflict-free. No merge or approval.

@thymikee

Copy link
Copy Markdown
Member Author

This PR is ready at 70acf88. The two points from the earlier review (#3761 (comment)) are fixed: a nil window list now throws window-list-unreadable instead of collapsing to window-not-found, and requireAppWindowPoint now reads appWindowInfo(), so one CGWindowListCopyWindowInfo definition remains. Not blocking, and you can take or leave these: requireAppWindowPoint (BackgroundInteraction.swift:309) still refuses an unreadable list as noAccessibleTarget, so press and fill never reach window-list-unreadable (the rule would be that every nil appWindowInfo() read refuses with window-list-unreadable), and the window-list hint in runMacOsHelper (helper.ts:386) is a literal, where a Record<WindowListRefusalReason,string> table beside MACOS_SCREENSHOT_REFUSAL_HINTS would give one hint mechanism.

On the open threads, both Cubic threads are fixed at this head, so you can resolve them: #3761 (comment) (nil list now throws) and #3761 (comment) (single window-info read).

All 20 checks pass at 70acf88, and no conflicts are known. I did not run the Swift or Vitest suites. I judged the nil-list test from the diff, since the old ?? [] returned nil and did not throw. There is no live run at 70acf88: the nil-list path cannot be provoked on a real host, and the earlier live run at 5e16f81 covers the unchanged success path. I also did not check that kCGWindowIsOnscreen and kCGWindowAlpha bridge as Bool and Double on every macOS version. Nothing else needs to happen before merge.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
The helper owns each refusal's reason and remedy, so its details
envelope carries the hint and the TypeScript client needs no lookup
table, wrapper, or special case. Collapse the one-case WindowListRefusal
enum and the screenshot Result mapping into the throw sites that already
own them, and keep the front-window selection as one plain function.
Press, fill, and their window fallback use the non-throwing path;
requireAppWindowPoint has already refused an unreadable list there.

The front-window help sentence states the layer-0 rule and the alert
exclusion once.
@thymikee

Copy link
Copy Markdown
Member Author

Head 6d40fbc0f2 (applied the approved smaller design; fast-forward over 70acf8844c).

  • Hints moved into Swift: AppWindowScreenshotRefusal.error(bundleId:) builds reason, hint, and frontWindowId; frontWindow throws window-list-unreadable with its hint.
  • Removed from TS: MACOS_SCREENSHOT_REFUSAL_HINTS, withScreenshotRefusalHint, isMacOsScreenshotRefusalReason, the window-list-unreadable special case in runMacOsHelper, and the screenshot/window-list reason lists with their fixture entries (nothing branches on them). Removed the one-case WindowListRefusal enum; frontShareableWindow(candidates, front:windowID:) replaces frontCaptureWindow + the separate refusal mapper. Press/fill/actionWindow use the non-throwing path (requireAppWindowPoint refuses first); screenshot and scroll keep propagating the typed refusal. Help/doc front-window sentence stated once. Packaging (package.json files, Package.swift) stays for its own PR.
  • Size (npm pack --dry-run, same machine, base origin/main): unpacked 5,377,473 B / 624 files base vs 5,332,905 B / 611 files here; packed 1,619,366 B vs 1,604,387 B.
  • Receipt: pnpm check:affected --run --head 6d40fbc0f2 passed (exactCommit=true, receipt run-ggnrE5). Swift: build + 22 tests green.
  • Live (native backend, Screen Recording granted): TextEdit front-window capture OK; menu-bar-only app with its last window closed refuses via CLI with details.reason: "window-not-found" and details.hint: "Open or unminimize a window of the app on the current Space...". Sessions closed, state dir removed.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 9 files (changes from recent commits).

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift">

<violation number="1" location="apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift:370">
P2: `try?` here turns an unreadable window list into "no front window", so press and fill refuse with a generic `no-accessible-target` reason instead of `window-list-unreadable`. That contradicts the `frontWindow` doc comment. Let `actionWindow` throw again and propagate the error through `resolvePressTarget` and `fillInBackground`, or pass the window info read by `requireAppWindowPoint` into it.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

if let window = elementAttribute(hit, attribute: kAXWindowAttribute as String) { return window }
}
guard let front = frontWindowFrame(pid: app.processIdentifier) else { return nil }
guard let front = try? frontWindowFrame(pid: app.processIdentifier) else { return nil }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: try? here turns an unreadable window list into "no front window", so press and fill refuse with a generic no-accessible-target reason instead of window-list-unreadable. That contradicts the frontWindow doc comment. Let actionWindow throw again and propagate the error through resolvePressTarget and fillInBackground, or pass the window info read by requireAppWindowPoint into it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift, line 370:

<comment>`try?` here turns an unreadable window list into "no front window", so press and fill refuse with a generic `no-accessible-target` reason instead of `window-list-unreadable`. That contradicts the `frontWindow` doc comment. Let `actionWindow` throw again and propagate the error through `resolvePressTarget` and `fillInBackground`, or pass the window info read by `requireAppWindowPoint` into it.</comment>

<file context>
@@ -360,13 +360,14 @@ private func pressSearchChain(from hit: AXUIElement) -> [AXUIElement] {
     if let window = elementAttribute(hit, attribute: kAXWindowAttribute as String) { return window }
   }
-  guard let front = try frontWindowFrame(pid: app.processIdentifier) else { return nil }
+  guard let front = try? frontWindowFrame(pid: app.processIdentifier) else { return nil }
   return windows(of: AXUIElementCreateApplication(app.processIdentifier)).first { window in
     guard let rect = rectAttribute(window) else { return false }
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reachable as described: press and fill call requireAppWindowPoint before actionWindow (BackgroundInteraction.swift:98, 171), so an unreadable list already refused there (no-accessible-target, "the app's on-screen windows could not be read") before frontWindowFrame is asked. The try? only covers the race where the list turns unreadable between those two reads, and the approved design treats that as no fallback window. window-list-unreadable is owned by frontWindow and propagates through screenshot and scroll, which have no earlier read.

@thymikee

Copy link
Copy Markdown
Member Author

The code at 6d40fbc looks good, and I found nothing new to fix. The earlier review at 70acf88 was clean (#3761 (comment)). The delta since then removes the extra window-list definition and makes frontWindow throw window-list-unreadable on a nil list.

CI is still running. Smoke Tests, Repo Guards & Compatibility and Coverage are queued or in progress, with no failures so far. Repo Guards covers the deleted contracts fixture keys and the helper.test.ts vocabulary test, so that job matters for this delta. Smoke Tests do not exercise the native macOS helper route. There are no conflicts.

I did not re-run the live macOS evidence, and I did not check that the helper was rebuilt from this head, as the PR body says. I did not run the Swift tests. The helper.test.ts pass-through test uses a hand-written envelope, so it shows that TS forwards details, not that Swift emits them.

Two cubic-dev-ai threads are fixed at this head, so please resolve them. #3761 (comment) is fixed because requireAppWindowPoint now reads appWindowInfo() (BackgroundInteraction.swift:309), which leaves one CGWindowListCopyWindowInfo definition (:511). #3761 (comment) is fixed because frontWindow throws window-list-unreadable on a nil list (BackgroundInteraction.swift:519-528) and no longer falls back to ?? []. One more cubic-dev-ai thread does not apply, so please resolve it too. press and fill already refuse an unreadable list as no-accessible-target in requireAppWindowPoint (BackgroundInteraction.swift:308-311), before actionWindow runs. The try? at :370 only covers a read that fails within milliseconds after that, and it yields a refusal, never a wrong target. This matches main and the reason contract you chose last round. #3761 (comment)

Before merge, the pending Smoke Tests, Repo Guards & Compatibility and Coverage jobs need to finish green.

@thymikee

Copy link
Copy Markdown
Member Author

The delta since 70acf88 holds up. At 6d40fbc the code is still in good shape: captures now refuse when the window or panel identity is unknown, and I found no new problems. CI is green, with all 20 checks passing, and there are no conflicts. The Swift hint pins in assertRefusal and assertUnreadableList look right from the diff. They would fail if Swift dropped a hint, because the TS fallback that supplied hints is gone.

I did not reproduce the live macOS native run or run the Swift tests. The PR body reports the live run at 6d40fbc: TextEdit captures, plus a window-not-found refusal that carries its hint. The capture-excluded and not-shareable refusals are covered by fixtures only. The author's live run therefore supplies the evidence that a real capture succeeds and that a real refusal carries its hint. No further run is needed from me.

One question on simplicity. The app's front window (OnScreenWindow, frontWindowLayer, appWindowInfo, frontWindow and its window-list-unreadable refusal) is now shared by screenshot capture and point actions. Should it move out of BackgroundInteraction.swift into its own module, such as AppFrontWindow.swift, so AppWindowScreenshot.swift does not depend on the interaction file? And should requireAppWindowPoint use the same window-list-unreadable refusal? Today one condition, an unreadable WindowServer list, reports two typed reasons: no-accessible-target for press and fill, and window-list-unreadable for screenshot and scroll.

The cubic-dev-ai P2 thread at #3761 (comment) does not apply, so you can resolve it. Press and fill call requireAppWindowPoint first, and it refuses on an unreadable list at BackgroundInteraction.swift:309. The try? at :370 only covers a list that becomes unreadable between the two reads, and the merge-base actionWindow already treated a nil frame as no window.

Nothing else blocks this PR. Please answer the simplicity question above, or say why the current split is better, before it goes to human review.

@thymikee thymikee removed the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
@thymikee

Copy link
Copy Markdown
Member Author

At 6d40fbc0f25725146b6a8d5bd22edca81b415120, all required checks pass and the merge state is CLEAN. The check list has 19 passes and one skipped cache-matrix job. I read the issue comments, reviews, and inline threads.

On the simplicity question: a separate AppFrontWindow.swift file is a reasonable later refactor. The current code already has one appWindowInfo query owner and one front-window selection owner. Both files belong to the same Swift target. The file location does not create a second implementation.

I recommend that this PR keep the approved refusal behavior. Press and fill use no-accessible-target, which the native interactor maps to UNSUPPORTED_OPERATION. Screenshot and scroll propagate window-list-unreadable as COMMAND_FAILED. Unifying the reasons would change the public error behavior.

The rejected try? thread remains open for the maintainer, as instructed. No code change is needed for that claim. This PR is ready for the maintainer's decision.

@thymikee

Copy link
Copy Markdown
Member Author

Thanks for the answer at 6d40fbc. Keeping the current refusal reasons makes sense: unifying them would change the public error codes for press and fill, and the front-window code already has one owner, so moving it to its own file can wait for a later refactor. That clears the simplicity question. Nothing else blocks this PR, and your live macOS run covers the changed capture and refusal paths, so it is ready for human review.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
@thymikee

Copy link
Copy Markdown
Member Author

The design audit at 6d40fbc0f2 found no actionable code defects. The independent reviewer ran 13 focused Swift tests; all passed. No source changes were made.

The shared front-window selector serves capture, scroll, and point fallback. The broader point guard permits menus and sheets. Snapshot's all-window query serves menu-bar fallback. These eligibility rules differ, so combining them would change behavior. The small overlap in PID/bounds parsing does not justify another abstraction.

Swift owns screenshot reasons and hints. TypeScript forwards them. The window-ID projection keeps selection testable without a new protocol. Moving the selector to another file would change file layout within the same target, with no new behavior or interface benefit.

The existing tests cover selection, refusal, unreadable versus empty lists, and emitted hints. No additional test is needed for this audit. Existing live evidence remains the author's evidence.

One documentation follow-up remains: ADR 0025 must describe refusal when neither panel power nor primary identity selects a panel. I recorded this for the stale-facts follow-up, conditional on this PR merging. The rejected try? thread remains unchanged.

@thymikee
thymikee merged commit feca8cb into main Oct 18, 2026
20 checks passed
@thymikee
thymikee deleted the fix/adr-drift-apple-capture-identity branch October 18, 2026 10:06
thymikee added a commit that referenced this pull request Oct 18, 2026
…fusal in ADR 0025

shouldFallbackToRunner gates the runner path in
packages/platform-apple/src/core/screenshot.ts: a failure the policy rejects
rethrows before any runner capture. The glance row implied the runner follows
every simctl failure. State the policy condition. The app-window display facts
are unchanged.

#3761 merged into main as feca8cb (2026-10-18). When neither the sole lit
panel nor CoreDevice's primary panel identifies a panel,
resolveAppleCaptureTarget now refuses with COMMAND_FAILED and
details.reason capture-panel-unidentified (display-inventory.ts); array order
is never used. Update the Capture target body and add the glance row. The
former deferred claim is removed.

Fixes #3796 review thread discussion_r4306650971.
thymikee added a commit that referenced this pull request Oct 18, 2026
…ntract

The gate asserted the pre-#3761 help literal, so the merged pair (#3788 test,
#3761 help owner) fails only in combination on main. Replace the stale rule
with the current full contract line, including the capture-panel-unidentified
refusal. The help text itself is unchanged; the assertion now matches the
rendered first screen.

CI evidence: Coverage job 117649571201 at 73d562b fails this assertion on
unchanged-base integration; it is not caused by this PR's edits and not a
flake.
thymikee added a commit that referenced this pull request Oct 18, 2026
* docs(adr): identify the derived daemon registry owners

Item A. packages/command-registry/src/registry.ts declares the separately typed daemon facet; daemon-command-descriptor.ts owns DaemonCommandDescriptor. packages/daemon/src/core/daemon-command-registry.ts derives the table and exposes predicates. Its tests pin policy and routes.

4648051 (#906) introduced the dormant descriptor and parity tests. 47abc8c (#907) activated derivation and deleted the hand table. This corrects the brief attribution to #906 alone.

Known invariant-3 gap: types.ts CommandDescriptorBase exposes daemon?: DaemonCommandTraits, and src/cli/command-explain.ts describeCommandDaemon reads and publishes route/traits. Preserve the invariant and report the gap; do not change behavior.

* docs(adr): match guarantee declarations and fixture consumers

Items 1 and 2. packages/contracts/src/interaction-guarantees.ts declares 14 guarantees, including keyboardOcclusion, parentOwnedTouchPoint, settleObservation, resolutionDisclosure, outcomeObservation, and targetStability. Match the complete list. rg finds only the TypeScript snapshot-visibility-tap-point-policy.test.ts and RunnerTests+TapPointPolicyTests.swift reading tap-point-policy.json; the matrix parityTable is metadata. No fake runner reads it. Keep migration step 3 open.

* docs(adr): correct the default lease inactivity period

Item 3. packages/daemon/src/resources/lease-registry-scope.ts DEFAULT_LEASE_TTL_MS = 60_000; normalizeLeaseTtlMs uses it when no positive TTL is supplied. The admitted-work section already states one minute.

* docs(adr): record the active settle timing detector

Item 4. gh issue view 1299 reports CLOSED, closedAt 2026-07-21T08:54:50Z. packages/maestro/test/conformance/differential/scenarios.ts settle-after-tap has no knownDivergence and requires settleLatches >= 1 and settleTimeouts <= 0. invariants.test.ts exercises synthetic traces. This is repository evidence, not a new live-device result.

* docs(adr): include recording consumers of the gesture codec

Item 5. a13a683 (#1567) added target-authored drag. packages/daemon/src/execution/interaction/internal/interaction-gesture.ts calls gesturePayloadToPositionals; packages/daemon/src/core/session-script-writer.ts calls dragGesturePayloadFromPositionals. PUBLIC_GESTURE_SYNTAX still defines arity for the two parsers.

* docs(adr): record retired aliases and current client result gaps

Items A and 6. 47abc8c (#907) activated the derived daemon table after parity in 4648051 (#906). Mark the hand-table inventory as pre-migration context. 189b062 (#960) moved client-types.ts into client/. 494eb52 (#1877) removed read. gh issue view 1153 reports CLOSED, closedAt 2026-07-08T16:35:29Z, but src/client/client-types.ts alert and reactNative still return CommandRequestResult. Do not infer total narrowing from closure.

* docs(adr): exclude bridge preparation from the generation circuit

Item 7. packages/platform-apple/src/snapshot-route.ts opensGenerationCircuit excludes failure.kind === preparing and WINDOW_COORDINATE_SPACE_UNRESOLVED. snapshot-route.test.ts pins that bridge preparation sends only the current capture to XCTest. Keep glance and body consistent.

* docs(adr): replace deleted migration references in code

Item 8. ADR 0011 Layer 2 and ADR 0014 Mutation admission and read behavior / Command effects are daemon-owned and completely classified replace removed phase, step, and escape-hatch references. Change comments and one test name only.

R56 evidence: c77bc40 (#2021) deleted the legacy src/core/dispatch.ts app-switcher arm and dispatch facet. The current descriptor retains daemon.route generic, but platformExecution is device-runtime; generic-runtime-execution.ts routes system buttons through system-button-runtime.ts, whose app-switcher row uses appSwitcherRuntimeUse. Retained generic routing is intentional, not evidence of a surviving legacy leaf or runtime bug. Clarify that distinction.

* docs(adr): identify the current generic-dispatch rejection boundary

Item 9. c77bc40 (#2021) retired src/core/dispatch.ts and dispatchKnownCommand. packages/daemon/src/server/request-router.ts calls resolveGenericRuntimeExecution before dispatchGenericCommand. packages/daemon/src/execution/generic-runtime-execution.ts rejects its default with COMMAND_FAILED / generic-route-runtime-missing. Its __tests__/generic-route-runtime-completeness.test.ts pins unknown-command rejection. No dispatch-keyboard.test.ts exists. Keep the proposed fragment INVALID_ARGS contract conditional and identify the absent format-specific proof rather than claim this routing-gap test proves it.

* docs(adr): remove the dangling historical hard-reject pointer

Item 10. git log -S hard-reject identifies 66fe801 (#1177), which held the rejection rationale in Consequences, not Alternatives. The later structural-equivalence amendment superseded geometric mutation selection; 36957e8 rewrote the retained consequence. Remove only the dangling pointer in historical acceptance evidence. Do not restore an obsolete current selection rule.

* docs(adr): align the proposed durable-resource amendment

Item 11. ADR 0019 section 5 proposed amendment already requires native-path disposition, retention by fence generation until both facts settle, and a failed-finish test for each kind before its cleanup changes. ADR 0024 sections 2.4 and 2.5 already retain archived generations; glance rule 6 already has the per-kind test gate. Match section 2.7 to these constraints. Both ADRs remain Proposed for this amendment.

* docs(adr): point exhaustive runtime facts to section 2

Item 12. ADR 0019 section 2 Runtime use joins facts and narrows the bound runtime contains the exhaustive per-device-shape fact rule. Section 3 owns provider resolution. Change the citation only.

* docs(adr): cite the withdrawn allocator design for its historical move

Item 13. ADR 0021 section 3 now states consequences, including deletion of managed-allocation. Section 4 records the withdrawn allocator design and its ownership. 4565606 (#3287) is the historical collocation move; retain the historical row and point its rationale to section 4.

* docs(adr): record the coupling ban and its landing date

Item 14. 1612e33 (#3431) and b9005d8 (#3430) both landed on 2026-10-12. The latter removed the remaining selector coupling and replaced R76 classification machinery with a ban. Status now matches Decision and enforcement; do not claim a current classification inventory.

* docs(adr): record the active shape rule and shared test owner

Items 15 and 16. Root src/__tests__/install-source.test.ts exercises shared provision-kit behavior and family adapters; cli-install-from-source.test.ts exercises the CLI boundary. Add the missing body owner matching the existing glance. efd2108 (#2808) added the no-fact-cell rule; 746e9b3 (#2806) enforces shape refusal at the web owner, pinned by packages/platform-web/src/runtime.test.ts. Mark the rule in force and correct its section pointer to section 2. Item 11: the section 5 proposed amendment is already correct and remains unchanged.

* docs(adr): correct the direction of the superseded hop comparison

Item 17. The ADR trace counts 41 distinct files for press/Android and 30 shared plus 17 AX / 19 runner for snapshot/iOS (47/49). Both exceed the superseded 38/29. The facade PRs predate e624ef9, so they cannot explain the difference; the old count is not higher.

* docs(adr): date hinge-stream evidence by toolchain

Item 18. 057dfb7 (2026-09-20) recorded the initial toolchain examination; e8da1e1 (2026-09-21, #2717) and 6cb160a (#2734) recorded a working stream. 590f171 (2026-10-14, #3587) records no motion subcommand in devicectl 651.13.5 / Xcode 27.1 build 27A9275, then called RC, and adds dispatched-unverified refusal. Read-only host check on 2026-10-16: xcodebuild -version reports Xcode 27.1, 27A9275; devicectl --version reports 651.13.5; device help lists no motion subcommand. Preserve dated successes without claiming every 27.1 build supports the stream. Sync the glance to existing no-sample behavior. No device state changed.

* docs(adr): describe the permitted capture-only second bind

Item 19. ADR 0019 section 9 permits one second bind for --settle when the action use has no capture, after facts admission. The second bind is capture-only; captureSnapshot in the action use reuses the cached binding. Correct the fixture comment only.

* docs(adr): date the completed root-pass move correction

Item 20. gh issue view 3294: CLOSED 2026-10-08T07:57:29Z. gh issue view 3281: CLOSED 2026-10-07T18:48:47Z. 8c5bce0 (#3297) moved daemon diagnostics to src/daemon-contracts; 75b414a (#3299) moved the assembly pair into src/command-runtime. Current owners are packages/daemon-contracts/src/daemon-diagnostics-scope.ts and src/command-runtime/{runtime-command-surface,runtime-factory}.ts. Add a 2026-10-16 correction to the dated observation without declaring every root move complete.

* docs(adr): record daemon trait enforcement gaps

M1. Registry tests pin the derived table; types do not fully enforce invariants 3 and 4. Keep both invariants required and synchronize Rules at a glance.

Evidence: packages/command-registry/src/types.ts:213 makes daemon optional. DaemonCommandDescriptor requires command and route but makes the other traits optional. packages/daemon/src/core/daemon-command-registry.ts:34-35 defaults missing descriptors to generic. src/cli/command-explain.ts:205-210 reads daemon traits in the public CLI projection. Registry policy pins are in packages/daemon/src/core/__tests__/daemon-command-registry.test.ts.

Maintainer follow-up: determine how types should enforce invariant 4 without weakening the accepted declaration boundary.

* docs(adr): describe pending bridge preparation precisely

L6. Describe a bridge preparation that is still running. Do not invent a preparation queue.

Evidence: packages/platform-apple/src/snapshot-source/preparation.ts:119-123 reports bridge-preparation-pending through attempts.value. packages/platform-apple/src/snapshot-route.ts:314-315 excludes preparing failures from the generation circuit. The existing glance exception already agrees.

* docs(adr): restore client result migration prerequisites

M2, L1, L2. Restore the contract-move prerequisite and its reason. State the full extent of broad client results. Retain the historical client path and restore prerequisite wording without the unrelated ADR 0010 pointer. Synchronize the client-contract rule in Rules at a glance.

Evidence: src/client/client-types.ts has 21 Promise<CommandRequestResult> methods, including alert, reactNative, and interactions.swipe. packages/command-registry/src/command-result.ts:41-47 documents dynamic payload exclusions; its result map remains nonexhaustive. packages/daemon/src/execution/interaction/internal/interaction-gesture.ts:59-74 retains local result types and Record payloads. Git commit 189b062 (#960) moved src/client-types.ts into src/client/. gh issue view 1153 confirms CLOSED at 2026-07-08T16:35:29Z.

* docs(adr): restore the retained rejection rationale pointer

L7. Repoint the historical hard-reject rationale to Consequences instead of deleting the useful pointer or restoring an obsolete alternative.

Evidence: git log -S hard-reject identifies 66fe801 (#1177), 36957e8, and 9362026. The current Consequences paragraph at line 1356 retains the reason blanket rejection is unsuitable: RN row/button/text wrapper chains. The structural-equivalence amendment remains authoritative.

* docs(adr): include replay and publication gesture codec callers

L3. Add the two omitted consumers of the positional drag codec.

Evidence: packages/replay-port/src/daemon-port/target-token.ts:13 calls dragGesturePayloadFromPositionals to select a source or destination target. packages/daemon/src/core/session-script-active-publication.ts:129 calls it through readRecordedDrag; assertPortableDragBindings at lines 102-123 validates portable endpoints and recorded target evidence before publication. No syntax or migration rule changes.

* docs(adr): trace old-reader guard failures without a codec promise

M3. Keep failure before platform execution as the proposed safety property. Record the session-dependent guard-request errors and the unresolved INVALID_ARGS requirement. A future codec cannot change an old reader. Keep Rules at a glance and both body descriptions aligned.

Evidence path: packages/ad-script/src/internal/script.ts:270-283,483 accepts an unknown command as an action. packages/ad-replay/src/internal/verify-dispatch.ts:65,280-287 dispatches the unannotated action. packages/replay-port/src/daemon-port/session-replay-runtime-engine-adapter.ts:221-237 calls invokeReplayAction. session-replay-action-runtime.ts:171,199 preserves the command. packages/daemon/src/server/request-router.ts:407,658-662 returns unknown replay actions to the full request path because core/daemon-command-registry.ts:58-59 has no replayScopedAction trait for them.

After admission, request-router.ts:346,393 reaches sessions/request-handler-chain.ts:127 and core/daemon-command-registry.ts:34-35. The unknown command routes to generic; the handler at request-handler-chain.ts:110-112 returns null. request-router.ts:596-598 returns noActiveSessionError with no session; packages/kernel/src/contracts.ts:263-264 sets SESSION_NOT_FOUND. With a session, request-router.ts:602-617 reaches execution/generic-runtime-execution.ts:116-123, which returns COMMAND_FAILED / generic-route-runtime-missing before dispatchGenericCommand at request-router.ts:619-620.

The replay adapter wraps the failure through session-replay-runtime-engine-adapter.ts:295-303,384-398 and session-replay-runtime-failure.ts:106. session-replay-runtime-failure-response.ts:108 emits REPLAY_DIVERGENCE. packages/ad-replay/src/internal/step-loop.ts:178-185 stops the loop before later open or include actions. These are the current paths after admission, not universal error guarantees for every historical release. The existing generic-route completeness test proves only the resolver branch.

Maintainer question: accept the existing old-reader errors, or keep INVALID_ARGS as an unresolved requirement?

* docs(adr): scope hop counts to their recorded trace

L9. Attribute 41 and 47/49 to this trace, without claiming that they are current repository counts.

Evidence: the Counting definition names commit 27a97ee and the retained trace tables measure press/Android and the two snapshot/iOS arms. Only the attribution changes; the counts and scope remain unchanged.

* docs(adr): retain the outstanding physical module moves

L4. Retain the dated corrections for completed moves and explicitly state that other physical moves remain outstanding as of 2026-10-16.

Evidence: scripts/layering/root-module-zones.ts:9-14 states that logical classification is not physical collocation and that the table should shrink. Its core, command-runtime, and platform-runtime entries still name root src/*.ts modules. No layering rule or completed-move claim changes.

* docs(adr): qualify the capture-only second bind

L5. Add the missing condition to the fixture comment: the settle observation may acquire a second capture-only bind only when the action use has no capture.

Evidence: docs/adr/0019-request-bound-platform-runtime.md:762-768 permits that second bind and requires reuse when the action already requires captureSnapshot. This commit changes only the comment.

Validation: pnpm format passed. All five fixture consumer suites and generic-route-runtime-completeness.test.ts passed: 6 files, 58 tests.

* docs(adr): state that the ADR 0020 old-reader trace starts after admission

Admission can resolve a device for lock and provider selection
(request-binding.ts, request-platform-provider-context.ts). It only reads the
inventory, so the safety property holds, but the trace does not cover
admission-time failures. Found in review round 2.

* docs(adr): align ADR 0003 invariants 3 and 4 with the shipped derivation

The 2026-06 update required types to stop every public projection from
reading daemon traits and a compile error for a missing trait. The shipped
derivation (#906, #907) does neither: the daemon facet is optional, a command
without one gets the generic route, and command explain displays the facet.

Invariant 3 now bans public contracts (CLI grammar, Node options, MCP schemas,
help, capability support) that derive from daemon traits, and allows a
diagnostic display. Only command-explain.ts and the daemon's own policy file
(daemon-policy-file.ts) read the facet. Invariant 4 now requires one daemon
facet per command and no second table. Decided with the maintainer.

* docs(adr): accept existing old-reader errors in ADR 0020

An old reader cannot learn a new error from a later codec. The traced path
already fails before platform execution with SESSION_NOT_FOUND or
COMMAND_FAILED (generic-route-runtime-missing), wrapped as REPLAY_DIVERGENCE.
The proposal now accepts these errors for old readers and keeps INVALID_ARGS
for readers that ship with the fragment codec. Decided with the maintainer.

* docs(adr): list the full provider-scenario exclusion buckets in ADR 0001

* docs(adr): scope the Android helper rule to instrumentation touch in ADR 0002

* docs(adr): record the exempt commands and grown allowlist in ADR 0005

* docs(adr): state per-fate wire-gate outcomes in ADR 0006

* docs(adr): name the platformExecution facet and execute seam in ADR 0008

* docs(adr): point the visionOS discovery claim at its test file in ADR 0009

* docs(adr): state the remote logPathUnavailable case in ADR 0010

* docs(adr): correct the scroll keyboard-probe condition in ADR 0011

* docs(adr): scope the linear-schedule sentence to endpoint-hold in ADR 0013

* docs(adr): name the frame value module and empty-result rule in ADR 0014

* docs(adr): add the we-are-lenient outcome to ADR 0015

* docs(adr): state the session event hides fill text in ADR 0017

* docs(adr): include translation hops in the collapse rule of ADR 0022

* docs(adr): separate the runner capture path and complete refusal causes in ADR 0025

* docs(adr): scope the novelty fact to verbatim transplants in ADR 0027

* docs(adr): correct the freshness row and cycle wording in ADR 0033

* docs(adr): state the platformExecution execution shapes in ADR 0008

platformExecution is a discriminated union of none, host, inventory, and
device-runtime (packages/contracts/src/command-platform-execution.ts). The
active design claimed it always names a runtime use; only the device-runtime
variants do. Describe the shape and qualify the bound runtime use to those
variants.

Fixes #3796 review thread discussion_r4306650944.

* docs(adr): match the active design to platformExecution in ADR 0009

The active design still said it composes with ADR 0008's descriptor capability
facet. That facet is retired; the per-OS predicates live in request-bound
runtime facts (packages/platform-apple/src/gesture-facts.ts and siblings).
Match ADR 0008's platformExecution declaration. Dated history and the
retirement notes keep their wording.

Fixes #3796 review thread discussion_r4306650938.

* docs(adr): scope the diagnostic fields to the request scope in ADR 0010

Malformed RPC method params throw in packages/daemon/src/server/http/http-rpc-methods.ts
before the router opens the request diagnostics scope (request-router.ts
handleRequestOnLedger), so http-server.ts answers with normalizeError output
that carries neither diagnosticId nor logPath; the validation tests pin that
400 / -32602 envelope. Scope the flush and field guarantee to errors inside a
request diagnostics scope and state that pre-scope failures omit the fields.
The remote logPathUnavailable rule is unchanged.

Fixes #3796 review thread discussion_r4306650954.

* docs(adr): confine frame construction claims to the slot class in ADR 0014

ref-frame.ts calls issuedRefFrame (ref-frame.ts activateRefFrame) and derives
expiry through expiredRefFrame, both exported factories of ref-frame-slot.ts.
The original sentence claimed no module outside the slot file can construct or
derive a frame value. Confine the claim to the private class, its private
fields, and direct construction; state the exported factory APIs and keep
transitions and admission owned by ref-frame.ts. The slot-file comment carried
the same overstatement and is corrected at the same scope.

Fixes #3796 review thread discussion_r4306650966.

* docs(core): state the pinned-generation warning exception in ref-frame.ts

resolveRefStalenessWarning (execution/session-snapshot.ts) warns on an active
frame when a pinned ref's generation differs from the frame epoch; the
existing test covers all three branches (expired any-ref, active plain/matching
pin clean, active mismatched pin warns). The header comment claimed an active
frame never warns. State the exact behavior.

Fixes #3796 review thread discussion_r4306650958.

* docs(adr): qualify the runner fallback policy and record the panel refusal in ADR 0025

shouldFallbackToRunner gates the runner path in
packages/platform-apple/src/core/screenshot.ts: a failure the policy rejects
rethrows before any runner capture. The glance row implied the runner follows
every simctl failure. State the policy condition. The app-window display facts
are unchanged.

#3761 merged into main as feca8cb (2026-10-18). When neither the sole lit
panel nor CoreDevice's primary panel identifies a panel,
resolveAppleCaptureTarget now refuses with COMMAND_FAILED and
details.reason capture-panel-unidentified (display-inventory.ts); array order
is never used. Update the Capture target body and add the glance row. The
former deferred claim is removed.

Fixes #3796 review thread discussion_r4306650971.

* docs(registry): separate app-switcher admission from execution

admitRuntimeOperations (packages/daemon/src/core/runtime-admission.ts) performs
the side-effect-free facts inspection of the owner's appSwitcher fact and only
then hands back the binding gateway; execution
(packages/daemon/src/execution/system-button-runtime.ts) uses the bound
operation. The comment implied admission itself uses the bound operation.
State the two stages separately.

Fixes #3796 review thread discussion_r4306650973.

* test(help): pin the foldable first-screen rule to the merged #3761 contract

The gate asserted the pre-#3761 help literal, so the merged pair (#3788 test,
#3761 help owner) fails only in combination on main. Replace the stale rule
with the current full contract line, including the capture-panel-unidentified
refusal. The help text itself is unchanged; the assertion now matches the
rendered first screen.

CI evidence: Coverage job 117649571201 at 73d562b fails this assertion on
unchanged-base integration; it is not caused by this PR's edits and not a
flake.

* docs(adr): state the attempted flush and field conditions in ADR 0010

withDiagnosticsScope (packages/host-kit/src/internal/diagnostics.ts) runs no
finalizer. Only finalizeThrownRequestError (request-router.ts) flushes, and it
is reached by thrown errors alone: the token refusal in runRequestWithinScope
is a returned normalizeError output without context, and
flushDiagnosticsToSessionFile returns no record for zero events or a write
failure, so an in-scope thrown error can still omit logPath. State that a
thrown request error attempts one forced flush, diagnosticId requires an
active scope, logPath appears only when the flush produces a file, and
returned or pre-scope failures may omit both fields. The remote
logPathUnavailable rule and the no-secrets rule are unchanged.

Follow-up qualification on discussion_r4306650954.

* docs(adr): scope the primary-panel fallback row to a primary panel in ADR 0025

buildInventory names the primary panel and emits apple_display_capture_ambiguous
only when a primary panel exists (display-inventory.ts: the capture display is
the sole lit panel or the primary panel); with neither, resolveAppleCaptureTarget
refuses with capture-panel-unidentified. The two glance rows overlapped on zero
or multiple lit panels. Qualify the fallback row condition.

Fixes #3796 review thread discussion_r4306742872.

* docs(adr): scope the flush owner to the router finalizer in ADR 0010

The previous sentence generalized over every thrown request error, but a
malformed-RPC throw never reaches the router. Name the owner: the router's
thrown-error finalizer attempts the flush. Replace the generic
returned/pre-scope sentence with the two concrete cases that omit both fields,
so the text no longer claims every returned or pre-scope failure must omit
them; normalization preserves any existing metadata.

Completes the qualification on discussion_r4306650954.
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