From 62b64eacb550550719b7d27724e0c5db08a96b89 Mon Sep 17 00:00:00 2001 From: Julien Martel Date: Wed, 23 Sep 2026 06:50:56 -0500 Subject: [PATCH 1/2] probes: inert-keys.sh, and the macOS 27.0 re-run record The two "writes and lies" rows of haus's macos-settings.md (com.apple.Accessibility, AppleInterfaceStyle) had no probe; inert-keys.sh is one, with System Events as the control. README records the 27.0 re-run: 26.6.2 and 27.0 tart guests matched line for line, and names the three rows a SIP-off, battery-less guest cannot answer. Co-Authored-By: Claude Opus 5.5 (1M context) --- script/probes/README.md | 26 +++++++++ script/probes/inert-keys.sh | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100755 script/probes/inert-keys.sh diff --git a/script/probes/README.md b/script/probes/README.md index 683e5c64..c196391c 100644 --- a/script/probes/README.md +++ b/script/probes/README.md @@ -16,6 +16,7 @@ swift script/probes/displays.swift # displays, persistent UUIDs ./script/probes/sound-sweep.sh # alert volume, beep sound, startup chime ./script/probes/locale-sweep.sh # region keys, input sources ./script/probes/power-sweep.sh # sleep/pmset (section C needs root) +./script/probes/inert-keys.sh # com.apple.Accessibility, AppleInterfaceStyle (VM only) ``` `accessibility-effective.swift` reports what macOS *actually* honours, not what @@ -111,6 +112,31 @@ effect) from keys with none (persistence only — it pauses ~10s so you can look That split is deliberate: "the write succeeded" was never sufficient evidence here, since `com.apple.Accessibility` writes succeed and change nothing. +## `inert-keys.sh` + +The two "writes and lies" rows that had no probe: `com.apple.Accessibility` +(section A) and a `defaults` write of `AppleInterfaceStyle` (B). Section C +flips dark mode through System Events as the control, because a "nothing moved" +only counts once the oracle has been seen to move. C repaints the desktop, so +run the whole thing in a tart VM, never on the Mac someone is using. + +## Last full re-run: macOS 27.0 (26A428), 2026-09-23 + +Every sweep ran in two tart guests side by side, a 26.6.2 control and a 27.0 +guest, and the outputs matched line for line apart from the base image's own +defaults (27's guest starts on `ABC` with a long language list). The read-only +probes ran on a 27.0 host and agreed with the doc. Three rows a VM cannot +answer, so they still stand on 26 evidence: + +- **The FDA refusal.** cirruslabs guests run with SIP off, so TCC doesn't gate + Full Disk Access: a launchd agent with no grant read `TCC.db` and wrote + `com.apple.universalaccess` without complaint. +- **The by-eye rows.** The private `CGSGetCursorScale` read 1.0 after a + `mouseDriverCursorSize = 3.0` write and a `universalaccessd` restart on the + 26 control too, so it is no oracle in a headless guest. +- **Power's battery/AC split.** No battery in a guest; `pmset -c` didn't land + there on either version. + ## `sound-sweep.sh` · `locale-sweep.sh` · `power-sweep.sh` — §5.6's last three groups Added 2026-08-08 to settle the three curated-settings groups the roadmap had diff --git a/script/probes/inert-keys.sh b/script/probes/inert-keys.sh new file mode 100755 index 00000000..62aba2d0 --- /dev/null +++ b/script/probes/inert-keys.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# The two "writes and lies" rows of haus's docs/macos-settings.md that had no +# probe of their own: `com.apple.Accessibility` and +# `NSGlobalDomain AppleInterfaceStyle`. Both write cleanly and both move nothing, +# so the only evidence is an effective-state oracle read in a FRESH process. +# +# A. com.apple.Accessibility write the modern-looking keys, read NSWorkspace +# B. AppleInterfaceStyle write Dark (+ activateSettings -u), read AppKit's +# effective appearance and listen for +# AppleInterfaceThemeChangedNotification +# C. the control for B flip dark mode through System Events, which +# SHOULD move both. Without it, a "nothing moved" +# in B could be a broken oracle. +# +# RUN IT IN A VM. Section C repaints the whole desktop, and if macOS ever makes +# B live it repaints there too; that is the question being asked. `haus skill +# vm` has the loop. C also needs an Automation grant for System Events; a +# cirruslabs guest over ssh has one. +# +# Safe: A deletes exactly the keys it wrote. B and C put the appearance back to +# what the oracle read before they started, through System Events, and restore +# AppleInterfaceStyle's plist value. No sudo, no rebuild. +# +# Run history (dates belong here, not in the doc): +# 2026-09-23 26.6.2 (25G83) tart control and 27.0 (26A428) tart guest, +# identical: A inert, B inert with no notification, C flips +# appearance and posts the notification. + +set -uo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +tmp="$(mktemp -d)" +say() { printf '\n\033[1m%s\033[0m\n' "$*"; } + +cat >"$tmp/appearance.swift" <<'EOF' +import AppKit +let app = NSApplication.shared +Thread.sleep(forTimeInterval: 0.5) +let dark = app.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua +print("appearance=\(dark ? "dark" : "light") key=\(UserDefaults.standard.string(forKey: "AppleInterfaceStyle") ?? "")") +EOF +cat >"$tmp/notewatch.swift" <<'EOF' +import Foundation +let c = DistributedNotificationCenter.default() +var seen = false +c.addObserver(forName: NSNotification.Name("AppleInterfaceThemeChangedNotification"), object: nil, queue: nil) { _ in + seen = true; print(" POSTED AppleInterfaceThemeChangedNotification") +} +RunLoop.main.run(until: Date().addingTimeInterval(Double(CommandLine.arguments[1]) ?? 6)) +if !seen { print(" no AppleInterfaceThemeChangedNotification") } +EOF + +a11y() { swift "$here/accessibility-effective.swift" 2>/dev/null; } +look() { swift "$tmp/appearance.swift" 2>/dev/null; } +se_dark() { osascript -e "tell application \"System Events\" to tell appearance preferences to set dark mode to $1" 2>&1; } + +ax_keys=(ReduceMotionEnabled ReduceTransparencyEnabled DifferentiateWithoutColor EnhancedBackgroundContrastEnabled) +# Per-key XML fragments, so a restore keeps the type (bash 3.2: no assoc arrays). +for k in "${ax_keys[@]}"; do + /usr/libexec/PlistBuddy -x -c "Print :$k" ~/Library/Preferences/com.apple.Accessibility.plist >"$tmp/ax.$k" 2>/dev/null || rm -f "$tmp/ax.$k" +done +style_before="$(defaults read -g AppleInterfaceStyle 2>/dev/null || true)" +start="$(look)" +start_dark=false; case "$start" in appearance=dark*) start_dark=true ;; esac + +cleanup() { + printf '\n→ restoring…\n' + for k in "${ax_keys[@]}"; do + if [ -s "$tmp/ax.$k" ]; then defaults write com.apple.Accessibility "$k" "$(cat "$tmp/ax.$k")" + else defaults delete com.apple.Accessibility "$k" >/dev/null 2>&1; fi + done + se_dark "$start_dark" >/dev/null + if [ -n "$style_before" ]; then defaults write -g AppleInterfaceStyle "$style_before" + else defaults delete -g AppleInterfaceStyle >/dev/null 2>&1; fi + printf ' now: %s (started %s)\n' "$(look)" "$start" + rm -rf "$tmp" +} +trap cleanup EXIT INT TERM + +say "A. com.apple.Accessibility — expect: plist moves, NSWorkspace does not" +printf ' before: %s\n' "$(a11y)" +for k in "${ax_keys[@]}"; do defaults write com.apple.Accessibility "$k" -bool true; done +notifyutil -p com.apple.accessibility.cache.ax 2>/dev/null +sleep 2 +printf ' wrote %s = 1, poked com.apple.accessibility.cache.ax\n' "${ax_keys[*]}" +printf ' after: %s\n' "$(a11y)" + +say "B. AppleInterfaceStyle via defaults — expect: key moves, appearance does not" +printf ' before: %s\n' "$start" +swift "$tmp/notewatch.swift" 8 >"$tmp/nw.out" 2>&1 & +sleep 2 +if $start_dark; then defaults delete -g AppleInterfaceStyle 2>/dev/null; w="delete"; else defaults write -g AppleInterfaceStyle Dark; w="write Dark"; fi +/System/Library/PrivateFrameworks/SystemAdministration.framework/Resources/activateSettings -u >/dev/null 2>&1 +sleep 2 +printf ' %s + activateSettings -u → %s\n' "$w" "$(look)" +wait +cat "$tmp/nw.out" + +say "C. Control: System Events — expect: appearance moves, notification posted" +swift "$tmp/notewatch.swift" 8 >"$tmp/nw.out" 2>&1 & +sleep 2 +if $start_dark; then target=false; else target=true; fi +out="$(se_dark "$target")" || printf ' osascript refused: %s (no Automation grant? then C proves nothing)\n' "$out" +sleep 1 +printf ' dark mode → %s: %s\n' "$target" "$(look)" +wait +cat "$tmp/nw.out" + +say "Read it: A and B moving nothing only counts if C moved. Record in haus's docs/macos-settings.md." From 9467d43cf2536ba6ef244b673442dc3e80131373 Mon Sep 17 00:00:00 2001 From: Julien Martel Date: Thu, 24 Sep 2026 03:07:13 -0500 Subject: [PATCH 2/2] probes: the FDA refusal re-checked on a 27.0 host Co-Authored-By: Claude Opus 5.5 (1M context) --- script/probes/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/script/probes/README.md b/script/probes/README.md index c196391c..28865c38 100644 --- a/script/probes/README.md +++ b/script/probes/README.md @@ -125,12 +125,17 @@ run the whole thing in a tart VM, never on the Mac someone is using. Every sweep ran in two tart guests side by side, a 26.6.2 control and a 27.0 guest, and the outputs matched line for line apart from the base image's own defaults (27's guest starts on `ABC` with a long language list). The read-only -probes ran on a 27.0 host and agreed with the doc. Three rows a VM cannot -answer, so they still stand on 26 evidence: +probes ran on a 27.0 host and agreed with the doc. + +The FDA refusal needs the host too: cirruslabs guests run with SIP off, so TCC +doesn't gate Full Disk Access there (a launchd agent with no grant read +`TCC.db` and wrote `com.apple.universalaccess` without complaint). On the 27.0 +host, a throwaway launchd agent with no grant wrote an unprotected domain fine +and got `Could not write domain com.apple.universalaccess; exiting`, exit 1, +for a junk key. Unchanged. + +Two rows a VM cannot answer, so they still stand on 26 evidence: -- **The FDA refusal.** cirruslabs guests run with SIP off, so TCC doesn't gate - Full Disk Access: a launchd agent with no grant read `TCC.db` and wrote - `com.apple.universalaccess` without complaint. - **The by-eye rows.** The private `CGSGetCursorScale` read 1.0 after a `mouseDriverCursorSize = 3.0` write and a `universalaccessd` restart on the 26 control too, so it is no oracle in a headless guest.