Fall back to System Settings when the Mail consent prompt is the known beta bug

Confirmed via multiple live rounds with Rune this is a macOS 27 beta issue,
not a Confab bug — matches an already-documented pattern in this project
(Calendar/Contacts requestAccess failing identically). Every failure
returns in single-digit milliseconds, ruled out threading, wrong API,
build/signing, and notarization; even a fresh notarized build fails
identically, while Terminal->Mail (first-party) works with no prompt at
all, and the same-style bug already has a drafted Apple Feedback report
for a different permission category.

Kept the real Apple Event attempt as the primary path (the way this
should work once the OS bug is fixed), but now time it: a failure faster
than any human could plausibly answer a real dialog (default 300ms) is
classified as the platform bug rather than a genuine denial, and the UI
falls back to opening System Settings' Automation pane directly with an
explanatory note, instead of the button silently doing nothing.
This commit is contained in:
2026-08-19 14:55:24 +02:00
parent 56a5d1d56f
commit 1dc485badb
3 changed files with 102 additions and 21 deletions
+25
View File
@@ -188,4 +188,29 @@ struct AppleMailServiceTests {
#expect(AppleMailService.mapAutomationPermissionStatus(-1744) == .notDetermined)
#expect(AppleMailService.mapAutomationPermissionStatus(-9999) == .notDetermined)
}
// MARK: - classifyRequestOutcome
@Test("classifyRequestOutcome maps success to granted regardless of elapsed time")
func classifiesSuccessAsGranted() {
#expect(AppleMailService.classifyRequestOutcome(succeeded: true, elapsedSeconds: 0.001) == .granted)
#expect(AppleMailService.classifyRequestOutcome(succeeded: true, elapsedSeconds: 5.0) == .granted)
}
@Test("classifyRequestOutcome treats an implausibly fast failure as the suspected platform bug")
func classifiesFastFailureAsSuspectedBug() {
#expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: 0.009) == .suspectedPlatformBug)
#expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: 0.0) == .suspectedPlatformBug)
}
@Test("classifyRequestOutcome treats a slower failure as a real denial")
func classifiesSlowFailureAsDenied() {
#expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: 1.5) == .denied)
}
@Test("classifyRequestOutcome threshold is right at the boundary")
func classifiesThresholdBoundary() {
#expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: AppleMailService.suspectedBugThreseholdSeconds) == .denied)
#expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: AppleMailService.suspectedBugThreseholdSeconds - 0.001) == .suspectedPlatformBug)
}
}