feat(privacy-host): add signed native calendar contacts and reminders host

Add the owner-only AF_UNIX Reyna CLI privacy host, strict signed-app installation, and typed native routing for Calendar, Contacts, and Reminders.\n\nAdd bounded system-status paths and config-only direct local-service wrappers. Preserve MacMiniMCP pending explicit cutover approval.\n\nApple Notes is intentionally deferred: no native Notes operations, Apple Events declaration, or Automation helper are included; legacy Notes handling remains untouched.
This commit is contained in:
Adolfo Reyna
2026-08-03 20:27:54 -04:00
parent 6e2117188e
commit 9fd04b0ce4
56 changed files with 14239 additions and 50 deletions
@@ -0,0 +1,348 @@
import XCTest
@testable import ReynaCLIHostCore
// Tests for calendar.request_full_access – TDD, fake providers only
final class CalendarAuthorizationTests: XCTestCase {
// Fake auth providers
struct AlreadyAuthorizedProvider: CalendarAuthorizationProviding {
var requested = false
func authorizationStatus() -> CalendarAuthorizationStatus { .authorized }
func requestFullAccess() throws -> Bool {
XCTFail("requestFullAccess must not be called when already authorized")
return false
}
}
struct NotDeterminedGrantedProvider: CalendarAuthorizationProviding {
var statusCall = 0
func authorizationStatus() -> CalendarAuthorizationStatus { .notDetermined }
func requestFullAccess() throws -> Bool { true }
}
struct NotDeterminedDeniedProvider: CalendarAuthorizationProviding {
func authorizationStatus() -> CalendarAuthorizationStatus { .notDetermined }
func requestFullAccess() throws -> Bool { false }
}
struct DeniedProvider: CalendarAuthorizationProviding {
func authorizationStatus() -> CalendarAuthorizationStatus { .denied }
func requestFullAccess() throws -> Bool { false }
}
struct TimeoutProvider: CalendarAuthorizationProviding {
func authorizationStatus() -> CalendarAuthorizationStatus { .notDetermined }
func requestFullAccess() throws -> Bool {
throw CalendarProviderError.unavailable("calendar authorization timed out")
}
}
struct ErrorProvider: CalendarAuthorizationProviding {
func authorizationStatus() -> CalendarAuthorizationStatus { .notDetermined }
func requestFullAccess() throws -> Bool {
throw CalendarProviderError.unavailable("disk error")
}
}
struct EmptyListProvider: CalendarListProviding {
func listCalendars() throws -> [CalendarListItem] { [] }
}
// already-full permission returns state authorized without asking
func testAlreadyAuthorizedReturnsAuthorizedWithoutRequesting() {
let auth = AlreadyAuthorizedProvider()
let req = Request(id: "a1", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: auth)
XCTAssertTrue(resp.ok)
XCTAssertEqual(resp.result?.status, "authorized")
XCTAssertEqual(resp.result?.operation, "calendar.request_full_access")
XCTAssertEqual(resp.id, "a1")
}
// notDetermined reaches request path
func testNotDeterminedReachesRequestPath() {
final class TrackingProvider: CalendarAuthorizationProviding, @unchecked Sendable {
var didRequest = false
var status: CalendarAuthorizationStatus = .notDetermined
func authorizationStatus() -> CalendarAuthorizationStatus { status }
func requestFullAccess() throws -> Bool {
didRequest = true
return true
}
}
let tracking = TrackingProvider()
let req = Request(id: "a2", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: tracking)
XCTAssertTrue(tracking.didRequest, "requestFullAccess must be called when notDetermined")
XCTAssertTrue(resp.ok)
}
// granted response returns {status:"authorized"}
func testGrantedReturnsAuthorizedResult() {
let auth = NotDeterminedGrantedProvider()
let req = Request(id: "a3", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: auth)
XCTAssertTrue(resp.ok)
XCTAssertEqual(resp.result?.status, "authorized")
XCTAssertEqual(resp.result?.protocol_version, PROTOCOL_VERSION)
XCTAssertNil(resp.error)
XCTAssertNil(resp.result?.calendars, "must not output calendar content")
}
// denied returns structured permission_denied
func testDeniedReturnsPermissionDenied() {
let auth = NotDeterminedDeniedProvider()
let req = Request(id: "a4", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: auth)
XCTAssertFalse(resp.ok)
XCTAssertEqual(resp.error?.code, "permission_denied")
XCTAssertNotNil(resp.error?.message)
XCTAssertNil(resp.result)
}
// denied when already denied also permission_denied
func testAlreadyDeniedPathAlsoDenies() {
let auth = DeniedProvider()
let req = Request(id: "a5", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: auth)
XCTAssertFalse(resp.ok)
XCTAssertEqual(resp.error?.code, "permission_denied")
}
// async timeout/error returns calendar_unavailable
func testTimeoutReturnsCalendarUnavailable() {
let auth = TimeoutProvider()
let req = Request(id: "a6", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: auth)
XCTAssertFalse(resp.ok)
XCTAssertEqual(resp.error?.code, "calendar_unavailable")
XCTAssertTrue(resp.error?.message.lowercased().contains("timed out") ?? false)
}
func testErrorReturnsCalendarUnavailable() {
let auth = ErrorProvider()
let req = Request(id: "a7", operation: "calendar.request_full_access", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: EmptyListProvider(), authProvider: auth)
XCTAssertFalse(resp.ok)
XCTAssertEqual(resp.error?.code, "calendar_unavailable")
}
// event/calendar list cannot call request method (verify list path never requests)
func testCalendarListDoesNotCallAuthRequest() {
final class SpyListProvider: CalendarListProviding, @unchecked Sendable {
var called = false
func listCalendars() throws -> [CalendarListItem] {
called = true
return []
}
}
final class SpyAuthProvider: CalendarAuthorizationProviding, @unchecked Sendable {
var didCallStatus = false
var didCallRequest = false
func authorizationStatus() -> CalendarAuthorizationStatus {
didCallStatus = true
return .authorized
}
func requestFullAccess() throws -> Bool {
didCallRequest = true
return false
}
}
let list = SpyListProvider()
let auth = SpyAuthProvider()
let req = Request(id: "list-1", operation: "calendar.list", arguments: .object([:]))
let resp = dispatch(request: req, calendarProvider: list, authProvider: auth)
XCTAssertTrue(resp.ok)
XCTAssertFalse(auth.didCallRequest, "calendar.list must never call requestFullAccess")
XCTAssertFalse(auth.didCallStatus, "calendar.list must not touch auth provider")
XCTAssertTrue(list.called)
}
// MARK: - Shared holder to satisfy Swift 6 Sendable checks
final class TestBox<T>: @unchecked Sendable {
var value: T
init(_ v: T) { value = v }
}
// MARK: - EventKitMainRunLoopBridge – deterministic pump tests (no real EventKit/TCC)
func testBridgePumpsMainRunLoopAndDeliversMainQueueCallback() throws {
let exp = expectation(description: "bridge completes")
let grantedBox = TestBox(false)
let errorBox = TestBox<Error?>(nil)
DispatchQueue.main.async {
let bridge = EventKitMainRunLoopBridge()
do {
// Deterministic seam: schedule callback onto next main run loop turn via Timer,
// simulating EventKit delivering completion on main run loop.
grantedBox.value = try bridge.requestAccess(timeout: 2) { completion in
// Timer on main run loop – only fires when run loop is pumped
Timer.scheduledTimer(withTimeInterval: 0.02, repeats: false) { _ in
completion(true, nil)
}
}
} catch {
errorBox.value = error
}
exp.fulfill()
}
wait(for: [exp], timeout: 5)
XCTAssertNil(errorBox.value, "bridge must not timeout when it pumps main run loop; got \(String(describing: errorBox.value))")
XCTAssertTrue(grantedBox.value, "granted should be true after main-queue callback is pumped")
}
func testSemaphoreDeadlocksButBridgeDoesNot() throws {
let semExp = expectation(description: "old impl would timeout")
let completedBox = TestBox(false)
DispatchQueue.main.async {
let sem = DispatchSemaphore(value: 0)
Timer.scheduledTimer(withTimeInterval: 0.02, repeats: false) { _ in
completedBox.value = true
sem.signal()
}
let res = sem.wait(timeout: .now() + 0.2)
XCTAssertEqual(res, .timedOut, "Blocking semaphore on main thread must deadlock main-run-loop callback – proving old bug")
XCTAssertFalse(completedBox.value, "Callback must not have run while semaphore blocked main loop")
semExp.fulfill()
}
wait(for: [semExp], timeout: 2)
}
func testBridgeHandlesCompletionExactlyOnce() throws {
let exp = expectation(description: "exactly once")
let resultBox = TestBox(false)
DispatchQueue.main.async {
let bridge = EventKitMainRunLoopBridge()
do {
resultBox.value = try bridge.requestAccess(timeout: 1) { completion in
completion(true, nil)
completion(false, NSError(domain: "should-be-ignored", code: 1))
}
} catch {}
exp.fulfill()
}
wait(for: [exp], timeout: 2)
XCTAssertTrue(resultBox.value, "First completion should win")
}
func testBridgeThreadSafetyForConcurrentCompletion() throws {
let exp = expectation(description: "thread-safe")
let grantedBox = TestBox(false)
let doneBox = TestBox(false)
DispatchQueue.main.async {
let bridge = EventKitMainRunLoopBridge()
do {
grantedBox.value = try bridge.requestAccess(timeout: 1) { completion in
DispatchQueue.global().async { completion(true, nil) }
DispatchQueue.global().async { completion(false, nil) }
}
doneBox.value = true
} catch {}
exp.fulfill()
}
wait(for: [exp], timeout: 2)
XCTAssertTrue(doneBox.value, "bridge must complete even with concurrent completions")
}
func testBridgePropagatesError() throws {
let exp = expectation(description: "error propagation")
let caughtBox = TestBox(false)
DispatchQueue.main.async {
let bridge = EventKitMainRunLoopBridge()
do {
_ = try bridge.requestAccess(timeout: 1) { completion in
completion(false, NSError(domain: "test", code: 2, userInfo: [NSLocalizedDescriptionKey: "fake EK error"]))
}
} catch let err as CalendarProviderError {
if case .unavailable(let msg) = err {
caughtBox.value = msg.contains("fake EK error")
}
} catch {}
exp.fulfill()
}
wait(for: [exp], timeout: 2)
XCTAssertTrue(caughtBox.value, "Error from EK completion must be wrapped as calendar_unavailable")
}
func testBridgeTimeoutReturnsCorrectError() throws {
let exp = expectation(description: "timeout")
let codeBox = TestBox("")
DispatchQueue.main.async {
let bridge = EventKitMainRunLoopBridge()
do {
_ = try bridge.requestAccess(timeout: 0.15) { _ in }
XCTFail("Should have thrown")
} catch let err as CalendarProviderError {
if case .unavailable(let msg) = err {
codeBox.value = msg
}
} catch {}
exp.fulfill()
}
wait(for: [exp], timeout: 2)
XCTAssertTrue(codeBox.value.lowercased().contains("timed out"), "Timeout must produce 'calendar authorization timed out' message, got \(codeBox.value)")
}
// Production code location check: only CalendarAuthorizationProvider.swift calls requestFullAccessToEvents
func testOnlyOneFileCallsRequestFullAccessToEvents() throws {
let fm = FileManager.default
// Walk up to find repo root containing native/ReynaCLIHost/Sources
var cur = URL(fileURLWithPath: fm.currentDirectoryPath)
var dirs: [URL] = []
for _ in 0..<10 {
let cand = cur.appendingPathComponent("native/ReynaCLIHost/Sources/ReynaCLIHost")
if fm.fileExists(atPath: cand.path) {
dirs.append(cand)
}
let candCore = cur.appendingPathComponent("native/ReynaCLIHost/Sources/ReynaCLIHostCore")
if fm.fileExists(atPath: candCore.path) {
dirs.append(candCore)
}
let cand2 = cur.appendingPathComponent("Sources/ReynaCLIHost")
if fm.fileExists(atPath: cand2.path) {
dirs.append(cand2)
}
let candCore2 = cur.appendingPathComponent("Sources/ReynaCLIHostCore")
if fm.fileExists(atPath: candCore2.path) {
dirs.append(candCore2)
}
if !dirs.isEmpty { break }
cur = cur.deletingLastPathComponent()
}
guard !dirs.isEmpty else {
XCTFail("Could not locate Sources/ReynaCLIHost dir")
return
}
var hits: [String] = []
for srcDir in dirs {
let files = (try? fm.contentsOfDirectory(at: srcDir, includingPropertiesForKeys: nil)) ?? []
for file in files where file.pathExtension == "swift" {
guard let content = try? String(contentsOf: file) else { continue }
if content.contains("requestFullAccessToEvents") {
hits.append(file.lastPathComponent)
}
}
}
// Dedupe + sort for stable assertion
let uniqueSorted = Array(Set(hits)).sorted()
XCTAssertEqual(uniqueSorted, ["CalendarAuthorizationProvider.swift"], "requestFullAccessToEvents must only appear in CalendarAuthorizationProvider.swift, found in \(uniqueSorted)")
}
func testNoOutputCalendarContentOnAuthOperations() {
// Both authorized and denied paths must not include calendars
let authOk = NotDeterminedGrantedProvider()
let reqOk = Request(id: "ok", operation: "calendar.request_full_access", arguments: .object([:]))
let respOk = dispatch(request: reqOk, calendarProvider: EmptyListProvider(), authProvider: authOk)
XCTAssertNil(respOk.result?.calendars)
let authDen = NotDeterminedDeniedProvider()
let reqDen = Request(id: "den", operation: "calendar.request_full_access", arguments: .object([:]))
let respDen = dispatch(request: reqDen, calendarProvider: EmptyListProvider(), authProvider: authDen)
// denied has nil result, so no calendars by construction
XCTAssertNil(respDen.result)
}
}