Skip to content

Fail closed on unknown AX window observations#335

Merged
xrf9268-hue merged 2 commits into
xrf9268-hue:mainfrom
zjlgdx:codex/issue-320-ax-window-observation
Jul 18, 2026
Merged

Fail closed on unknown AX window observations#335
xrf9268-hue merged 2 commits into
xrf9268-hue:mainfrom
zjlgdx:codex/issue-320-ax-window-observation

Conversation

@zjlgdx

@zjlgdx zjlgdx commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #320

Summary

  • Require a successful AX window observation before zero visible windows can confirm deactivation; isHidden == true remains an independent affirmative signal.
  • Keep failed observations in the owned deactivation session until a real hide signal arrives or the existing bounded timeout restores the stable session.
  • Add regression and hardening coverage plus a compile-time-only packaged AX-read fault-injection profile whose clean production counterpart cannot activate the seam.

Acceptance mapping

  • Unknown AX read + frontmost=false + isHidden=false: failedWindowObservationDoesNotConfirmDeactivationOrClearSession proves no confirmation and preserves the same stable/pending/deactivating ownership.
  • Independent hidden signal: failedWindowObservationCanConfirmWhenTargetIsHidden proves isHidden=true may confirm even when the AX read fails.
  • Successful zero-window behavior: successfulZeroWindowObservationStillConfirmsDeactivation preserves supported windowless behavior.
  • Bounded failure handling: persistentFailedWindowObservationTimesOutAndRestoresStableSession proves the existing timeout restores activeStable rather than clearing ownership.
  • Existing paths: targeted successful-visible, workspace-notification-first, hidden-state-lag, stale-generation, and ordinary hide tests remain green.
  • Packaged failure proof: the exact-head injected trace records windowObservationSucceeded=false, targetHidden=false, and confirmed=false, then confirms only after the real hidden-state transition.

Exact-head verification

  • Base SHA: 118deead760eeb847cdc0d4b2b1697a88fdb630b
  • Reviewed/source HEAD: 2636a036fc6fd9deca8281dcc630d03dba49c833
  • Six targeted normal-path AppSwitcher tests — PASS.
  • Two tests compiled with WINK_AX_WINDOW_OBSERVATION_FAULT_INJECTION — PASS.
  • swift test — 472 tests / 44 suites PASS.
  • swift build -c release --skip-update — PASS.
  • bash scripts/package-app.sh in injected and clean modes — PASS.
  • shellcheck scripts/package-app.sh plus invalid-profile, mutual-exclusion, and production-cleanup cases — PASS.
  • Standards review against the recorded base — PASS, no confirmed findings after restoring visible-window regression coverage.
  • Spec review against the same base — PASS, no confirmed findings.

Exact-head packaged runtime evidence

  • Host: macOS 15.7.5 (24G624), arm64.
  • Injected app: /Users/yvan/developer/Wink/.worktrees/issue-320-ax-window-observation/build/validation/issue-320-2636a036fc6fd9deca8281dcc630d03dba49c833-20260718T071504Z/injected/Wink.app.
  • Injected executable SHA-256: abc2b72baf3db0f6f343adda0113ce717edd413f18a461b0b816ecc3cafa4378.
  • Clean app: /Users/yvan/developer/Wink/.worktrees/issue-320-ax-window-observation/build/validation/issue-320-2636a036fc6fd9deca8281dcc630d03dba49c833-20260718T071504Z/clean/Wink.app.
  • Clean executable SHA-256: d079a97346440c0715b208474ce25f0815427cdda14de592d6d05fab2accda6c.
  • Both bundles pass codesign --verify --deep --strict. The clean bundle has no validation profile/revision keys or fault-injection marker.
  • Injected attempt 4E0F5B4F-0091-437B-BC83-AC46FEEEADC0, generation 1: exactly one failed read stayed phase=deactivating confirmed=false; three following successful reads still saw one visible window and remained unconfirmed; a real hide then emitted TOGGLE_HIDE_CONFIRMED at 252 ms.
  • Injected trace: /Users/yvan/developer/Wink/.worktrees/issue-320-ax-window-observation/build/validation/issue-320-2636a036fc6fd9deca8281dcc630d03dba49c833-20260718T071504Z/logs/injected-runtime.log.
  • Clean standard-only E2E: 0 FAIL, four PASS and two expected fixture WARNs (no Hyper binding; only one shortcut).
  • Clean mixed standard+Hyper E2E: 6/6 PASS, 0 WARN.
  • Complete sanitized ledger: /Users/yvan/developer/Wink/.worktrees/issue-320-ax-window-observation/build/validation/issue-320-2636a036fc6fd9deca8281dcc630d03dba49c833-20260718T071504Z/RESULTS.md.

State restoration

  • Original shortcut payload restored at SHA-256 821b3cfd866445435ace749f4b711c63e7f41a8c096d8b162134bb689e764113.
  • Original debug log restored, the generated E2E backup removed, and normal /Users/yvan/developer/Wink/build/Wink.app relaunched.

Validation Status

  • Not runtime-sensitive
  • macOS runtime validation pending
  • macOS runtime validation complete

Docs sync check

  • Architecture, handoff, lessons, and signing/release validation guidance updated.
  • No archive document is used as current source of truth.

@xrf9268-hue xrf9268-hue left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Approved against exact head 2636a03 after Standards + Spec review and exact-head macOS runtime validation.

@xrf9268-hue
xrf9268-hue merged commit 3a964b4 into xrf9268-hue:main Jul 18, 2026
5 checks passed
@zjlgdx
zjlgdx deleted the codex/issue-320-ax-window-observation branch July 18, 2026 07:31
xrf9268-hue added a commit that referenced this pull request Jul 21, 2026
* perf: instrument and bound main-actor AX observation cost

Measurement-first work for the remaining synchronous AX observation on
the main actor. Every window observation now runs through a
phase-labeled wrapper (preAction, activationConfirmation,
deactivationConfirmation, launchContinuation, launchConfirmation,
snapshotFallback) that emits an os_signpost interval under
com.wink.app/AXObservation and an AX_OBSERVATION_SLOW diagnostic when a
capture exceeds the documented 50ms observation latency budget. The
observation result and all toggle decision logic are unchanged.

Measured baselines via scripts/profile-ax-observation.swift (the exact
live AX sequence, Apple silicon, macOS 15, 30 iterations): 1 window
p50 0.12ms/max 0.17ms; 20 windows p50 0.50ms/p95 8.8ms; 100 windows
p50 4.8ms/p95 12.5ms - the healthy path stays far inside the budget, so
no structural optimization is applied. The one measured violation is an
unresponsive target (SIGSTOP fixture): each AX roundtrip blocks until
the messaging timeout, ~18s per observation at the 6s global default.
The live capture therefore sets a bounded 1s AXUIElementSetMessagingTimeout
on the app and window elements (~3s worst case per observation); a
timeout surfaces as a failed read that the #335 fail-closed handling
already treats correctly.

The instrumentation seam adds defaulted now/onSlowObservation closures
to ApplicationObservation.Client, so existing fakes and call sites are
source-compatible; tests cover pass-through, over/under-budget
reporting, and the snapshot-fallback phase.

Fixes #321

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: scope the bounded AX messaging timeout to the app element

Review follow-up: stamping the 1s timeout on window elements would let
a timed-out per-window kAXMinimized read count a stalled app's
minimized window as visible (fabricated stable-activation evidence) and
drop it from minimizedWindows (lost unminimize), and the sticky
per-element stamp would silently bound the later unminimize write
through the same stored refs. Per-window reads keep their pre-existing
global-timeout semantics; the app-element bound alone still caps the
hung-target observation at the measured ~3s (the measurement harness
already stamped only the app element).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: yvan <yvan.yang@outlook.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

Do not confirm toggle-off from an unknown AX window observation

2 participants