apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

commit 35aedb6b54ff645b663fecff26082b3e91fcb232
parent 368572e266628319a2697b35efd85ee449ab1704
Author: triesap <tyson@radroots.org>
Date:   Tue,  1 Sep 2026 01:44:31 +0000

apple-kit: close public error vocabulary

- replace arbitrary diagnostic payloads with stable error classifications
- bind background-transfer failures to a closed persisted vocabulary
- map dependency and filesystem failures at public adapter boundaries
- verify whole-chain redaction and refresh the exact API baseline

Diffstat:
MSources/RadrootsKit/RadrootsAppLocalStateReset.swift | 27++++++++++++++++++++++-----
MSources/RadrootsKit/RadrootsAppleBackgroundTasks.swift | 56+++++++++++++++++++++++++++++++++++++++++---------------
MSources/RadrootsKit/RadrootsAppleBackgroundTransfer.swift | 193++++++++++++++++++++++++++++++++++++-------------------------------------------
MSources/RadrootsKit/RadrootsAppleDocumentScanner.swift | 33++++++++++++++++++++++-----------
MSources/RadrootsKit/RadrootsAppleExternalActions.swift | 16+++++++---------
MSources/RadrootsKit/RadrootsAppleFileError.swift | 25++++++++++---------------
MSources/RadrootsKit/RadrootsAppleKeychainSecureStore.swift | 62++++++++++++++++++++++++++++++++++++--------------------------
MSources/RadrootsKit/RadrootsAppleLocationServices.swift | 103+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
MSources/RadrootsKit/RadrootsAppleMediaPicker.swift | 157+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
MSources/RadrootsKit/RadrootsAppleMediaPreparation.swift | 123+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
MSources/RadrootsKit/RadrootsAppleMobileStore.swift | 11+++++++++++
MSources/RadrootsKit/RadrootsAppleSecurityError.swift | 40++++++++++++++++------------------------
MSources/RadrootsKit/RadrootsAppleUserPresence.swift | 100++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
MSources/RadrootsKit/RadrootsBackgroundTasks.swift | 57+++++++++++++++++++++++++++------------------------------
MSources/RadrootsKit/RadrootsBackgroundTransfer.swift | 247+++++++++++++++++++++++++++++++------------------------------------------------
MSources/RadrootsKit/RadrootsCaptureIntake.swift | 112+++++++++++++++++++++++++++++++++++++++++++------------------------------------
MSources/RadrootsKit/RadrootsDocumentInterchange.swift | 139++++++++++++++++++++++++++++++++++++++++++-------------------------------------
MSources/RadrootsKit/RadrootsDocumentPresentation.swift | 59++++++++++++++++++++++++++++++++++-------------------------
MSources/RadrootsKit/RadrootsExternalActions.swift | 58++++++++++++++++++++++++++++------------------------------
MSources/RadrootsKit/RadrootsFileAccess.swift | 227++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
MSources/RadrootsKit/RadrootsPermissionLocation.swift | 75++++++++++++++++++++++++++++++++++++++-------------------------------------
MSources/RadrootsKit/RadrootsSecureStore.swift | 10+++++-----
MSources/RadrootsKit/RadrootsTelemetry.swift | 57+++++++++++++++++++++++++++------------------------------
MSources/RadrootsKit/RadrootsUserPresence.swift | 37+++++++++++++++----------------------
MSources/RadrootsKit/RadrootsVerifiedArtifactAccess.swift | 12++++++++++++
MSources/RadrootsKitTesting/RadrootsBackgroundTransferTesting.swift | 32++++++++++++++------------------
MTests/RadrootsKitTestingTests/RadrootsBackgroundTaskTestingTests.swift | 4++--
MTests/RadrootsKitTestingTests/RadrootsBackgroundTransferTestingTests.swift | 15+++++++++------
MTests/RadrootsKitTestingTests/RadrootsCaptureIntakeTestingTests.swift | 12++++++------
MTests/RadrootsKitTestingTests/RadrootsExternalActionsTestingTests.swift | 4++--
MTests/RadrootsKitTestingTests/RadrootsKitTestingTests.swift | 10++++++----
MTests/RadrootsKitTestingTests/RadrootsUserPresenceTestingTests.swift | 7++++---
MTests/RadrootsKitTests/RadrootsAppleBackgroundTaskSchedulerTests.swift | 14++++++++------
MTests/RadrootsKitTests/RadrootsAppleBackgroundTransferTests.swift | 53++++++++++++++++++++++++++---------------------------
MTests/RadrootsKitTests/RadrootsAppleDocumentScannerTests.swift | 5+++--
ATests/RadrootsKitTests/RadrootsAppleErrorSafetyTests.swift | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTests/RadrootsKitTests/RadrootsAppleExternalActionsTests.swift | 7++++---
MTests/RadrootsKitTests/RadrootsAppleLocationServicesTests.swift | 48++++++++++++++++++++++++++++--------------------
MTests/RadrootsKitTests/RadrootsAppleMediaPickerTests.swift | 11++++++-----
MTests/RadrootsKitTests/RadrootsAppleMediaPreparationTests.swift | 44+++++++++++++++++++++++++++++---------------
MTests/RadrootsKitTests/RadrootsAppleUserPresenceTests.swift | 17+++++++++--------
MTests/RadrootsKitTests/RadrootsBackgroundTaskTests.swift | 25+++++++++++++------------
MTests/RadrootsKitTests/RadrootsBackgroundTransferTests.swift | 88+++++++++++++++++++++++++++++++++++--------------------------------------------
MTests/RadrootsKitTests/RadrootsCaptureIntakeTests.swift | 17+++++++++--------
MTests/RadrootsKitTests/RadrootsExternalActionsTests.swift | 19++++++++++---------
MTests/RadrootsKitTests/RadrootsSecureStoreTests.swift | 15++++++++++-----
MTests/RadrootsKitTests/RadrootsTelemetryTests.swift | 13+++++++------
MTests/RadrootsKitTests/RadrootsUserPresenceTests.swift | 4++--
Mcontracts/api_baselines/apple_kit.txt | 322+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mtools/verify-boundaries.sh | 9+++++++++
50 files changed, 1828 insertions(+), 1259 deletions(-)

diff --git a/Sources/RadrootsKit/RadrootsAppLocalStateReset.swift b/Sources/RadrootsKit/RadrootsAppLocalStateReset.swift @@ -25,8 +25,9 @@ public enum RadrootsAppLocalStateReset { ) throws { let trimmed = appIdentifier.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsAppLocalStateResetError.invalidRequest("app identifier cannot be empty") + throw RadrootsAppLocalStateResetError.invalidRequest } + do { let root = try fileManager.url( for: .applicationSupportDirectory, in: .userDomainMask, @@ -36,12 +37,17 @@ public enum RadrootsAppLocalStateReset { if fileManager.fileExists(atPath: root.path) { try fileManager.removeItem(at: root) } + } catch let error as RadrootsAppLocalStateResetError { + throw error + } catch { + throw RadrootsAppLocalStateResetError.fileSystemFailure + } } public static func clearKeychainService(_ serviceName: String) throws { let trimmed = serviceName.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsAppLocalStateResetError.invalidRequest("keychain service name cannot be empty") + throw RadrootsAppLocalStateResetError.invalidRequest } let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, @@ -49,12 +55,23 @@ public enum RadrootsAppLocalStateReset { ] let status = SecItemDelete(query as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { - throw RadrootsAppLocalStateResetError.keychainStatus(status, "keychain service reset failed") + throw RadrootsAppLocalStateResetError.keychainFailure } } } public enum RadrootsAppLocalStateResetError: Error, Equatable, Sendable { - case invalidRequest(String) - case keychainStatus(Int32, String) + case invalidRequest + case fileSystemFailure + case keychainFailure +} + +extension RadrootsAppLocalStateResetError: LocalizedError { + public var errorDescription: String? { + switch self { + case .invalidRequest: "The local-state reset request is invalid." + case .fileSystemFailure: "The local application state could not be reset." + case .keychainFailure: "The secure local state could not be reset." + } + } } diff --git a/Sources/RadrootsKit/RadrootsAppleBackgroundTasks.swift b/Sources/RadrootsKit/RadrootsAppleBackgroundTasks.swift @@ -86,15 +86,15 @@ public struct RadrootsAppleBackgroundTaskSchedulerAdapters: Sendable { public static let unavailable = Self( register: { _ in - throw RadrootsBackgroundTaskError.unavailable("background task scheduling is unavailable on this platform") + throw RadrootsBackgroundTaskError.unavailable }, submit: { _ in - throw RadrootsBackgroundTaskError.unavailable("background task scheduling is unavailable on this platform") + throw RadrootsBackgroundTaskError.unavailable }, cancel: { _ in }, cancelAll: {}, pendingTasks: { - throw RadrootsBackgroundTaskError.unavailable("background task scheduling is unavailable on this platform") + throw RadrootsBackgroundTaskError.unavailable } ) } @@ -108,17 +108,30 @@ public final class RadrootsAppleBackgroundTaskScheduler: RadrootsBackgroundTaskS @discardableResult public func register(_ registration: RadrootsAppleBackgroundTaskRegistration) async throws -> Bool { - let registered = try await adapters.register(registration) + let registered: Bool + do { + registered = try await adapters.register(registration) + } catch let error as RadrootsBackgroundTaskError { + throw error + } catch { + throw RadrootsBackgroundTaskError.schedulerFailure + } guard registered else { - throw RadrootsBackgroundTaskError.schedulerFailure( - "background task registration was rejected" - ) + throw RadrootsBackgroundTaskError.schedulerFailure } return registered } - public func submit(_ request: RadrootsBackgroundTaskRequest) async throws -> RadrootsBackgroundTaskSnapshot { + public func submit(_ request: RadrootsBackgroundTaskRequest) async throws + -> RadrootsBackgroundTaskSnapshot + { + do { try await adapters.submit(request) + } catch let error as RadrootsBackgroundTaskError { + throw error + } catch { + throw RadrootsBackgroundTaskError.schedulerFailure + } return try RadrootsBackgroundTaskSnapshot( request: request, submittedAt: adapters.now() @@ -134,13 +147,21 @@ public final class RadrootsAppleBackgroundTaskScheduler: RadrootsBackgroundTaskS } public func pendingTasks() async throws -> [RadrootsBackgroundTaskSnapshot] { - try await adapters.pendingTasks() + do { + return try await adapters.pendingTasks() + } catch let error as RadrootsBackgroundTaskError { + throw error + } catch { + throw RadrootsBackgroundTaskError.schedulerFailure + } } } #if canImport(BackgroundTasks) && os(iOS) - private extension RadrootsAppleBackgroundTaskSchedulerAdapters { - static func platformRequest(for request: RadrootsBackgroundTaskRequest) -> BGTaskRequest { + extension RadrootsAppleBackgroundTaskSchedulerAdapters { + fileprivate static func platformRequest(for request: RadrootsBackgroundTaskRequest) + -> BGTaskRequest + { let platformRequest: BGTaskRequest switch request.kind { case .appRefresh: @@ -155,12 +176,17 @@ public final class RadrootsAppleBackgroundTaskScheduler: RadrootsBackgroundTaskS return platformRequest } - static func pendingPlatformTaskSnapshots() async throws -> [RadrootsBackgroundTaskSnapshot] { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[RadrootsBackgroundTaskSnapshot], Error>) in + fileprivate static func pendingPlatformTaskSnapshots() async throws + -> [RadrootsBackgroundTaskSnapshot] + { + try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation<[RadrootsBackgroundTaskSnapshot], Error>) in BGTaskScheduler.shared.getPendingTaskRequests { requests in do { - let snapshots: [RadrootsBackgroundTaskSnapshot] = try requests.compactMap { request -> RadrootsBackgroundTaskSnapshot? in - guard let identifier = try? RadrootsBackgroundTaskIdentifier(request.identifier) else { + let snapshots: [RadrootsBackgroundTaskSnapshot] = try requests.compactMap { + request -> RadrootsBackgroundTaskSnapshot? in + guard let identifier = try? RadrootsBackgroundTaskIdentifier(request.identifier) + else { return nil } let kind: RadrootsBackgroundTaskKind diff --git a/Sources/RadrootsKit/RadrootsAppleBackgroundTransfer.swift b/Sources/RadrootsKit/RadrootsAppleBackgroundTransfer.swift @@ -4,10 +4,8 @@ public struct RadrootsAppleBackgroundTransferAdapters: Sendable { public let now: @Sendable () -> Date public let enqueue: @Sendable (RadrootsBackgroundTransferRequest) async throws -> Void public let cancel: @Sendable (RadrootsBackgroundTransferIdentifier) async throws -> Void - public let activeTransferIdentifiers: - @Sendable () async throws -> Set<RadrootsBackgroundTransferIdentifier> - public let handleBackgroundEvents: - @Sendable (String, @escaping @Sendable () -> Void) async -> Void + public let activeTransferIdentifiers: @Sendable () async throws -> Set<RadrootsBackgroundTransferIdentifier> + public let handleBackgroundEvents: @Sendable (String, @escaping @Sendable () -> Void) async -> Void public init( now: @escaping @Sendable () -> Date = Date.init, @@ -27,16 +25,13 @@ public struct RadrootsAppleBackgroundTransferAdapters: Sendable { public static let unavailable = Self( enqueue: { _ in - throw RadrootsBackgroundTransferError.unavailable( - "background transfer is unavailable on this platform") + throw RadrootsBackgroundTransferError.unavailable }, cancel: { _ in - throw RadrootsBackgroundTransferError.unavailable( - "background transfer is unavailable on this platform") + throw RadrootsBackgroundTransferError.unavailable }, activeTransferIdentifiers: { - throw RadrootsBackgroundTransferError.unavailable( - "background transfer is unavailable on this platform") + throw RadrootsBackgroundTransferError.unavailable }, handleBackgroundEvents: { _, completionHandler in completionHandler() } ) @@ -128,11 +123,10 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { -> RadrootsBackgroundTransferHandle { guard - try await store.loadSnapshots().contains(where: { $0.identifier == request.identifier }) + try await loadStoredSnapshots().contains(where: { $0.identifier == request.identifier }) == false else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier already exists") + throw RadrootsBackgroundTransferError.invalidRequest } return try await schedule(request) } @@ -141,23 +135,23 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { -> RadrootsBackgroundTransferHandle { guard - let existing = try await store.loadSnapshots().first(where: { + let existing = try await loadStoredSnapshots().first(where: { $0.identifier == request.identifier }), existing.state == .failed || existing.state == .interrupted || existing.state == .cancelled || existing.state == .expired, try existing.request.redactedForPersistence() == request.redactedForPersistence() else { - throw RadrootsBackgroundTransferError.invalidRequest("background transfer is not retryable") + throw RadrootsBackgroundTransferError.invalidRequest } do { guard try await !adapters.activeTransferIdentifiers().contains(request.identifier) else { - throw RadrootsBackgroundTransferError.invalidRequest("background transfer is still active") + throw RadrootsBackgroundTransferError.invalidRequest } } catch let error as RadrootsBackgroundTransferError { throw error } catch { - throw RadrootsBackgroundTransferError.transferFailure("background transfer recovery failed") + throw RadrootsBackgroundTransferError.transferFailure } return try await schedule(request) } @@ -165,26 +159,25 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { private func schedule(_ request: RadrootsBackgroundTransferRequest) async throws -> RadrootsBackgroundTransferHandle { - try await store.saveSnapshot( + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( request: request, state: .queued, updatedAt: adapters.now())) do { try await adapters.enqueue(request) } catch { do { - try await store.saveSnapshot( + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( - request: request, state: .failed, errorMessage: "background_transfer_enqueue_failed", + request: request, state: .failed, failure: .enqueueFailed, updatedAt: adapters.now() ) ) } catch { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer enqueue failure could not be persisted") + throw RadrootsBackgroundTransferError.persistenceFailure } - throw RadrootsBackgroundTransferError.transferFailure("background transfer enqueue failed") + throw RadrootsBackgroundTransferError.transferFailure } - let current = try await store.loadSnapshots().first { $0.identifier == request.identifier } + let current = try await loadStoredSnapshots().first { $0.identifier == request.identifier } if current?.state == .queued { - try await store.saveSnapshot( + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( request: request, state: .running, updatedAt: adapters.now()) ) @@ -193,12 +186,11 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { } public func cancel(_ identifier: RadrootsBackgroundTransferIdentifier) async throws { - if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) - { + if let existing = try await loadStoredSnapshots().first(where: { $0.identifier == identifier }) { guard existing.state == .queued || existing.state == .running || existing.state == .interrupted else { return } - try await store.saveSnapshot( + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( request: existing.request, state: .cancelled, progress: existing.progress, possibleRemoteOrphan: existing.request.isUpload && existing.state == .running, @@ -207,29 +199,26 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { ) } do { try await adapters.cancel(identifier) } catch { - throw RadrootsBackgroundTransferError.transferFailure( - "background transfer cancellation failed") + throw RadrootsBackgroundTransferError.transferFailure } } public func expire(_ identifier: RadrootsBackgroundTransferIdentifier) async throws { - if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) - { + if let existing = try await loadStoredSnapshots().first(where: { $0.identifier == identifier }) { guard existing.state == .queued || existing.state == .running || existing.state == .interrupted else { return } - try await store.saveSnapshot( + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( request: existing.request, state: .expired, progress: existing.progress, - errorMessage: "background_transfer_expired", + failure: .expired, possibleRemoteOrphan: existing.request.isUpload && existing.state == .running, updatedAt: adapters.now() ) ) } do { try await adapters.cancel(identifier) } catch { - throw RadrootsBackgroundTransferError.transferFailure( - "background transfer expiration cancellation failed") + throw RadrootsBackgroundTransferError.transferFailure } } @@ -238,31 +227,25 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { verification: RadrootsBackgroundTransferVerification ) async throws { guard - let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }), + let existing = try await loadStoredSnapshots().first(where: { $0.identifier == identifier }), existing.state == .awaitingVerification else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer is not awaiting verification") + throw RadrootsBackgroundTransferError.invalidRequest } switch verification { case .accepted: - try await store.saveSnapshot( + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( request: existing.request, state: .completed, progress: existing.progress, response: existing.response, downloadedArtifact: existing.downloadedArtifact, updatedAt: adapters.now() ) ) - case .rejected(let code): - guard code.range(of: "^background_transfer_[a-z0-9_]+$", options: .regularExpression) != nil - else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer verification code is invalid") - } - try await store.saveSnapshot( + case .rejected(let failure): + try await saveStoredSnapshot( RadrootsBackgroundTransferSnapshot( request: existing.request, state: .failed, progress: existing.progress, - errorMessage: code, + failure: failure, possibleRemoteOrphan: existing.request.isUpload, updatedAt: adapters.now() ) ) @@ -278,14 +261,13 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { public func snapshots() async throws -> [RadrootsBackgroundTransferSnapshot] { let activeIdentifiers: Set<RadrootsBackgroundTransferIdentifier> do { activeIdentifiers = try await adapters.activeTransferIdentifiers() } catch { - throw RadrootsBackgroundTransferError.transferFailure("background transfer recovery failed") + throw RadrootsBackgroundTransferError.transferFailure } - let storedSnapshots = try await store.loadSnapshots() + let storedSnapshots = try await loadStoredSnapshots() let storedIdentifiers = Set(storedSnapshots.map(\.identifier)) for orphanedIdentifier in activeIdentifiers.subtracting(storedIdentifiers) { do { try await adapters.cancel(orphanedIdentifier) } catch { - throw RadrootsBackgroundTransferError.transferFailure( - "background transfer orphan cancellation failed") + throw RadrootsBackgroundTransferError.transferFailure } } var reconciled: [RadrootsBackgroundTransferSnapshot] = [] @@ -295,21 +277,21 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { { let runningSnapshot = try RadrootsBackgroundTransferSnapshot( request: snapshot.request, state: .running, progress: snapshot.progress, - errorMessage: snapshot.errorMessage, + failure: snapshot.failure, updatedAt: adapters.now() ) - try await store.saveSnapshot(runningSnapshot) + try await saveStoredSnapshot(runningSnapshot) reconciled.append(runningSnapshot) } else if !activeIdentifiers.contains(snapshot.identifier), snapshot.state == .queued || snapshot.state == .running { let interruptedSnapshot = try RadrootsBackgroundTransferSnapshot( request: snapshot.request, state: .interrupted, progress: snapshot.progress, - errorMessage: "background_transfer_interrupted", + failure: .interrupted, possibleRemoteOrphan: snapshot.request.isUpload && snapshot.state == .running, updatedAt: adapters.now() ) - try await store.saveSnapshot(interruptedSnapshot) + try await saveStoredSnapshot(interruptedSnapshot) reconciled.append(interruptedSnapshot) } else { reconciled.append(snapshot) @@ -323,6 +305,26 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer { ) async { await adapters.handleBackgroundEvents(identifier, completionHandler) } + + private func loadStoredSnapshots() async throws -> [RadrootsBackgroundTransferSnapshot] { + do { + return try await store.loadSnapshots() + } catch let error as RadrootsBackgroundTransferError { + throw error + } catch { + throw RadrootsBackgroundTransferError.persistenceFailure + } + } + + private func saveStoredSnapshot(_ snapshot: RadrootsBackgroundTransferSnapshot) async throws { + do { + try await store.saveSnapshot(snapshot) + } catch let error as RadrootsBackgroundTransferError { + throw error + } catch { + throw RadrootsBackgroundTransferError.persistenceFailure + } + } } enum RadrootsStagedBackgroundDownloadResult: Sendable, Equatable { @@ -391,7 +393,7 @@ actor RadrootsAppleBackgroundTransferCoordinator { try? await store.saveSnapshot( try RadrootsBackgroundTransferSnapshot( request: existing.request, state: .running, progress: progress, - errorMessage: existing.errorMessage, + failure: existing.failure, response: existing.response, possibleRemoteOrphan: existing.possibleRemoteOrphan, updatedAt: now() ) @@ -414,7 +416,7 @@ actor RadrootsAppleBackgroundTransferCoordinator { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( existing: existing, - code: "background_transfer_transfer_too_large", + code: .transferTooLarge, possibleRemoteOrphan: existing.request.isUpload && bytesTransferred > 0 ) return @@ -422,21 +424,21 @@ actor RadrootsAppleBackgroundTransferCoordinator { if httpResult.bodyExceeded { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( - existing: existing, code: "background_transfer_response_too_large", + existing: existing, code: .responseTooLarge, possibleRemoteOrphan: existing.request.isUpload) return } if httpResult.mediaTypeWasMalformed { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( - existing: existing, code: "background_transfer_response_media_type", + existing: existing, code: .responseMediaType, possibleRemoteOrphan: existing.request.isUpload && bytesTransferred > 0) return } if platformError != nil { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( - existing: existing, code: "background_transfer_platform_failure", + existing: existing, code: .platformFailure, possibleRemoteOrphan: existing.request.isUpload && bytesTransferred > 0 ) return @@ -444,7 +446,7 @@ actor RadrootsAppleBackgroundTransferCoordinator { guard let statusCode = httpResult.statusCode else { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( - existing: existing, code: "background_transfer_response_missing", + existing: existing, code: .responseMissing, possibleRemoteOrphan: existing.request.isUpload && bytesTransferred > 0 ) return @@ -453,7 +455,7 @@ actor RadrootsAppleBackgroundTransferCoordinator { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( existing: existing, - code: "background_transfer_http_status_\(statusCode)", + code: .httpStatus, possibleRemoteOrphan: existing.request.isUpload && bytesTransferred > 0 ) return @@ -461,18 +463,16 @@ actor RadrootsAppleBackgroundTransferCoordinator { let response: RadrootsBackgroundTransferResponse do { response = try Self.validatedResponse(for: existing.request, httpResult: httpResult) - } catch let transferError - as RadrootsBackgroundTransferError - { + } catch let validationError as RadrootsBackgroundResponseValidationError { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( - existing: existing, code: transferError.stableCode, + existing: existing, code: validationError.failure, possibleRemoteOrphan: existing.request.isUpload) return } catch { Self.removeStagedDownload(stagedDownloadResult, fileManager: fileManager) await fail( - existing: existing, code: "background_transfer_response_invalid", + existing: existing, code: .responseInvalid, possibleRemoteOrphan: existing.request.isUpload) return } @@ -491,8 +491,7 @@ actor RadrootsAppleBackgroundTransferCoordinator { } } - func handleBackgroundEvents(identifier: String, completionHandler: @escaping @Sendable () -> Void) - { + func handleBackgroundEvents(identifier: String, completionHandler: @escaping @Sendable () -> Void) { guard identifier == sessionIdentifier else { completionHandler() return @@ -547,19 +546,17 @@ actor RadrootsAppleBackgroundTransferCoordinator { bytesTransferred: Int64, totalBytesExpected: Int64? ) async { guard case .file(let stagedFileURL) = stagedDownloadResult else { - await fail(existing: existing, code: "background_transfer_download_staging_failure") + await fail(existing: existing, code: .downloadStagingFailure) return } do { guard case .file(let destinationReference) = destination else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer download destination is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } let destinationURL = try fileResolver.resolve(destination) let fileSize = try Self.fileSize(at: stagedFileURL, fileManager: fileManager) guard fileSize > 0, UInt64(fileSize) <= existing.request.maximumTransferBytes else { - throw RadrootsBackgroundTransferError.transferFailure( - "background_transfer_transfer_too_large") + throw RadrootsBackgroundTransferError.transferFailure } let downloadedArtifact = try RadrootsBackgroundDownloadedArtifact( file: destinationReference, @@ -591,16 +588,18 @@ actor RadrootsAppleBackgroundTransferCoordinator { ) } catch { Self.removeStagedDownload(.file(stagedFileURL), fileManager: fileManager) - await fail(existing: existing, code: "background_transfer_destination_failure") + await fail(existing: existing, code: .destinationFailure) } } private func fail( - existing: RadrootsBackgroundTransferSnapshot, code: String, possibleRemoteOrphan: Bool = false + existing: RadrootsBackgroundTransferSnapshot, + code: RadrootsBackgroundTransferFailure, + possibleRemoteOrphan: Bool = false ) async { try? await store.saveSnapshot( try RadrootsBackgroundTransferSnapshot( - request: existing.request, state: .failed, progress: existing.progress, errorMessage: code, + request: existing.request, state: .failed, progress: existing.progress, failure: code, possibleRemoteOrphan: possibleRemoteOrphan, updatedAt: now() ) ) @@ -664,28 +663,25 @@ actor RadrootsAppleBackgroundTransferCoordinator { -> RadrootsBackgroundTransferResponse { guard let statusCode = httpResult.statusCode else { - throw RadrootsBackgroundTransferError.transferFailure("background_transfer_response_missing") + throw RadrootsBackgroundResponseValidationError(failure: .responseMissing) } if request.responsePolicy == .discard { return try RadrootsBackgroundTransferResponse( statusCode: statusCode, mediaType: nil, contentEncoding: nil, body: nil) } guard let body = httpResult.body else { - throw RadrootsBackgroundTransferError.transferFailure("background_transfer_response_missing") + throw RadrootsBackgroundResponseValidationError(failure: .responseMissing) } guard let mediaType = httpResult.mediaType, request.responsePolicy.acceptedMediaTypes.contains(mediaType) else { - throw RadrootsBackgroundTransferError.transferFailure( - "background_transfer_response_media_type") + throw RadrootsBackgroundResponseValidationError(failure: .responseMediaType) } guard body.count <= request.responsePolicy.maximumBodyBytes else { - throw RadrootsBackgroundTransferError.transferFailure( - "background_transfer_response_too_large") + throw RadrootsBackgroundResponseValidationError(failure: .responseTooLarge) } guard httpResult.contentEncoding == nil || httpResult.contentEncoding == "identity" else { - throw RadrootsBackgroundTransferError.transferFailure( - "background_transfer_response_content_encoding") + throw RadrootsBackgroundResponseValidationError(failure: .responseContentEncoding) } return try RadrootsBackgroundTransferResponse( statusCode: statusCode, mediaType: mediaType, @@ -790,8 +786,7 @@ struct RadrootsBackgroundURLTaskDescriptor: Sendable, Equatable { if let expectedDigest = request.expectedSourceSHA256, RadrootsAppleFileDigest.sha256(sourceData) != expectedDigest { - throw RadrootsBackgroundTransferError.invalidRequest( - "background upload source does not match its digest") + throw RadrootsBackgroundTransferError.invalidRequest } task = session.uploadTask(with: urlRequest, fromFile: sourceURL) } @@ -911,8 +906,7 @@ struct RadrootsBackgroundURLTaskDescriptor: Sendable, Equatable { fileSize >= 0, UInt64(fileSize) <= descriptor.maximumTransferBytes else { - throw RadrootsBackgroundTransferError.transferFailure( - "background_transfer_transfer_too_large") + throw RadrootsBackgroundTransferError.transferFailure } try fileManager.createDirectory(at: downloadStagingRoot, withIntermediateDirectories: true) let destination = downloadStagingRoot.appendingPathComponent( @@ -1024,8 +1018,7 @@ struct RadrootsBackgroundURLTaskDescriptor: Sendable, Equatable { stagedDownloadResultsByTaskIdentifier[taskIdentifier] = result } - private func takeDownloadResult(taskIdentifier: Int) -> RadrootsStagedBackgroundDownloadResult? - { + private func takeDownloadResult(taskIdentifier: Int) -> RadrootsStagedBackgroundDownloadResult? { lock.lock() defer { lock.unlock() } return stagedDownloadResultsByTaskIdentifier.removeValue(forKey: taskIdentifier) @@ -1113,18 +1106,6 @@ extension RadrootsBackgroundTransferRequest { } } -extension RadrootsBackgroundTransferError { - fileprivate var stableCode: String { - let value: String = - switch self { - case .invalidRequest(let message), .unavailable(let message), .transferFailure(let message), - .persistenceFailure(let message): - message - } - guard value.range(of: "^background_transfer_[a-z0-9_]+$", options: .regularExpression) != nil - else { - return "background_transfer_response_invalid" - } - return value - } +private struct RadrootsBackgroundResponseValidationError: Error { + let failure: RadrootsBackgroundTransferFailure } diff --git a/Sources/RadrootsKit/RadrootsAppleDocumentScanner.swift b/Sources/RadrootsKit/RadrootsAppleDocumentScanner.swift @@ -62,15 +62,22 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche #endif } - public func scanDocument(_ request: RadrootsDocumentScanRequest) async throws -> RadrootsScannedDocument { + public func scanDocument(_ request: RadrootsDocumentScanRequest) async throws + -> RadrootsScannedDocument + { #if canImport(UIKit) && canImport(VisionKit) let support = try await currentSupport() guard support.interactiveScanAvailable else { - throw RadrootsCaptureIntakeError.unavailable("document scanner is unavailable") + throw RadrootsCaptureIntakeError.unavailable } - let presenter = try await MainActor.run { + let presenter: UIViewController + do { + presenter = try await MainActor.run { try viewControllerProvider() } + } catch { + throw RadrootsAppleMediaPicker.adapt(error: error) + } let writer = RadrootsAppleDocumentScanWriter(fileAccess: fileAccess) let coordinatorID = UUID() return try await RadrootsAppleCaptureAsyncSupport.awaitMainActorCallback( @@ -92,7 +99,7 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche presenter.present(controller, animated: true) } #else - throw RadrootsCaptureIntakeError.unavailable("document scanner is unavailable") + throw RadrootsCaptureIntakeError.unavailable #endif } @@ -107,7 +114,9 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche #if canImport(UIKit) && canImport(VisionKit) @MainActor - private final class RadrootsAppleDocumentScannerCoordinator: NSObject, @preconcurrency VNDocumentCameraViewControllerDelegate { + private final class RadrootsAppleDocumentScannerCoordinator: NSObject, + @preconcurrency VNDocumentCameraViewControllerDelegate + { var completion: (@Sendable (Result<RadrootsScannedDocument, RadrootsCaptureIntakeError>) -> Void)? private let writer: RadrootsAppleDocumentScanWriter @@ -128,7 +137,7 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) { controller.dismiss(animated: true) - finish(.failure(.userCancelled("document scan was cancelled"))) + finish(.failure(.userCancelled)) } func documentCameraViewController( @@ -146,7 +155,9 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche controller.dismiss(animated: true) do { let rendered = try Self.renderPDF(scan) - try finish(.success(writer.persistPDF( + try finish( + .success( + writer.persistPDF( data: rendered.data, pageCount: rendered.pageCount, destinationScope: request.destinationScope @@ -159,10 +170,10 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche private static func renderPDF(_ scan: VNDocumentCameraScan) throws -> (data: Data, pageCount: UInt16) { let pageCount = scan.pageCount guard pageCount > 0 else { - throw RadrootsCaptureIntakeError.invalidRequest("document scanner requires at least one page") + throw RadrootsCaptureIntakeError.invalidRequest } guard pageCount <= Int(UInt16.max) else { - throw RadrootsCaptureIntakeError.invalidRequest("document scanner page count exceeds supported range") + throw RadrootsCaptureIntakeError.invalidRequest } let images = (0 ..< pageCount).map { scan.imageOfPage(at: $0) } let bounds = pageBounds(images: images) @@ -174,7 +185,7 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche } } guard !data.isEmpty else { - throw RadrootsCaptureIntakeError.transientFailure("document scanner failed to render a pdf") + throw RadrootsCaptureIntakeError.transientFailure } return (data, UInt16(pageCount)) } @@ -206,7 +217,7 @@ public final class RadrootsAppleDocumentScanner: RadrootsDocumentScanner, @unche func cancelPresentation(_ controller: VNDocumentCameraViewController) { guard !didResolve else { return } controller.dismiss(animated: true) - finish(.failure(.transientFailure("document scanner presentation was cancelled"))) + finish(.failure(.transientFailure)) } private func finish(_ result: Result<RadrootsScannedDocument, RadrootsCaptureIntakeError>) { diff --git a/Sources/RadrootsKit/RadrootsAppleExternalActions.swift b/Sources/RadrootsKit/RadrootsAppleExternalActions.swift @@ -79,7 +79,9 @@ public final class RadrootsAppleExternalActions: RadrootsExternalActions, Sendab self.adapters = adapters } - public func canOpen(_ destination: RadrootsExternalActionDestination) async -> RadrootsExternalActionCapability { + public func canOpen(_ destination: RadrootsExternalActionDestination) async + -> RadrootsExternalActionCapability + { let url = await resolvedURL(for: destination) guard let url else { return RadrootsExternalActionCapability(destination: destination, canOpen: false) @@ -93,15 +95,11 @@ public final class RadrootsAppleExternalActions: RadrootsExternalActions, Sendab public func open(_ request: RadrootsExternalActionRequest) async throws { guard let url = await resolvedURL(for: request.destination) else { - throw RadrootsExternalActionError.unavailable( - "\(request.destination.kind.rawValue) external action is unavailable" - ) + throw RadrootsExternalActionError.unavailable } let success = await adapters.openURL(url) guard success else { - throw RadrootsExternalActionError.transientFailure( - "failed to open \(request.destination.kind.rawValue) external action" - ) + throw RadrootsExternalActionError.transientFailure } } @@ -116,9 +114,9 @@ public final class RadrootsAppleExternalActions: RadrootsExternalActions, Sendab } #if canImport(UIKit) - private extension RadrootsAppleExternalActionsAdapters { + extension RadrootsAppleExternalActionsAdapters { @MainActor - static func openUIKitURL(_ url: URL) async -> Bool { + fileprivate static func openUIKitURL(_ url: URL) async -> Bool { await withCheckedContinuation { continuation in UIApplication.shared.open(url, options: [:]) { success in continuation.resume(returning: success) diff --git a/Sources/RadrootsKit/RadrootsAppleFileError.swift b/Sources/RadrootsKit/RadrootsAppleFileError.swift @@ -1,26 +1,21 @@ import Foundation public enum RadrootsAppleFileError: Error, Equatable, Sendable { - case invalidRequest(String) - case notFound(String) - case permissionDenied(String) - case transientFailure(String) - case permanentFailure(String) + case invalidRequest + case notFound + case permissionDenied + case transientFailure + case permanentFailure } extension RadrootsAppleFileError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .notFound(message): - message - case let .permissionDenied(message): - message - case let .transientFailure(message): - message - case let .permanentFailure(message): - message + case .invalidRequest: "The file request is invalid." + case .notFound: "The file was not found." + case .permissionDenied: "File access was denied." + case .transientFailure: "The file operation could not be completed temporarily." + case .permanentFailure: "The file operation could not be completed." } } } diff --git a/Sources/RadrootsKit/RadrootsAppleKeychainSecureStore.swift b/Sources/RadrootsKit/RadrootsAppleKeychainSecureStore.swift @@ -34,12 +34,13 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche case errSecDuplicateItem: break default: - throw Self.mapStatus(addStatus, defaultMessage: "keychain write failed") + throw Self.mapStatus(addStatus) } - let updateStatus = try SecItemUpdate(baseQuery(for: key) as CFDictionary, attributes as CFDictionary) + let updateStatus = try SecItemUpdate( + baseQuery(for: key) as CFDictionary, attributes as CFDictionary) guard updateStatus == errSecSuccess else { - throw Self.mapStatus(updateStatus, defaultMessage: "keychain update failed") + throw Self.mapStatus(updateStatus) } } @@ -52,7 +53,7 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche return false } guard status == errSecSuccess else { - throw Self.mapStatus(status, defaultMessage: "keychain presence check failed") + throw Self.mapStatus(status) } return true } @@ -68,10 +69,10 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche return nil } guard status == errSecSuccess else { - throw Self.mapStatus(status, defaultMessage: "keychain read failed") + throw Self.mapStatus(status) } guard let data = result as? Data else { - throw RadrootsAppleSecurityError.permanentFailure("keychain returned an invalid value type") + throw RadrootsAppleSecurityError.permanentFailure } return data } @@ -79,14 +80,14 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche public func delete(_ key: RadrootsSecureStoreKey) throws { let status = try SecItemDelete(baseQuery(for: key) as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { - throw Self.mapStatus(status, defaultMessage: "keychain delete failed") + throw Self.mapStatus(status) } } public func deleteNamespace(_ namespace: String) throws { let status = try SecItemDelete(namespaceQuery(namespace) as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { - throw Self.mapStatus(status, defaultMessage: "keychain namespace delete failed") + throw Self.mapStatus(status) } } @@ -122,7 +123,9 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche } } - func keychainPolicyMapping(for policy: RadrootsSecretAccessPolicy) -> RadrootsKeychainSecretPolicyMapping { + func keychainPolicyMapping(for policy: RadrootsSecretAccessPolicy) + -> RadrootsKeychainSecretPolicyMapping + { RadrootsKeychainSecretPolicyMapping( accessibilityConstant: accessibilityConstant(for: policy), usesAccessControl: policy.userPresenceRequired, @@ -135,7 +138,13 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche } func accessControl(for mapping: RadrootsKeychainSecretPolicyMapping) throws -> SecAccessControl { - try accessControlFactory(mapping) + do { + return try accessControlFactory(mapping) + } catch let error as RadrootsAppleSecurityError { + throw error + } catch { + throw RadrootsAppleSecurityError.keychainFailure + } } private func mutationAttributes( @@ -144,7 +153,7 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche ) throws -> [String: Any] { let mapping = keychainPolicyMapping(for: policy) var attributes: [String: Any] = [ - kSecValueData as String: value, + kSecValueData as String: value ] if mapping.usesAccessControl { attributes[kSecAttrAccessControl as String] = try accessControl(for: mapping) @@ -154,35 +163,36 @@ public final class RadrootsAppleKeychainSecureStore: RadrootsSecureStore, @unche return attributes } - private static func makeAccessControl(for mapping: RadrootsKeychainSecretPolicyMapping) throws -> SecAccessControl { - var error: Unmanaged<CFError>? - guard let accessControl = SecAccessControlCreateWithFlags( + private static func makeAccessControl(for mapping: RadrootsKeychainSecretPolicyMapping) throws + -> SecAccessControl + { + guard + let accessControl = SecAccessControlCreateWithFlags( nil, mapping.accessibilityConstant, mapping.accessControlFlags, - &error - ) else { - let message = (error?.takeRetainedValue() as Error?)?.localizedDescription - ?? "keychain access control initialization failed" - throw RadrootsAppleSecurityError.invalidRequest(message) + nil + ) + else { + throw RadrootsAppleSecurityError.invalidRequest } return accessControl } - static func mapStatus(_ status: OSStatus, defaultMessage: String) -> RadrootsAppleSecurityError { + static func mapStatus(_ status: OSStatus) -> RadrootsAppleSecurityError { switch status { case errSecItemNotFound: - .notFound(defaultMessage) + .notFound case errSecAuthFailed: - .permissionDenied(defaultMessage) + .permissionDenied case errSecInteractionNotAllowed: - .transientFailure(defaultMessage) + .transientFailure case errSecUserCanceled: - .userCancelled(defaultMessage) + .userCancelled case errSecNotAvailable: - .unavailable(defaultMessage) + .unavailable default: - .keychainStatus(status, defaultMessage) + .keychainFailure } } } diff --git a/Sources/RadrootsKit/RadrootsAppleLocationServices.swift b/Sources/RadrootsKit/RadrootsAppleLocationServices.swift @@ -9,14 +9,17 @@ public struct RadrootsAppleLocationServicesAdapters: Sendable { public let locationServicesEnabled: @Sendable () -> Bool public let authorizationStatus: @Sendable () -> RadrootsLocationAuthorization public let requestWhenInUseAuthorization: @Sendable (TimeInterval) async throws -> RadrootsLocationAuthorization - public let requestCurrentLocation: @Sendable (RadrootsCurrentLocationRequest) async throws -> RadrootsLocationReading + public let requestCurrentLocation: + @Sendable (RadrootsCurrentLocationRequest) async throws -> RadrootsLocationReading public init( now: @escaping @Sendable () -> Date = Date.init, locationServicesEnabled: @escaping @Sendable () -> Bool, authorizationStatus: @escaping @Sendable () -> RadrootsLocationAuthorization, - requestWhenInUseAuthorization: @escaping @Sendable (TimeInterval) async throws -> RadrootsLocationAuthorization, - requestCurrentLocation: @escaping @Sendable (RadrootsCurrentLocationRequest) async throws -> RadrootsLocationReading + requestWhenInUseAuthorization: + @escaping @Sendable (TimeInterval) async throws -> RadrootsLocationAuthorization, + requestCurrentLocation: + @escaping @Sendable (RadrootsCurrentLocationRequest) async throws -> RadrootsLocationReading ) { self.now = now self.locationServicesEnabled = locationServicesEnabled @@ -49,10 +52,10 @@ public struct RadrootsAppleLocationServicesAdapters: Sendable { locationServicesEnabled: { false }, authorizationStatus: { .unsupported }, requestWhenInUseAuthorization: { _ in - throw RadrootsLocationServicesError.unavailable("CoreLocation is not available") + throw RadrootsLocationServicesError.unavailable }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.unavailable("CoreLocation is not available") + throw RadrootsLocationServicesError.unavailable } ) #endif @@ -104,52 +107,68 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send public func requestWhenInUseAuthorization() async throws -> RadrootsLocationAuthorization { let availability = await currentAvailability() guard availability.locationServicesEnabled else { - throw RadrootsLocationServicesError.unavailable("location services are disabled") + throw RadrootsLocationServicesError.unavailable } switch availability.authorization { case .notDetermined: + do { return try await adapters.requestWhenInUseAuthorization(12) + } catch let error as RadrootsLocationServicesError { + throw error + } catch { + throw RadrootsLocationServicesError.permanentFailure + } case .authorizedWhenInUse: return .authorizedWhenInUse case .authorizedAlways: return .authorizedAlways case .denied: - throw RadrootsLocationServicesError.permissionDenied("location permission is denied") + throw RadrootsLocationServicesError.permissionDenied case .restricted: - throw RadrootsLocationServicesError.permissionDenied("location permission is restricted") + throw RadrootsLocationServicesError.permissionDenied case .unavailable: - throw RadrootsLocationServicesError.unavailable("location services are unavailable") + throw RadrootsLocationServicesError.unavailable case .unsupported: - throw RadrootsLocationServicesError.unavailable("location services are unsupported") + throw RadrootsLocationServicesError.unavailable } } - public func currentLocation(_ request: RadrootsCurrentLocationRequest) async throws -> RadrootsCurrentLocationResult { + public func currentLocation(_ request: RadrootsCurrentLocationRequest) async throws + -> RadrootsCurrentLocationResult + { let availability = await currentAvailability() guard availability.locationServicesEnabled else { - throw RadrootsLocationServicesError.unavailable("location services are disabled") + throw RadrootsLocationServicesError.unavailable } guard availability.canRequestCurrentLocation else { switch availability.authorization { case .denied: - throw RadrootsLocationServicesError.permissionDenied("location permission is denied") + throw RadrootsLocationServicesError.permissionDenied case .restricted: - throw RadrootsLocationServicesError.permissionDenied("location permission is restricted") + throw RadrootsLocationServicesError.permissionDenied case .notDetermined: - throw RadrootsLocationServicesError.permissionDenied("location permission has not been requested") + throw RadrootsLocationServicesError.permissionDenied case .unavailable: - throw RadrootsLocationServicesError.unavailable("location services are unavailable") + throw RadrootsLocationServicesError.unavailable case .unsupported: - throw RadrootsLocationServicesError.unavailable("location services are unsupported") + throw RadrootsLocationServicesError.unavailable case .authorizedWhenInUse, .authorizedAlways: break } - throw RadrootsLocationServicesError.unavailable("location services are unavailable") + throw RadrootsLocationServicesError.unavailable + } + let reading: RadrootsLocationReading + do { + reading = try await adapters.requestCurrentLocation(request) + } catch let error as RadrootsLocationServicesError { + throw error + } catch { + throw RadrootsLocationServicesError.permanentFailure } - let reading = try await adapters.requestCurrentLocation(request) if let maximumAgeSeconds = request.maximumCachedReadingAgeSeconds { - guard try reading.isFresh(relativeTo: adapters.now(), maximumAgeSeconds: maximumAgeSeconds) else { - throw RadrootsLocationServicesError.transientFailure("location reading is older than the requested maximum age") + guard try reading.isFresh(relativeTo: adapters.now(), maximumAgeSeconds: maximumAgeSeconds) + else { + throw RadrootsLocationServicesError.transientFailure } } return try RadrootsCurrentLocationResult( @@ -161,7 +180,9 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send #if canImport(CoreLocation) @MainActor - private final class RadrootsCoreLocationAuthorizationSession: NSObject, @preconcurrency CLLocationManagerDelegate { + private final class RadrootsCoreLocationAuthorizationSession: NSObject, + @preconcurrency CLLocationManagerDelegate + { private var continuation: CheckedContinuation<RadrootsLocationAuthorization, any Error>? private var manager: CLLocationManager? private var timeoutTask: Task<Void, Never>? @@ -176,13 +197,13 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send timeoutTask = Task { [weak self] in let nanoseconds = UInt64(timeoutSeconds * 1_000_000_000) try? await Task.sleep(nanoseconds: nanoseconds) - self?.finish(.failure(.timeout("location authorization timed out"))) + self?.finish(.failure(.timeout)) } manager.requestWhenInUseAuthorization() } } onCancel: { Task { @MainActor [weak self] in - self?.finish(.failure(.cancelled("location authorization was cancelled"))) + self?.finish(.failure(.cancelled)) } } } @@ -198,7 +219,9 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send finish(.success(authorization)) } - private func finish(_ result: Result<RadrootsLocationAuthorization, RadrootsLocationServicesError>) { + private func finish( + _ result: Result<RadrootsLocationAuthorization, RadrootsLocationServicesError> + ) { guard let continuation else { return } @@ -208,16 +231,18 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send manager?.delegate = nil manager = nil switch result { - case let .success(authorization): + case .success(let authorization): continuation.resume(returning: authorization) - case let .failure(error): + case .failure(let error): continuation.resume(throwing: error) } } } @MainActor - private final class RadrootsCoreLocationReadingSession: NSObject, @preconcurrency CLLocationManagerDelegate { + private final class RadrootsCoreLocationReadingSession: NSObject, + @preconcurrency CLLocationManagerDelegate + { private var continuation: CheckedContinuation<RadrootsLocationReading, any Error>? private var manager: CLLocationManager? private var timeoutTask: Task<Void, Never>? @@ -235,20 +260,20 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send timeoutTask = Task { [weak self] in let nanoseconds = UInt64(request.timeoutSeconds * 1_000_000_000) try? await Task.sleep(nanoseconds: nanoseconds) - self?.finish(.failure(.timeout("current location request timed out"))) + self?.finish(.failure(.timeout)) } manager.requestLocation() } } onCancel: { Task { @MainActor [weak self] in - self?.finish(.failure(.cancelled("current location request was cancelled"))) + self?.finish(.failure(.cancelled)) } } } func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let location = locations.sorted(by: { $0.timestamp < $1.timestamp }).last else { - finish(.failure(.transientFailure("CoreLocation returned no locations"))) + finish(.failure(.transientFailure)) return } do { @@ -256,7 +281,7 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send } catch let error as RadrootsLocationServicesError { finish(.failure(error)) } catch { - finish(.failure(.permanentFailure(error.localizedDescription))) + finish(.failure(.permanentFailure)) } } @@ -264,16 +289,16 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send if let coreLocationError = error as? CLError { switch coreLocationError.code { case .denied: - finish(.failure(.permissionDenied("location permission is denied"))) + finish(.failure(.permissionDenied)) case .locationUnknown: - finish(.failure(.transientFailure("current location is temporarily unknown"))) + finish(.failure(.transientFailure)) case .network: - finish(.failure(.transientFailure("location network lookup failed"))) + finish(.failure(.transientFailure)) default: - finish(.failure(.permanentFailure(coreLocationError.localizedDescription))) + finish(.failure(.permanentFailure)) } } else { - finish(.failure(.permanentFailure(error.localizedDescription))) + finish(.failure(.permanentFailure)) } } @@ -287,9 +312,9 @@ public final class RadrootsAppleLocationServices: RadrootsLocationServices, Send manager?.delegate = nil manager = nil switch result { - case let .success(reading): + case .success(let reading): continuation.resume(returning: reading) - case let .failure(error): + case .failure(let error): continuation.resume(throwing: error) } } diff --git a/Sources/RadrootsKit/RadrootsAppleMediaPicker.swift b/Sources/RadrootsKit/RadrootsAppleMediaPicker.swift @@ -21,7 +21,8 @@ import Foundation #endif #if canImport(UIKit) - public typealias RadrootsAppleViewControllerProvider = @MainActor @Sendable () throws -> UIViewController + public typealias RadrootsAppleViewControllerProvider = + @MainActor @Sendable () throws -> UIViewController #endif public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sendable { @@ -80,16 +81,23 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen #endif } - public func importMedia(_ request: RadrootsMediaImportRequest) async throws -> RadrootsMediaImportResult { + public func importMedia(_ request: RadrootsMediaImportRequest) async throws + -> RadrootsMediaImportResult + { #if canImport(UIKit) && canImport(PhotosUI) let support = try await currentSupport() guard support.importAvailable else { - throw RadrootsCaptureIntakeError.unavailable("media import is unavailable") + throw RadrootsCaptureIntakeError.unavailable } let writer = RadrootsAppleMediaAssetWriter(fileAccess: fileAccess, fileManager: fileManager) - let presenter = try await MainActor.run { + let presenter: UIViewController + do { + presenter = try await MainActor.run { try viewControllerProvider() } + } catch { + throw Self.adapt(error: error) + } let coordinatorID = UUID() return try await RadrootsAppleCaptureAsyncSupport.awaitMainActorCallback( timeout: callbackTimeout, @@ -113,21 +121,28 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen presenter.present(picker, animated: true) } #else - throw RadrootsCaptureIntakeError.unavailable("media import is unavailable") + throw RadrootsCaptureIntakeError.unavailable #endif } - public func captureMedia(_ request: RadrootsMediaCaptureRequest) async throws -> RadrootsMediaCaptureResult { + public func captureMedia(_ request: RadrootsMediaCaptureRequest) async throws + -> RadrootsMediaCaptureResult + { #if canImport(UIKit) let support = try await currentSupport() guard support.cameraCaptureAvailable else { - throw RadrootsCaptureIntakeError.unavailable("camera photo capture is unavailable") + throw RadrootsCaptureIntakeError.unavailable } try await Self.requestCameraAccessIfNeeded() let writer = RadrootsAppleMediaAssetWriter(fileAccess: fileAccess, fileManager: fileManager) - let presenter = try await MainActor.run { + let presenter: UIViewController + do { + presenter = try await MainActor.run { try viewControllerProvider() } + } catch { + throw Self.adapt(error: error) + } let coordinatorID = UUID() return try await RadrootsAppleCaptureAsyncSupport.awaitMainActorCallback( timeout: callbackTimeout, @@ -151,7 +166,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen presenter.present(picker, animated: true) } #else - throw RadrootsCaptureIntakeError.unavailable("camera photo capture is unavailable") + throw RadrootsCaptureIntakeError.unavailable #endif } @@ -172,31 +187,33 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen if let fileError = error as? RadrootsAppleFileError { return adapt(fileError: fileError) } - return RadrootsCaptureIntakeError.transientFailure((error as NSError).localizedDescription) + return RadrootsCaptureIntakeError.transientFailure } static func adapt(fileError: RadrootsAppleFileError) -> RadrootsCaptureIntakeError { switch fileError { - case let .invalidRequest(message): - .invalidRequest(message) - case let .notFound(message): - .transientFailure(message) - case let .permissionDenied(message): - .permissionDenied(message) - case let .transientFailure(message): - .transientFailure(message) - case let .permanentFailure(message): - .permanentFailure(message) + case .invalidRequest: + .invalidRequest + case .notFound: + .transientFailure + case .permissionDenied: + .permissionDenied + case .transientFailure: + .transientFailure + case .permanentFailure: + .permanentFailure } } } #if canImport(UIKit) - private extension RadrootsAppleMediaPicker { + extension RadrootsAppleMediaPicker { @MainActor - static func liveSupport() throws -> RadrootsMediaPickerSupport { - let cameraAvailable = UIImagePickerController.isSourceTypeAvailable(.camera) && - UIImagePickerController.availableMediaTypes(for: .camera)?.contains(imageTypeIdentifier()) == true + fileprivate static func liveSupport() throws -> RadrootsMediaPickerSupport { + let cameraAvailable = + UIImagePickerController.isSourceTypeAvailable(.camera) + && UIImagePickerController.availableMediaTypes(for: .camera)?.contains( + imageTypeIdentifier()) == true return try RadrootsMediaPickerSupport( importAvailable: true, cameraCaptureAvailable: cameraAvailable, @@ -206,7 +223,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen ) } - static func imageTypeIdentifier() -> String { + fileprivate static func imageTypeIdentifier() -> String { #if canImport(UniformTypeIdentifiers) UTType.image.identifier #else @@ -214,7 +231,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen #endif } - static func requestCameraAccessIfNeeded() async throws { + fileprivate static func requestCameraAccessIfNeeded() async throws { #if canImport(AVFoundation) switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: @@ -222,17 +239,17 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen case .notDetermined: let granted = await AVCaptureDevice.requestAccess(for: .video) guard granted else { - throw RadrootsCaptureIntakeError.permissionDenied("camera access was not granted") + throw RadrootsCaptureIntakeError.permissionDenied } case .denied: - throw RadrootsCaptureIntakeError.permissionDenied("camera access is denied") + throw RadrootsCaptureIntakeError.permissionDenied case .restricted: - throw RadrootsCaptureIntakeError.permissionDenied("camera access is restricted") + throw RadrootsCaptureIntakeError.permissionDenied @unknown default: - throw RadrootsCaptureIntakeError.unavailable("camera authorization is unavailable") + throw RadrootsCaptureIntakeError.unavailable } #else - throw RadrootsCaptureIntakeError.unavailable("camera authorization is unavailable") + throw RadrootsCaptureIntakeError.unavailable #endif } } @@ -246,11 +263,13 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen scene.activationState == .foregroundActive || scene.activationState == .foregroundInactive } let windows = scenes.flatMap(\.windows) - guard let window = windows.first(where: \.isKeyWindow) ?? windows.first(where: { !$0.isHidden }) else { - throw RadrootsCaptureIntakeError.unavailable("\(service) requires an active foreground window") + guard + let window = windows.first(where: \.isKeyWindow) ?? windows.first(where: { !$0.isHidden }) + else { + throw RadrootsCaptureIntakeError.unavailable } guard let rootViewController = window.rootViewController else { - throw RadrootsCaptureIntakeError.unavailable("\(service) requires an active foreground view controller") + throw RadrootsCaptureIntakeError.unavailable } return topViewController(rootViewController) } @@ -299,7 +318,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen picker.dismiss(animated: true) selectedResults = Array(results.prefix(request.selectionLimit)) guard !selectedResults.isEmpty else { - finish(.failure(.userCancelled("media import was cancelled"))) + finish(.failure(.userCancelled)) return } loadResult(at: 0, collected: []) @@ -316,14 +335,18 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen } let provider = selectedResults[index].itemProvider let suggestedName = provider.suggestedName ?? "photo" - guard provider.hasItemConformingToTypeIdentifier(RadrootsAppleMediaPicker.imageTypeIdentifier()) else { - finish(.failure(.transientFailure("media import could not resolve an image file representation"))) + guard + provider.hasItemConformingToTypeIdentifier(RadrootsAppleMediaPicker.imageTypeIdentifier()) + else { + finish(.failure(.transientFailure)) return } let writer = writer let destinationScope = request.destinationScope let mediaTypeHint = mediaTypeHint(from: provider) - provider.loadFileRepresentation(forTypeIdentifier: RadrootsAppleMediaPicker.imageTypeIdentifier()) { url, error in + provider.loadFileRepresentation( + forTypeIdentifier: RadrootsAppleMediaPicker.imageTypeIdentifier() + ) { url, error in if let error { Task { @MainActor in self.finish(.failure(RadrootsAppleMediaPicker.adapt(error: error))) @@ -332,7 +355,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen } guard let url else { Task { @MainActor in - self.finish(.failure(.transientFailure("media import finished without an image file representation"))) + self.finish(.failure(.transientFailure)) } return } @@ -352,11 +375,11 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen } Task { @MainActor in switch result { - case let .success(asset): + case .success(let asset): var nextCollected = collected nextCollected.append(asset) self.loadResult(at: index + 1, collected: nextCollected) - case let .failure(error): + case .failure(let error): self.finish(.failure(error)) } } @@ -377,7 +400,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen func cancelPresentation(_ picker: PHPickerViewController) { guard !didResolve else { return } picker.dismiss(animated: true) - finish(.failure(.transientFailure("media import presentation was cancelled"))) + finish(.failure(.transientFailure)) } private func finish(_ result: Result<RadrootsMediaImportResult, RadrootsCaptureIntakeError>) { @@ -391,7 +414,9 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen } @MainActor - private final class RadrootsAppleCameraCaptureCoordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { + private final class RadrootsAppleCameraCaptureCoordinator: NSObject, + UIImagePickerControllerDelegate, UINavigationControllerDelegate + { var completion: (@Sendable (Result<RadrootsMediaCaptureResult, RadrootsCaptureIntakeError>) -> Void)? private let writer: RadrootsAppleMediaAssetWriter @@ -412,7 +437,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) - finish(.failure(.userCancelled("camera photo capture was cancelled"))) + finish(.failure(.userCancelled)) } func imagePickerController( @@ -427,7 +452,9 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen } } - private func buildAsset(info: [UIImagePickerController.InfoKey: Any]) throws -> RadrootsMediaAsset { + private func buildAsset(info: [UIImagePickerController.InfoKey: Any]) throws + -> RadrootsMediaAsset + { if let imageURL = info[.imageURL] as? URL { return try writer.persistExternalImage( sourceURL: imageURL, @@ -440,7 +467,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen guard let image = (info[.editedImage] as? UIImage) ?? (info[.originalImage] as? UIImage), let jpegData = image.jpegData(compressionQuality: 0.92) else { - throw RadrootsCaptureIntakeError.transientFailure("camera photo capture finished without a usable image") + throw RadrootsCaptureIntakeError.transientFailure } return try writer.persistCapturedJPEG( data: jpegData, @@ -452,7 +479,7 @@ public final class RadrootsAppleMediaPicker: RadrootsMediaPicker, @unchecked Sen func cancelPresentation(_ picker: UIImagePickerController) { guard !didResolve else { return } picker.dismiss(animated: true) - finish(.failure(.transientFailure("camera photo capture presentation was cancelled"))) + finish(.failure(.transientFailure)) } private func finish(_ result: Result<RadrootsMediaCaptureResult, RadrootsCaptureIntakeError>) { @@ -539,7 +566,8 @@ private final class RadrootsAppleMediaAssetWriter: @unchecked Sendable { fallbackBasename: "captured_photo", fallbackExtension: "jpg" ) - let file = try destinationFile(source: .cameraCapture, scope: destinationScope, filename: filename) + let file = try destinationFile( + source: .cameraCapture, scope: destinationScope, filename: filename) try fileAccess.write(.inline(data), to: file) return try RadrootsMediaAsset( source: .cameraCapture, @@ -548,8 +576,10 @@ private final class RadrootsAppleMediaAssetWriter: @unchecked Sendable { mediaType: "image/jpeg", suggestedFilename: filename, sizeBytes: UInt64(data.count), - pixelWidth: image.cgImage.map { UInt32($0.width) } ?? positiveRoundedUInt32(image.size.width), - pixelHeight: image.cgImage.map { UInt32($0.height) } ?? positiveRoundedUInt32(image.size.height), + pixelWidth: image.cgImage.map { UInt32($0.width) } + ?? positiveRoundedUInt32(image.size.width), + pixelHeight: image.cgImage.map { UInt32($0.height) } + ?? positiveRoundedUInt32(image.size.height), capturedAt: Date() ) } @@ -560,7 +590,8 @@ private final class RadrootsAppleMediaAssetWriter: @unchecked Sendable { scope: RadrootsFileScope, filename: String ) throws -> RadrootsFileReference { - let namespace = switch source { + let namespace = + switch source { case .libraryImport: "library_import" case .cameraCapture: @@ -569,7 +600,8 @@ private final class RadrootsAppleMediaAssetWriter: @unchecked Sendable { let validatedFilename = try RadrootsCaptureIntakeValidation.normalizedFilename(filename) return RadrootsFileReference( scope: scope, - relativePath: "capture_intake/media/\(namespace)/\(UUID().uuidString.lowercased())/\(validatedFilename)" + relativePath: + "capture_intake/media/\(namespace)/\(UUID().uuidString.lowercased())/\(validatedFilename)" ) } @@ -583,11 +615,8 @@ private final class RadrootsAppleMediaAssetWriter: @unchecked Sendable { let lastComponent = URL(fileURLWithPath: trimmed).lastPathComponent let raw = lastComponent.isEmpty || lastComponent == "/" ? fallback : lastComponent let sanitizedScalars = raw.unicodeScalars.map { scalar -> Character in - if CharacterSet.controlCharacters.contains(scalar) || - scalar == "/" || - scalar == "\\" || - scalar == "\0" || - scalar == ":" + if CharacterSet.controlCharacters.contains(scalar) || scalar == "/" || scalar == "\\" + || scalar == "\0" || scalar == ":" { return "_" } @@ -633,7 +662,8 @@ private final class RadrootsAppleMediaAssetWriter: @unchecked Sendable { private func imageDimensions(fileURL: URL) -> (width: UInt32, height: UInt32)? { #if canImport(ImageIO) guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil), - let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any], + let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) + as? [CFString: Any], let width = properties[kCGImagePropertyPixelWidth] as? NSNumber, let height = properties[kCGImagePropertyPixelHeight] as? NSNumber, width.uint32Value > 0, @@ -659,7 +689,8 @@ enum RadrootsAppleCaptureAsyncSupport { static func awaitMainActorCallback<Value: Sendable>( timeout: TimeInterval, timeoutMessage: String, - operation: @escaping @MainActor ( + operation: + @escaping @MainActor ( @escaping @Sendable (Result<Value, RadrootsCaptureIntakeError>) -> Void, @escaping @MainActor @Sendable (@escaping @MainActor @Sendable () -> Void) -> Void ) -> Void @@ -675,7 +706,7 @@ enum RadrootsAppleCaptureAsyncSupport { } catch { return } - state.resolve(.failure(.transientFailure(timeoutMessage))) + state.resolve(.failure(.transientFailure)) } Task { @MainActor in operation( @@ -690,7 +721,7 @@ enum RadrootsAppleCaptureAsyncSupport { } } } onCancel: { - state.resolve(.failure(.userCancelled("capture request was cancelled"))) + state.resolve(.failure(.userCancelled)) } } } @@ -767,9 +798,9 @@ private final class RadrootsAppleCaptureAsyncCallbackState<Value: Sendable>: @un with result: Result<Value, RadrootsCaptureIntakeError> ) { switch result { - case let .success(value): + case .success(let value): continuation.resume(returning: value) - case let .failure(error): + case .failure(let error): continuation.resume(throwing: error) } } diff --git a/Sources/RadrootsKit/RadrootsAppleMediaPreparation.swift b/Sources/RadrootsKit/RadrootsAppleMediaPreparation.swift @@ -4,15 +4,17 @@ import ImageIO import UniformTypeIdentifiers public enum RadrootsAppleMediaPreparationError: Error, Equatable, Sendable { - case invalidRequest(String) - case unavailable(String) - case preparationFailure(String) + case invalidRequest + case unavailable + case preparationFailure } extension RadrootsAppleMediaPreparationError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message), let .unavailable(message), let .preparationFailure(message): message + case .invalidRequest: "The media preparation request is invalid." + case .unavailable: "Media preparation is unavailable." + case .preparationFailure: "The media could not be prepared." } } } @@ -30,9 +32,9 @@ public struct RadrootsAppleImagePreparationRequest: Sendable, Equatable, Hashabl ) throws { guard (1 ... (40 * 1024 * 1024)).contains(maximumInputBytes), (1 ... (10 * 1024 * 1024)).contains(maximumOutputBytes), (1 ... 40_000_000).contains(maximumPixelCount), (1 ... 8192).contains(maximumDimension) - else { throw RadrootsAppleMediaPreparationError.invalidRequest("image preparation limits are invalid") } + else { throw RadrootsAppleMediaPreparationError.invalidRequest } do { try RadrootsBackgroundTransferValidation.validateLocalFile(source) } catch { - throw RadrootsAppleMediaPreparationError.invalidRequest("image source handle is invalid") + throw RadrootsAppleMediaPreparationError.invalidRequest } self.source = source self.maximumInputBytes = maximumInputBytes @@ -42,16 +44,21 @@ public struct RadrootsAppleImagePreparationRequest: Sendable, Equatable, Hashabl } } -public struct RadrootsApplePreparedImage: Sendable, Equatable, Hashable, CustomDebugStringConvertible { +public struct RadrootsApplePreparedImage: Sendable, Equatable, Hashable, + CustomDebugStringConvertible +{ public let file: RadrootsStagedBlobReference public let sha256: String public let width: UInt32 public let height: UInt32 - public init(file: RadrootsStagedBlobReference, sha256: String, width: UInt32, height: UInt32) throws { - guard sha256.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil, width > 0, height > 0, file.sizeBytes > 0, + public init(file: RadrootsStagedBlobReference, sha256: String, width: UInt32, height: UInt32) + throws + { + guard sha256.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil, width > 0, + height > 0, file.sizeBytes > 0, file.mediaType == "image/png" - else { throw RadrootsAppleMediaPreparationError.invalidRequest("prepared image commitment is invalid") } + else { throw RadrootsAppleMediaPreparationError.invalidRequest } self.file = file self.sha256 = sha256 self.width = width @@ -70,7 +77,8 @@ public actor RadrootsAppleMediaPreparer { private let protectedData: RadrootsProtectedDataProvider public init( - roots: RadrootsAppleFileRoots, fileManager: FileManager = .default, protectedData: RadrootsProtectedDataProvider = .available + roots: RadrootsAppleFileRoots, fileManager: FileManager = .default, + protectedData: RadrootsProtectedDataProvider = .available ) { self.roots = roots resolver = RadrootsAppleBackgroundTransferFileResolver(roots: roots) @@ -78,41 +86,56 @@ public actor RadrootsAppleMediaPreparer { self.protectedData = protectedData } - public func prepareImage(_ request: RadrootsAppleImagePreparationRequest) async throws -> RadrootsApplePreparedImage { - do { return try await prepareValidatedImage(request) } catch is CancellationError { throw CancellationError() } catch let error + public func prepareImage(_ request: RadrootsAppleImagePreparationRequest) async throws + -> RadrootsApplePreparedImage + { + do { return try await prepareValidatedImage(request) } catch is CancellationError { + throw CancellationError() + } catch let error as RadrootsAppleMediaPreparationError - { throw error } catch { throw RadrootsAppleMediaPreparationError.preparationFailure("image preparation failed") } + { throw error } catch { throw RadrootsAppleMediaPreparationError.preparationFailure } } - private func prepareValidatedImage(_ request: RadrootsAppleImagePreparationRequest) async throws -> RadrootsApplePreparedImage { + private func prepareValidatedImage(_ request: RadrootsAppleImagePreparationRequest) async throws + -> RadrootsApplePreparedImage + { try Task.checkCancellation() try requireProtectedData() let sourceData: Data do { sourceData = try resolver.read(request.source, maximumBytes: request.maximumInputBytes) } catch { - throw RadrootsAppleMediaPreparationError.invalidRequest("image source is unavailable or exceeds its byte limit") + throw RadrootsAppleMediaPreparationError.invalidRequest } guard !sourceData.isEmpty else { - throw RadrootsAppleMediaPreparationError.invalidRequest("image source is unavailable or exceeds its byte limit") + throw RadrootsAppleMediaPreparationError.invalidRequest } - guard let source = CGImageSourceCreateWithData(sourceData as CFData, [kCGImageSourceShouldCache: false] as CFDictionary), + guard + let source = CGImageSourceCreateWithData( + sourceData as CFData, [kCGImageSourceShouldCache: false] as CFDictionary), CGImageSourceGetCount(source) == 1 - else { throw RadrootsAppleMediaPreparationError.invalidRequest("image source must contain exactly one decodable image") } + else { throw RadrootsAppleMediaPreparationError.invalidRequest } let dimensions = try Self.sourceDimensions(source) guard dimensions.pixelCount <= request.maximumPixelCount else { - throw RadrootsAppleMediaPreparationError.invalidRequest("image source exceeds its pixel limit") + throw RadrootsAppleMediaPreparationError.invalidRequest } let thumbnailOptions: [CFString: Any] = [ - kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true, - kCGImageSourceShouldCacheImmediately: true, kCGImageSourceThumbnailMaxPixelSize: request.maximumDimension, + kCGImageSourceCreateThumbnailFromImageAlways: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceThumbnailMaxPixelSize: request.maximumDimension, ] - guard let normalizedImage = CGImageSourceCreateThumbnailAtIndex(source, 0, thumbnailOptions as CFDictionary) else { - throw RadrootsAppleMediaPreparationError.preparationFailure("image normalization failed") + guard + let normalizedImage = CGImageSourceCreateThumbnailAtIndex( + source, 0, thumbnailOptions as CFDictionary) + else { + throw RadrootsAppleMediaPreparationError.preparationFailure } try Task.checkCancellation() - let temporaryURL = roots.temporaryRoot.appendingPathComponent("media_preparation", isDirectory: true).appendingPathComponent( + let temporaryURL = roots.temporaryRoot.appendingPathComponent( + "media_preparation", isDirectory: true + ).appendingPathComponent( "\(UUID().uuidString.lowercased()).png" ).standardizedFileURL defer { @@ -120,23 +143,28 @@ public actor RadrootsAppleMediaPreparer { try? fileManager.removeItem(at: temporaryURL) } } - try fileManager.createDirectory(at: temporaryURL.deletingLastPathComponent(), withIntermediateDirectories: true) + try fileManager.createDirectory( + at: temporaryURL.deletingLastPathComponent(), withIntermediateDirectories: true) #if os(iOS) try fileManager.setAttributes( - [.protectionKey: FileProtectionType.complete], ofItemAtPath: temporaryURL.deletingLastPathComponent().path + [.protectionKey: FileProtectionType.complete], + ofItemAtPath: temporaryURL.deletingLastPathComponent().path ) #endif - guard let destination = CGImageDestinationCreateWithURL(temporaryURL as CFURL, UTType.png.identifier as CFString, 1, nil) else { - throw RadrootsAppleMediaPreparationError.preparationFailure("image destination could not be created") + guard + let destination = CGImageDestinationCreateWithURL( + temporaryURL as CFURL, UTType.png.identifier as CFString, 1, nil) + else { + throw RadrootsAppleMediaPreparationError.preparationFailure } CGImageDestinationAddImage(destination, normalizedImage, [:] as CFDictionary) guard CGImageDestinationFinalize(destination) else { - throw RadrootsAppleMediaPreparationError.preparationFailure("image encoding failed") + throw RadrootsAppleMediaPreparationError.preparationFailure } try Task.checkCancellation() let outputSize = try Self.fileSize(at: temporaryURL) guard outputSize > 0, outputSize <= request.maximumOutputBytes else { - throw RadrootsAppleMediaPreparationError.invalidRequest("normalized image exceeds its byte limit") + throw RadrootsAppleMediaPreparationError.invalidRequest } let digest = try RadrootsAppleFileDigest.sha256(at: temporaryURL) let staged = try RadrootsStagedBlobReference( @@ -156,11 +184,13 @@ public actor RadrootsAppleMediaPreparer { } #if os(iOS) try fileManager.setAttributes( - [.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: stagedURL.path + [.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], + ofItemAtPath: stagedURL.path ) #endif return try RadrootsApplePreparedImage( - file: staged, sha256: digest, width: UInt32(normalizedImage.width), height: UInt32(normalizedImage.height) + file: staged, sha256: digest, width: UInt32(normalizedImage.width), + height: UInt32(normalizedImage.height) ) } @@ -175,35 +205,42 @@ public actor RadrootsAppleMediaPreparer { ) guard preparedData.count == preparedImage.file.sizeBytes, RadrootsAppleFileDigest.sha256(preparedData) == preparedImage.sha256 - else { throw RadrootsAppleMediaPreparationError.invalidRequest("prepared image no longer matches its commitment") } + else { throw RadrootsAppleMediaPreparationError.invalidRequest } return try RadrootsBackgroundTransferRequest( - identifier: identifier, remoteURL: remoteURL, method: .put, operation: .upload(source: .stagedBlob(preparedImage.file)), + identifier: identifier, remoteURL: remoteURL, method: .put, + operation: .upload(source: .stagedBlob(preparedImage.file)), headers: ["Authorization": authorization, "Content-Type": "image/png"], - metadata: ["purpose": "blossom_upload", "sha256": preparedImage.sha256], networkPolicy: networkPolicy, + metadata: ["purpose": "blossom_upload", "sha256": preparedImage.sha256], + networkPolicy: networkPolicy, responsePolicy: .boundedJSON(), expectedSourceSHA256: preparedImage.sha256 ) - } catch let error as RadrootsAppleMediaPreparationError { throw error } catch let error as RadrootsBackgroundTransferError { + } catch let error as RadrootsAppleMediaPreparationError { throw error } catch let error + as RadrootsBackgroundTransferError + { throw error - } catch { throw RadrootsAppleMediaPreparationError.preparationFailure("prepared image commitment could not be verified") } + } catch { throw RadrootsAppleMediaPreparationError.preparationFailure } } private func requireProtectedData() throws { guard protectedData.currentState() == .available else { - throw RadrootsAppleMediaPreparationError.unavailable("image preparation protected data is unavailable") + throw RadrootsAppleMediaPreparationError.unavailable } } - private static func sourceDimensions(_ source: CGImageSource) throws -> (width: Int, height: Int, pixelCount: Int) { + private static func sourceDimensions(_ source: CGImageSource) throws -> ( + width: Int, height: Int, pixelCount: Int + ) { guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any], let width = (properties[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue, - let height = (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue, width > 0, height > 0, width <= Int.max / height - else { throw RadrootsAppleMediaPreparationError.invalidRequest("image dimensions are invalid") } + let height = (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue, width > 0, + height > 0, width <= Int.max / height + else { throw RadrootsAppleMediaPreparationError.invalidRequest } return (width, height, width * height) } private static func fileSize(at url: URL) throws -> Int { guard let size = try url.resourceValues(forKeys: [.fileSizeKey]).fileSize else { - throw RadrootsAppleMediaPreparationError.preparationFailure("prepared image size is unavailable") + throw RadrootsAppleMediaPreparationError.preparationFailure } return size } diff --git a/Sources/RadrootsKit/RadrootsAppleMobileStore.swift b/Sources/RadrootsKit/RadrootsAppleMobileStore.swift @@ -31,6 +31,17 @@ public enum RadrootsAppleMobileStoreError: Error, Sendable, Equatable { case fileSystemFailure } +extension RadrootsAppleMobileStoreError: LocalizedError { + public var errorDescription: String? { + switch self { + case .invalidPublicKey: "The public key is invalid." + case .protectedDataUnavailable: "Protected data is unavailable." + case .invalidDirectoryLayout: "The mobile-store directory layout is invalid." + case .fileSystemFailure: "The mobile store could not be prepared." + } + } +} + public enum RadrootsAppleMobileStore { private static let productDirectory = "radroots" private static let userDirectory = "users" diff --git a/Sources/RadrootsKit/RadrootsAppleSecurityError.swift b/Sources/RadrootsKit/RadrootsAppleSecurityError.swift @@ -1,35 +1,27 @@ import Foundation public enum RadrootsAppleSecurityError: Error, Equatable, Sendable { - case invalidRequest(String) - case notFound(String) - case permissionDenied(String) - case userCancelled(String) - case transientFailure(String) - case unavailable(String) - case permanentFailure(String) - case keychainStatus(Int32, String) + case invalidRequest + case notFound + case permissionDenied + case userCancelled + case transientFailure + case unavailable + case permanentFailure + case keychainFailure } extension RadrootsAppleSecurityError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .notFound(message): - message - case let .permissionDenied(message): - message - case let .userCancelled(message): - message - case let .transientFailure(message): - message - case let .unavailable(message): - message - case let .permanentFailure(message): - message - case let .keychainStatus(_, message): - message + case .invalidRequest: "The secure-store request is invalid." + case .notFound: "The secure-store item was not found." + case .permissionDenied: "Secure-store access was denied." + case .userCancelled: "Secure-store access was cancelled." + case .transientFailure: "The secure-store operation could not be completed temporarily." + case .unavailable: "The secure store is unavailable." + case .permanentFailure: "The secure-store operation could not be completed." + case .keychainFailure: "The secure-store operation failed." } } } diff --git a/Sources/RadrootsKit/RadrootsAppleUserPresence.swift b/Sources/RadrootsKit/RadrootsAppleUserPresence.swift @@ -10,7 +10,8 @@ public struct RadrootsAppleUserPresenceAdapters: Sendable { public init( currentStatus: @escaping @Sendable () async throws -> RadrootsUserPresenceStatus, - verify: @escaping @Sendable (RadrootsUserPresenceRequest) async throws -> RadrootsUserPresenceResult + verify: + @escaping @Sendable (RadrootsUserPresenceRequest) async throws -> RadrootsUserPresenceResult ) { self.currentStatus = currentStatus self.verify = verify @@ -34,10 +35,10 @@ public struct RadrootsAppleUserPresenceAdapters: Sendable { #else Self( currentStatus: { - throw RadrootsUserPresenceError.unavailable("user presence is unavailable") + throw RadrootsUserPresenceError.unavailable }, verify: { _ in - throw RadrootsUserPresenceError.unavailable("user presence is unavailable") + throw RadrootsUserPresenceError.unavailable } ) #endif @@ -47,16 +48,55 @@ public struct RadrootsAppleUserPresenceAdapters: Sendable { public final class RadrootsAppleUserPresence: RadrootsUserPresence, Sendable { private let adapters: RadrootsAppleUserPresenceAdapters - public init(adapters: RadrootsAppleUserPresenceAdapters = RadrootsAppleUserPresenceAdapters.live()) { + public init( + adapters: RadrootsAppleUserPresenceAdapters = RadrootsAppleUserPresenceAdapters.live() + ) { self.adapters = adapters } public func currentStatus() async throws -> RadrootsUserPresenceStatus { - try await adapters.currentStatus() + do { + return try await adapters.currentStatus() + } catch { + throw RadrootsAppleUserPresenceAdapters.adapt(error: error) + } } - public func verify(_ request: RadrootsUserPresenceRequest) async throws -> RadrootsUserPresenceResult { - try await adapters.verify(request) + public func verify(_ request: RadrootsUserPresenceRequest) async throws + -> RadrootsUserPresenceResult + { + do { + return try await adapters.verify(request) + } catch { + throw RadrootsAppleUserPresenceAdapters.adapt(error: error) + } + } +} + +extension RadrootsAppleUserPresenceAdapters { + static func adapt(error: Error) -> RadrootsUserPresenceError { + if let error = error as? RadrootsUserPresenceError { + return error + } + + #if canImport(LocalAuthentication) + if let error = error as? LAError { + switch error.code { + case .userCancel, .userFallback: + return .userCancelled + case .appCancel, .systemCancel, .notInteractive: + return .transientFailure + case .biometryNotAvailable, .biometryNotEnrolled, .passcodeNotSet: + return .unavailable + case .authenticationFailed: + return .permissionDenied + default: + return .permanentFailure + } + } + #endif + + return .permanentFailure } } @@ -84,7 +124,8 @@ public final class RadrootsAppleUserPresence: RadrootsUserPresence, Sendable { error: &deviceCredentialError ) - let support: RadrootsUserPresenceSupport = if canEvaluateBiometrics { + let support: RadrootsUserPresenceSupport = + if canEvaluateBiometrics { .biometricsOrDeviceCredential } else if canEvaluateDeviceCredential { .deviceCredential @@ -131,34 +172,13 @@ public final class RadrootsAppleUserPresence: RadrootsUserPresence, Sendable { if let error { completion(.failure(adapt(error: error))) } else { - completion(.success(RadrootsUserPresenceResult(policy: request.policy, verified: success))) + completion( + .success(RadrootsUserPresenceResult(policy: request.policy, verified: success))) } } } } - static func adapt(error: Error) -> RadrootsUserPresenceError { - if let error = error as? RadrootsUserPresenceError { - return error - } - - if let error = error as? LAError { - switch error.code { - case .userCancel, .userFallback: - return .userCancelled(error.localizedDescription) - case .appCancel, .systemCancel, .notInteractive: - return .transientFailure(error.localizedDescription) - case .biometryNotAvailable, .biometryNotEnrolled, .passcodeNotSet: - return .unavailable(error.localizedDescription) - case .authenticationFailed: - return .permissionDenied(error.localizedDescription) - default: - return .permanentFailure(error.localizedDescription) - } - } - - return .permanentFailure(error.localizedDescription) - } } #endif @@ -177,27 +197,29 @@ enum RadrootsAppleUserPresenceAsyncSupport { } Task { try? await Task.sleep(nanoseconds: try Self.timeoutNanoseconds(timeout)) - state.resume(.failure(.timeout(timeoutMessage))) + state.resume(.failure(.timeout)) } } } onCancel: { - state.resume(.failure(.userCancelled("user presence verification was cancelled"))) + state.resume(.failure(.userCancelled)) } } private static func timeoutNanoseconds(_ timeout: TimeInterval) throws -> UInt64 { guard timeout.isFinite, timeout > 0 else { - throw RadrootsUserPresenceError.invalidRequest("user presence timeout must be finite and greater than zero") + throw RadrootsUserPresenceError.invalidRequest } let nanoseconds = timeout * 1_000_000_000 guard nanoseconds <= Double(UInt64.max) else { - throw RadrootsUserPresenceError.invalidRequest("user presence timeout is too large") + throw RadrootsUserPresenceError.invalidRequest } return UInt64(nanoseconds) } } -private final class RadrootsAppleUserPresenceAsyncCallbackState<Value: Sendable>: @unchecked Sendable { +private final class RadrootsAppleUserPresenceAsyncCallbackState<Value: Sendable>: + @unchecked Sendable +{ private let lock = NSLock() private var continuation: CheckedContinuation<Value, any Error>? private var didResolve = false @@ -206,7 +228,7 @@ private final class RadrootsAppleUserPresenceAsyncCallbackState<Value: Sendable> lock.lock() defer { lock.unlock() } guard !didResolve else { - continuation.resume(throwing: RadrootsUserPresenceError.transientFailure("user presence verification already resolved")) + continuation.resume(throwing: RadrootsUserPresenceError.transientFailure) return } self.continuation = continuation @@ -225,9 +247,9 @@ private final class RadrootsAppleUserPresenceAsyncCallbackState<Value: Sendable> lock.unlock() switch result { - case let .success(value): + case .success(let value): pending?.resume(returning: value) - case let .failure(error): + case .failure(let error): pending?.resume(throwing: error) } } diff --git a/Sources/RadrootsKit/RadrootsBackgroundTasks.swift b/Sources/RadrootsKit/RadrootsBackgroundTasks.swift @@ -6,20 +6,17 @@ public enum RadrootsBackgroundTaskKind: String, Sendable, Equatable, Hashable, C } public enum RadrootsBackgroundTaskError: Error, Equatable, Sendable { - case invalidRequest(String) - case unavailable(String) - case schedulerFailure(String) + case invalidRequest + case unavailable + case schedulerFailure } extension RadrootsBackgroundTaskError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .unavailable(message): - message - case let .schedulerFailure(message): - message + case .invalidRequest: "The background task request is invalid." + case .unavailable: "Background tasks are unavailable." + case .schedulerFailure: "The background task could not be scheduled." } } } @@ -103,7 +100,7 @@ public struct RadrootsBackgroundTaskSnapshot: Sendable, Equatable, Hashable { requiresExternalPower: requiresExternalPower ) guard submittedAt.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsBackgroundTaskError.invalidRequest("background task submitted date must be finite") + throw RadrootsBackgroundTaskError.invalidRequest } self.identifier = identifier self.kind = kind @@ -126,34 +123,32 @@ public struct RadrootsBackgroundTaskSnapshot: Sendable, Equatable, Hashable { } public protocol RadrootsBackgroundTaskScheduler: Sendable { - func submit(_ request: RadrootsBackgroundTaskRequest) async throws -> RadrootsBackgroundTaskSnapshot + func submit(_ request: RadrootsBackgroundTaskRequest) async throws + -> RadrootsBackgroundTaskSnapshot func cancel(_ identifier: RadrootsBackgroundTaskIdentifier) async throws func cancelAll() async throws func pendingTasks() async throws -> [RadrootsBackgroundTaskSnapshot] } public struct RadrootsUnavailableBackgroundTaskScheduler: RadrootsBackgroundTaskScheduler, Sendable { - private let reason: String + public init() {} - public init(reason: String = "background task scheduling is unavailable on this platform") { - let trimmedReason = reason.trimmingCharacters(in: .whitespacesAndNewlines) - self.reason = trimmedReason.isEmpty ? "background task scheduling is unavailable on this platform" : trimmedReason - } - - public func submit(_: RadrootsBackgroundTaskRequest) async throws -> RadrootsBackgroundTaskSnapshot { - throw RadrootsBackgroundTaskError.unavailable(reason) + public func submit(_: RadrootsBackgroundTaskRequest) async throws + -> RadrootsBackgroundTaskSnapshot + { + throw RadrootsBackgroundTaskError.unavailable } public func cancel(_: RadrootsBackgroundTaskIdentifier) async throws { - throw RadrootsBackgroundTaskError.unavailable(reason) + throw RadrootsBackgroundTaskError.unavailable } public func cancelAll() async throws { - throw RadrootsBackgroundTaskError.unavailable(reason) + throw RadrootsBackgroundTaskError.unavailable } public func pendingTasks() async throws -> [RadrootsBackgroundTaskSnapshot] { - throw RadrootsBackgroundTaskError.unavailable(reason) + throw RadrootsBackgroundTaskError.unavailable } } @@ -161,19 +156,21 @@ public enum RadrootsBackgroundTaskValidation { public static func normalizedIdentifier(_ value: String) throws -> String { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard !trimmed.isEmpty else { - throw RadrootsBackgroundTaskError.invalidRequest("background task identifier must not be empty") + throw RadrootsBackgroundTaskError.invalidRequest } guard trimmed.count <= 255 else { - throw RadrootsBackgroundTaskError.invalidRequest("background task identifier is too long") + throw RadrootsBackgroundTaskError.invalidRequest } - guard trimmed.range( + guard + trimmed.range( of: "^[a-z0-9][a-z0-9._-]*[a-z0-9]$|^[a-z0-9]$", options: .regularExpression - ) != nil else { - throw RadrootsBackgroundTaskError.invalidRequest("background task identifier must use lowercase safe identifier characters") + ) != nil + else { + throw RadrootsBackgroundTaskError.invalidRequest } guard !trimmed.contains("..") else { - throw RadrootsBackgroundTaskError.invalidRequest("background task identifier cannot contain empty path components") + throw RadrootsBackgroundTaskError.invalidRequest } return trimmed } @@ -186,11 +183,11 @@ public enum RadrootsBackgroundTaskValidation { ) throws { if let earliestBeginDate { guard earliestBeginDate.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsBackgroundTaskError.invalidRequest("background task earliest begin date must be finite") + throw RadrootsBackgroundTaskError.invalidRequest } } guard kind == .processing || (!requiresNetworkConnectivity && !requiresExternalPower) else { - throw RadrootsBackgroundTaskError.invalidRequest("app refresh tasks cannot require network connectivity or external power") + throw RadrootsBackgroundTaskError.invalidRequest } } } diff --git a/Sources/RadrootsKit/RadrootsBackgroundTransfer.swift b/Sources/RadrootsKit/RadrootsBackgroundTransfer.swift @@ -1,19 +1,19 @@ import Foundation public enum RadrootsBackgroundTransferError: Error, Equatable, Sendable { - case invalidRequest(String) - case unavailable(String) - case transferFailure(String) - case persistenceFailure(String) + case invalidRequest + case unavailable + case transferFailure + case persistenceFailure } extension RadrootsBackgroundTransferError: LocalizedError { public var errorDescription: String? { switch self { - case .invalidRequest(let message): message - case .unavailable(let message): message - case .transferFailure(let message): message - case .persistenceFailure(let message): message + case .invalidRequest: "The background transfer request is invalid." + case .unavailable: "Background transfer is unavailable." + case .transferFailure: "The background transfer could not be completed." + case .persistenceFailure: "The background transfer state could not be saved." } } } @@ -83,8 +83,26 @@ public enum RadrootsBackgroundTransferState: String, Sendable, Equatable, Hashab case interrupted } -public enum RadrootsBackgroundTransferNetworkPolicy: String, Sendable, Equatable, Hashable, Codable +public enum RadrootsBackgroundTransferFailure: String, Sendable, Equatable, Hashable, Codable, + CaseIterable { + case enqueueFailed = "background_transfer_enqueue_failed" + case expired = "background_transfer_expired" + case interrupted = "background_transfer_interrupted" + case verificationRejected = "background_transfer_verification_rejected" + case transferTooLarge = "background_transfer_transfer_too_large" + case responseTooLarge = "background_transfer_response_too_large" + case responseMediaType = "background_transfer_response_media_type" + case responseContentEncoding = "background_transfer_response_content_encoding" + case platformFailure = "background_transfer_platform_failure" + case responseMissing = "background_transfer_response_missing" + case httpStatus = "background_transfer_http_status" + case responseInvalid = "background_transfer_response_invalid" + case downloadStagingFailure = "background_transfer_download_staging_failure" + case destinationFailure = "background_transfer_destination_failure" +} + +public enum RadrootsBackgroundTransferNetworkPolicy: String, Sendable, Equatable, Hashable, Codable { case publicHTTPS case simulatorLoopbackHTTP } @@ -95,16 +113,14 @@ public struct RadrootsBackgroundTransferResponsePolicy: Sendable, Equatable, Has public init(maximumBodyBytes: Int = 0, acceptedMediaTypes: [String] = []) throws { guard (0...65536).contains(maximumBodyBytes), acceptedMediaTypes.count <= 8 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response policy is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } let normalized = try acceptedMediaTypes.map { try RadrootsBackgroundTransferValidation.normalizedMediaType($0) } guard Set(normalized).count == normalized.count, (maximumBodyBytes == 0) == normalized.isEmpty else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response policy is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } self.maximumBodyBytes = maximumBodyBytes self.acceptedMediaTypes = normalized @@ -151,8 +167,7 @@ public struct RadrootsBackgroundTransferResponse: Sendable, Equatable, Hashable, statusCode: Int, mediaType: String?, contentEncoding: String? = nil, body: Data? ) throws { guard (100...599).contains(statusCode), body?.count ?? 0 <= 65536 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } self.statusCode = statusCode self.mediaType = try mediaType.map { @@ -161,8 +176,7 @@ public struct RadrootsBackgroundTransferResponse: Sendable, Equatable, Hashable, let normalizedEncoding = contentEncoding?.trimmingCharacters(in: .whitespacesAndNewlines) .lowercased() guard normalizedEncoding == nil || normalizedEncoding == "identity" else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response content encoding is denied") + throw RadrootsBackgroundTransferError.invalidRequest } self.contentEncoding = normalizedEncoding self.body = body @@ -312,8 +326,7 @@ public struct RadrootsBackgroundDownloadedArtifact: Sendable, Equatable, Hashabl guard sha256.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil, byteSize > 0, byteSize <= RadrootsBackgroundTransferRequest.absoluteMaximumTransferBytes else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer downloaded artifact is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } try RadrootsBackgroundTransferValidation.validateLocalFile(.file(file)) self.file = file @@ -331,7 +344,7 @@ public struct RadrootsBackgroundDownloadedArtifact: Sendable, Equatable, Hashabl public enum RadrootsBackgroundTransferVerification: Sendable, Equatable { case accepted - case rejected(code: String) + case rejected(failure: RadrootsBackgroundTransferFailure) } public struct RadrootsBackgroundTransferHandle: Sendable, Equatable, Hashable, Codable, @@ -360,8 +373,7 @@ public struct RadrootsBackgroundTransferHandle: Sendable, Equatable, Hashable, C RadrootsBackgroundTransferIdentifier.self, forKey: .identifier) let request = try values.decode(RadrootsBackgroundTransferRequest.self, forKey: .request) guard identifier == request.identifier else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer handle identifier does not match its request") + throw RadrootsBackgroundTransferError.invalidRequest } self.init(request: request) } @@ -379,18 +391,14 @@ public struct RadrootsBackgroundTransferProgress: Sendable, Equatable, Hashable, public init(bytesTransferred: Int64, totalBytesExpected: Int64? = nil) throws { guard bytesTransferred >= 0 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer bytes transferred cannot be negative") + throw RadrootsBackgroundTransferError.invalidRequest } if let totalBytesExpected { guard totalBytesExpected >= 0 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer expected byte count cannot be negative") + throw RadrootsBackgroundTransferError.invalidRequest } guard totalBytesExpected >= bytesTransferred else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer expected byte count cannot be less than transferred bytes" - ) + throw RadrootsBackgroundTransferError.invalidRequest } } self.bytesTransferred = bytesTransferred @@ -432,7 +440,7 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, public let request: RadrootsBackgroundTransferRequest public let state: RadrootsBackgroundTransferState public let progress: RadrootsBackgroundTransferProgress - public let errorMessage: String? + public let failure: RadrootsBackgroundTransferFailure? public let response: RadrootsBackgroundTransferResponse? public let downloadedArtifact: RadrootsBackgroundDownloadedArtifact? public let possibleRemoteOrphan: Bool @@ -440,18 +448,17 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, public init( request: RadrootsBackgroundTransferRequest, state: RadrootsBackgroundTransferState = .queued, - progress: RadrootsBackgroundTransferProgress = .zero, errorMessage: String? = nil, + progress: RadrootsBackgroundTransferProgress = .zero, + failure: RadrootsBackgroundTransferFailure? = nil, response: RadrootsBackgroundTransferResponse? = nil, downloadedArtifact: RadrootsBackgroundDownloadedArtifact? = nil, possibleRemoteOrphan: Bool = false, updatedAt: Date = Date() ) throws { guard updatedAt.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer updated date must be finite") + throw RadrootsBackgroundTransferError.invalidRequest } guard response == nil || state == .awaitingVerification || state == .completed else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response requires verification state") + throw RadrootsBackgroundTransferError.invalidRequest } if let response { try RadrootsBackgroundTransferValidation.validate( @@ -461,8 +468,7 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, guard case .upload = request.operation, state == .failed || state == .interrupted || state == .cancelled || state == .expired else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer orphan marker is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } } if let downloadedArtifact { @@ -470,16 +476,14 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, destination == .file(downloadedArtifact.file), state == .awaitingVerification || state == .completed else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer downloaded artifact state is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } } identifier = request.identifier self.request = request self.state = state self.progress = progress - self.errorMessage = try RadrootsBackgroundTransferValidation.normalizedOptionalMessage( - errorMessage) + self.failure = failure self.response = response self.downloadedArtifact = downloadedArtifact self.possibleRemoteOrphan = possibleRemoteOrphan @@ -495,7 +499,7 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, func redactedForPersistence() throws -> Self { try Self( request: request.redactedForPersistence(), state: state, progress: progress, - errorMessage: errorMessage, response: response, + failure: failure, response: response, downloadedArtifact: downloadedArtifact, possibleRemoteOrphan: possibleRemoteOrphan, updatedAt: updatedAt ) @@ -505,7 +509,7 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, case request case state case progress - case errorMessage + case failure case response case downloadedArtifact case possibleRemoteOrphan @@ -518,7 +522,7 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, request: values.decode(RadrootsBackgroundTransferRequest.self, forKey: .request), state: values.decode(RadrootsBackgroundTransferState.self, forKey: .state), progress: values.decode(RadrootsBackgroundTransferProgress.self, forKey: .progress), - errorMessage: values.decodeIfPresent(String.self, forKey: .errorMessage), + failure: values.decodeIfPresent(RadrootsBackgroundTransferFailure.self, forKey: .failure), response: values.decodeIfPresent(RadrootsBackgroundTransferResponse.self, forKey: .response), downloadedArtifact: values.decodeIfPresent( RadrootsBackgroundDownloadedArtifact.self, forKey: .downloadedArtifact), @@ -533,7 +537,7 @@ public struct RadrootsBackgroundTransferSnapshot: Sendable, Equatable, Hashable, try values.encode(request, forKey: .request) try values.encode(state, forKey: .state) try values.encode(progress, forKey: .progress) - try values.encodeIfPresent(errorMessage, forKey: .errorMessage) + try values.encodeIfPresent(failure, forKey: .failure) try values.encodeIfPresent(response, forKey: .response) try values.encodeIfPresent(downloadedArtifact, forKey: .downloadedArtifact) try values.encode(possibleRemoteOrphan, forKey: .possibleRemoteOrphan) @@ -593,16 +597,14 @@ public struct RadrootsAppleBackgroundTransferFileResolver: RadrootsBackgroundTra let resolvedRoot = root.resolvingSymlinksInPath().standardizedFileURL let resolvedCandidate = candidate.resolvingSymlinksInPath().standardizedFileURL guard resolvedCandidate.path.hasPrefix(resolvedRoot.path + "/") else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer local file resolved outside its root") + throw RadrootsBackgroundTransferError.invalidRequest } return candidate } public func read(_ file: RadrootsBackgroundTransferLocalFile, maximumBytes: Int) throws -> Data { guard maximumBytes >= 0 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer local file byte limit is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } let root: URL let relativePath: String @@ -624,15 +626,13 @@ public struct RadrootsAppleBackgroundTransferFileResolver: RadrootsBackgroundTra maximumBytes: maximumBytes ) guard expectedBytes == nil || expectedBytes == data.count else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer local file size does not match its handle") + throw RadrootsBackgroundTransferError.invalidRequest } return data } catch let error as RadrootsBackgroundTransferError { throw error } catch { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer local file failed governed admission") + throw RadrootsBackgroundTransferError.invalidRequest } } } @@ -690,8 +690,7 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto let usedLegacyEncoding: Bool if let envelope = try? decoder.decode(Envelope.self, from: data) { guard envelope.schemaVersion == 1 else { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence version is unsupported") + throw RadrootsBackgroundTransferError.persistenceFailure } decoded = envelope.snapshots usedLegacyEncoding = false @@ -710,8 +709,7 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto } return snapshots } catch { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence could not be read") + throw RadrootsBackgroundTransferError.persistenceFailure } } @@ -723,8 +721,7 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto try snapshots.append(snapshot.redactedForPersistence()) try write(snapshots.sorted { left, right in left.identifier < right.identifier }) } catch let error as RadrootsBackgroundTransferError { throw error } catch { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence could not be written") + throw RadrootsBackgroundTransferError.persistenceFailure } } @@ -735,8 +732,7 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto snapshots.removeAll { $0.identifier == identifier } try write(snapshots) } catch let error as RadrootsBackgroundTransferError { throw error } catch { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence could not be written") + throw RadrootsBackgroundTransferError.persistenceFailure } } @@ -747,8 +743,7 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto try fileManager.removeItem(at: url) } } catch let error as RadrootsBackgroundTransferError { throw error } catch { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence could not be removed") + throw RadrootsBackgroundTransferError.persistenceFailure } } @@ -758,8 +753,7 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto at: url.deletingLastPathComponent(), withIntermediateDirectories: true) let data = try encoder.encode(Envelope(snapshots: snapshots)) guard data.count <= Self.maximumPersistenceBytes else { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence exceeds its byte limit") + throw RadrootsBackgroundTransferError.persistenceFailure } try data.write(to: url, options: [.atomic]) #if os(iOS) @@ -782,56 +776,49 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto private func requireProtectedData() throws { guard protectedData.currentState() == .available else { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer protected data is unavailable") + throw RadrootsBackgroundTransferError.persistenceFailure } } } public struct RadrootsUnavailableBackgroundTransfer: RadrootsBackgroundTransfer, Sendable { - private let reason: String - - public init(reason: String = "background transfer is unavailable on this platform") { - let trimmedReason = reason.trimmingCharacters(in: .whitespacesAndNewlines) - self.reason = - trimmedReason.isEmpty ? "background transfer is unavailable on this platform" : trimmedReason - } + public init() {} public func enqueue(_: RadrootsBackgroundTransferRequest) async throws -> RadrootsBackgroundTransferHandle { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func retry(_: RadrootsBackgroundTransferRequest) async throws -> RadrootsBackgroundTransferHandle { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func cancel(_: RadrootsBackgroundTransferIdentifier) async throws { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func expire(_: RadrootsBackgroundTransferIdentifier) async throws { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func settle( _: RadrootsBackgroundTransferIdentifier, verification _: RadrootsBackgroundTransferVerification ) async throws { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func snapshot(for _: RadrootsBackgroundTransferIdentifier) async throws -> RadrootsBackgroundTransferSnapshot? { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func snapshots() async throws -> [RadrootsBackgroundTransferSnapshot] { - throw RadrootsBackgroundTransferError.unavailable(reason) + throw RadrootsBackgroundTransferError.unavailable } public func handleEventsForBackgroundURLSession( @@ -845,24 +832,19 @@ public enum RadrootsBackgroundTransferValidation { public static func normalizedIdentifier(_ value: String) throws -> String { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard !trimmed.isEmpty else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier must not be empty") + throw RadrootsBackgroundTransferError.invalidRequest } guard trimmed.count <= 128 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier is too long") + throw RadrootsBackgroundTransferError.invalidRequest } guard trimmed.range(of: "^[a-z0-9][a-z0-9._-]*[a-z0-9]$|^[a-z0-9]$", options: .regularExpression) != nil else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier must use lowercase safe identifier characters" - ) + throw RadrootsBackgroundTransferError.invalidRequest } guard !trimmed.contains("..") else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier cannot contain empty path components") + throw RadrootsBackgroundTransferError.invalidRequest } return trimmed } @@ -881,44 +863,25 @@ public enum RadrootsBackgroundTransferValidation { guard maximumTransferBytes > 0, maximumTransferBytes <= RadrootsBackgroundTransferRequest.absoluteMaximumTransferBytes else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer byte limit is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } if case .download = operation, responsePolicy != .discard { - throw RadrootsBackgroundTransferError.invalidRequest( - "background downloads must discard the separate response body") + throw RadrootsBackgroundTransferError.invalidRequest } if let expectedSourceSHA256 { guard case .upload = operation, expectedSourceSHA256.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer source digest is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } } if case .upload(.stagedBlob(let blob)) = operation, UInt64(blob.sizeBytes) > maximumTransferBytes { - throw RadrootsBackgroundTransferError.invalidRequest( - "background upload source exceeds its byte limit") + throw RadrootsBackgroundTransferError.invalidRequest } } - public static func normalizedOptionalMessage(_ value: String?) throws -> String? { - guard let value else { return nil } - let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { return nil } - guard trimmed.count <= 240 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer message is too long") - } - guard doesNotContainControlCharacters(trimmed) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer message cannot contain control characters") - } - return trimmed - } - private static func validate( remoteURL: URL, networkPolicy: RadrootsBackgroundTransferNetworkPolicy ) throws { @@ -927,26 +890,22 @@ public enum RadrootsBackgroundTransferValidation { components.user == nil, components.password == nil, components.query == nil, components.fragment == nil else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer remote URL is unsafe") + throw RadrootsBackgroundTransferError.invalidRequest } let scheme = components.scheme?.lowercased() switch networkPolicy { case .publicHTTPS: guard scheme == "https" else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer remote URL must use public HTTPS") + throw RadrootsBackgroundTransferError.invalidRequest } case .simulatorLoopbackHTTP: #if os(iOS) && !targetEnvironment(simulator) - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer simulator policy is unavailable on this device") + throw RadrootsBackgroundTransferError.invalidRequest #else guard scheme == "http", let host = components.host?.lowercased(), host == "localhost" || host == "127.0.0.1" || host == "::1" else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer simulator URL must use loopback HTTP") + throw RadrootsBackgroundTransferError.invalidRequest } #endif } @@ -958,18 +917,15 @@ public enum RadrootsBackgroundTransferValidation { switch operation { case .download(let destination): guard method == .get else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background download transfers must use GET") + throw RadrootsBackgroundTransferError.invalidRequest } guard case .file = destination else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background downloads require a scoped destination file") + throw RadrootsBackgroundTransferError.invalidRequest } try validateLocalFile(destination) case .upload(let source): guard method == .post || method == .put else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background upload transfers must use POST or PUT") + throw RadrootsBackgroundTransferError.invalidRequest } try validateLocalFile(source) } @@ -983,8 +939,7 @@ public enum RadrootsBackgroundTransferValidation { !path.split(separator: "/", omittingEmptySubsequences: false).contains(where: { $0 == ".." } ) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer local file is unsafe") + throw RadrootsBackgroundTransferError.invalidRequest } case .stagedBlob(let blob): guard @@ -993,24 +948,21 @@ public enum RadrootsBackgroundTransferValidation { filenameHint: blob.filenameHint )) != nil else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer staged blob is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } } } private static func validate(headers: [String: String]) throws { guard headers.count <= 32 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer header count is too large") + throw RadrootsBackgroundTransferError.invalidRequest } for (key, value) in headers { try validateSafeText(key, field: "background transfer header name", maximumLength: 80) guard key.range(of: "^[!#$%&'*+.^_`|~0-9A-Za-z-]+$", options: .regularExpression) != nil, !["connection", "content-length", "host", "transfer-encoding"].contains(key.lowercased()) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer header name is unsafe") + throw RadrootsBackgroundTransferError.invalidRequest } try validateSafeText(value, field: "background transfer header value", maximumLength: 8192) } @@ -1018,8 +970,7 @@ public enum RadrootsBackgroundTransferValidation { private static func validate(metadata: [String: String]) throws { guard metadata.count <= 32 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer metadata count is too large") + throw RadrootsBackgroundTransferError.invalidRequest } for (key, value) in metadata { try validateSafeText(key, field: "background transfer metadata key", maximumLength: 80) @@ -1029,8 +980,7 @@ public enum RadrootsBackgroundTransferValidation { !["auth", "token", "secret", "credential", "cookie", "header", "path", "url"].contains( where: { unsafeKey.contains($0) }) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer metadata key is sensitive") + throw RadrootsBackgroundTransferError.invalidRequest } } } @@ -1045,8 +995,7 @@ public enum RadrootsBackgroundTransferValidation { != nil, normalized.utf8.count <= 127 else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer media type is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } return normalized } @@ -1055,13 +1004,11 @@ public enum RadrootsBackgroundTransferValidation { response: RadrootsBackgroundTransferResponse, policy: RadrootsBackgroundTransferResponsePolicy ) throws { guard (200...299).contains(response.statusCode) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response status is invalid") + throw RadrootsBackgroundTransferError.invalidRequest } if policy.maximumBodyBytes == 0 { guard response.body == nil, response.mediaType == nil else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response body is not permitted") + throw RadrootsBackgroundTransferError.invalidRequest } return } @@ -1069,22 +1016,20 @@ public enum RadrootsBackgroundTransferValidation { let mediaType = response.mediaType, policy.acceptedMediaTypes.contains(mediaType) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "background transfer response violates its policy") + throw RadrootsBackgroundTransferError.invalidRequest } } private static func validateSafeText(_ value: String, field: String, maximumLength: Int) throws { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsBackgroundTransferError.invalidRequest("\(field) must not be empty") + throw RadrootsBackgroundTransferError.invalidRequest } guard trimmed.count <= maximumLength else { - throw RadrootsBackgroundTransferError.invalidRequest("\(field) is too long") + throw RadrootsBackgroundTransferError.invalidRequest } guard doesNotContainControlCharacters(trimmed) else { - throw RadrootsBackgroundTransferError.invalidRequest( - "\(field) cannot contain control characters") + throw RadrootsBackgroundTransferError.invalidRequest } } diff --git a/Sources/RadrootsKit/RadrootsCaptureIntake.swift b/Sources/RadrootsKit/RadrootsCaptureIntake.swift @@ -1,29 +1,23 @@ import Foundation public enum RadrootsCaptureIntakeError: Error, Equatable, Sendable { - case invalidRequest(String) - case unavailable(String) - case permissionDenied(String) - case userCancelled(String) - case transientFailure(String) - case permanentFailure(String) + case invalidRequest + case unavailable + case permissionDenied + case userCancelled + case transientFailure + case permanentFailure } extension RadrootsCaptureIntakeError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .unavailable(message): - message - case let .permissionDenied(message): - message - case let .userCancelled(message): - message - case let .transientFailure(message): - message - case let .permanentFailure(message): - message + case .invalidRequest: "The capture request is invalid." + case .unavailable: "Capture is unavailable." + case .permissionDenied: "Capture permission was denied." + case .userCancelled: "Capture was cancelled." + case .transientFailure: "Capture could not be completed temporarily." + case .permanentFailure: "Capture could not be completed." } } } @@ -51,7 +45,8 @@ public struct RadrootsMediaImportRequest: Sendable, Equatable, Hashable { allowedMediaKinds, field: "media import" ) - self.selectionLimit = try RadrootsCaptureIntakeValidation.normalizedSelectionLimit(selectionLimit) + self.selectionLimit = try RadrootsCaptureIntakeValidation.normalizedSelectionLimit( + selectionLimit) self.destinationScope = destinationScope } } @@ -85,11 +80,15 @@ public struct RadrootsMediaPickerSupport: Sendable, Equatable, Hashable { ) throws { self.importAvailable = importAvailable self.cameraCaptureAvailable = cameraCaptureAvailable - self.supportedImportKinds = importAvailable - ? try RadrootsCaptureIntakeValidation.normalizedMediaKinds(supportedImportKinds, field: "media import support") + self.supportedImportKinds = + importAvailable + ? try RadrootsCaptureIntakeValidation.normalizedMediaKinds( + supportedImportKinds, field: "media import support") : [] - self.supportedCaptureKinds = cameraCaptureAvailable - ? try RadrootsCaptureIntakeValidation.normalizedMediaKinds(supportedCaptureKinds, field: "camera capture support") + self.supportedCaptureKinds = + cameraCaptureAvailable + ? try RadrootsCaptureIntakeValidation.normalizedMediaKinds( + supportedCaptureKinds, field: "camera capture support") : [] self.multipleSelectionSupported = multipleSelectionSupported } @@ -121,16 +120,20 @@ public struct RadrootsMediaAsset: Sendable, Equatable, Hashable { self.kind = kind self.file = file self.mediaType = try RadrootsCaptureIntakeValidation.normalizedMediaType(mediaType) - self.suggestedFilename = try RadrootsCaptureIntakeValidation.normalizedFilename(suggestedFilename) + self.suggestedFilename = try RadrootsCaptureIntakeValidation.normalizedFilename( + suggestedFilename) self.sizeBytes = sizeBytes - self.pixelWidth = try RadrootsCaptureIntakeValidation.normalizedDimension(pixelWidth, field: "pixel width") - self.pixelHeight = try RadrootsCaptureIntakeValidation.normalizedDimension(pixelHeight, field: "pixel height") + self.pixelWidth = try RadrootsCaptureIntakeValidation.normalizedDimension( + pixelWidth, field: "pixel width") + self.pixelHeight = try RadrootsCaptureIntakeValidation.normalizedDimension( + pixelHeight, field: "pixel height") if self.pixelWidth == nil || self.pixelHeight == nil { guard self.pixelWidth == nil, self.pixelHeight == nil else { - throw RadrootsCaptureIntakeError.invalidRequest("image dimensions must include width and height together") + throw RadrootsCaptureIntakeError.invalidRequest } } - self.capturedAt = try RadrootsCaptureIntakeValidation.normalizedDate(capturedAt, field: "captured timestamp") + self.capturedAt = try RadrootsCaptureIntakeValidation.normalizedDate( + capturedAt, field: "captured timestamp") } } @@ -139,7 +142,7 @@ public struct RadrootsMediaImportResult: Sendable, Equatable, Hashable { public init(items: [RadrootsMediaAsset]) throws { guard !items.isEmpty else { - throw RadrootsCaptureIntakeError.invalidRequest("media import result cannot be empty") + throw RadrootsCaptureIntakeError.invalidRequest } self.items = items } @@ -156,7 +159,8 @@ public struct RadrootsMediaCaptureResult: Sendable, Equatable, Hashable { public protocol RadrootsMediaPicker: Sendable { func currentSupport() async throws -> RadrootsMediaPickerSupport func importMedia(_ request: RadrootsMediaImportRequest) async throws -> RadrootsMediaImportResult - func captureMedia(_ request: RadrootsMediaCaptureRequest) async throws -> RadrootsMediaCaptureResult + func captureMedia(_ request: RadrootsMediaCaptureRequest) async throws + -> RadrootsMediaCaptureResult } public enum RadrootsDocumentScannerOutputKind: String, Sendable, Equatable, Hashable, CaseIterable { @@ -175,7 +179,8 @@ public struct RadrootsDocumentScannerSupport: Sendable, Equatable, Hashable { ) throws { self.interactiveScanAvailable = interactiveScanAvailable self.multiPageSupported = multiPageSupported && interactiveScanAvailable - self.supportedOutputKinds = interactiveScanAvailable + self.supportedOutputKinds = + interactiveScanAvailable ? try RadrootsCaptureIntakeValidation.normalizedScannerOutputKinds(supportedOutputKinds) : [] } @@ -214,14 +219,16 @@ public struct RadrootsScannedDocument: Sendable, Equatable, Hashable { ) throws { self.file = file self.outputKind = outputKind - self.suggestedFilename = try RadrootsCaptureIntakeValidation.normalizedFilename(suggestedFilename) + self.suggestedFilename = try RadrootsCaptureIntakeValidation.normalizedFilename( + suggestedFilename) self.mediaType = try RadrootsCaptureIntakeValidation.normalizedMediaType(mediaType) guard pageCount > 0 else { - throw RadrootsCaptureIntakeError.invalidRequest("scanned document page count must be positive") + throw RadrootsCaptureIntakeError.invalidRequest } self.pageCount = pageCount self.sizeBytes = sizeBytes - self.capturedAt = try RadrootsCaptureIntakeValidation.normalizedDate(capturedAt, field: "scanned document timestamp") + self.capturedAt = try RadrootsCaptureIntakeValidation.normalizedDate( + capturedAt, field: "scanned document timestamp") } } @@ -231,7 +238,9 @@ public protocol RadrootsDocumentScanner: Sendable { } public enum RadrootsCaptureIntakeValidation { - public static func normalizedMediaKinds(_ kinds: [RadrootsMediaKind], field: String) throws -> [RadrootsMediaKind] { + public static func normalizedMediaKinds(_ kinds: [RadrootsMediaKind], field: String) throws + -> [RadrootsMediaKind] + { var seen = Set<RadrootsMediaKind>() let normalized = kinds.filter { kind in if seen.contains(kind) { @@ -241,17 +250,17 @@ public enum RadrootsCaptureIntakeValidation { return true } guard !normalized.isEmpty else { - throw RadrootsCaptureIntakeError.invalidRequest("\(field) must allow at least one media kind") + throw RadrootsCaptureIntakeError.invalidRequest } return normalized } public static func normalizedSelectionLimit(_ selectionLimit: Int) throws -> Int { guard selectionLimit > 0 else { - throw RadrootsCaptureIntakeError.invalidRequest("media import selection limit must be positive") + throw RadrootsCaptureIntakeError.invalidRequest } guard selectionLimit <= 100 else { - throw RadrootsCaptureIntakeError.invalidRequest("media import selection limit cannot exceed 100") + throw RadrootsCaptureIntakeError.invalidRequest } return selectionLimit } @@ -268,7 +277,7 @@ public enum RadrootsCaptureIntakeValidation { return true } guard !normalized.isEmpty else { - throw RadrootsCaptureIntakeError.invalidRequest("document scanner must support at least one output kind") + throw RadrootsCaptureIntakeError.invalidRequest } return normalized } @@ -276,22 +285,22 @@ public enum RadrootsCaptureIntakeValidation { public static func normalizedFilename(_ filename: String) throws -> String { let trimmed = filename.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsCaptureIntakeError.invalidRequest("capture filename cannot be empty") + throw RadrootsCaptureIntakeError.invalidRequest } guard trimmed != ".", trimmed != ".." else { - throw RadrootsCaptureIntakeError.invalidRequest("capture filename cannot be a path segment") + throw RadrootsCaptureIntakeError.invalidRequest } guard !NSString(string: trimmed).isAbsolutePath else { - throw RadrootsCaptureIntakeError.invalidRequest("capture filename cannot be absolute") + throw RadrootsCaptureIntakeError.invalidRequest } guard !trimmed.contains("/"), !trimmed.contains("\\"), !trimmed.contains("\0") else { - throw RadrootsCaptureIntakeError.invalidRequest("capture filename cannot contain path separators") + throw RadrootsCaptureIntakeError.invalidRequest } guard trimmed.rangeOfCharacter(from: .controlCharacters) == nil else { - throw RadrootsCaptureIntakeError.invalidRequest("capture filename cannot contain control characters") + throw RadrootsCaptureIntakeError.invalidRequest } guard trimmed.utf8.count <= 255 else { - throw RadrootsCaptureIntakeError.invalidRequest("capture filename is too long") + throw RadrootsCaptureIntakeError.invalidRequest } return trimmed } @@ -299,14 +308,15 @@ public enum RadrootsCaptureIntakeValidation { public static func normalizedMediaType(_ mediaType: String) throws -> String { let trimmed = mediaType.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsCaptureIntakeError.invalidRequest("capture media type cannot be empty") + throw RadrootsCaptureIntakeError.invalidRequest } - guard trimmed.rangeOfCharacter(from: .whitespacesAndNewlines.union(.controlCharacters)) == nil else { - throw RadrootsCaptureIntakeError.invalidRequest("capture media type cannot contain whitespace") + guard trimmed.rangeOfCharacter(from: .whitespacesAndNewlines.union(.controlCharacters)) == nil + else { + throw RadrootsCaptureIntakeError.invalidRequest } let parts = trimmed.split(separator: "/", omittingEmptySubsequences: false) guard parts.count == 2, parts.allSatisfy({ !$0.isEmpty }) else { - throw RadrootsCaptureIntakeError.invalidRequest("capture media type must be type/subtype") + throw RadrootsCaptureIntakeError.invalidRequest } return trimmed.lowercased() } @@ -316,14 +326,14 @@ public enum RadrootsCaptureIntakeValidation { return nil } guard dimension > 0 else { - throw RadrootsCaptureIntakeError.invalidRequest("\(field) must be positive") + throw RadrootsCaptureIntakeError.invalidRequest } return dimension } public static func normalizedDate(_ date: Date, field: String) throws -> Date { guard date.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsCaptureIntakeError.invalidRequest("\(field) must be finite") + throw RadrootsCaptureIntakeError.invalidRequest } return date } diff --git a/Sources/RadrootsKit/RadrootsDocumentInterchange.swift b/Sources/RadrootsKit/RadrootsDocumentInterchange.swift @@ -9,29 +9,23 @@ public enum RadrootsDocumentContentKind: String, Sendable, Equatable, Hashable, } public enum RadrootsDocumentInterchangeError: Error, Equatable, Sendable { - case invalidRequest(String) - case notFound(String) - case userCancelled(String) - case permissionDenied(String) - case transientFailure(String) - case permanentFailure(String) + case invalidRequest + case notFound + case userCancelled + case permissionDenied + case transientFailure + case permanentFailure } extension RadrootsDocumentInterchangeError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .notFound(message): - message - case let .userCancelled(message): - message - case let .permissionDenied(message): - message - case let .transientFailure(message): - message - case let .permanentFailure(message): - message + case .invalidRequest: "The document request is invalid." + case .notFound: "The document was not found." + case .userCancelled: "The document operation was cancelled." + case .permissionDenied: "Document access was denied." + case .transientFailure: "The document operation could not be completed temporarily." + case .permanentFailure: "The document operation could not be completed." } } } @@ -64,7 +58,7 @@ public struct RadrootsDocumentImportRequest: Sendable, Equatable, Hashable { return true } guard !normalized.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("document import must allow at least one content kind") + throw RadrootsDocumentInterchangeError.invalidRequest } return normalized } @@ -86,7 +80,8 @@ public struct RadrootsImportedDocument: Sendable, Equatable, Hashable { ) throws { self.file = file self.originalURL = try Self.normalizedOriginalURL(originalURL) - self.suggestedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename(suggestedFilename) + self.suggestedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename( + suggestedFilename) self.mediaType = try RadrootsDocumentInterchangeValidation.normalizedMediaType(mediaType) self.sizeBytes = sizeBytes } @@ -96,7 +91,7 @@ public struct RadrootsImportedDocument: Sendable, Equatable, Hashable { return nil } guard originalURL.isFileURL else { - throw RadrootsDocumentInterchangeError.invalidRequest("imported document original url must be a file url") + throw RadrootsDocumentInterchangeError.invalidRequest } return originalURL.standardizedFileURL } @@ -107,7 +102,7 @@ public struct RadrootsDocumentImportResult: Sendable, Equatable, Hashable { public init(documents: [RadrootsImportedDocument]) throws { guard !documents.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("document import result cannot be empty") + throw RadrootsDocumentInterchangeError.invalidRequest } self.documents = documents } @@ -116,11 +111,13 @@ public struct RadrootsDocumentImportResult: Sendable, Equatable, Hashable { public enum RadrootsShareItem: Sendable, Equatable, Hashable { case text(String) case url(URL) - case file(RadrootsFileReference, suggestedFilename: String?, mediaType: String?, sizeBytes: UInt64?) + case file( + RadrootsFileReference, suggestedFilename: String?, mediaType: String?, sizeBytes: UInt64?) case stagedBlob(RadrootsStagedBlobReference, suggestedFilename: String?) public static func validatedText(_ value: String) throws -> Self { - try .text(RadrootsDocumentInterchangeValidation.normalizedPublicText(value, field: "share text")) + try .text( + RadrootsDocumentInterchangeValidation.normalizedPublicText(value, field: "share text")) } public static func validatedURL(_ value: URL) throws -> Self { @@ -133,10 +130,12 @@ public enum RadrootsShareItem: Sendable, Equatable, Hashable { mediaType: String? = nil, sizeBytes: UInt64? = nil ) throws -> Self { - let normalizedFile = try RadrootsDocumentInterchangeValidation.normalizedScopedFileReference(file) + let normalizedFile = try RadrootsDocumentInterchangeValidation.normalizedScopedFileReference( + file) return try .file( normalizedFile, - suggestedFilename: RadrootsDocumentInterchangeValidation.normalizedOptionalFilename(suggestedFilename), + suggestedFilename: RadrootsDocumentInterchangeValidation.normalizedOptionalFilename( + suggestedFilename), mediaType: RadrootsDocumentInterchangeValidation.normalizedMediaType(mediaType), sizeBytes: sizeBytes ) @@ -152,20 +151,22 @@ public enum RadrootsShareItem: Sendable, Equatable, Hashable { ) return try .stagedBlob( stagedBlob, - suggestedFilename: RadrootsDocumentInterchangeValidation.normalizedOptionalFilename(suggestedFilename) + suggestedFilename: RadrootsDocumentInterchangeValidation.normalizedOptionalFilename( + suggestedFilename) ) } public var normalized: Self { get throws { switch self { - case let .text(text): + case .text(let text): try Self.validatedText(text) - case let .url(url): + case .url(let url): try Self.validatedURL(url) - case let .file(file, suggestedFilename, mediaType, sizeBytes): - try Self.validatedFile(file, suggestedFilename: suggestedFilename, mediaType: mediaType, sizeBytes: sizeBytes) - case let .stagedBlob(stagedBlob, suggestedFilename): + case .file(let file, let suggestedFilename, let mediaType, let sizeBytes): + try Self.validatedFile( + file, suggestedFilename: suggestedFilename, mediaType: mediaType, sizeBytes: sizeBytes) + case .stagedBlob(let stagedBlob, let suggestedFilename): try Self.validatedStagedBlob(stagedBlob, suggestedFilename: suggestedFilename) } } @@ -179,10 +180,11 @@ public struct RadrootsShareRequest: Sendable, Equatable, Hashable { public init(items: [RadrootsShareItem], subject: String? = nil) throws { let normalizedItems = try items.map { try $0.normalized } guard !normalizedItems.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("share request must contain at least one item") + throw RadrootsDocumentInterchangeError.invalidRequest } self.items = normalizedItems - self.subject = try RadrootsDocumentInterchangeValidation.normalizedOptionalPublicText(subject, field: "share subject") + self.subject = try RadrootsDocumentInterchangeValidation.normalizedOptionalPublicText( + subject, field: "share subject") } } @@ -213,7 +215,8 @@ public struct RadrootsExportDocumentRequest: Sendable, Equatable, Hashable { sizeBytes: UInt64? = nil ) throws { self.source = source - self.suggestedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename(suggestedFilename) + self.suggestedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename( + suggestedFilename) self.mediaType = try RadrootsDocumentInterchangeValidation.normalizedMediaType(mediaType) self.sizeBytes = try Self.normalizedSizeBytes(source: source, requestedSizeBytes: sizeBytes) } @@ -223,18 +226,18 @@ public struct RadrootsExportDocumentRequest: Sendable, Equatable, Hashable { requestedSizeBytes: UInt64? ) throws -> UInt64? { switch source { - case let .inlineData(data): + case .inlineData(let data): let actualSize = UInt64(data.count) if let requestedSizeBytes, requestedSizeBytes != actualSize { - throw RadrootsDocumentInterchangeError.invalidRequest("inline export byte count does not match data size") + throw RadrootsDocumentInterchangeError.invalidRequest } return actualSize case .file: return requestedSizeBytes - case let .stagedBlob(stagedBlob): + case .stagedBlob(let stagedBlob): let actualSize = UInt64(stagedBlob.sizeBytes) if let requestedSizeBytes, requestedSizeBytes != actualSize { - throw RadrootsDocumentInterchangeError.invalidRequest("staged blob export byte count does not match reference size") + throw RadrootsDocumentInterchangeError.invalidRequest } return actualSize } @@ -251,7 +254,8 @@ public struct RadrootsExportDocumentResult: Sendable, Equatable, Hashable { mediaType: String?, sizeBytes: UInt64? ) throws { - self.exportedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename(exportedFilename) + self.exportedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename( + exportedFilename) self.mediaType = try RadrootsDocumentInterchangeValidation.normalizedMediaType(mediaType) self.sizeBytes = sizeBytes } @@ -273,7 +277,8 @@ public struct RadrootsPreparedExportDocument: Sendable, Equatable, Hashable { ) throws { self.preparedID = try Self.normalizedPreparedID(preparedID) self.fileURL = try Self.normalizedFileURL(fileURL) - self.suggestedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename(suggestedFilename) + self.suggestedFilename = try RadrootsDocumentInterchangeValidation.normalizedFilename( + suggestedFilename) self.mediaType = try RadrootsDocumentInterchangeValidation.normalizedMediaType(mediaType) self.sizeBytes = sizeBytes } @@ -281,18 +286,19 @@ public struct RadrootsPreparedExportDocument: Sendable, Equatable, Hashable { public static func normalizedPreparedID(_ preparedID: String) throws -> String { let trimmed = preparedID.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("prepared export id cannot be empty") + throw RadrootsDocumentInterchangeError.invalidRequest } - let allowed = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") + let allowed = CharacterSet( + charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") guard trimmed.rangeOfCharacter(from: allowed.inverted) == nil else { - throw RadrootsDocumentInterchangeError.invalidRequest("prepared export id contains invalid characters") + throw RadrootsDocumentInterchangeError.invalidRequest } return trimmed } public static func normalizedFileURL(_ fileURL: URL) throws -> URL { guard fileURL.isFileURL else { - throw RadrootsDocumentInterchangeError.invalidRequest("prepared export url must be a file url") + throw RadrootsDocumentInterchangeError.invalidRequest } return fileURL.standardizedFileURL } @@ -302,22 +308,22 @@ public enum RadrootsDocumentInterchangeValidation { public static func normalizedFilename(_ filename: String) throws -> String { let trimmed = filename.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("document filename cannot be empty") + throw RadrootsDocumentInterchangeError.invalidRequest } guard trimmed != ".", trimmed != ".." else { - throw RadrootsDocumentInterchangeError.invalidRequest("document filename cannot be a path segment") + throw RadrootsDocumentInterchangeError.invalidRequest } guard !NSString(string: trimmed).isAbsolutePath else { - throw RadrootsDocumentInterchangeError.invalidRequest("document filename cannot be absolute") + throw RadrootsDocumentInterchangeError.invalidRequest } guard !trimmed.contains("/"), !trimmed.contains("\\"), !trimmed.contains("\0") else { - throw RadrootsDocumentInterchangeError.invalidRequest("document filename cannot contain path separators") + throw RadrootsDocumentInterchangeError.invalidRequest } guard trimmed.rangeOfCharacter(from: .controlCharacters) == nil else { - throw RadrootsDocumentInterchangeError.invalidRequest("document filename cannot contain control characters") + throw RadrootsDocumentInterchangeError.invalidRequest } guard trimmed.utf8.count <= 255 else { - throw RadrootsDocumentInterchangeError.invalidRequest("document filename is too long") + throw RadrootsDocumentInterchangeError.invalidRequest } try validateNoSecretMaterial(trimmed, field: "document filename") return trimmed @@ -336,14 +342,15 @@ public enum RadrootsDocumentInterchangeValidation { } let trimmed = mediaType.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("document media type cannot be empty") + throw RadrootsDocumentInterchangeError.invalidRequest } - guard trimmed.rangeOfCharacter(from: .whitespacesAndNewlines.union(.controlCharacters)) == nil else { - throw RadrootsDocumentInterchangeError.invalidRequest("document media type cannot contain whitespace") + guard trimmed.rangeOfCharacter(from: .whitespacesAndNewlines.union(.controlCharacters)) == nil + else { + throw RadrootsDocumentInterchangeError.invalidRequest } let parts = trimmed.split(separator: "/", omittingEmptySubsequences: false) guard parts.count == 2, parts.allSatisfy({ !$0.isEmpty }) else { - throw RadrootsDocumentInterchangeError.invalidRequest("document media type must be type/subtype") + throw RadrootsDocumentInterchangeError.invalidRequest } return trimmed.lowercased() } @@ -351,7 +358,7 @@ public enum RadrootsDocumentInterchangeValidation { public static func normalizedPublicText(_ text: String, field: String) throws -> String { let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("\(field) cannot be empty") + throw RadrootsDocumentInterchangeError.invalidRequest } try validateNoSecretMaterial(trimmed, field: field) return trimmed @@ -366,32 +373,34 @@ public enum RadrootsDocumentInterchangeValidation { public static func normalizedPublicURL(_ url: URL) throws -> URL { guard let scheme = url.scheme?.lowercased(), scheme == "https" || scheme == "http" else { - throw RadrootsDocumentInterchangeError.invalidRequest("share url must be http or https") + throw RadrootsDocumentInterchangeError.invalidRequest } guard url.host != nil else { - throw RadrootsDocumentInterchangeError.invalidRequest("share url must include a host") + throw RadrootsDocumentInterchangeError.invalidRequest } try validateNoSecretMaterial(url.absoluteString, field: "share url") return url } - public static func normalizedScopedFileReference(_ file: RadrootsFileReference) throws -> RadrootsFileReference { + public static func normalizedScopedFileReference(_ file: RadrootsFileReference) throws + -> RadrootsFileReference + { let trimmed = file.relativePath.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsDocumentInterchangeError.invalidRequest("share file path cannot be empty") + throw RadrootsDocumentInterchangeError.invalidRequest } guard !NSString(string: trimmed).isAbsolutePath else { - throw RadrootsDocumentInterchangeError.invalidRequest("share file path cannot be absolute") + throw RadrootsDocumentInterchangeError.invalidRequest } guard !trimmed.contains("\\"), !trimmed.contains("\0") else { - throw RadrootsDocumentInterchangeError.invalidRequest("share file path cannot contain unsafe separators") + throw RadrootsDocumentInterchangeError.invalidRequest } guard trimmed.rangeOfCharacter(from: .controlCharacters) == nil else { - throw RadrootsDocumentInterchangeError.invalidRequest("share file path cannot contain control characters") + throw RadrootsDocumentInterchangeError.invalidRequest } let components = trimmed.split(separator: "/", omittingEmptySubsequences: false) guard components.allSatisfy({ !$0.isEmpty && $0 != "." && $0 != ".." }) else { - throw RadrootsDocumentInterchangeError.invalidRequest("share file path cannot contain empty or parent segments") + throw RadrootsDocumentInterchangeError.invalidRequest } try validateNoSecretMaterial(trimmed, field: "share file path") return RadrootsFileReference(scope: file.scope, relativePath: trimmed) @@ -415,7 +424,7 @@ public enum RadrootsDocumentInterchangeValidation { "secret key", ] guard !unsafeFragments.contains(where: normalized.contains) else { - throw RadrootsDocumentInterchangeError.invalidRequest("\(field) cannot contain secret material") + throw RadrootsDocumentInterchangeError.invalidRequest } } } diff --git a/Sources/RadrootsKit/RadrootsDocumentPresentation.swift b/Sources/RadrootsKit/RadrootsDocumentPresentation.swift @@ -45,7 +45,9 @@ public enum RadrootsDocumentPresentationAdapter { ) } - public static func transferItem(for request: RadrootsShareRequest) throws -> RadrootsShareTransferItem { + public static func transferItem(for request: RadrootsShareRequest) throws + -> RadrootsShareTransferItem + { try transferItem(for: request, fileAccess: nil) } @@ -63,11 +65,11 @@ public enum RadrootsDocumentPresentationAdapter { ) throws -> RadrootsShareTransferItem { for item in request.items { switch try item.normalized { - case let .text(text): + case .text(let text): return try RadrootsShareTransferItem(text: text, subject: request.subject) - case let .url(url): + case .url(let url): return try RadrootsShareTransferItem(url: url, subject: request.subject) - case let .file(file, suggestedFilename, mediaType, sizeBytes): + case .file(let file, let suggestedFilename, let mediaType, let sizeBytes): guard let fileAccess else { continue } @@ -83,7 +85,7 @@ public enum RadrootsDocumentPresentationAdapter { ) ) return try RadrootsShareTransferItem(preparedExport: export, subject: request.subject) - case let .stagedBlob(stagedBlob, suggestedFilename): + case .stagedBlob(let stagedBlob, let suggestedFilename): guard let fileAccess else { continue } @@ -101,10 +103,12 @@ public enum RadrootsDocumentPresentationAdapter { return try RadrootsShareTransferItem(preparedExport: export, subject: request.subject) } } - throw RadrootsDocumentInterchangeError.invalidRequest("share request does not contain a supported public share item") + throw RadrootsDocumentInterchangeError.invalidRequest } - private static func shareFilename(explicitFilename: String?, fallbackFilename: String) throws -> String { + private static func shareFilename(explicitFilename: String?, fallbackFilename: String) throws + -> String + { if let explicitFilename { return try RadrootsDocumentInterchangeValidation.normalizedFilename(explicitFilename) } @@ -127,7 +131,9 @@ public struct RadrootsShareTransferItem: Transferable, Sendable, Equatable, Hash public let subject: String? public init(text: String, subject: String? = nil) throws { - payload = try .text(RadrootsDocumentInterchangeValidation.normalizedPublicText(text, field: "share transfer text")) + payload = try .text( + RadrootsDocumentInterchangeValidation.normalizedPublicText(text, field: "share transfer text") + ) self.subject = try RadrootsDocumentInterchangeValidation.normalizedOptionalPublicText( subject, field: "share transfer subject" @@ -152,9 +158,9 @@ public struct RadrootsShareTransferItem: Transferable, Sendable, Equatable, Hash public var text: String? { switch payload { - case let .text(text): + case .text(let text): text - case let .url(url): + case .url(let url): url.absoluteString case .file: nil @@ -162,14 +168,14 @@ public struct RadrootsShareTransferItem: Transferable, Sendable, Equatable, Hash } public var url: URL? { - guard case let .url(url) = payload else { + guard case .url(let url) = payload else { return nil } return url } public var preparedExport: RadrootsPreparedExportDocument? { - guard case let .file(preparedExport) = payload else { + guard case .file(let preparedExport) = payload else { return nil } return preparedExport @@ -177,11 +183,11 @@ public struct RadrootsShareTransferItem: Transferable, Sendable, Equatable, Hash public var transferText: String { switch payload { - case let .text(text): + case .text(let text): text - case let .url(url): + case .url(let url): url.absoluteString - case let .file(preparedExport): + case .file(let preparedExport): preparedExport.suggestedFilename } } @@ -203,7 +209,7 @@ public struct RadrootsPreparedExportFileDocument: FileDocument { } public init(configuration _: ReadConfiguration) throws { - throw RadrootsDocumentInterchangeError.invalidRequest("prepared export documents are write only") + throw RadrootsDocumentInterchangeError.invalidRequest } public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper { @@ -236,7 +242,9 @@ public struct RadrootsDocumentImportPresentationModifier: ViewModifier { } } ), - allowedContentTypes: request.map(RadrootsDocumentPresentationAdapter.contentTypes(for:)) ?? [.item], + allowedContentTypes: request.map(RadrootsDocumentPresentationAdapter.contentTypes(for:)) ?? [ + .item + ], allowsMultipleSelection: request?.allowsMultipleSelection ?? false ) { result in handleImportResult(result) @@ -251,7 +259,7 @@ public struct RadrootsDocumentImportPresentationModifier: ViewModifier { do { let urls = try result.get() guard !urls.isEmpty else { - throw RadrootsDocumentInterchangeError.userCancelled("document import was cancelled") + throw RadrootsDocumentInterchangeError.userCancelled } let documents = try urls.map { sourceURL in let destination = try RadrootsDocumentPresentationAdapter.importDestination( @@ -296,7 +304,8 @@ public struct RadrootsDocumentExportPresentationModifier: ViewModifier { } ), document: preparedExport.map(RadrootsPreparedExportFileDocument.init(preparedExport:)), - contentType: RadrootsDocumentPresentationAdapter.contentType(forMediaType: preparedExport?.mediaType), + contentType: RadrootsDocumentPresentationAdapter.contentType( + forMediaType: preparedExport?.mediaType), defaultFilename: preparedExport?.suggestedFilename ) { result in handleExportResult(result) @@ -353,21 +362,21 @@ public struct RadrootsSharePresentationLink<Label: View>: View { public var body: some View { switch transferItem.payload { - case let .text(text): + case .text(let text): ShareLink( item: text, subject: transferItem.subject.map(Text.init) ?? Text(""), message: Text(text), label: label ) - case let .url(url): + case .url(let url): ShareLink( item: url, subject: transferItem.subject.map(Text.init) ?? Text(""), message: Text(url.absoluteString), label: label ) - case let .file(preparedExport): + case .file(let preparedExport): ShareLink( item: preparedExport.fileURL, subject: transferItem.subject.map(Text.init) ?? Text(""), @@ -378,8 +387,8 @@ public struct RadrootsSharePresentationLink<Label: View>: View { } } -public extension View { - func radrootsDocumentImporter( +extension View { + public func radrootsDocumentImporter( request: Binding<RadrootsDocumentImportRequest?>, fileAccess: any RadrootsFileAccess, onCompletion: @escaping (Result<RadrootsDocumentImportResult, Error>) -> Void @@ -393,7 +402,7 @@ public extension View { ) } - func radrootsDocumentExporter( + public func radrootsDocumentExporter( preparedExport: Binding<RadrootsPreparedExportDocument?>, onCompletion: @escaping (Result<RadrootsExportDocumentResult, Error>) -> Void ) -> some View { diff --git a/Sources/RadrootsKit/RadrootsExternalActions.swift b/Sources/RadrootsKit/RadrootsExternalActions.swift @@ -1,6 +1,8 @@ import Foundation -public enum RadrootsExternalActionDestinationKind: String, Sendable, Equatable, Hashable, CaseIterable { +public enum RadrootsExternalActionDestinationKind: String, Sendable, Equatable, Hashable, + CaseIterable +{ case appSettings case web case nostr @@ -60,32 +62,28 @@ public struct RadrootsExternalActionCapability: Sendable, Equatable, Hashable { } public enum RadrootsExternalActionError: Error, Equatable, Sendable { - case invalidRequest(String) - case blockedByPolicy(String) - case unavailable(String) - case transientFailure(String) - case permanentFailure(String) + case invalidRequest + case blockedByPolicy + case unavailable + case transientFailure + case permanentFailure } extension RadrootsExternalActionError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .blockedByPolicy(message): - message - case let .unavailable(message): - message - case let .transientFailure(message): - message - case let .permanentFailure(message): - message + case .invalidRequest: "The external action request is invalid." + case .blockedByPolicy: "The external action is blocked by policy." + case .unavailable: "The external action is unavailable." + case .transientFailure: "The external action could not be completed temporarily." + case .permanentFailure: "The external action could not be completed." } } } public protocol RadrootsExternalActions: Sendable { - func canOpen(_ destination: RadrootsExternalActionDestination) async -> RadrootsExternalActionCapability + func canOpen(_ destination: RadrootsExternalActionDestination) async + -> RadrootsExternalActionCapability func open(_ request: RadrootsExternalActionRequest) async throws } @@ -100,7 +98,7 @@ public enum RadrootsExternalActionValidation { components.password == nil, let url = components.url else { - throw RadrootsExternalActionError.blockedByPolicy("external web urls must use https with a host") + throw RadrootsExternalActionError.blockedByPolicy } return url } @@ -109,25 +107,25 @@ public enum RadrootsExternalActionValidation { let trimmed = try trimmedNonEmpty(value, field: "nostr uri") try rejectWhitespaceOrControl(trimmed, field: "nostr uri") guard trimmed.lowercased().hasPrefix("nostr:") else { - throw RadrootsExternalActionError.blockedByPolicy("nostr uri must use the nostr scheme") + throw RadrootsExternalActionError.blockedByPolicy } let payload = String(trimmed.dropFirst("nostr:".count)) guard !payload.isEmpty else { - throw RadrootsExternalActionError.invalidRequest("nostr uri payload must not be empty") + throw RadrootsExternalActionError.invalidRequest } guard payload.range(of: "^[A-Za-z0-9]+$", options: .regularExpression) != nil else { - throw RadrootsExternalActionError.invalidRequest("nostr uri payload must be bech32-like public text") + throw RadrootsExternalActionError.invalidRequest } let normalizedPayload = payload.lowercased() if normalizedPayload.hasPrefix("nsec") { - throw RadrootsExternalActionError.blockedByPolicy("nostr secret payloads cannot be opened externally") + throw RadrootsExternalActionError.blockedByPolicy } let allowedPrefixes = ["npub1", "nprofile1", "note1", "nevent1", "naddr1", "nrelay1"] guard allowedPrefixes.contains(where: { normalizedPayload.hasPrefix($0) }) else { - throw RadrootsExternalActionError.blockedByPolicy("nostr uri payload must be a public Nostr identifier") + throw RadrootsExternalActionError.blockedByPolicy } guard let url = URL(string: "nostr:\(normalizedPayload)") else { - throw RadrootsExternalActionError.invalidRequest("nostr uri is not a valid url") + throw RadrootsExternalActionError.invalidRequest } return url } @@ -142,7 +140,7 @@ public enum RadrootsExternalActionValidation { components.password == nil, let url = components.url else { - throw RadrootsExternalActionError.blockedByPolicy("apple maps urls must use https://maps.apple.com") + throw RadrootsExternalActionError.blockedByPolicy } return url } @@ -159,7 +157,7 @@ public enum RadrootsExternalActionValidation { URLQueryItem( name: "ll", value: "\(coordinate.latitude),\(coordinate.longitude)" - ), + ) ] if let label { let normalizedLabel = try normalizedOptionalLabel(label) @@ -169,7 +167,7 @@ public enum RadrootsExternalActionValidation { } components.queryItems = queryItems guard let url = components.url else { - throw RadrootsExternalActionError.permanentFailure("failed to construct apple maps url") + throw RadrootsExternalActionError.permanentFailure } return url } @@ -177,7 +175,7 @@ public enum RadrootsExternalActionValidation { static func trimmedNonEmpty(_ value: String, field: String) throws -> String { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsExternalActionError.invalidRequest("\(field) must not be empty") + throw RadrootsExternalActionError.invalidRequest } return trimmed } @@ -185,7 +183,7 @@ public enum RadrootsExternalActionValidation { static func rejectWhitespaceOrControl(_ value: String, field: String) throws { let forbidden = CharacterSet.whitespacesAndNewlines.union(.controlCharacters) guard value.unicodeScalars.allSatisfy({ !forbidden.contains($0) }) else { - throw RadrootsExternalActionError.invalidRequest("\(field) cannot contain whitespace or control characters") + throw RadrootsExternalActionError.invalidRequest } } @@ -196,7 +194,7 @@ public enum RadrootsExternalActionValidation { } let forbidden = CharacterSet.controlCharacters guard trimmed.unicodeScalars.allSatisfy({ !forbidden.contains($0) }) else { - throw RadrootsExternalActionError.invalidRequest("apple maps label cannot contain control characters") + throw RadrootsExternalActionError.invalidRequest } return trimmed } diff --git a/Sources/RadrootsKit/RadrootsFileAccess.swift b/Sources/RadrootsKit/RadrootsFileAccess.swift @@ -54,7 +54,7 @@ public struct RadrootsStagedBlobReference: Sendable, Equatable, Hashable, Codabl ) throws { let normalizedBlobID = try Self.normalizedBlobID(blobID) guard sizeBytes >= 0 else { - throw RadrootsAppleFileError.invalidRequest("staged blob size cannot be negative") + throw RadrootsAppleFileError.invalidRequest } self.blobID = normalizedBlobID self.sizeBytes = sizeBytes @@ -65,11 +65,12 @@ public struct RadrootsStagedBlobReference: Sendable, Equatable, Hashable, Codabl public static func normalizedBlobID(_ blobID: String) throws -> String { let trimmed = blobID.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsAppleFileError.invalidRequest("staged blob id cannot be empty") + throw RadrootsAppleFileError.invalidRequest } - let allowed = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") + let allowed = CharacterSet( + charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") guard trimmed.rangeOfCharacter(from: allowed.inverted) == nil else { - throw RadrootsAppleFileError.invalidRequest("staged blob id contains invalid characters") + throw RadrootsAppleFileError.invalidRequest } return trimmed } @@ -80,10 +81,10 @@ public struct RadrootsStagedBlobReference: Sendable, Equatable, Hashable, Codabl } let trimmed = mediaType.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsAppleFileError.invalidRequest("staged blob media type cannot be empty") + throw RadrootsAppleFileError.invalidRequest } guard trimmed.rangeOfCharacter(from: .newlines) == nil else { - throw RadrootsAppleFileError.invalidRequest("staged blob media type cannot contain newlines") + throw RadrootsAppleFileError.invalidRequest } return trimmed } @@ -94,10 +95,10 @@ public struct RadrootsStagedBlobReference: Sendable, Equatable, Hashable, Codabl } let trimmed = filenameHint.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsAppleFileError.invalidRequest("staged blob filename hint cannot be empty") + throw RadrootsAppleFileError.invalidRequest } guard !trimmed.contains("/"), !trimmed.contains("\\"), !trimmed.contains("\0") else { - throw RadrootsAppleFileError.invalidRequest("staged blob filename hint cannot contain path separators") + throw RadrootsAppleFileError.invalidRequest } return trimmed } @@ -121,25 +122,33 @@ public enum RadrootsFileReadResult: Sendable, Equatable { public protocol RadrootsFileAccess { func write(_ payload: RadrootsFilePayload, to file: RadrootsFileReference) throws - func read(_ file: RadrootsFileReference, mode: RadrootsFileReadMode) throws -> RadrootsFileReadResult + func read(_ file: RadrootsFileReference, mode: RadrootsFileReadMode) throws + -> RadrootsFileReadResult func delete(_ file: RadrootsFileReference) throws func list(_ directory: RadrootsFileReference) throws -> [RadrootsFileEntry] func reset(scope: RadrootsFileScope) throws - @discardableResult func stageBlob(_ data: Data, mediaType: String?, filenameHint: String?) throws -> RadrootsStagedBlobReference - @discardableResult func stageFile(_ file: RadrootsFileReference, mediaType: String?, filenameHint: String?) throws -> RadrootsStagedBlobReference - @discardableResult func stageExternalFile(_ sourceURL: URL, mediaType: String?, filenameHint: String?) throws -> RadrootsStagedBlobReference + @discardableResult func stageBlob(_ data: Data, mediaType: String?, filenameHint: String?) throws + -> RadrootsStagedBlobReference + @discardableResult func stageFile( + _ file: RadrootsFileReference, mediaType: String?, filenameHint: String? + ) throws -> RadrootsStagedBlobReference + @discardableResult func stageExternalFile( + _ sourceURL: URL, mediaType: String?, filenameHint: String? + ) throws -> RadrootsStagedBlobReference @discardableResult func copyExternalFile( _ sourceURL: URL, to file: RadrootsFileReference, mediaType: String?, suggestedFilename: String? ) throws -> RadrootsImportedDocument - @discardableResult func prepareExport(_ request: RadrootsExportDocumentRequest) throws -> RadrootsPreparedExportDocument + @discardableResult func prepareExport(_ request: RadrootsExportDocumentRequest) throws + -> RadrootsPreparedExportDocument func preparedExportExists(_ preparedExport: RadrootsPreparedExportDocument) throws -> Bool func readStagedBlob(_ blob: RadrootsStagedBlobReference) throws -> Data func releaseStagedBlob(_ blob: RadrootsStagedBlobReference) throws func releasePreparedExport(_ preparedExport: RadrootsPreparedExportDocument) throws - @discardableResult func sweepStagedBlobs(olderThan cutoff: Date) throws -> [RadrootsStagedBlobReference] + @discardableResult func sweepStagedBlobs(olderThan cutoff: Date) throws + -> [RadrootsStagedBlobReference] func resetStagedBlobs() throws } @@ -158,18 +167,25 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { let url = try roots.resolvedURL(for: file) try createParentDirectory(for: url) switch payload { - case let .inline(inlineData): + case .inline(let inlineData): + try classifiedFileSystemOperation { try inlineData.write(to: url, options: [.atomic]) - case let .stagedBlob(stagedBlob): - try readStagedBlob(stagedBlob).write(to: url, options: [.atomic]) + } + case .stagedBlob(let stagedBlob): + let data = try readStagedBlob(stagedBlob) + try classifiedFileSystemOperation { + try data.write(to: url, options: [.atomic]) + } } } - public func read(_ file: RadrootsFileReference, mode: RadrootsFileReadMode) throws -> RadrootsFileReadResult { + public func read(_ file: RadrootsFileReference, mode: RadrootsFileReadMode) throws + -> RadrootsFileReadResult + { switch mode { - case let .inline(maxBytes): + case .inline(let maxBytes): return try .inline(readGovernedFile(file, maximumBytes: maxBytes)) - case let .preferInline(maxBytes): + case .preferInline(let maxBytes): do { return try .inline( readGovernedFile(file, maximumBytes: maxBytes, preserveTooLarge: true) @@ -191,8 +207,10 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { guard fileManager.fileExists(atPath: url.path) else { return } + try classifiedFileSystemOperation { try fileManager.removeItem(at: url) } + } public func list(_ directory: RadrootsFileReference) throws -> [RadrootsFileEntry] { let rootURL = roots.root(for: directory.scope).standardizedFileURL @@ -202,15 +220,18 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { return [] } guard isDirectory.boolValue else { - throw RadrootsAppleFileError.invalidRequest("file list target must be a directory") + throw RadrootsAppleFileError.invalidRequest } + return try classifiedFileSystemOperation { let urls = try fileManager.contentsOfDirectory( at: directoryURL, includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey, .contentModificationDateKey], options: [] ) return try urls.map { url in - let values = try url.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey, .contentModificationDateKey]) + let values = try url.resourceValues( + forKeys: [.isDirectoryKey, .fileSizeKey, .contentModificationDateKey] + ) let relativePath = try relativePath(for: url.standardizedFileURL, under: rootURL) return RadrootsFileEntry( file: RadrootsFileReference(scope: directory.scope, relativePath: relativePath), @@ -224,14 +245,17 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { left.file.relativePath < right.file.relativePath } } + } public func reset(scope: RadrootsFileScope) throws { let url = roots.root(for: scope) + try classifiedFileSystemOperation { if fileManager.fileExists(atPath: url.path) { try fileManager.removeItem(at: url) } try fileManager.createDirectory(at: url, withIntermediateDirectories: true) } + } @discardableResult public func stageBlob( @@ -240,7 +264,7 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { filenameHint: String? = nil ) throws -> RadrootsStagedBlobReference { guard data.count <= Self.maximumGovernedFileBytes else { - throw RadrootsAppleFileError.invalidRequest("staged blob exceeds the governed byte limit") + throw RadrootsAppleFileError.invalidRequest } let blobID = UUID().uuidString.lowercased() let blob = try RadrootsStagedBlobReference( @@ -250,8 +274,10 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { filenameHint: filenameHint ) let url = try stagedBlobURL(for: blob) + try classifiedFileSystemOperation { try fileManager.createDirectory(at: roots.stagedBlobsRoot, withIntermediateDirectories: true) try data.write(to: url, options: [.atomic]) + } return blob } @@ -307,21 +333,29 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { } @discardableResult - public func prepareExport(_ request: RadrootsExportDocumentRequest) throws -> RadrootsPreparedExportDocument { + public func prepareExport(_ request: RadrootsExportDocumentRequest) throws + -> RadrootsPreparedExportDocument + { let preparedID = UUID().uuidString.lowercased() let directoryURL = preparedExportsRoot.appendingPathComponent(preparedID, isDirectory: true) let fileURL = directoryURL.appendingPathComponent(request.suggestedFilename).standardizedFileURL + try classifiedFileSystemOperation { try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true) + } + let preparedData: Data switch request.source { - case let .inlineData(data): - try data.write(to: fileURL, options: [.atomic]) - case let .file(file): - try readGovernedFile(file, maximumBytes: Self.maximumGovernedFileBytes) - .write(to: fileURL, options: [.atomic]) - case let .stagedBlob(stagedBlob): - try readStagedBlob(stagedBlob).write(to: fileURL, options: [.atomic]) - } - let sizeBytes: UInt64 = if let requestSizeBytes = request.sizeBytes { + case .inlineData(let data): + preparedData = data + case .file(let file): + preparedData = try readGovernedFile(file, maximumBytes: Self.maximumGovernedFileBytes) + case .stagedBlob(let stagedBlob): + preparedData = try readStagedBlob(stagedBlob) + } + try classifiedFileSystemOperation { + try preparedData.write(to: fileURL, options: [.atomic]) + } + let sizeBytes: UInt64 = + if let requestSizeBytes = request.sizeBytes { requestSizeBytes } else { try fileSizeUInt64(at: fileURL) @@ -337,7 +371,7 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { public func readStagedBlob(_ blob: RadrootsStagedBlobReference) throws -> Data { guard (0 ... Self.maximumGovernedFileBytes).contains(blob.sizeBytes) else { - throw RadrootsAppleFileError.invalidRequest("staged blob byte size is invalid") + throw RadrootsAppleFileError.invalidRequest } let data: Data do { @@ -350,7 +384,7 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { throw mappedGovernedReadError(error) } guard data.count == blob.sizeBytes else { - throw RadrootsAppleFileError.permanentFailure("staged blob size does not match reference") + throw RadrootsAppleFileError.permanentFailure } return data } @@ -358,28 +392,33 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { public func releaseStagedBlob(_ blob: RadrootsStagedBlobReference) throws { let url = try stagedBlobURL(for: blob) if fileManager.fileExists(atPath: url.path) { + try classifiedFileSystemOperation { try fileManager.removeItem(at: url) } } + } public func preparedExportExists(_ preparedExport: RadrootsPreparedExportDocument) throws -> Bool { let directoryURL = try preparedExportDirectoryURL(for: preparedExport) - return fileManager.fileExists(atPath: directoryURL.path) && - fileManager.fileExists(atPath: preparedExport.fileURL.path) + return fileManager.fileExists(atPath: directoryURL.path) + && fileManager.fileExists(atPath: preparedExport.fileURL.path) } public func releasePreparedExport(_ preparedExport: RadrootsPreparedExportDocument) throws { let directoryURL = try preparedExportDirectoryURL(for: preparedExport) if fileManager.fileExists(atPath: directoryURL.path) { + try classifiedFileSystemOperation { try fileManager.removeItem(at: directoryURL) } } + } @discardableResult public func sweepStagedBlobs(olderThan cutoff: Date) throws -> [RadrootsStagedBlobReference] { guard fileManager.fileExists(atPath: roots.stagedBlobsRoot.path) else { return [] } + return try classifiedFileSystemOperation { let urls = try fileManager.contentsOfDirectory( at: roots.stagedBlobsRoot, includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey, .contentModificationDateKey], @@ -387,7 +426,9 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { ) var released: [RadrootsStagedBlobReference] = [] for url in urls { - let values = try url.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey, .contentModificationDateKey]) + let values = try url.resourceValues( + forKeys: [.isDirectoryKey, .fileSizeKey, .contentModificationDateKey] + ) guard values.isDirectory != true else { continue } @@ -405,13 +446,16 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { left.blobID < right.blobID } } + } public func resetStagedBlobs() throws { + try classifiedFileSystemOperation { if fileManager.fileExists(atPath: roots.stagedBlobsRoot.path) { try fileManager.removeItem(at: roots.stagedBlobsRoot) } try fileManager.createDirectory(at: roots.stagedBlobsRoot, withIntermediateDirectories: true) } + } public func resetFileRoots() throws { for scope in RadrootsFileScope.allCases { @@ -425,14 +469,20 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { } private var preparedExportsRoot: URL { - roots.temporaryRoot.appendingPathComponent("prepared_exports", isDirectory: true).standardizedFileURL - } - - private func preparedExportDirectoryURL(for preparedExport: RadrootsPreparedExportDocument) throws -> URL { - let normalizedPreparedID = try RadrootsPreparedExportDocument.normalizedPreparedID(preparedExport.preparedID) - let directoryURL = preparedExportsRoot.appendingPathComponent(normalizedPreparedID, isDirectory: true).standardizedFileURL + roots.temporaryRoot.appendingPathComponent("prepared_exports", isDirectory: true) + .standardizedFileURL + } + + private func preparedExportDirectoryURL(for preparedExport: RadrootsPreparedExportDocument) throws + -> URL + { + let normalizedPreparedID = try RadrootsPreparedExportDocument.normalizedPreparedID( + preparedExport.preparedID) + let directoryURL = preparedExportsRoot.appendingPathComponent( + normalizedPreparedID, isDirectory: true + ).standardizedFileURL guard preparedExport.fileURL.standardizedFileURL.path.hasPrefix(directoryURL.path + "/") else { - throw RadrootsAppleFileError.invalidRequest("prepared export file escaped its directory") + throw RadrootsAppleFileError.invalidRequest } return directoryURL } @@ -444,7 +494,7 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { ) throws -> RadrootsStagedBlobReference { let sizeBytes = try fileSizeInt(at: sourceURL) guard sizeBytes <= Self.maximumGovernedFileBytes else { - throw RadrootsAppleFileError.permanentFailure("file exceeds the governed byte limit") + throw RadrootsAppleFileError.permanentFailure } let blobID = UUID().uuidString.lowercased() let blob = try RadrootsStagedBlobReference( @@ -454,22 +504,24 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { filenameHint: filenameHint ) let destinationURL = try stagedBlobURL(for: blob) + try classifiedFileSystemOperation { try fileManager.createDirectory(at: roots.stagedBlobsRoot, withIntermediateDirectories: true) + } try copyReplacingItem(from: sourceURL, to: destinationURL) return blob } private func withSecurityScopedFile<T>(_ sourceURL: URL, _ body: (URL) throws -> T) throws -> T { guard sourceURL.isFileURL else { - throw RadrootsAppleFileError.invalidRequest("external file url must be a file url") + throw RadrootsAppleFileError.invalidRequest } let scopedURL = sourceURL.standardizedFileURL var isDirectory = ObjCBool(false) guard fileManager.fileExists(atPath: scopedURL.path, isDirectory: &isDirectory) else { - throw RadrootsAppleFileError.notFound("external file not found") + throw RadrootsAppleFileError.notFound } guard !isDirectory.boolValue else { - throw RadrootsAppleFileError.invalidRequest("external file url must reference a file") + throw RadrootsAppleFileError.invalidRequest } let didStartScope = scopedURL.startAccessingSecurityScopedResource() defer { @@ -482,17 +534,22 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { private func copyReplacingItem(from sourceURL: URL, to destinationURL: URL) throws { guard sourceURL.isFileURL, destinationURL.isFileURL else { - throw RadrootsAppleFileError.invalidRequest("copy source and destination must be file urls") + throw RadrootsAppleFileError.invalidRequest } try createParentDirectory(for: destinationURL) + try classifiedFileSystemOperation { if fileManager.fileExists(atPath: destinationURL.path) { try fileManager.removeItem(at: destinationURL) } try fileManager.copyItem(at: sourceURL, to: destinationURL) } + } private func createParentDirectory(for url: URL) throws { - try fileManager.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + try classifiedFileSystemOperation { + try fileManager.createDirectory( + at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + } } private func fileSize(at url: URL) throws -> Int { @@ -500,9 +557,11 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { } private func fileSizeInt(at url: URL) throws -> Int { - let values = try url.resourceValues(forKeys: [.fileSizeKey]) + let values = try classifiedFileSystemOperation { + try url.resourceValues(forKeys: [.fileSizeKey]) + } guard let size = values.fileSize else { - throw RadrootsAppleFileError.permanentFailure("file size is unavailable") + throw RadrootsAppleFileError.permanentFailure } return size } @@ -517,7 +576,7 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { preserveTooLarge: Bool = false ) throws -> Data { guard (0 ... Self.maximumGovernedFileBytes).contains(maximumBytes) else { - throw RadrootsAppleFileError.invalidRequest("inline byte limit is invalid") + throw RadrootsAppleFileError.invalidRequest } let root = roots.root(for: file.scope) let resolved = try roots.resolvedURL(for: file) @@ -536,16 +595,18 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { } } - private func mappedGovernedReadError(_ error: RadrootsGovernedFileReadError) -> RadrootsAppleFileError { + private func mappedGovernedReadError(_ error: RadrootsGovernedFileReadError) + -> RadrootsAppleFileError + { switch error { case .unavailable: - .notFound("file not found") + .notFound case .invalidRequest: - .invalidRequest("file request is invalid") + .invalidRequest case .tooLarge: - .permanentFailure("file exceeds the governed byte limit") + .permanentFailure case .invalidObject, .changedDuringRead, .ioFailure: - .permanentFailure("file failed governed admission") + .permanentFailure } } @@ -553,10 +614,22 @@ public final class RadrootsAppleFileAccess: RadrootsFileAccess { let rootPath = rootURL.standardizedFileURL.path let filePath = url.standardizedFileURL.path guard filePath.hasPrefix(rootPath + "/") else { - throw RadrootsAppleFileError.invalidRequest("file list entry escaped its scope") + throw RadrootsAppleFileError.invalidRequest } return String(filePath.dropFirst(rootPath.count + 1)) } + + private func classifiedFileSystemOperation<T>(_ operation: () throws -> T) throws -> T { + do { + return try operation() + } catch let error as RadrootsAppleFileError { + throw error + } catch let error as RadrootsDocumentInterchangeError { + throw error + } catch { + throw RadrootsAppleFileError.permanentFailure + } + } } public struct RadrootsAppleFileRoots: Sendable, Equatable { @@ -588,7 +661,8 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { field: "logsRoot" ) self.stagedBlobsRoot = try Self.normalizedRootURL( - stagedBlobsRoot ?? normalizedTemporaryRoot.appendingPathComponent("staged_blobs", isDirectory: true), + stagedBlobsRoot + ?? normalizedTemporaryRoot.appendingPathComponent("staged_blobs", isDirectory: true), field: "stagedBlobsRoot" ) } @@ -597,21 +671,25 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { appIdentifier: String, fileManager: FileManager = .default ) throws -> Self { + do { let normalizedAppIdentifier = try normalizedAppIdentifier(appIdentifier) - let dataBaseURL = try canonicalExistingDirectory(fileManager.url( + let dataBaseURL = try canonicalExistingDirectory( + fileManager.url( for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true )) - let cacheBaseURL = try canonicalExistingDirectory(fileManager.url( + let cacheBaseURL = try canonicalExistingDirectory( + fileManager.url( for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true )) let dataRoot = dataBaseURL.appendingPathComponent(normalizedAppIdentifier, isDirectory: true) - let cacheRoot = cacheBaseURL.appendingPathComponent(normalizedAppIdentifier, isDirectory: true) + let cacheRoot = cacheBaseURL.appendingPathComponent( + normalizedAppIdentifier, isDirectory: true) let temporaryRoot = try canonicalExistingDirectory(fileManager.temporaryDirectory) .appendingPathComponent(normalizedAppIdentifier, isDirectory: true) return try Self( @@ -620,6 +698,11 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { cacheRoot: cacheRoot, temporaryRoot: temporaryRoot ) + } catch let error as RadrootsAppleFileError { + throw error + } catch { + throw RadrootsAppleFileError.permanentFailure + } } public func root(for scope: RadrootsFileScope) -> URL { @@ -645,10 +728,10 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { if allowRootDirectory { return rootURL } - throw RadrootsAppleFileError.invalidRequest("file relative path cannot be empty") + throw RadrootsAppleFileError.invalidRequest } if NSString(string: trimmedPath).isAbsolutePath { - throw RadrootsAppleFileError.invalidRequest("file relative path must not be absolute") + throw RadrootsAppleFileError.invalidRequest } let candidateURL = rootURL.appendingPathComponent(trimmedPath).standardizedFileURL @@ -656,10 +739,10 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { if allowRootDirectory { return candidateURL } - throw RadrootsAppleFileError.invalidRequest("file relative path cannot resolve to its root") + throw RadrootsAppleFileError.invalidRequest } guard candidateURL.path.hasPrefix(rootURL.path + "/") else { - throw RadrootsAppleFileError.invalidRequest("file relative path must not escape its scope") + throw RadrootsAppleFileError.invalidRequest } return candidateURL } @@ -672,18 +755,18 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { public static func normalizedAppIdentifier(_ appIdentifier: String) throws -> String { let trimmed = appIdentifier.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsAppleFileError.invalidRequest("app identifier cannot be empty") + throw RadrootsAppleFileError.invalidRequest } return trimmed } public static func normalizedRootURL(_ rootURL: URL, field: String) throws -> URL { guard rootURL.isFileURL else { - throw RadrootsAppleFileError.invalidRequest("\(field) must be a file URL") + throw RadrootsAppleFileError.invalidRequest } let standardized = rootURL.standardizedFileURL guard standardized.path.hasPrefix("/") else { - throw RadrootsAppleFileError.invalidRequest("\(field) must be absolute") + throw RadrootsAppleFileError.invalidRequest } return standardized } @@ -692,7 +775,7 @@ public struct RadrootsAppleFileRoots: Sendable, Equatable { guard directory.isFileURL, let pointer = directory.path.withCString({ Darwin.realpath($0, nil) }) else { - throw RadrootsAppleFileError.permanentFailure("platform file root is unavailable") + throw RadrootsAppleFileError.permanentFailure } defer { Darwin.free(pointer) } return URL(fileURLWithPath: String(cString: pointer), isDirectory: true) diff --git a/Sources/RadrootsKit/RadrootsPermissionLocation.swift b/Sources/RadrootsKit/RadrootsPermissionLocation.swift @@ -35,8 +35,10 @@ public protocol RadrootsPermissionStatusProvider: Sendable { func snapshots(for kinds: [RadrootsPermissionKind]) async throws -> [RadrootsPermissionSnapshot] } -public extension RadrootsPermissionStatusProvider { - func snapshots(for kinds: [RadrootsPermissionKind]) async throws -> [RadrootsPermissionSnapshot] { +extension RadrootsPermissionStatusProvider { + public func snapshots(for kinds: [RadrootsPermissionKind]) async throws + -> [RadrootsPermissionSnapshot] + { var snapshots: [RadrootsPermissionSnapshot] = [] snapshots.reserveCapacity(kinds.count) for kind in kinds { @@ -89,7 +91,8 @@ public struct RadrootsLocationServicesAvailability: Sendable, Equatable, Hashabl } public var canRequestCurrentLocation: Bool { - locationServicesEnabled && (authorization == .authorizedWhenInUse || authorization == .authorizedAlways) + locationServicesEnabled + && (authorization == .authorizedWhenInUse || authorization == .authorizedAlways) } } @@ -99,10 +102,10 @@ public struct RadrootsLocationCoordinate: Sendable, Equatable, Hashable { public init(latitude: Double, longitude: Double) throws { guard latitude.isFinite, (-90.0 ... 90.0).contains(latitude) else { - throw RadrootsLocationServicesError.invalidRequest("latitude must be between -90 and 90") + throw RadrootsLocationServicesError.invalidRequest } guard longitude.isFinite, (-180.0 ... 180.0).contains(longitude) else { - throw RadrootsLocationServicesError.invalidRequest("longitude must be between -180 and 180") + throw RadrootsLocationServicesError.invalidRequest } self.latitude = latitude self.longitude = longitude @@ -143,27 +146,28 @@ public struct RadrootsLocationReading: Sendable, Equatable, Hashable { ) self.courseDegrees = try Self.normalizedOptionalCourse(courseDegrees) guard capturedAt.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsLocationServicesError.invalidRequest("captured timestamp must be finite") + throw RadrootsLocationServicesError.invalidRequest } self.capturedAt = capturedAt } public func age(relativeTo now: Date) throws -> TimeInterval { guard now.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsLocationServicesError.invalidRequest("comparison timestamp must be finite") + throw RadrootsLocationServicesError.invalidRequest } return now.timeIntervalSince(capturedAt) } public func isFresh(relativeTo now: Date, maximumAgeSeconds: TimeInterval) throws -> Bool { - let normalizedMaximumAge = try Self.normalizedNonNegativeFinite(maximumAgeSeconds, field: "maximum age") + let normalizedMaximumAge = try Self.normalizedNonNegativeFinite( + maximumAgeSeconds, field: "maximum age") let currentAge = try age(relativeTo: now) return currentAge >= 0 && currentAge <= normalizedMaximumAge } public static func normalizedNonNegativeFinite(_ value: Double, field: String) throws -> Double { guard value.isFinite, value >= 0 else { - throw RadrootsLocationServicesError.invalidRequest("\(field) must be finite and non-negative") + throw RadrootsLocationServicesError.invalidRequest } return value } @@ -173,12 +177,14 @@ public struct RadrootsLocationReading: Sendable, Equatable, Hashable { return nil } guard value.isFinite else { - throw RadrootsLocationServicesError.invalidRequest("\(field) must be finite") + throw RadrootsLocationServicesError.invalidRequest } return value } - public static func normalizedOptionalNonNegativeFinite(_ value: Double?, field: String) throws -> Double? { + public static func normalizedOptionalNonNegativeFinite(_ value: Double?, field: String) throws + -> Double? + { guard let value else { return nil } @@ -190,7 +196,7 @@ public struct RadrootsLocationReading: Sendable, Equatable, Hashable { return nil } guard value.isFinite, (0.0 ..< 360.0).contains(value) else { - throw RadrootsLocationServicesError.invalidRequest("course must be between 0 and 359.999 degrees") + throw RadrootsLocationServicesError.invalidRequest } return value } @@ -207,14 +213,15 @@ public struct RadrootsCurrentLocationRequest: Sendable, Equatable, Hashable { maximumCachedReadingAgeSeconds: TimeInterval? = nil ) throws { guard timeoutSeconds.isFinite, timeoutSeconds > 0 else { - throw RadrootsLocationServicesError.invalidRequest("location timeout must be finite and greater than zero") + throw RadrootsLocationServicesError.invalidRequest } self.timeoutSeconds = timeoutSeconds self.desiredAccuracyMeters = try RadrootsLocationReading.normalizedOptionalNonNegativeFinite( desiredAccuracyMeters, field: "desired accuracy" ) - self.maximumCachedReadingAgeSeconds = try RadrootsLocationReading.normalizedOptionalNonNegativeFinite( + self.maximumCachedReadingAgeSeconds = + try RadrootsLocationReading.normalizedOptionalNonNegativeFinite( maximumCachedReadingAgeSeconds, field: "maximum cached reading age" ) @@ -232,7 +239,7 @@ public struct RadrootsCurrentLocationResult: Sendable, Equatable, Hashable { servedFromCache: Bool = false ) throws { guard authorization == .authorizedWhenInUse || authorization == .authorizedAlways else { - throw RadrootsLocationServicesError.invalidRequest("current location result requires authorized location access") + throw RadrootsLocationServicesError.invalidRequest } self.reading = reading self.authorization = authorization @@ -241,32 +248,25 @@ public struct RadrootsCurrentLocationResult: Sendable, Equatable, Hashable { } public enum RadrootsLocationServicesError: Error, Equatable, Sendable { - case invalidRequest(String) - case permissionDenied(String) - case unavailable(String) - case timeout(String) - case cancelled(String) - case transientFailure(String) - case permanentFailure(String) + case invalidRequest + case permissionDenied + case unavailable + case timeout + case cancelled + case transientFailure + case permanentFailure } extension RadrootsLocationServicesError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .permissionDenied(message): - message - case let .unavailable(message): - message - case let .timeout(message): - message - case let .cancelled(message): - message - case let .transientFailure(message): - message - case let .permanentFailure(message): - message + case .invalidRequest: "The location request is invalid." + case .permissionDenied: "Location permission was denied." + case .unavailable: "Location services are unavailable." + case .timeout: "The location request timed out." + case .cancelled: "The location request was cancelled." + case .transientFailure: "Location could not be determined temporarily." + case .permanentFailure: "Location could not be determined." } } } @@ -274,5 +274,6 @@ extension RadrootsLocationServicesError: LocalizedError { public protocol RadrootsLocationServices: Sendable { func currentAvailability() async -> RadrootsLocationServicesAvailability func requestWhenInUseAuthorization() async throws -> RadrootsLocationAuthorization - func currentLocation(_ request: RadrootsCurrentLocationRequest) async throws -> RadrootsCurrentLocationResult + func currentLocation(_ request: RadrootsCurrentLocationRequest) async throws + -> RadrootsCurrentLocationResult } diff --git a/Sources/RadrootsKit/RadrootsSecureStore.swift b/Sources/RadrootsKit/RadrootsSecureStore.swift @@ -60,7 +60,7 @@ public struct RadrootsSecureStoreKey: Hashable, Sendable { public static func normalizedServicePrefix(_ servicePrefix: String) throws -> String { let trimmedPrefix = servicePrefix.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedPrefix.isEmpty else { - throw RadrootsAppleSecurityError.invalidRequest("secure store service prefix cannot be empty") + throw RadrootsAppleSecurityError.invalidRequest } return trimmedPrefix } @@ -68,7 +68,7 @@ public struct RadrootsSecureStoreKey: Hashable, Sendable { public static func normalizedNamespace(_ namespace: String) throws -> String { let trimmedNamespace = namespace.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedNamespace.isEmpty else { - throw RadrootsAppleSecurityError.invalidRequest("secure store namespace cannot be empty") + throw RadrootsAppleSecurityError.invalidRequest } return trimmedNamespace } @@ -76,7 +76,7 @@ public struct RadrootsSecureStoreKey: Hashable, Sendable { public static func normalizedName(_ name: String) throws -> String { let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedName.isEmpty else { - throw RadrootsAppleSecurityError.invalidRequest("secure store key name cannot be empty") + throw RadrootsAppleSecurityError.invalidRequest } return trimmedName } @@ -94,8 +94,8 @@ public protocol RadrootsSecureStore: AnyObject, Sendable { func deleteNamespace(_ namespace: String) throws } -public extension RadrootsSecureStore { - func put(_ value: Data, for key: RadrootsSecureStoreKey) throws { +extension RadrootsSecureStore { + public func put(_ value: Data, for key: RadrootsSecureStoreKey) throws { try put(value, for: key, policy: .secureLocalSecret) } } diff --git a/Sources/RadrootsKit/RadrootsTelemetry.swift b/Sources/RadrootsKit/RadrootsTelemetry.swift @@ -1,16 +1,11 @@ import Foundation public enum RadrootsTelemetryError: Error, Equatable, Sendable { - case invalidRequest(String) + case invalidRequest } extension RadrootsTelemetryError: LocalizedError { - public var errorDescription: String? { - switch self { - case let .invalidRequest(message): - message - } - } + public var errorDescription: String? { "The telemetry request is invalid." } } public enum RadrootsTelemetryLevel: String, Sendable, Equatable, Hashable, CaseIterable, Comparable { @@ -55,15 +50,15 @@ public enum RadrootsTelemetryFieldValue: Sendable, Equatable, Hashable { public var renderedValue: String { switch self { - case let .string(value): + case .string(let value): value - case let .integer(value): + case .integer(let value): String(value) - case let .double(value): + case .double(let value): String(value) - case let .bool(value): + case .bool(let value): value ? "true" : "false" - case let .stringList(value): + case .stringList(let value): value.joined(separator: ",") } } @@ -73,11 +68,11 @@ public enum RadrootsTelemetryFieldValue: Sendable, Equatable, Hashable { policy: RadrootsTelemetryRedactionPolicy ) -> RadrootsTelemetryFieldValue { switch self { - case let .string(value): + case .string(let value): return .string(policy.redactedString(value, key: key)) case .integer, .double, .bool: return policy.shouldRedactKey(key) ? .string(policy.replacement) : self - case let .stringList(values): + case .stringList(let values): if policy.shouldRedactKey(key) { return .string(policy.replacement) } @@ -163,11 +158,11 @@ public struct RadrootsTelemetryEvent: Sendable, Equatable, Hashable { ) let normalizedMessage = try RadrootsTelemetryValidation.normalizedMessage(message) guard occurredAt.timeIntervalSinceReferenceDate.isFinite else { - throw RadrootsTelemetryError.invalidRequest("telemetry event timestamp must be finite") + throw RadrootsTelemetryError.invalidRequest } let duplicateFieldKeys = Set(fields.map(\.key)).count != fields.count guard !duplicateFieldKeys else { - throw RadrootsTelemetryError.invalidRequest("telemetry event field keys must be unique") + throw RadrootsTelemetryError.invalidRequest } self.name = normalizedName self.category = normalizedCategory @@ -332,16 +327,18 @@ public enum RadrootsTelemetryValidation { ) throws -> String { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - throw RadrootsTelemetryError.invalidRequest("\(field) must not be empty") + throw RadrootsTelemetryError.invalidRequest } guard trimmed.count <= maximumLength else { - throw RadrootsTelemetryError.invalidRequest("\(field) is too long") + throw RadrootsTelemetryError.invalidRequest } - guard trimmed.range( + guard + trimmed.range( of: "^[a-z][a-z0-9._-]*$", options: .regularExpression - ) != nil else { - throw RadrootsTelemetryError.invalidRequest("\(field) must use lowercase safe identifier characters") + ) != nil + else { + throw RadrootsTelemetryError.invalidRequest } return trimmed } @@ -355,29 +352,29 @@ public enum RadrootsTelemetryValidation { return nil } guard doesNotContainControlCharacters(trimmed) else { - throw RadrootsTelemetryError.invalidRequest("telemetry event message cannot contain control characters") + throw RadrootsTelemetryError.invalidRequest } guard trimmed.count <= 500 else { - throw RadrootsTelemetryError.invalidRequest("telemetry event message is too long") + throw RadrootsTelemetryError.invalidRequest } return trimmed } public static func validate(_ value: RadrootsTelemetryFieldValue) throws { switch value { - case let .string(string): + case .string(let string): try validateStringValue(string) case .integer: return - case let .double(double): + case .double(let double): guard double.isFinite else { - throw RadrootsTelemetryError.invalidRequest("telemetry double field must be finite") + throw RadrootsTelemetryError.invalidRequest } case .bool: return - case let .stringList(values): + case .stringList(let values): guard values.count <= 24 else { - throw RadrootsTelemetryError.invalidRequest("telemetry string list field is too long") + throw RadrootsTelemetryError.invalidRequest } for value in values { try validateStringValue(value) @@ -387,10 +384,10 @@ public enum RadrootsTelemetryValidation { private static func validateStringValue(_ value: String) throws { guard doesNotContainControlCharacters(value) else { - throw RadrootsTelemetryError.invalidRequest("telemetry string field cannot contain control characters") + throw RadrootsTelemetryError.invalidRequest } guard value.count <= 500 else { - throw RadrootsTelemetryError.invalidRequest("telemetry string field is too long") + throw RadrootsTelemetryError.invalidRequest } } diff --git a/Sources/RadrootsKit/RadrootsUserPresence.swift b/Sources/RadrootsKit/RadrootsUserPresence.swift @@ -55,7 +55,7 @@ public struct RadrootsUserPresenceRequest: Sendable, Equatable, Hashable { ) throws { let normalizedReason = reason.trimmingCharacters(in: .whitespacesAndNewlines) guard !normalizedReason.isEmpty else { - throw RadrootsUserPresenceError.invalidRequest("user presence reason cannot be empty") + throw RadrootsUserPresenceError.invalidRequest } self.policy = policy self.reason = normalizedReason @@ -73,32 +73,25 @@ public struct RadrootsUserPresenceResult: Sendable, Equatable, Hashable { } public enum RadrootsUserPresenceError: Error, Equatable, Sendable { - case invalidRequest(String) - case userCancelled(String) - case permissionDenied(String) - case unavailable(String) - case timeout(String) - case transientFailure(String) - case permanentFailure(String) + case invalidRequest + case userCancelled + case permissionDenied + case unavailable + case timeout + case transientFailure + case permanentFailure } extension RadrootsUserPresenceError: LocalizedError { public var errorDescription: String? { switch self { - case let .invalidRequest(message): - message - case let .userCancelled(message): - message - case let .permissionDenied(message): - message - case let .unavailable(message): - message - case let .timeout(message): - message - case let .transientFailure(message): - message - case let .permanentFailure(message): - message + case .invalidRequest: "The user-presence request is invalid." + case .userCancelled: "User-presence verification was cancelled." + case .permissionDenied: "User-presence verification was denied." + case .unavailable: "User-presence verification is unavailable." + case .timeout: "User-presence verification timed out." + case .transientFailure: "User-presence verification could not be completed temporarily." + case .permanentFailure: "User-presence verification could not be completed." } } } diff --git a/Sources/RadrootsKit/RadrootsVerifiedArtifactAccess.swift b/Sources/RadrootsKit/RadrootsVerifiedArtifactAccess.swift @@ -8,6 +8,18 @@ public enum RadrootsVerifiedArtifactAccessError: Error, Equatable, Sendable { case fileSystemFailure } +extension RadrootsVerifiedArtifactAccessError: LocalizedError { + public var errorDescription: String? { + switch self { + case .invalidDescriptor: "The verified-artifact descriptor is invalid." + case .protectedDataUnavailable: "Protected data is unavailable." + case .artifactUnavailable: "The verified artifact is unavailable." + case .artifactCorrupt: "The verified artifact is corrupt." + case .fileSystemFailure: "The verified artifact could not be opened." + } + } +} + public struct RadrootsVerifiedArtifactDescriptor: Sendable, Equatable, Hashable { public let artifactID: String public let byteSize: UInt64 diff --git a/Sources/RadrootsKitTesting/RadrootsBackgroundTransferTesting.swift b/Sources/RadrootsKitTesting/RadrootsBackgroundTransferTesting.swift @@ -2,8 +2,7 @@ import Foundation import RadrootsKit public actor RadrootsInMemoryBackgroundTransferStore: RadrootsBackgroundTransferStore { - private var snapshotsByIdentifier: - [RadrootsBackgroundTransferIdentifier: RadrootsBackgroundTransferSnapshot] + private var snapshotsByIdentifier: [RadrootsBackgroundTransferIdentifier: RadrootsBackgroundTransferSnapshot] public init(snapshots: [RadrootsBackgroundTransferSnapshot] = []) { snapshotsByIdentifier = Dictionary(uniqueKeysWithValues: snapshots.map { ($0.identifier, $0) }) @@ -79,8 +78,7 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer { public func cancel(_ identifier: RadrootsBackgroundTransferIdentifier) async throws { cancelledIdentifiersValue.append(identifier) - if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) - { + if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) { let snapshot = try RadrootsBackgroundTransferSnapshot( request: existing.request, state: .cancelled, @@ -93,14 +91,13 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer { public func expire(_ identifier: RadrootsBackgroundTransferIdentifier) async throws { cancelledIdentifiersValue.append(identifier) - if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) - { + if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) { try await store.saveSnapshot( RadrootsBackgroundTransferSnapshot( request: existing.request, state: .expired, progress: existing.progress, - errorMessage: "background_transfer_expired", + failure: .expired, updatedAt: updatedAt ) ) @@ -114,8 +111,7 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer { guard let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) else { - throw RadrootsBackgroundTransferError.transferFailure( - "background transfer snapshot not found") + throw RadrootsBackgroundTransferError.transferFailure } switch verification { case .accepted: @@ -129,13 +125,13 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer { updatedAt: updatedAt ) ) - case .rejected(let code): + case .rejected(let failure): try await store.saveSnapshot( RadrootsBackgroundTransferSnapshot( request: existing.request, state: .failed, progress: existing.progress, - errorMessage: code, + failure: failure, updatedAt: updatedAt ) ) @@ -146,8 +142,7 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer { guard let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) else { - throw RadrootsBackgroundTransferError.transferFailure( - "background transfer snapshot not found") + throw RadrootsBackgroundTransferError.transferFailure } let snapshot = try RadrootsBackgroundTransferSnapshot( request: existing.request, @@ -158,19 +153,20 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer { try await store.saveSnapshot(snapshot) } - public func fail(_ identifier: RadrootsBackgroundTransferIdentifier, message: String) async throws - { + public func fail( + _ identifier: RadrootsBackgroundTransferIdentifier, + failure: RadrootsBackgroundTransferFailure + ) async throws { guard let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) else { - throw RadrootsBackgroundTransferError.transferFailure( - "background transfer snapshot not found") + throw RadrootsBackgroundTransferError.transferFailure } let snapshot = try RadrootsBackgroundTransferSnapshot( request: existing.request, state: .failed, progress: existing.progress, - errorMessage: message, + failure: failure, updatedAt: updatedAt ) try await store.saveSnapshot(snapshot) diff --git a/Tests/RadrootsKitTestingTests/RadrootsBackgroundTaskTestingTests.swift b/Tests/RadrootsKitTestingTests/RadrootsBackgroundTaskTestingTests.swift @@ -48,14 +48,14 @@ import Testing @Test func fakeBackgroundTaskSchedulerCanReturnSubmitFailures() async throws { let scheduler = RadrootsFakeBackgroundTaskScheduler( - submitOutcome: .failure(.schedulerFailure("scheduler rejected request")) + submitOutcome: .failure(.schedulerFailure) ) let request = try RadrootsBackgroundTaskRequest( identifier: "org.radroots.field-ios.background.refresh", kind: .appRefresh ) - await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure("scheduler rejected request")) { + await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure) { _ = try await scheduler.submit(request) } #expect(await scheduler.submittedRequests == [request]) diff --git a/Tests/RadrootsKitTestingTests/RadrootsBackgroundTransferTestingTests.swift b/Tests/RadrootsKitTestingTests/RadrootsBackgroundTransferTestingTests.swift @@ -15,7 +15,8 @@ import Testing try await store.saveSnapshot(first) try await store.saveSnapshot(second) - #expect(try await store.loadSnapshots().map(\.identifier.rawValue) == [ + #expect( + try await store.loadSnapshots().map(\.identifier.rawValue) == [ "field.transfer.a", "field.transfer.b", ]) @@ -38,7 +39,9 @@ import Testing #expect(handle.identifier == request.identifier) #expect(await transfer.enqueuedRequests == [request]) #expect(try await transfer.snapshot(for: request.identifier)?.state == .queued) - #expect(try await transfer.snapshot(for: request.identifier)?.updatedAt == Date(timeIntervalSince1970: 42)) + #expect( + try await transfer.snapshot(for: request.identifier)?.updatedAt + == Date(timeIntervalSince1970: 42)) try await transfer.cancel(request.identifier) @@ -54,21 +57,21 @@ import Testing _ = try await transfer.enqueue(completed) _ = try await transfer.enqueue(failed) try await transfer.complete(completed.identifier) - try await transfer.fail(failed.identifier, message: "network unavailable") + try await transfer.fail(failed.identifier, failure: .platformFailure) #expect(try await transfer.snapshot(for: completed.identifier)?.state == .completed) let failedSnapshot = try await transfer.snapshot(for: failed.identifier) #expect(failedSnapshot?.state == .failed) - #expect(failedSnapshot?.errorMessage == "network unavailable") + #expect(failedSnapshot?.failure == .platformFailure) } @Test func fakeBackgroundTransferCanReturnEnqueueFailures() async throws { let transfer = RadrootsFakeBackgroundTransfer( - enqueueOutcome: .failure(.transferFailure("queue unavailable")) + enqueueOutcome: .failure(.transferFailure) ) let request = try testTransferRequest(identifier: "field.transfer.failure") - await #expect(throws: RadrootsBackgroundTransferError.transferFailure("queue unavailable")) { + await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { _ = try await transfer.enqueue(request) } #expect(await transfer.enqueuedRequests == [request]) diff --git a/Tests/RadrootsKitTestingTests/RadrootsCaptureIntakeTestingTests.swift b/Tests/RadrootsKitTestingTests/RadrootsCaptureIntakeTestingTests.swift @@ -41,17 +41,17 @@ import Testing supportedCaptureKinds: [.image], multipleSelectionSupported: false ), - importOutcome: .failure(.userCancelled("media import was cancelled")), + importOutcome: .failure(.userCancelled), captureOutcome: .success(RadrootsMediaCaptureResult(item: asset)) ) - await #expect(throws: RadrootsCaptureIntakeError.userCancelled("media import was cancelled")) { + await #expect(throws: RadrootsCaptureIntakeError.userCancelled) { _ = try await picker.importMedia(RadrootsMediaImportRequest()) } - await picker.setCaptureOutcome(.failure(.permissionDenied("camera access is denied"))) + await picker.setCaptureOutcome(.failure(.permissionDenied)) - await #expect(throws: RadrootsCaptureIntakeError.permissionDenied("camera access is denied")) { + await #expect(throws: RadrootsCaptureIntakeError.permissionDenied) { _ = try await picker.captureMedia(RadrootsMediaCaptureRequest()) } } @@ -82,10 +82,10 @@ import Testing multiPageSupported: false, supportedOutputKinds: [] ), - scanOutcome: .failure(.unavailable("document scanner unavailable")) + scanOutcome: .failure(.unavailable) ) - await #expect(throws: RadrootsCaptureIntakeError.unavailable("document scanner unavailable")) { + await #expect(throws: RadrootsCaptureIntakeError.unavailable) { _ = try await scanner.scanDocument(RadrootsDocumentScanRequest()) } } diff --git a/Tests/RadrootsKitTestingTests/RadrootsExternalActionsTestingTests.swift b/Tests/RadrootsKitTestingTests/RadrootsExternalActionsTestingTests.swift @@ -25,10 +25,10 @@ import Testing @Test func fakeExternalActionsReturnsConfiguredFailures() async throws { let destination = RadrootsExternalActionDestination.appSettings let actions = RadrootsFakeExternalActions( - openOutcome: .failure(.unavailable("external actions unavailable")) + openOutcome: .failure(.unavailable) ) - await #expect(throws: RadrootsExternalActionError.unavailable("external actions unavailable")) { + await #expect(throws: RadrootsExternalActionError.unavailable) { try await actions.open(RadrootsExternalActionRequest(destination: destination)) } diff --git a/Tests/RadrootsKitTestingTests/RadrootsKitTestingTests.swift b/Tests/RadrootsKitTestingTests/RadrootsKitTestingTests.swift @@ -10,7 +10,8 @@ import Testing ) #expect(config.environment["RADROOTS_TEST"] == "true") - #expect(config.arguments == [ + #expect( + config.arguments == [ "--radroots-test", "-AppleLanguages", "(en)", @@ -25,7 +26,8 @@ import Testing arguments: [] ) - #expect(config.mergedEnvironment(over: ["A": "old", "C": "keep"]) == [ + #expect( + config.mergedEnvironment(over: ["A": "old", "C": "keep"]) == [ "A": "override", "B": "new", "C": "keep", @@ -126,9 +128,9 @@ import Testing ), currentLocationOutcome: .success(reading) ) - await service.setCurrentLocationOutcome(.failure(.timeout("timed out"))) + await service.setCurrentLocationOutcome(.failure(.timeout)) - await #expect(throws: RadrootsLocationServicesError.timeout("timed out")) { + await #expect(throws: RadrootsLocationServicesError.timeout) { _ = try await service.currentLocation(RadrootsCurrentLocationRequest(timeoutSeconds: 2)) } } diff --git a/Tests/RadrootsKitTestingTests/RadrootsUserPresenceTestingTests.swift b/Tests/RadrootsKitTestingTests/RadrootsUserPresenceTestingTests.swift @@ -23,11 +23,11 @@ import Testing @Test func fakeUserPresenceReturnsConfiguredFailures() async throws { let presence = RadrootsFakeUserPresence( - verificationOutcome: .failure(.userCancelled("verification cancelled")) + verificationOutcome: .failure(.userCancelled) ) let request = try RadrootsUserPresenceRequest(reason: "Delete local Nostr identity") - await #expect(throws: RadrootsUserPresenceError.userCancelled("verification cancelled")) { + await #expect(throws: RadrootsUserPresenceError.userCancelled) { try await presence.verify(request) } @@ -36,7 +36,8 @@ import Testing } @Test func fakeUserPresenceCanUpdateStatusAndOutcome() async throws { - let presence = RadrootsFakeUserPresence(status: .unavailable, verificationOutcome: .success(false)) + let presence = RadrootsFakeUserPresence( + status: .unavailable, verificationOutcome: .success(false)) let initialStatus = try await presence.currentStatus() await presence.setStatus( diff --git a/Tests/RadrootsKitTests/RadrootsAppleBackgroundTaskSchedulerTests.swift b/Tests/RadrootsKitTests/RadrootsAppleBackgroundTaskSchedulerTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func appleBackgroundTaskSchedulerRegistersAndSubmitsThroughAdapters() async throws { let probe = RadrootsAppleBackgroundTaskSchedulerProbe(now: Date(timeIntervalSince1970: 100)) let scheduler = RadrootsAppleBackgroundTaskScheduler(adapters: probe.adapters()) @@ -27,7 +28,8 @@ import Testing } @Test func appleBackgroundTaskSchedulerCancelsAndListsPendingTasksThroughAdapters() async throws { - let identifier = try RadrootsBackgroundTaskIdentifier("org.radroots.field-ios.background.processing") + let identifier = try RadrootsBackgroundTaskIdentifier( + "org.radroots.field-ios.background.processing") let request = try RadrootsBackgroundTaskRequest( identifier: identifier, kind: .processing, @@ -50,7 +52,7 @@ import Testing @Test func appleBackgroundTaskSchedulerMapsAdapterSubmitFailures() async throws { let probe = RadrootsAppleBackgroundTaskSchedulerProbe( - submitOutcome: .failure(.schedulerFailure("submit rejected")) + submitOutcome: .failure(.schedulerFailure) ) let scheduler = RadrootsAppleBackgroundTaskScheduler(adapters: probe.adapters()) let request = try RadrootsBackgroundTaskRequest( @@ -58,7 +60,7 @@ import Testing kind: .appRefresh ) - await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure("submit rejected")) { + await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure) { _ = try await scheduler.submit(request) } } @@ -73,7 +75,7 @@ import Testing handler: { true } ) - await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure("background task registration was rejected")) { + await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure) { _ = try await scheduler.register(registration) } #expect(await probe.registeredIdentifiers == [identifier]) @@ -138,7 +140,7 @@ private actor RadrootsAppleBackgroundTaskSchedulerProbe { switch submitOutcome { case .success: return - case let .failure(error): + case .failure(let error): throw error } } diff --git a/Tests/RadrootsKitTests/RadrootsAppleBackgroundTransferTests.swift b/Tests/RadrootsKitTests/RadrootsAppleBackgroundTransferTests.swift @@ -28,8 +28,7 @@ import Testing _ = try await transfer.enqueue(request) await #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier already exists") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try await transfer.enqueue(request) } @@ -41,20 +40,20 @@ import Testing let store = RadrootsInMemoryBackgroundTransferStore() let probe = RadrootsAppleBackgroundTransferProbe( now: Date(timeIntervalSince1970: 200), - enqueueOutcome: .failure(.transferFailure("adapter rejected transfer")) + enqueueOutcome: .failure(.transferFailure) ) let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters()) let request = try appleTransferRequest(identifier: "field.transfer.failed") await #expect( - throws: RadrootsBackgroundTransferError.transferFailure("background transfer enqueue failed") + throws: RadrootsBackgroundTransferError.transferFailure ) { _ = try await transfer.enqueue(request) } let snapshot = try await transfer.snapshot(for: request.identifier) #expect(snapshot?.state == .failed) - #expect(snapshot?.errorMessage == "background_transfer_enqueue_failed") + #expect(snapshot?.failure == .enqueueFailed) #expect(snapshot?.updatedAt == Date(timeIntervalSince1970: 200)) } @@ -105,7 +104,7 @@ import Testing let snapshot = try #require(try await transfer.snapshots().first) #expect(snapshot.state == .interrupted) - #expect(snapshot.errorMessage == "background_transfer_interrupted") + #expect(snapshot.failure == .interrupted) #expect(snapshot.possibleRemoteOrphan) #expect(snapshot.updatedAt == Date(timeIntervalSince1970: 401)) } @@ -190,7 +189,7 @@ import Testing let snapshot = try #require(try await store.loadSnapshots().first) #expect(snapshot.state == .failed) - #expect(snapshot.errorMessage == "background_transfer_response_media_type") + #expect(snapshot.failure == .responseMediaType) #expect(snapshot.possibleRemoteOrphan) } @@ -217,7 +216,7 @@ import Testing let snapshot = try await store.loadSnapshots().first #expect(snapshot?.state == .failed) - #expect(snapshot?.errorMessage == "background_transfer_download_staging_failure") + #expect(snapshot?.failure == .downloadStagingFailure) #expect(snapshot?.updatedAt == Date(timeIntervalSince1970: 600)) } @@ -314,7 +313,7 @@ import Testing let snapshot = try #require(try await store.loadSnapshots().first) #expect(snapshot.state == .failed) - #expect(snapshot.errorMessage == "background_transfer_response_content_encoding") + #expect(snapshot.failure == .responseContentEncoding) #expect(snapshot.possibleRemoteOrphan) } @@ -336,7 +335,7 @@ import Testing totalBytesExpected: 10 ) #expect( - try await store.loadSnapshots().first?.errorMessage == "background_transfer_http_status_503") + try await store.loadSnapshots().first?.failure == .httpStatus) let bodyRequest = try appleUploadRequest( identifier: "field.transfer.upload.oversized", @@ -354,7 +353,7 @@ import Testing let oversized = try #require( try await store.loadSnapshots().first { $0.identifier == bodyRequest.identifier }) #expect(oversized.state == .failed) - #expect(oversized.errorMessage == "background_transfer_response_too_large") + #expect(oversized.failure == .responseTooLarge) #expect(oversized.possibleRemoteOrphan) } @@ -420,11 +419,13 @@ import Testing let secondCompletion = RadrootsCompletionProbe() let unrelated = RadrootsCompletionProbe() - await coordinator.handleBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") - { completion.markCompleted() } + await coordinator.handleBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") { + completion.markCompleted() + } #expect(!completion.completed) - await coordinator.handleBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") - { secondCompletion.markCompleted() } + await coordinator.handleBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") { + secondCompletion.markCompleted() + } await coordinator.handleBackgroundEvents(identifier: "other.session") { unrelated.markCompleted() @@ -449,8 +450,9 @@ import Testing let completion = RadrootsCompletionProbe() await coordinator.finishBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") - await coordinator.handleBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") - { completion.markCompleted() } + await coordinator.handleBackgroundEvents(identifier: "org.radroots.field-ios.background.transfer") { + completion.markCompleted() + } #expect(completion.completionCount == 1) } @@ -477,8 +479,7 @@ import Testing #expect(try await store.loadSnapshots().first?.state == .completed) #expect(try await store.loadSnapshots().first?.response == response) await #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer is not awaiting verification") + throws: RadrootsBackgroundTransferError.invalidRequest ) { try await transfer.settle(request.identifier, verification: .accepted) } @@ -504,12 +505,12 @@ import Testing try await transfer.settle( request.identifier, - verification: .rejected(code: "background_transfer_rust_verification_failed") + verification: .rejected(failure: .verificationRejected) ) let snapshot = try #require(try await store.loadSnapshots().first) #expect(snapshot.state == .failed) - #expect(snapshot.errorMessage == "background_transfer_rust_verification_failed") + #expect(snapshot.failure == .verificationRejected) #expect(snapshot.possibleRemoteOrphan) #expect(snapshot.response == nil) } @@ -540,7 +541,7 @@ import Testing let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters()) await #expect( - throws: RadrootsBackgroundTransferError.invalidRequest("background transfer is still active") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try await transfer.retry(request) } @@ -585,7 +586,7 @@ import Testing let snapshot = try #require(try await store.loadSnapshots().first) #expect(snapshot.state == .failed) - #expect(snapshot.errorMessage == "background_transfer_transfer_too_large") + #expect(snapshot.failure == .transferTooLarge) #expect(snapshot.possibleRemoteOrphan) } @@ -640,8 +641,7 @@ import Testing var snapshot = try await transfer.snapshot(for: request.identifier) while snapshot?.state == .queued || snapshot?.state == .running { guard Date() < deadline else { - throw RadrootsBackgroundTransferError.transferFailure( - "background_transfer_live_test_timed_out") + throw RadrootsBackgroundTransferError.transferFailure } try await Task.sleep(nanoseconds: 50_000_000) snapshot = try await transfer.snapshot(for: request.identifier) @@ -822,8 +822,7 @@ private func appleTransferRoots() throws -> RadrootsAppleFileRoots { ) try FileManager.default.createDirectory(at: unresolvedRoot, withIntermediateDirectories: false) guard let resolvedPointer = unresolvedRoot.path.withCString({ Darwin.realpath($0, nil) }) else { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer test root is unavailable") + throw RadrootsBackgroundTransferError.persistenceFailure } defer { Darwin.free(resolvedPointer) } let root = URL(fileURLWithPath: String(cString: resolvedPointer), isDirectory: true) diff --git a/Tests/RadrootsKitTests/RadrootsAppleDocumentScannerTests.swift b/Tests/RadrootsKitTests/RadrootsAppleDocumentScannerTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + #if !(canImport(UIKit) && canImport(VisionKit)) @Test func appleDocumentScannerReportsUnavailableWithoutVisionKitScanner() async throws { let scanner = try RadrootsAppleDocumentScanner(fileAccess: documentScannerTestFileAccess()) @@ -11,7 +12,7 @@ import Testing #expect(!support.multiPageSupported) #expect(support.supportedOutputKinds.isEmpty) - await #expect(throws: RadrootsCaptureIntakeError.unavailable("document scanner is unavailable")) { + await #expect(throws: RadrootsCaptureIntakeError.unavailable) { _ = try await scanner.scanDocument(RadrootsDocumentScanRequest()) } } diff --git a/Tests/RadrootsKitTests/RadrootsAppleErrorSafetyTests.swift b/Tests/RadrootsKitTests/RadrootsAppleErrorSafetyTests.swift @@ -0,0 +1,256 @@ +import Foundation +import RadrootsKitTesting +import Testing + +@testable import RadrootsKit + +@Test func publicAppleErrorsUseClosedPathFreeDescriptions() { + let errors: [any Error] = [ + RadrootsCaptureIntakeError.invalidRequest, + RadrootsCaptureIntakeError.unavailable, + RadrootsCaptureIntakeError.permissionDenied, + RadrootsCaptureIntakeError.userCancelled, + RadrootsCaptureIntakeError.transientFailure, + RadrootsCaptureIntakeError.permanentFailure, + RadrootsBackgroundTransferError.invalidRequest, + RadrootsBackgroundTransferError.unavailable, + RadrootsBackgroundTransferError.transferFailure, + RadrootsBackgroundTransferError.persistenceFailure, + RadrootsDocumentInterchangeError.invalidRequest, + RadrootsDocumentInterchangeError.notFound, + RadrootsDocumentInterchangeError.userCancelled, + RadrootsDocumentInterchangeError.permissionDenied, + RadrootsDocumentInterchangeError.transientFailure, + RadrootsDocumentInterchangeError.permanentFailure, + RadrootsAppLocalStateResetError.invalidRequest, + RadrootsAppLocalStateResetError.fileSystemFailure, + RadrootsAppLocalStateResetError.keychainFailure, + RadrootsAppleMediaPreparationError.invalidRequest, + RadrootsAppleMediaPreparationError.unavailable, + RadrootsAppleMediaPreparationError.preparationFailure, + RadrootsTelemetryError.invalidRequest, + RadrootsExternalActionError.invalidRequest, + RadrootsExternalActionError.blockedByPolicy, + RadrootsExternalActionError.unavailable, + RadrootsExternalActionError.transientFailure, + RadrootsExternalActionError.permanentFailure, + RadrootsAppleFileError.invalidRequest, + RadrootsAppleFileError.notFound, + RadrootsAppleFileError.permissionDenied, + RadrootsAppleFileError.transientFailure, + RadrootsAppleFileError.permanentFailure, + RadrootsLocationServicesError.invalidRequest, + RadrootsLocationServicesError.permissionDenied, + RadrootsLocationServicesError.unavailable, + RadrootsLocationServicesError.timeout, + RadrootsLocationServicesError.cancelled, + RadrootsLocationServicesError.transientFailure, + RadrootsLocationServicesError.permanentFailure, + RadrootsUserPresenceError.invalidRequest, + RadrootsUserPresenceError.userCancelled, + RadrootsUserPresenceError.permissionDenied, + RadrootsUserPresenceError.unavailable, + RadrootsUserPresenceError.timeout, + RadrootsUserPresenceError.transientFailure, + RadrootsUserPresenceError.permanentFailure, + RadrootsBackgroundTaskError.invalidRequest, + RadrootsBackgroundTaskError.unavailable, + RadrootsBackgroundTaskError.schedulerFailure, + RadrootsAppleSecurityError.invalidRequest, + RadrootsAppleSecurityError.notFound, + RadrootsAppleSecurityError.permissionDenied, + RadrootsAppleSecurityError.userCancelled, + RadrootsAppleSecurityError.transientFailure, + RadrootsAppleSecurityError.unavailable, + RadrootsAppleSecurityError.permanentFailure, + RadrootsAppleSecurityError.keychainFailure, + RadrootsAppleMobileStoreError.invalidPublicKey, + RadrootsAppleMobileStoreError.protectedDataUnavailable, + RadrootsAppleMobileStoreError.invalidDirectoryLayout, + RadrootsAppleMobileStoreError.fileSystemFailure, + RadrootsVerifiedArtifactAccessError.invalidDescriptor, + RadrootsVerifiedArtifactAccessError.protectedDataUnavailable, + RadrootsVerifiedArtifactAccessError.artifactUnavailable, + RadrootsVerifiedArtifactAccessError.artifactCorrupt, + RadrootsVerifiedArtifactAccessError.fileSystemFailure, + ] + let forbidden = [ + "/Users/example/private.sqlite", + "https://secret.example.invalid/token", + "nsec1secretcanary", + ] + + for error in errors { + let renderings = [ + String(describing: error), + String(reflecting: error), + (error as NSError).localizedDescription, + ] + #expect(renderings.allSatisfy { !$0.isEmpty }) + #expect( + renderings.allSatisfy { rendering in + forbidden.allSatisfy { !rendering.contains($0) } + }) + } +} + +@Test func dependencyDiagnosticsCannotBecomePublicErrorText() { + let canary = NSError( + domain: "/Users/example/private.sqlite", + code: 7, + userInfo: [ + NSLocalizedDescriptionKey: + "https://secret.example.invalid/token nsec1secretcanary" + ] + ) + + let captureError = RadrootsAppleMediaPicker.adapt(error: canary) + #expect(captureError == .transientFailure) + #expect(!String(reflecting: captureError).contains("private.sqlite")) + #expect(!(captureError.errorDescription ?? "").contains("secret.example.invalid")) + + #if canImport(LocalAuthentication) + let presenceError = RadrootsAppleUserPresenceAdapters.adapt(error: canary) + #expect(presenceError == .permanentFailure) + #expect(!String(reflecting: presenceError).contains("nsec1secretcanary")) + #expect(!(presenceError.errorDescription ?? "").contains("secret.example.invalid")) + #endif +} + +@Test func throwingAppleAdaptersMapDependencyDiagnosticsToClosedErrors() async throws { + let backgroundTasks = RadrootsAppleBackgroundTaskScheduler( + adapters: RadrootsAppleBackgroundTaskSchedulerAdapters( + now: { Date(timeIntervalSince1970: 1) }, + register: { _ in throw dependencyCanaryError() }, + submit: { _ in throw dependencyCanaryError() }, + cancel: { _ in }, + cancelAll: {}, + pendingTasks: { throw dependencyCanaryError() } + ) + ) + let registration = try RadrootsAppleBackgroundTaskRegistration( + identifier: RadrootsBackgroundTaskIdentifier("org.radroots.error-safety.refresh"), + kind: .appRefresh, + handler: { true } + ) + await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure) { + _ = try await backgroundTasks.register(registration) + } + await #expect(throws: RadrootsBackgroundTaskError.schedulerFailure) { + _ = try await backgroundTasks.pendingTasks() + } + + let location = RadrootsAppleLocationServices( + adapters: RadrootsAppleLocationServicesAdapters( + now: { Date(timeIntervalSince1970: 1) }, + locationServicesEnabled: { true }, + authorizationStatus: { .notDetermined }, + requestWhenInUseAuthorization: { _ in throw dependencyCanaryError() }, + requestCurrentLocation: { _ in throw dependencyCanaryError() } + ) + ) + await #expect(throws: RadrootsLocationServicesError.permanentFailure) { + _ = try await location.requestWhenInUseAuthorization() + } + + let presence = RadrootsAppleUserPresence( + adapters: RadrootsAppleUserPresenceAdapters( + currentStatus: { throw dependencyCanaryError() }, + verify: { _ in throw dependencyCanaryError() } + ) + ) + await #expect(throws: RadrootsUserPresenceError.permanentFailure) { + _ = try await presence.currentStatus() + } + let request = try RadrootsUserPresenceRequest(reason: "Verify local identity") + await #expect(throws: RadrootsUserPresenceError.permanentFailure) { + _ = try await presence.verify(request) + } + + let transferRequest = try RadrootsBackgroundTransferRequest( + identifier: RadrootsBackgroundTransferIdentifier("error-safety.transfer"), + remoteURL: #require(URL(string: "https://radroots.org/error-safety")), + method: .get, + operation: .download( + destination: .file( + RadrootsFileReference(scope: .cache, relativePath: "error-safety.bin") + ) + ) + ) + let storageFailure = RadrootsAppleBackgroundTransfer( + store: ThrowingBackgroundTransferStore(), + adapters: .unavailable + ) + await #expect(throws: RadrootsBackgroundTransferError.persistenceFailure) { + _ = try await storageFailure.enqueue(transferRequest) + } + + let adapterFailure = RadrootsAppleBackgroundTransfer( + store: RadrootsInMemoryBackgroundTransferStore(), + adapters: RadrootsAppleBackgroundTransferAdapters( + now: { Date(timeIntervalSince1970: 1) }, + enqueue: { _ in throw dependencyCanaryError() }, + cancel: { _ in }, + activeTransferIdentifiers: { [] }, + handleBackgroundEvents: { _, completion in completion() } + ) + ) + await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { + _ = try await adapterFailure.enqueue(transferRequest) + } +} + +@Test func fileSystemDiagnosticsCannotEscapeTheClosedFileErrorBoundary() throws { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("radroots-error-safety-\(UUID().uuidString)", isDirectory: true) + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + let invalidDataRoot = root.appendingPathComponent("private.sqlite") + try Data("not a directory".utf8).write(to: invalidDataRoot) + let roots = try RadrootsAppleFileRoots( + appIdentifier: "org.radroots.error-safety", + dataRoot: invalidDataRoot, + cacheRoot: root.appendingPathComponent("cache", isDirectory: true), + temporaryRoot: root.appendingPathComponent("temporary", isDirectory: true) + ) + let access = RadrootsAppleFileAccess(roots: roots) + + do { + try access.write( + .inline(Data("payload".utf8)), + to: RadrootsFileReference(scope: .data, relativePath: "secret.example.invalid") + ) + Issue.record("expected the invalid filesystem layout to fail") + } catch let error as RadrootsAppleFileError { + #expect(error == .permanentFailure) + #expect(!String(reflecting: error).contains("private.sqlite")) + #expect(!(error.errorDescription ?? "").contains("secret.example.invalid")) + } catch { + Issue.record("unexpected dependency-owned error: \(type(of: error))") + } +} + +private func dependencyCanaryError() -> NSError { + NSError( + domain: "/Users/example/private.sqlite", + code: 9, + userInfo: [NSLocalizedDescriptionKey: "https://secret.example.invalid/token"] + ) +} + +private actor ThrowingBackgroundTransferStore: RadrootsBackgroundTransferStore { + func loadSnapshots() async throws -> [RadrootsBackgroundTransferSnapshot] { + throw dependencyCanaryError() + } + + func saveSnapshot(_: RadrootsBackgroundTransferSnapshot) async throws { + throw dependencyCanaryError() + } + + func removeSnapshot(for _: RadrootsBackgroundTransferIdentifier) async throws { + throw dependencyCanaryError() + } + + func removeAllSnapshots() async throws { + throw dependencyCanaryError() + } +} diff --git a/Tests/RadrootsKitTests/RadrootsAppleExternalActionsTests.swift b/Tests/RadrootsKitTests/RadrootsAppleExternalActionsTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func appleExternalActionsOpensAppSettingsThroughAdapter() async throws { let settingsURL = try #require(URL(string: "app-settings:radroots")) let probe = RadrootsExternalActionAdapterProbe(appSettingsURL: settingsURL) @@ -23,7 +24,7 @@ import Testing let capability = await service.canOpen(.appSettings) #expect(!capability.canOpen) - await #expect(throws: RadrootsExternalActionError.unavailable("appSettings external action is unavailable")) { + await #expect(throws: RadrootsExternalActionError.unavailable) { try await service.open(RadrootsExternalActionRequest(destination: .appSettings)) } #expect(await probe.canOpenURLs.isEmpty) @@ -36,7 +37,7 @@ import Testing let probe = RadrootsExternalActionAdapterProbe(openResult: false) let service = RadrootsAppleExternalActions(adapters: probe.adapters()) - await #expect(throws: RadrootsExternalActionError.transientFailure("failed to open web external action")) { + await #expect(throws: RadrootsExternalActionError.transientFailure) { try await service.open(RadrootsExternalActionRequest(destination: destination)) } diff --git a/Tests/RadrootsKitTests/RadrootsAppleLocationServicesTests.swift b/Tests/RadrootsKitTests/RadrootsAppleLocationServicesTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + #if canImport(CoreLocation) import CoreLocation #endif @@ -13,7 +14,7 @@ import Testing authorizationStatus: { .authorizedWhenInUse }, requestWhenInUseAuthorization: { _ in .authorizedWhenInUse }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.permanentFailure("not used") + throw RadrootsLocationServicesError.permanentFailure } ) ) @@ -31,10 +32,10 @@ import Testing locationServicesEnabled: { true }, authorizationStatus: { .authorizedWhenInUse }, requestWhenInUseAuthorization: { _ in - throw RadrootsLocationServicesError.permanentFailure("should not request") + throw RadrootsLocationServicesError.permanentFailure }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.permanentFailure("not used") + throw RadrootsLocationServicesError.permanentFailure } ) ) @@ -46,15 +47,15 @@ import Testing locationServicesEnabled: { true }, authorizationStatus: { .denied }, requestWhenInUseAuthorization: { _ in - throw RadrootsLocationServicesError.permanentFailure("should not request") + throw RadrootsLocationServicesError.permanentFailure }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.permanentFailure("not used") + throw RadrootsLocationServicesError.permanentFailure } ) ) - await #expect(throws: RadrootsLocationServicesError.permissionDenied("location permission is denied")) { + await #expect(throws: RadrootsLocationServicesError.permissionDenied) { _ = try await denied.requestWhenInUseAuthorization() } } @@ -69,7 +70,7 @@ import Testing return .authorizedWhenInUse }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.permanentFailure("not used") + throw RadrootsLocationServicesError.permanentFailure } ) ) @@ -97,7 +98,8 @@ import Testing ) ) - let result = try await service.currentLocation(RadrootsCurrentLocationRequest( + let result = try await service.currentLocation( + RadrootsCurrentLocationRequest( timeoutSeconds: 3, maximumCachedReadingAgeSeconds: 5 )) @@ -113,12 +115,12 @@ import Testing authorizationStatus: { .notDetermined }, requestWhenInUseAuthorization: { _ in .authorizedWhenInUse }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.permanentFailure("should not request") + throw RadrootsLocationServicesError.permanentFailure } ) ) - await #expect(throws: RadrootsLocationServicesError.permissionDenied("location permission has not been requested")) { + await #expect(throws: RadrootsLocationServicesError.permissionDenied) { _ = try await service.currentLocation(RadrootsCurrentLocationRequest(timeoutSeconds: 1)) } } @@ -139,8 +141,9 @@ import Testing ) ) - await #expect(throws: RadrootsLocationServicesError.transientFailure("location reading is older than the requested maximum age")) { - _ = try await service.currentLocation(RadrootsCurrentLocationRequest( + await #expect(throws: RadrootsLocationServicesError.transientFailure) { + _ = try await service.currentLocation( + RadrootsCurrentLocationRequest( timeoutSeconds: 1, maximumCachedReadingAgeSeconds: 5 )) @@ -154,36 +157,41 @@ import Testing authorizationStatus: { .authorizedWhenInUse }, requestWhenInUseAuthorization: { _ in .authorizedWhenInUse }, requestCurrentLocation: { _ in - throw RadrootsLocationServicesError.timeout("timed out") + throw RadrootsLocationServicesError.timeout } ) ) - await #expect(throws: RadrootsLocationServicesError.timeout("timed out")) { + await #expect(throws: RadrootsLocationServicesError.timeout) { _ = try await service.currentLocation(RadrootsCurrentLocationRequest(timeoutSeconds: 1)) } } #if canImport(CoreLocation) @Test func appleLocationServicesMapsCoreLocationAuthorization() { - #expect(RadrootsAppleLocationServicesAdapters.authorization( + #expect( + RadrootsAppleLocationServicesAdapters.authorization( for: CLAuthorizationStatus.notDetermined, locationServicesEnabled: true ) == .notDetermined) - #expect(RadrootsAppleLocationServicesAdapters.authorization( + #expect( + RadrootsAppleLocationServicesAdapters.authorization( for: CLAuthorizationStatus.denied, locationServicesEnabled: true ) == .denied) - #expect(RadrootsAppleLocationServicesAdapters.authorization( + #expect( + RadrootsAppleLocationServicesAdapters.authorization( for: CLAuthorizationStatus.authorizedAlways, locationServicesEnabled: true ) == .authorizedAlways) - #expect(RadrootsAppleLocationServicesAdapters.authorization( + #expect( + RadrootsAppleLocationServicesAdapters.authorization( for: CLAuthorizationStatus.authorizedAlways, locationServicesEnabled: false ) == .unavailable) #if os(iOS) - #expect(RadrootsAppleLocationServicesAdapters.authorization( + #expect( + RadrootsAppleLocationServicesAdapters.authorization( for: CLAuthorizationStatus.authorizedWhenInUse, locationServicesEnabled: true ) == .authorizedWhenInUse) diff --git a/Tests/RadrootsKitTests/RadrootsAppleMediaPickerTests.swift b/Tests/RadrootsKitTests/RadrootsAppleMediaPickerTests.swift @@ -1,11 +1,12 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func captureAsyncSupportRunsCleanupOnTimeout() async throws { let probe = RadrootsCaptureCleanupProbe() - await #expect(throws: RadrootsCaptureIntakeError.transientFailure("timeout")) { + await #expect(throws: RadrootsCaptureIntakeError.transientFailure) { let _: Int = try await RadrootsAppleCaptureAsyncSupport.awaitMainActorCallback( timeout: 0.001, timeoutMessage: "timeout" @@ -54,7 +55,7 @@ import Testing task.cancel() - await #expect(throws: RadrootsCaptureIntakeError.userCancelled("capture request was cancelled")) { + await #expect(throws: RadrootsCaptureIntakeError.userCancelled) { _ = try await task.value } try await Task.sleep(nanoseconds: 20_000_000) @@ -71,10 +72,10 @@ import Testing #expect(support.supportedImportKinds.isEmpty) #expect(support.supportedCaptureKinds.isEmpty) - await #expect(throws: RadrootsCaptureIntakeError.unavailable("media import is unavailable")) { + await #expect(throws: RadrootsCaptureIntakeError.unavailable) { _ = try await picker.importMedia(RadrootsMediaImportRequest()) } - await #expect(throws: RadrootsCaptureIntakeError.unavailable("camera photo capture is unavailable")) { + await #expect(throws: RadrootsCaptureIntakeError.unavailable) { _ = try await picker.captureMedia(RadrootsMediaCaptureRequest()) } } diff --git a/Tests/RadrootsKitTests/RadrootsAppleMediaPreparationTests.swift b/Tests/RadrootsKitTests/RadrootsAppleMediaPreparationTests.swift @@ -2,10 +2,11 @@ import CoreGraphics import Darwin import Foundation import ImageIO -@testable import RadrootsKit import Testing import UniformTypeIdentifiers +@testable import RadrootsKit + @Test func appleMediaPreparationNormalizesAndCommitsStableFinalBytes() async throws { let roots = try mediaPreparationRoots() let sourceReference = RadrootsFileReference(scope: .cache, relativePath: "capture/source.jpg") @@ -26,7 +27,8 @@ import UniformTypeIdentifiers #expect(!first.debugDescription.contains(roots.temporaryRoot.path)) let outputURL = try roots.stagedBlobURL(for: first.file) let outputSource = try #require(CGImageSourceCreateWithURL(outputURL as CFURL, nil)) - let outputProperties = try #require(CGImageSourceCopyPropertiesAtIndex(outputSource, 0, nil) as? [CFString: Any]) + let outputProperties = try #require( + CGImageSourceCopyPropertiesAtIndex(outputSource, 0, nil) as? [CFString: Any]) #expect(outputProperties[kCGImagePropertyGPSDictionary] == nil) #expect((outputProperties[kCGImagePropertyOrientation] as? NSNumber)?.intValue ?? 1 == 1) } @@ -36,10 +38,13 @@ import UniformTypeIdentifiers let sourceReference = RadrootsFileReference(scope: .cache, relativePath: "capture/source.jpg") try writeOrientedImageWithMetadata(to: roots.resolvedURL(for: sourceReference)) let preparer = RadrootsAppleMediaPreparer(roots: roots) - let prepared = try await preparer.prepareImage(RadrootsAppleImagePreparationRequest(source: .file(sourceReference))) + let prepared = try await preparer.prepareImage( + RadrootsAppleImagePreparationRequest(source: .file(sourceReference))) let request = try await preparer.blossomUploadRequest( - preparedImage: prepared, remoteURL: #require(URL(string: "https://blossom.radroots.org/upload")), authorization: "Nostr signed-event", + preparedImage: prepared, + remoteURL: #require(URL(string: "https://blossom.radroots.org/upload")), + authorization: "Nostr signed-event", identifier: RadrootsBackgroundTransferIdentifier("field.media.upload") ) @@ -55,9 +60,11 @@ import UniformTypeIdentifiers var tampered = try Data(contentsOf: preparedURL) tampered[tampered.startIndex] ^= 0x01 try tampered.write(to: preparedURL, options: .atomic) - await #expect(throws: RadrootsAppleMediaPreparationError.invalidRequest("prepared image no longer matches its commitment")) { + await #expect(throws: RadrootsAppleMediaPreparationError.invalidRequest) { _ = try await preparer.blossomUploadRequest( - preparedImage: prepared, remoteURL: #require(URL(string: "https://blossom.radroots.org/upload")), authorization: "Nostr signed-event" + preparedImage: prepared, + remoteURL: #require(URL(string: "https://blossom.radroots.org/upload")), + authorization: "Nostr signed-event" ) } } @@ -68,13 +75,16 @@ import UniformTypeIdentifiers try writeOrientedImageWithMetadata(to: roots.resolvedURL(for: sourceReference)) let bounded = RadrootsAppleMediaPreparer(roots: roots) - await #expect(throws: RadrootsAppleMediaPreparationError.invalidRequest("image source is unavailable or exceeds its byte limit")) { - _ = try await bounded.prepareImage(RadrootsAppleImagePreparationRequest(source: .file(sourceReference), maximumInputBytes: 1)) + await #expect(throws: RadrootsAppleMediaPreparationError.invalidRequest) { + _ = try await bounded.prepareImage( + RadrootsAppleImagePreparationRequest(source: .file(sourceReference), maximumInputBytes: 1)) } - let locked = RadrootsAppleMediaPreparer(roots: roots, protectedData: RadrootsProtectedDataProvider { .locked }) - await #expect(throws: RadrootsAppleMediaPreparationError.unavailable("image preparation protected data is unavailable")) { - _ = try await locked.prepareImage(RadrootsAppleImagePreparationRequest(source: .file(sourceReference))) + let locked = RadrootsAppleMediaPreparer( + roots: roots, protectedData: RadrootsProtectedDataProvider { .locked }) + await #expect(throws: RadrootsAppleMediaPreparationError.unavailable) { + _ = try await locked.prepareImage( + RadrootsAppleImagePreparationRequest(source: .file(sourceReference))) } } @@ -89,10 +99,13 @@ private func writeOrientedImageWithMetadata(to url: URL) throws { context.setFillColor(CGColor(red: 0.2, green: 0.7, blue: 0.3, alpha: 1)) context.fill(CGRect(x: 0, y: 0, width: 2, height: 3)) let image = try #require(context.makeImage()) - try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) - let destination = try #require(CGImageDestinationCreateWithURL(url as CFURL, UTType.jpeg.identifier as CFString, 1, nil)) + try FileManager.default.createDirectory( + at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + let destination = try #require( + CGImageDestinationCreateWithURL(url as CFURL, UTType.jpeg.identifier as CFString, 1, nil)) let properties: [CFString: Any] = [ - kCGImagePropertyOrientation: 6, kCGImagePropertyGPSDictionary: [kCGImagePropertyGPSLatitude: 45.0], + kCGImagePropertyOrientation: 6, + kCGImagePropertyGPSDictionary: [kCGImagePropertyGPSLatitude: 45.0], kCGImageDestinationLossyCompressionQuality: 0.9, ] CGImageDestinationAddImage(destination, image, properties as CFDictionary) @@ -108,7 +121,8 @@ private func mediaPreparationRoots() throws -> RadrootsAppleFileRoots { defer { Darwin.free(pointer) } let root = URL(fileURLWithPath: String(cString: pointer), isDirectory: true) return try RadrootsAppleFileRoots( - appIdentifier: "org.radroots.tests", dataRoot: root.appendingPathComponent("data", isDirectory: true), + appIdentifier: "org.radroots.tests", + dataRoot: root.appendingPathComponent("data", isDirectory: true), cacheRoot: root.appendingPathComponent("cache", isDirectory: true), temporaryRoot: root.appendingPathComponent("tmp", isDirectory: true) ) diff --git a/Tests/RadrootsKitTests/RadrootsAppleUserPresenceTests.swift b/Tests/RadrootsKitTests/RadrootsAppleUserPresenceTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func appleUserPresenceReportsStatusThroughAdapter() async throws { let expectedStatus = RadrootsUserPresenceStatus( support: .deviceCredential, @@ -54,18 +55,18 @@ import Testing .unavailable }, verify: { _ in - throw RadrootsUserPresenceError.unavailable("user presence unavailable") + throw RadrootsUserPresenceError.unavailable } ) ) - await #expect(throws: RadrootsUserPresenceError.unavailable("user presence unavailable")) { + await #expect(throws: RadrootsUserPresenceError.unavailable) { try await service.verify(RadrootsUserPresenceRequest(reason: "Delete local Nostr identity")) } } @Test func appleUserPresenceAsyncSupportTimesOutUnresolvedCallbacks() async { - await #expect(throws: RadrootsUserPresenceError.timeout("timed out")) { + await #expect(throws: RadrootsUserPresenceError.timeout) { let _: Bool = try await RadrootsAppleUserPresenceAsyncSupport.awaitCallback( timeout: 0.001, timeoutMessage: "timed out" @@ -78,12 +79,12 @@ import Testing @Test func appleUserPresenceMapsLocalAuthenticationPolicies() { #expect( - RadrootsAppleUserPresenceAdapters.platformPolicy(.deviceOwnerAuthentication) == - LAPolicy.deviceOwnerAuthentication + RadrootsAppleUserPresenceAdapters.platformPolicy(.deviceOwnerAuthentication) + == LAPolicy.deviceOwnerAuthentication ) #expect( - RadrootsAppleUserPresenceAdapters.platformPolicy(.deviceOwnerAuthenticationWithBiometrics) == - LAPolicy.deviceOwnerAuthenticationWithBiometrics + RadrootsAppleUserPresenceAdapters.platformPolicy(.deviceOwnerAuthenticationWithBiometrics) + == LAPolicy.deviceOwnerAuthenticationWithBiometrics ) } diff --git a/Tests/RadrootsKitTests/RadrootsBackgroundTaskTests.swift b/Tests/RadrootsKitTests/RadrootsBackgroundTaskTests.swift @@ -1,19 +1,20 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func backgroundTaskIdentifierNormalizesAndRejectsUnsafeValues() throws { let identifier = try RadrootsBackgroundTaskIdentifier(" ORG.RADROOTS.FIELD-IOS.refresh ") #expect(identifier.rawValue == "org.radroots.field-ios.refresh") - #expect(throws: RadrootsBackgroundTaskError.invalidRequest("background task identifier must not be empty")) { + #expect(throws: RadrootsBackgroundTaskError.invalidRequest) { _ = try RadrootsBackgroundTaskIdentifier(" ") } - #expect(throws: RadrootsBackgroundTaskError.invalidRequest("background task identifier must use lowercase safe identifier characters")) { + #expect(throws: RadrootsBackgroundTaskError.invalidRequest) { _ = try RadrootsBackgroundTaskIdentifier(".org.radroots") } - #expect(throws: RadrootsBackgroundTaskError.invalidRequest("background task identifier cannot contain empty path components")) { + #expect(throws: RadrootsBackgroundTaskError.invalidRequest) { _ = try RadrootsBackgroundTaskIdentifier("org.radroots..refresh") } } @@ -40,14 +41,14 @@ import Testing #expect(processing.requiresNetworkConnectivity) #expect(processing.requiresExternalPower) - #expect(throws: RadrootsBackgroundTaskError.invalidRequest("app refresh tasks cannot require network connectivity or external power")) { + #expect(throws: RadrootsBackgroundTaskError.invalidRequest) { _ = try RadrootsBackgroundTaskRequest( identifier: "org.radroots.field-ios.background.refresh", kind: .appRefresh, requiresNetworkConnectivity: true ) } - #expect(throws: RadrootsBackgroundTaskError.invalidRequest("background task earliest begin date must be finite")) { + #expect(throws: RadrootsBackgroundTaskError.invalidRequest) { _ = try RadrootsBackgroundTaskRequest( identifier: "org.radroots.field-ios.background.refresh", kind: .appRefresh, @@ -75,7 +76,7 @@ import Testing #expect(snapshot.requiresNetworkConnectivity) #expect(!snapshot.requiresExternalPower) - #expect(throws: RadrootsBackgroundTaskError.invalidRequest("background task submitted date must be finite")) { + #expect(throws: RadrootsBackgroundTaskError.invalidRequest) { _ = try RadrootsBackgroundTaskSnapshot( request: request, submittedAt: Date(timeIntervalSinceReferenceDate: .infinity) @@ -84,20 +85,20 @@ import Testing } @Test func unavailableBackgroundTaskSchedulerThrowsTypedErrors() async throws { - let scheduler = RadrootsUnavailableBackgroundTaskScheduler(reason: "missing background support") + let scheduler = RadrootsUnavailableBackgroundTaskScheduler() let identifier = try RadrootsBackgroundTaskIdentifier("org.radroots.field-ios.background.refresh") let request = try RadrootsBackgroundTaskRequest(identifier: identifier, kind: .appRefresh) - await #expect(throws: RadrootsBackgroundTaskError.unavailable("missing background support")) { + await #expect(throws: RadrootsBackgroundTaskError.unavailable) { _ = try await scheduler.submit(request) } - await #expect(throws: RadrootsBackgroundTaskError.unavailable("missing background support")) { + await #expect(throws: RadrootsBackgroundTaskError.unavailable) { try await scheduler.cancel(identifier) } - await #expect(throws: RadrootsBackgroundTaskError.unavailable("missing background support")) { + await #expect(throws: RadrootsBackgroundTaskError.unavailable) { try await scheduler.cancelAll() } - await #expect(throws: RadrootsBackgroundTaskError.unavailable("missing background support")) { + await #expect(throws: RadrootsBackgroundTaskError.unavailable) { _ = try await scheduler.pendingTasks() } } diff --git a/Tests/RadrootsKitTests/RadrootsBackgroundTransferTests.swift b/Tests/RadrootsKitTests/RadrootsBackgroundTransferTests.swift @@ -10,19 +10,15 @@ import Testing #expect(identifier.rawValue == "field-ios.transfer_1") #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier must not be empty") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferIdentifier(" ") } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier must use lowercase safe identifier characters" - ) + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferIdentifier("/escape") } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer identifier cannot contain empty path components") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferIdentifier("field..transfer") } @@ -51,8 +47,7 @@ import Testing #expect(authorized.headers["Authorization"] == longAuthorization) #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer remote URL must use public HTTPS") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "http://radroots.org/relay.json")), method: .get, @@ -60,8 +55,7 @@ import Testing ) } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer remote URL is unsafe") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/relay.json?token=secret")), @@ -76,8 +70,7 @@ import Testing ) #expect(simulatorRequest.networkPolicy == .simulatorLoopbackHTTP) #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background download transfers must use GET") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/relay.json")), method: .post, @@ -85,8 +78,7 @@ import Testing ) } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer header value cannot contain control characters") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/relay.json")), method: .get, @@ -95,8 +87,7 @@ import Testing ) } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer header name is unsafe") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/relay.json")), method: .get, @@ -118,8 +109,7 @@ import Testing #expect(request.operation == .upload(source: source)) #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background upload transfers must use POST or PUT") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/upload")), method: .get, @@ -141,8 +131,7 @@ import Testing #expect(request.maximumTransferBytes == 1024) #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer byte limit is invalid") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/unbounded.bin")), @@ -152,8 +141,7 @@ import Testing ) } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background downloads require a scoped destination file") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferRequest( remoteURL: #require(URL(string: "https://radroots.org/blob.bin")), @@ -169,25 +157,22 @@ import Testing let request = try testDownloadRequest(identifier: "field.transfer.snapshot") let progress = try RadrootsBackgroundTransferProgress(bytesTransferred: 5, totalBytesExpected: 10) let snapshot = try RadrootsBackgroundTransferSnapshot( - request: request, state: .running, progress: progress, errorMessage: " running ", + request: request, state: .running, progress: progress, failure: .platformFailure, updatedAt: Date(timeIntervalSince1970: 1) ) #expect(snapshot.identifier == request.identifier) #expect(snapshot.state == .running) #expect(snapshot.progress == progress) - #expect(snapshot.errorMessage == "running") + #expect(snapshot.failure == .platformFailure) #expect(snapshot.response == nil) #expect(!snapshot.possibleRemoteOrphan) #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer expected byte count cannot be less than transferred bytes" - ) + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferProgress(bytesTransferred: 10, totalBytesExpected: 5) } #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer updated date must be finite") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try RadrootsBackgroundTransferSnapshot( request: request, updatedAt: Date(timeIntervalSinceReferenceDate: .infinity)) @@ -206,7 +191,13 @@ import Testing headers: ["Authorization": "Nostr secret-token"], metadata: ["purpose": "blossom_upload", "sha256": String(repeating: "a", count: 64)] ) - try await store.saveSnapshot(RadrootsBackgroundTransferSnapshot(request: request)) + try await store.saveSnapshot( + RadrootsBackgroundTransferSnapshot( + request: request, + state: .failed, + failure: .verificationRejected + ) + ) let persistedURL = roots.dataRoot.appendingPathComponent("background_transfers/transfers.json") let persisted = try String(contentsOf: persistedURL, encoding: .utf8) @@ -220,15 +211,19 @@ import Testing #expect(!persisted.contains("blossom_upload")) #expect(!persisted.contains(String(repeating: "a", count: 64))) #expect(persisted.contains("\"schemaVersion\":1")) + #expect(persisted.contains("background_transfer_verification_rejected")) let recovered = try #require(try await store.loadSnapshots().first) #expect(recovered.request.headers.isEmpty) #expect(recovered.request.metadata.isEmpty) + #expect(recovered.failure == .verificationRejected) - let malformed = persisted.replacingOccurrences(of: "field.transfer.redacted", with: "../unsafe") + let malformed = persisted.replacingOccurrences( + of: "background_transfer_verification_rejected", + with: "https://secret.example.invalid/token" + ) try Data(malformed.utf8).write(to: persistedURL, options: .atomic) await #expect( - throws: RadrootsBackgroundTransferError.persistenceFailure( - "background transfer persistence could not be read") + throws: RadrootsBackgroundTransferError.persistenceFailure ) { _ = try await store.loadSnapshots() } @@ -277,8 +272,7 @@ import Testing roots: roots, protectedData: RadrootsProtectedDataProvider { .locked }) await #expect( - throws: RadrootsBackgroundTransferError.persistenceFailure( - "background transfer protected data is unavailable") + throws: RadrootsBackgroundTransferError.persistenceFailure ) { _ = try await store.loadSnapshots() } @@ -310,8 +304,7 @@ import Testing let symlink = roots.dataRoot.appendingPathComponent("escape.bin") try FileManager.default.createSymbolicLink(at: symlink, withDestinationURL: outside) #expect( - throws: RadrootsBackgroundTransferError.invalidRequest( - "background transfer local file resolved outside its root") + throws: RadrootsBackgroundTransferError.invalidRequest ) { _ = try resolver.resolve(.file(RadrootsFileReference(scope: .data, relativePath: "escape.bin"))) } @@ -321,13 +314,12 @@ import Testing maximumBytes: 64) } - #expect(throws: RadrootsAppleFileError.invalidRequest("file relative path must not be absolute")) - { + #expect(throws: RadrootsAppleFileError.invalidRequest) { _ = try resolver.resolve( .file(RadrootsFileReference(scope: .data, relativePath: "/tmp/escape"))) } #expect( - throws: RadrootsAppleFileError.invalidRequest("staged blob id contains invalid characters") + throws: RadrootsAppleFileError.invalidRequest ) { _ = try resolver.resolve( .stagedBlob(RadrootsStagedBlobReference(blobID: "../escape", sizeBytes: 1))) @@ -405,27 +397,26 @@ import Testing } @Test func unavailableBackgroundTransferThrowsTypedErrors() async throws { - let transfer = RadrootsUnavailableBackgroundTransfer( - reason: "missing background transfer support") + let transfer = RadrootsUnavailableBackgroundTransfer() let request = try testDownloadRequest(identifier: "field.transfer.unavailable") await #expect( - throws: RadrootsBackgroundTransferError.unavailable("missing background transfer support") + throws: RadrootsBackgroundTransferError.unavailable ) { _ = try await transfer.enqueue(request) } await #expect( - throws: RadrootsBackgroundTransferError.unavailable("missing background transfer support") + throws: RadrootsBackgroundTransferError.unavailable ) { try await transfer.cancel(request.identifier) } await #expect( - throws: RadrootsBackgroundTransferError.unavailable("missing background transfer support") + throws: RadrootsBackgroundTransferError.unavailable ) { _ = try await transfer.snapshot(for: request.identifier) } await #expect( - throws: RadrootsBackgroundTransferError.unavailable("missing background transfer support") + throws: RadrootsBackgroundTransferError.unavailable ) { _ = try await transfer.snapshots() } @@ -447,8 +438,7 @@ private func testBackgroundTransferRoots() throws -> RadrootsAppleFileRoots { ) try FileManager.default.createDirectory(at: unresolvedRoot, withIntermediateDirectories: false) guard let resolvedPointer = unresolvedRoot.path.withCString({ Darwin.realpath($0, nil) }) else { - throw RadrootsBackgroundTransferError.persistenceFailure( - "background transfer test root is unavailable") + throw RadrootsBackgroundTransferError.persistenceFailure } defer { Darwin.free(resolvedPointer) } let root = URL(fileURLWithPath: String(cString: resolvedPointer), isDirectory: true) diff --git a/Tests/RadrootsKitTests/RadrootsCaptureIntakeTests.swift b/Tests/RadrootsKitTests/RadrootsCaptureIntakeTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func mediaImportRequestNormalizesKindsAndSelectionLimit() throws { let request = try RadrootsMediaImportRequest( allowedMediaKinds: [.image, .image], @@ -15,10 +16,10 @@ import Testing } @Test func mediaImportRequestRejectsInvalidSelectionLimits() { - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("media import selection limit must be positive")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsMediaImportRequest(selectionLimit: 0) } - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("media import selection limit cannot exceed 100")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsMediaImportRequest(selectionLimit: 101) } } @@ -60,7 +61,7 @@ import Testing } @Test func mediaAssetRejectsUnsafeMetadata() { - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("capture filename cannot contain path separators")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsMediaAsset( source: .libraryImport, kind: .image, @@ -71,7 +72,7 @@ import Testing capturedAt: Date(timeIntervalSince1970: 10) ) } - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("capture media type must be type/subtype")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsMediaAsset( source: .libraryImport, kind: .image, @@ -82,7 +83,7 @@ import Testing capturedAt: Date(timeIntervalSince1970: 10) ) } - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("image dimensions must include width and height together")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsMediaAsset( source: .libraryImport, kind: .image, @@ -101,7 +102,7 @@ import Testing let result = try RadrootsMediaImportResult(items: [asset]) #expect(result.items == [asset]) - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("media import result cannot be empty")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsMediaImportResult(items: []) } } @@ -136,7 +137,7 @@ import Testing } @Test func scannedDocumentRejectsEmptyPageCount() { - #expect(throws: RadrootsCaptureIntakeError.invalidRequest("scanned document page count must be positive")) { + #expect(throws: RadrootsCaptureIntakeError.invalidRequest) { _ = try RadrootsScannedDocument( file: RadrootsFileReference(scope: .temporary, relativePath: "capture/scan.pdf"), outputKind: .pdf, diff --git a/Tests/RadrootsKitTests/RadrootsExternalActionsTests.swift b/Tests/RadrootsKitTests/RadrootsExternalActionsTests.swift @@ -1,23 +1,24 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func webDestinationAcceptsOnlyHttpsUrlsWithHosts() throws { let destination = try RadrootsExternalActionDestination.web(" https://radroots.org/field ") #expect(destination.kind == .web) #expect(destination.url?.absoluteString == "https://radroots.org/field") - #expect(throws: RadrootsExternalActionError.blockedByPolicy("external web urls must use https with a host")) { + #expect(throws: RadrootsExternalActionError.blockedByPolicy) { _ = try RadrootsExternalActionDestination.web("http://radroots.org") } - #expect(throws: RadrootsExternalActionError.blockedByPolicy("external web urls must use https with a host")) { + #expect(throws: RadrootsExternalActionError.blockedByPolicy) { _ = try RadrootsExternalActionDestination.web("wss://radroots.org") } - #expect(throws: RadrootsExternalActionError.blockedByPolicy("external web urls must use https with a host")) { + #expect(throws: RadrootsExternalActionError.blockedByPolicy) { _ = try RadrootsExternalActionDestination.web("https:///missing-host") } - #expect(throws: RadrootsExternalActionError.invalidRequest("web url cannot contain whitespace or control characters")) { + #expect(throws: RadrootsExternalActionError.invalidRequest) { _ = try RadrootsExternalActionDestination.web("https://radroots.org/a b") } } @@ -34,13 +35,13 @@ import Testing _ = try RadrootsExternalActionDestination.nostr("nostr:naddr1qqqqqq") _ = try RadrootsExternalActionDestination.nostr("nostr:nrelay1qqqqqq") - #expect(throws: RadrootsExternalActionError.blockedByPolicy("nostr secret payloads cannot be opened externally")) { + #expect(throws: RadrootsExternalActionError.blockedByPolicy) { _ = try RadrootsExternalActionDestination.nostr("nostr:nsec1qqqqqq") } - #expect(throws: RadrootsExternalActionError.blockedByPolicy("nostr uri payload must be a public Nostr identifier")) { + #expect(throws: RadrootsExternalActionError.blockedByPolicy) { _ = try RadrootsExternalActionDestination.nostr("nostr:relay1qqqqqq") } - #expect(throws: RadrootsExternalActionError.invalidRequest("nostr uri cannot contain whitespace or control characters")) { + #expect(throws: RadrootsExternalActionError.invalidRequest) { _ = try RadrootsExternalActionDestination.nostr("nostr:npub1qq q") } } @@ -60,7 +61,7 @@ import Testing _ = try RadrootsExternalActionDestination.appleMaps("https://maps.apple.com/?q=Field") - #expect(throws: RadrootsExternalActionError.blockedByPolicy("apple maps urls must use https://maps.apple.com")) { + #expect(throws: RadrootsExternalActionError.blockedByPolicy) { _ = try RadrootsExternalActionDestination.appleMaps("https://example.com/maps") } } diff --git a/Tests/RadrootsKitTests/RadrootsSecureStoreTests.swift b/Tests/RadrootsKitTests/RadrootsSecureStoreTests.swift @@ -1,8 +1,9 @@ import Foundation -@testable import RadrootsKit import Security import Testing +@testable import RadrootsKit + @Test func secureStoreKeyBuildsServiceName() throws { let key = RadrootsSecureStoreKey(namespace: "session", name: "token") #expect(try key.serviceName(servicePrefix: "org.radroots.test") == "org.radroots.test.session") @@ -104,7 +105,7 @@ import Testing let failingStore = RadrootsAppleKeychainSecureStore( servicePrefix: servicePrefix, accessControlFactory: { _ in - throw RadrootsAppleSecurityError.invalidRequest("forced access control failure") + throw RadrootsAppleSecurityError.invalidRequest } ) @@ -121,7 +122,8 @@ import Testing let store = RadrootsAppleKeychainSecureStore() let mapping = store.keychainPolicyMapping(for: .secureLocalSecret) - #expect(String(mapping.accessibilityConstant) == String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) + #expect( + String(mapping.accessibilityConstant) == String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) #expect(mapping.usesAccessControl == false) #expect(mapping.accessControlFlags.isEmpty) } @@ -130,7 +132,8 @@ import Testing let store = RadrootsAppleKeychainSecureStore() let mapping = store.keychainPolicyMapping(for: .userPresenceLocalSecret) - #expect(String(mapping.accessibilityConstant) == String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) + #expect( + String(mapping.accessibilityConstant) == String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) #expect(mapping.usesAccessControl == true) #expect(mapping.accessControlFlags == .userPresence) _ = try store.accessControl(for: mapping) @@ -146,7 +149,9 @@ import Testing ) ) - #expect(String(mapping.accessibilityConstant) == String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)) + #expect( + String(mapping.accessibilityConstant) + == String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)) #expect(mapping.usesAccessControl == false) #expect(mapping.accessControlFlags.isEmpty) } diff --git a/Tests/RadrootsKitTests/RadrootsTelemetryTests.swift b/Tests/RadrootsKitTests/RadrootsTelemetryTests.swift @@ -1,7 +1,8 @@ import Foundation -@testable import RadrootsKit import Testing +@testable import RadrootsKit + @Test func telemetryEventNormalizesSafeIdentifiersAndFields() throws { let event = try RadrootsTelemetryEvent( name: "field_ios.startup.begin", @@ -25,16 +26,16 @@ import Testing } @Test func telemetryEventRejectsUnsafeShape() throws { - #expect(throws: RadrootsTelemetryError.invalidRequest("telemetry event name must use lowercase safe identifier characters")) { + #expect(throws: RadrootsTelemetryError.invalidRequest) { _ = try RadrootsTelemetryEvent(name: "FieldIos.Startup") } - #expect(throws: RadrootsTelemetryError.invalidRequest("telemetry field key must use lowercase safe identifier characters")) { + #expect(throws: RadrootsTelemetryError.invalidRequest) { _ = try RadrootsTelemetryField.string("Relay Light", "green") } - #expect(throws: RadrootsTelemetryError.invalidRequest("telemetry double field must be finite")) { + #expect(throws: RadrootsTelemetryError.invalidRequest) { _ = try RadrootsTelemetryField.double("elapsed_seconds", .infinity) } - #expect(throws: RadrootsTelemetryError.invalidRequest("telemetry event field keys must be unique")) { + #expect(throws: RadrootsTelemetryError.invalidRequest) { _ = try RadrootsTelemetryEvent( name: "field_ios.relay.status", fields: [ @@ -81,7 +82,7 @@ import Testing name: "field_ios.identity.import", message: "imported nsec1secret", fields: [ - .string("identity_state", "imported"), + .string("identity_state", "imported") ] ) diff --git a/Tests/RadrootsKitTests/RadrootsUserPresenceTests.swift b/Tests/RadrootsKitTests/RadrootsUserPresenceTests.swift @@ -26,7 +26,7 @@ import Testing } @Test func userPresenceErrorsExposeLocalizedMessages() { - let error = RadrootsUserPresenceError.permissionDenied("presence denied") + let error = RadrootsUserPresenceError.permissionDenied - #expect(error.errorDescription == "presence denied") + #expect(error.errorDescription == "User-presence verification was denied.") } diff --git a/contracts/api_baselines/apple_kit.txt b/contracts/api_baselines/apple_kit.txt @@ -78,6 +78,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileRootsV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14AppleFileRootsV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -101,6 +102,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryEventV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 34:s:11RadrootsKit0A14TelemetryEventV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -182,6 +184,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 37:s:11RadrootsKit0A17UserPresenceErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 37:s:11RadrootsKit0A17UserPresenceErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 37:s:11RadrootsKit0A17UserPresenceErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 37:s:11RadrootsKit0A17UserPresenceErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 37:s:11RadrootsKit0A17UserPresenceErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleMediaPreparerC 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleMediaPreparerC 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -197,6 +200,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleSecurityErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleSecurityErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleSecurityErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleSecurityErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18AppleSecurityErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18BackgroundTaskKindO 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18BackgroundTaskKindO 18:s:s12CaseIterableP 18:Swift.CaseIterable @@ -212,6 +216,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18CaptureIntakeErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18CaptureIntakeErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18CaptureIntakeErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18CaptureIntakeErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18CaptureIntakeErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18IdentityPassphraseV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 38:s:11RadrootsKit0A18IdentityPassphraseV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -274,6 +279,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19BackgroundTaskErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19BackgroundTaskErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19BackgroundTaskErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19BackgroundTaskErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19BackgroundTaskErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19DocumentContentKindO 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19DocumentContentKindO 18:s:s12CaseIterableP 18:Swift.CaseIterable @@ -291,6 +297,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19ExternalActionErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19ExternalActionErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19ExternalActionErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19ExternalActionErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19ExternalActionErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19MediaCaptureRequestV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 39:s:11RadrootsKit0A19MediaCaptureRequestV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -368,8 +375,11 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleLocationServicesC 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleLocationServicesC 36:s:11RadrootsKit0A16LocationServicesP 0: 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 10:s:s5ErrorP 11:Swift.Error +12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 13:s:s8CopyableP 14:Swift.Copyable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 13:s:s8SendableP 14:Swift.Sendable +12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype +12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21BackgroundTaskRequestV 13:s:s8SendableP 14:Swift.Sendable @@ -405,6 +415,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21LocationServicesErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21LocationServicesErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21LocationServicesErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21LocationServicesErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21LocationServicesErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21ProtectedDataProviderV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 41:s:11RadrootsKit0A21ProtectedDataProviderV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -431,8 +442,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 42:s:11RadrootsKit0A22PreparedExportDocumentV 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 42:s:11RadrootsKit0A22PreparedExportDocumentV 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 10:s:s5ErrorP 11:Swift.Error +12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 13:s:s8CopyableP 14:Swift.Copyable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 13:s:s8SendableP 14:Swift.Sendable +12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype +12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppleBackgroundTransferC 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23AppleBackgroundTransferC 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -449,6 +464,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferStateO 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 43:s:11RadrootsKit0A23BackgroundTransferStateO 18:s:s12CaseIterableP 18:Swift.CaseIterable @@ -489,6 +505,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24ExternalActionCapabilityV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 44:s:11RadrootsKit0A24ExternalActionCapabilityV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -507,6 +524,14 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25AppLocalStateResetRequestV 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25AppleUserPresenceAdaptersV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25AppleUserPresenceAdaptersV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 13:s:s8SendableP 14:Swift.Sendable +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 18:s:s12CaseIterableP 18:Swift.CaseIterable +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 4:s:SE 15:Swift.Encodable +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 4:s:SH 14:Swift.Hashable +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 4:s:SQ 15:Swift.Equatable +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 4:s:SY 22:Swift.RawRepresentable +12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferFailureO 4:s:Se 15:Swift.Decodable 12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferRequestV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferRequestV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 45:s:11RadrootsKit0A25BackgroundTransferRequestV 34:s:s28CustomDebugStringConvertibleP 34:Swift.CustomDebugStringConvertible @@ -537,6 +562,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError +12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26BackgroundTransferProgressV 13:s:s8SendableP 14:Swift.Sendable 12:relationship 11:RadrootsKit 10:conformsTo 46:s:11RadrootsKit0A26BackgroundTransferProgressV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype @@ -582,8 +608,11 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV 34:s:s28CustomDebugStringConvertibleP 34:Swift.CustomDebugStringConvertible 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 10:s:s5ErrorP 11:Swift.Error +12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 13:s:s8CopyableP 14:Swift.Copyable 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 13:s:s8SendableP 14:Swift.Sendable +12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 14:s:s9EscapableP 15:Swift.Escapable 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 22:s:s16SendableMetatypeP 22:Swift.SendableMetatype +12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 31:s:10Foundation14LocalizedErrorP 25:Foundation.LocalizedError 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 4:s:SH 14:Swift.Hashable 12:relationship 11:RadrootsKit 10:conformsTo 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 4:s:SQ 15:Swift.Equatable 12:relationship 11:RadrootsKit 10:conformsTo 48:s:11RadrootsKit0A28AppleBackgroundTaskSchedulerC 13:s:s8SendableP 14:Swift.Sendable @@ -843,7 +872,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 228:s:11RadrootsKit0A29AppleLocationServicesAdaptersV3now08locationE7Enabled19authorizationStatus29requestWhenInUseAuthorization0l7CurrentD0AC10Foundation4DateVyYbc_SbyYbcAA0adP0OyYbcAMSdYaYbKcAA0aD7ReadingVAA0aqD7RequestVYaYbKctcfc 49:s:11RadrootsKit0A29AppleLocationServicesAdaptersV 0: 12:relationship 11:RadrootsKit 8:memberOf 229:s:11RadrootsKit0A18AppleMediaPreparerC20blossomUploadRequest13preparedImage9remoteURL13authorization13networkPolicy10identifierAA0a18BackgroundTransferH0VAA0ac8PreparedJ0V_10Foundation0L0VSSAA0aqr7NetworkO0OAA0aqR10IdentifierVtKF 38:s:11RadrootsKit0A18AppleMediaPreparerC 0: 12:relationship 11:RadrootsKit 8:memberOf 243:s:11RadrootsKit0A36AppleBackgroundTaskSchedulerAdaptersV3now8register6submit6cancel0K3All12pendingTasksAC10Foundation4DateVyYbc_SbAA0acdE12RegistrationVYaYbKcyAA0adE7RequestVYaYbKcyAA0adE10IdentifierVYaYbcyyYaYbcSayAA0adE8SnapshotVGyYaYbKctcfc 56:s:11RadrootsKit0A36AppleBackgroundTaskSchedulerAdaptersV 0: -12:relationship 11:RadrootsKit 8:memberOf 259:s:11RadrootsKit0A26BackgroundTransferSnapshotV7request5state8progress12errorMessage8response18downloadedArtifact20possibleRemoteOrphan9updatedAtAcA0acD7RequestV_AA0acD5StateOAA0acD8ProgressVSSSgAA0acD8ResponseVSgAA0ac10DownloadedM0VSgSb10Foundation4DateVtKcfc 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: +12:relationship 11:RadrootsKit 8:memberOf 266:s:11RadrootsKit0A26BackgroundTransferSnapshotV7request5state8progress7failure8response18downloadedArtifact20possibleRemoteOrphan9updatedAtAcA0acD7RequestV_AA0acD5StateOAA0acD8ProgressVAA0acD7FailureOSgAA0acD8ResponseVSgAA0ac10DownloadedL0VSgSb10Foundation4DateVtKcfc 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 275:s:11RadrootsKit0A28BackgroundTransferValidationO8validate9remoteURL6method9operation7headers8metadata13networkPolicy08responseN020expectedSourceSHA25607maximumD5Bytesy10Foundation0H0V_AA0acD6MethodOAA0acD9OperationOSDyS2SGAuA0acd7NetworkN0OAA0acd8ResponseN0VSSSgs6UInt64VtKFZ 48:s:11RadrootsKit0A28BackgroundTransferValidationO 0: 12:relationship 11:RadrootsKit 8:memberOf 295:s:11RadrootsKit0A25BackgroundTransferRequestV10identifier9remoteURL6method9operation7headers8metadata13networkPolicy08responseN020expectedSourceSHA25607maximumD5BytesAcA0acD10IdentifierV_10Foundation0H0VAA0acD6MethodOAA0acD9OperationOSDyS2SGAwA0acd7NetworkN0OAA0acd8ResponseN0VSSSgs6UInt64VtKcfc 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 37:s:11RadrootsKit0A9FileEntryV4nameSSvp 28:s:11RadrootsKit0A9FileEntryV 0: @@ -890,6 +919,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 48:s:11RadrootsKit0A14TelemetryLevelO7warningyA2CmF 34:s:11RadrootsKit0A14TelemetryLevelO 0: 12:relationship 11:RadrootsKit 8:memberOf 48:s:11RadrootsKit0A17ShareTransferItemV4textSSSgvp 37:s:11RadrootsKit0A17ShareTransferItemV 0: 12:relationship 11:RadrootsKit 8:memberOf 48:s:11RadrootsKit0A9MediaKindO8rawValueACSgSS_tcfc 28:s:11RadrootsKit0A9MediaKindO 0: +12:relationship 11:RadrootsKit 8:memberOf 49:s:11RadrootsKit0A14AppleFileErrorO8notFoundyA2CmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 49:s:11RadrootsKit0A14PermissionKindO8locationyA2CmF 34:s:11RadrootsKit0A14PermissionKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 49:s:11RadrootsKit0A14TelemetryLevelO1loiySbAC_ACtFZ 34:s:11RadrootsKit0A14TelemetryLevelO 0: 12:relationship 11:RadrootsKit 8:memberOf 49:s:11RadrootsKit0A14TelemetryLevelO8criticalyA2CmF 34:s:11RadrootsKit0A14TelemetryLevelO 0: @@ -914,6 +944,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A14SecureStoreKeyV10normalizedACyKF 34:s:11RadrootsKit0A14SecureStoreKeyV 0: 12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A14TelemetryFieldV4boolyACSS_SbtKFZ 34:s:11RadrootsKit0A14TelemetryFieldV 0: 12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A17ShareTransferItemV7subjectSSSgvp 37:s:11RadrootsKit0A17ShareTransferItemV 0: +12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A17UserPresenceErrorO7timeoutyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A18LocationCoordinateV8latitudeSdvp 38:s:11RadrootsKit0A18LocationCoordinateV 0: 12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A18ProtectedDataStateO6lockedyA2CmF 38:s:11RadrootsKit0A18ProtectedDataStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 51:s:11RadrootsKit0A18UserPresenceResultV8verifiedSbvp 38:s:11RadrootsKit0A18UserPresenceResultV 0: @@ -932,12 +963,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A10MediaAssetV17suggestedFilenameSSvp 30:s:11RadrootsKit0A10MediaAssetV 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A13FileReferenceV5scopeAA0aC5ScopeOvp 33:s:11RadrootsKit0A13FileReferenceV 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A13IdentityStateO8rawValueACSgSS_tcfc 33:s:11RadrootsKit0A13IdentityStateO 0: -12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A14AppleFileErrorO8notFoundyACSScACmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A14AppleFileRootsV13appIdentifierSSvp 34:s:11RadrootsKit0A14AppleFileRootsV 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A14TelemetryFieldV6doubleyACSS_SdtKFZ 34:s:11RadrootsKit0A14TelemetryFieldV 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A14TelemetryFieldV6stringyACSS_SStKFZ 34:s:11RadrootsKit0A14TelemetryFieldV 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A15AppleFileAccessC05resetD5RootsyyKF 35:s:11RadrootsKit0A15AppleFileAccessC 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A15OpaqueSignatureV12publicKeyHexSSvp 35:s:11RadrootsKit0A15OpaqueSignatureV 0: +12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A18AppleSecurityErrorO8notFoundyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A19StagedBlobReferenceV9sizeBytesSivp 39:s:11RadrootsKit0A19StagedBlobReferenceV 0: 12:relationship 11:RadrootsKit 8:memberOf 53:s:11RadrootsKit0A24BackgroundTaskIdentifierVyACSSKcfc 44:s:11RadrootsKit0A24BackgroundTaskIdentifierV 0: 12:relationship 11:RadrootsKit 8:memberOf 54:s:11RadrootsKit0A14PermissionKindO8rawValueACSgSS_tcfc 34:s:11RadrootsKit0A14PermissionKindO 0: @@ -963,15 +994,18 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A17OpaqueSignRequestV12publicKeyHexSSvp 37:s:11RadrootsKit0A17OpaqueSignRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A17OpaqueSignRequestV12signerHandleSSvp 37:s:11RadrootsKit0A17OpaqueSignRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A17ShareTransferItemV12transferTextSSvp 37:s:11RadrootsKit0A17ShareTransferItemV 0: -12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A17UserPresenceErrorO7timeoutyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A18ApplePreparedImageV5widths6UInt32Vvp 38:s:11RadrootsKit0A18ApplePreparedImageV 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A19DocumentContentKindO9plainTextyA2CmF 39:s:11RadrootsKit0A19DocumentContentKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A19StagedBlobReferenceV9mediaTypeSSSgvp 39:s:11RadrootsKit0A19StagedBlobReferenceV 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A19TelemetryFieldValueO08renderedE0SSvp 39:s:11RadrootsKit0A19TelemetryFieldValueO 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A20IdentityCustodyErrorO8timedOutyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A21LocationServicesErrorO7timeoutyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A24BackgroundTransferMethodO4postyA2CmF 44:s:11RadrootsKit0A24BackgroundTransferMethodO 0: 12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A25DocumentScannerOutputKindO3pdfyA2CmF 45:s:11RadrootsKit0A25DocumentScannerOutputKindO 0: +12:relationship 11:RadrootsKit 8:memberOf 55:s:11RadrootsKit0A29UnavailableBackgroundTransferVACycfc 49:s:11RadrootsKit0A29UnavailableBackgroundTransferV 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A10MediaAssetV11pixelHeights6UInt32VSgvp 30:s:11RadrootsKit0A10MediaAssetV 0: +12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A14AppleFileErrorO14invalidRequestyA2CmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A14TelemetryErrorO14invalidRequestyA2CmF 34:s:11RadrootsKit0A14TelemetryErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A15AppleFileAccessC5rootsAA0acD5RootsVvp 35:s:11RadrootsKit0A15AppleFileAccessC 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A15LocationReadingV13courseDegreesSdSgvp 35:s:11RadrootsKit0A15LocationReadingV 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A15ScannedDocumentV9pageCounts6UInt16Vvp 35:s:11RadrootsKit0A15ScannedDocumentV 0: @@ -980,6 +1014,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A16IdentitySnapshotV12signerHandleSSSgvp 36:s:11RadrootsKit0A16IdentitySnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A16IdentitySnapshotV5stateAA0aC5StateOvp 36:s:11RadrootsKit0A16IdentitySnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A16PermissionStatusO8rawValueACSgSS_tcfc 36:s:11RadrootsKit0A16PermissionStatusO 0: +12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A17UserPresenceErrorO11unavailableyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A18ApplePreparedImageV6heights6UInt32Vvp 38:s:11RadrootsKit0A18ApplePreparedImageV 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A18BackgroundTaskKindO10appRefreshyA2CmF 38:s:11RadrootsKit0A18BackgroundTaskKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 56:s:11RadrootsKit0A18BackgroundTaskKindO10processingyA2CmF 38:s:11RadrootsKit0A18BackgroundTaskKindO 0: @@ -1008,11 +1043,13 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A16ImportedDocumentV9sizeBytess6UInt64Vvp 36:s:11RadrootsKit0A16ImportedDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A16PermissionStatusO13notDeterminedyA2CmF 36:s:11RadrootsKit0A16PermissionStatusO 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A17OpaqueSignPurposeO8rawValueACSgSS_tcfc 37:s:11RadrootsKit0A17OpaqueSignPurposeO 0: -12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A18AppleSecurityErrorO8notFoundyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A18AppleSecurityErrorO11unavailableyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A18CaptureIntakeErrorO11unavailableyA2CmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A18MediaCaptureResultV4itemAA0aC5AssetVvp 38:s:11RadrootsKit0A18MediaCaptureResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A18ProtectedDataStateO11unavailableyA2CmF 38:s:11RadrootsKit0A18ProtectedDataStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A19DocumentContentKindO10stagedBlobyA2CmF 39:s:11RadrootsKit0A19DocumentContentKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A21ExportDocumentRequestV9mediaTypeSSSgvp 41:s:11RadrootsKit0A21ExportDocumentRequestV 0: +12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A21LocationServicesErrorO9cancelledyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A23AppleTelemetryLogRecordV9subsystemSSvp 43:s:11RadrootsKit0A23AppleTelemetryLogRecordV 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A23BackgroundTransferStateO7expiredyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A23BackgroundTransferStateO7runningyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: @@ -1023,14 +1060,20 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 57:s:11RadrootsKit0A9ShareItemO3urlyAC10Foundation3URLVcACmF 28:s:11RadrootsKit0A9ShareItemO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A12FileReadModeO12preferInlineyACSi_tcACmF 32:s:11RadrootsKit0A12FileReadModeO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A14AppleFileErrorO16errorDescriptionSSSgvp 34:s:11RadrootsKit0A14AppleFileErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A14AppleFileErrorO16permanentFailureyA2CmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A14AppleFileErrorO16permissionDeniedyA2CmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A14AppleFileErrorO16transientFailureyA2CmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A14TelemetryErrorO16errorDescriptionSSSgvp 34:s:11RadrootsKit0A14TelemetryErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A15ScannedDocumentV17suggestedFilenameSSvp 35:s:11RadrootsKit0A15ScannedDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A17OpaqueSignPurposeO13blossomUploadyA2CmF 37:s:11RadrootsKit0A17OpaqueSignPurposeO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A17ShareTransferItemV7payloadAC7PayloadOvp 37:s:11RadrootsKit0A17ShareTransferItemV 0: +12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A17UserPresenceErrorO13userCancelledyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A18BackgroundTaskKindO8rawValueACSgSS_tcfc 38:s:11RadrootsKit0A18BackgroundTaskKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A18MediaImportRequestV14selectionLimitSivp 38:s:11RadrootsKit0A18MediaImportRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A18ProtectedDataStateO8rawValueACSgSS_tcfc 38:s:11RadrootsKit0A18ProtectedDataStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A18UserPresencePolicyO8rawValueACSgSS_tcfc 38:s:11RadrootsKit0A18UserPresencePolicyO 0: +12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A19BackgroundTaskErrorO11unavailableyA2CmF 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A19ExternalActionErrorO11unavailableyA2CmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A20IdentityPublicRecordV12publicKeyHexSSvp 40:s:11RadrootsKit0A20IdentityPublicRecordV 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A22PreparedExportDocumentV10preparedIDSSvp 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 58:s:11RadrootsKit0A22PreparedExportDocumentV9mediaTypeSSSgvp 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: @@ -1039,6 +1082,9 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A14TelemetryEventV6fieldsSayAA0aC5FieldVGvp 34:s:11RadrootsKit0A14TelemetryEventV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A16ImportedDocumentV17suggestedFilenameSSvp 36:s:11RadrootsKit0A16ImportedDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A17OpaqueSignRequestV16debugDescriptionSSvp 37:s:11RadrootsKit0A17OpaqueSignRequestV 0: +12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A17UserPresenceErrorO14invalidRequestyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A18AppleSecurityErrorO13userCancelledyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A18CaptureIntakeErrorO13userCancelledyA2CmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A18MediaPickerSupportV15importAvailableSbvp 38:s:11RadrootsKit0A18MediaPickerSupportV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A18SecretAccessPolicyV011secureLocalC0ACvpZ 38:s:11RadrootsKit0A18SecretAccessPolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A18SecretAccessPolicyV15deviceLocalOnlySbvp 38:s:11RadrootsKit0A18SecretAccessPolicyV 0: @@ -1049,21 +1095,21 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A19UserPresenceSupportO8rawValueACSgSS_tcfc 39:s:11RadrootsKit0A19UserPresenceSupportO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A20IdentityCustodyErrorO11staleSigneryA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A21LocationAuthorizationO10restrictedyA2CmF 41:s:11RadrootsKit0A21LocationAuthorizationO 0: -12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A21LocationServicesErrorO7timeoutyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A23BackgroundTransferStateO9cancelledyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A23BackgroundTransferStateO9completedyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A24BackgroundTaskIdentifierV1loiySbAC_ACtFZ 44:s:11RadrootsKit0A24BackgroundTaskIdentifierV 0: +12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A24DocumentInterchangeErrorO8notFoundyA2CmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A25BackgroundTransferFailureO7expiredyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A25ExternalActionDestinationV5nostryACSSKFZ 45:s:11RadrootsKit0A25ExternalActionDestinationV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A28BackgroundDownloadedArtifactV6sha256SSvp 48:s:11RadrootsKit0A28BackgroundDownloadedArtifactV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A29AppleLocationServicesAdaptersV4liveACvpZ 49:s:11RadrootsKit0A29AppleLocationServicesAdaptersV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV4liveACvpZ 49:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV 0: 12:relationship 11:RadrootsKit 8:memberOf 59:s:11RadrootsKit0A29ExternalActionDestinationKindO3webyA2CmF 49:s:11RadrootsKit0A29ExternalActionDestinationKindO 0: -12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A14AppleFileErrorO14invalidRequestyACSScACmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A14SecureStoreKeyV9namespace4nameACSS_SStcfc 34:s:11RadrootsKit0A14SecureStoreKeyV 0: -12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A14TelemetryErrorO14invalidRequestyACSScACmF 34:s:11RadrootsKit0A14TelemetryErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A14TelemetryFieldV7integeryACSS_s5Int64VtKFZ 34:s:11RadrootsKit0A14TelemetryFieldV 0: -12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A17UserPresenceErrorO11unavailableyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A18ApplePreparedImageV16debugDescriptionSSvp 38:s:11RadrootsKit0A18ApplePreparedImageV 0: +12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A18AppleSecurityErrorO14invalidRequestyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A18CaptureIntakeErrorO14invalidRequestyA2CmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A18IdentityPassphraseV16debugDescriptionSSvp 38:s:11RadrootsKit0A18IdentityPassphraseV 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A18PermissionSnapshotV6statusAA0aC6StatusOvp 38:s:11RadrootsKit0A18PermissionSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A20IdentityCustodyErrorO8rawValueACSgSS_tcfc 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: @@ -1072,27 +1118,29 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A21BackgroundTaskRequestV4kindAA0acD4KindOvp 41:s:11RadrootsKit0A21BackgroundTaskRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A21LocationAuthorizationO11unavailableyA2CmF 41:s:11RadrootsKit0A21LocationAuthorizationO 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A21LocationAuthorizationO11unsupportedyA2CmF 41:s:11RadrootsKit0A21LocationAuthorizationO 0: +12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A21LocationServicesErrorO11unavailableyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A26VerifiedArtifactDescriptorV9mediaTypeSSvp 46:s:11RadrootsKit0A26VerifiedArtifactDescriptorV 0: +12:relationship 11:RadrootsKit 8:memberOf 60:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerVACycfc 54:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerV 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A15IdentityCustodyC7recoverAA0aC8SnapshotVyKF 35:s:11RadrootsKit0A15IdentityCustodyC 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A15IdentityCustodyC8snapshotAA0aC8SnapshotVyF 35:s:11RadrootsKit0A15IdentityCustodyC 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A17MediaImportResultV5itemsSayAA0aC5AssetVGvp 37:s:11RadrootsKit0A17MediaImportResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A17ShareTransferItemV7PayloadO4textyAESScAEmF 46:s:11RadrootsKit0A17ShareTransferItemV7PayloadO 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A17UserPresenceErrorO16errorDescriptionSSSgvp 37:s:11RadrootsKit0A17UserPresenceErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A18AppleSecurityErrorO11unavailableyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A18CaptureIntakeErrorO11unavailableyACSScACmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A17UserPresenceErrorO16permanentFailureyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A17UserPresenceErrorO16permissionDeniedyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A17UserPresenceErrorO16transientFailureyA2CmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A18AppleSecurityErrorO15keychainFailureyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A18MediaCaptureResultV4itemAcA0aC5AssetV_tcfc 38:s:11RadrootsKit0A18MediaCaptureResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A18UserPresenceResultV6policyAA0acD6PolicyOvp 38:s:11RadrootsKit0A18UserPresenceResultV 0: +12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A19BackgroundTaskErrorO14invalidRequestyA2CmF 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A19ExternalActionErrorO14invalidRequestyA2CmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A20IdentityCustodyErrorO13invalidSecretyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A21LocationAuthorizationO8rawValueACSgSS_tcfc 41:s:11RadrootsKit0A21LocationAuthorizationO 0: -12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A21LocationServicesErrorO9cancelledyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A22BackgroundTaskSnapshotV4kindAA0acD4KindOvp 42:s:11RadrootsKit0A22BackgroundTaskSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A24TelemetryRedactionPolicyV11replacementSSvp 44:s:11RadrootsKit0A24TelemetryRedactionPolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A28BackgroundTransferIdentifierV8rawValueSSvp 48:s:11RadrootsKit0A28BackgroundTransferIdentifierV 0: 12:relationship 11:RadrootsKit 8:memberOf 61:s:11RadrootsKit0A29ExternalActionDestinationKindO5nostryA2CmF 49:s:11RadrootsKit0A29ExternalActionDestinationKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A10MediaAssetV10capturedAt10Foundation4DateVvp 30:s:11RadrootsKit0A10MediaAssetV 0: -12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A14AppleFileErrorO16permanentFailureyACSScACmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A14AppleFileErrorO16permissionDeniedyACSScACmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A14AppleFileErrorO16transientFailureyACSScACmF 34:s:11RadrootsKit0A14AppleFileErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A14AppleFileRootsV8dataRoot10Foundation3URLVvp 34:s:11RadrootsKit0A14AppleFileRootsV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A14AppleFileRootsV8logsRoot10Foundation3URLVvp 34:s:11RadrootsKit0A14AppleFileRootsV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A14SecureStoreKeyV19normalizedNamespaceyS2SKFZ 34:s:11RadrootsKit0A14SecureStoreKeyV 0: @@ -1101,13 +1149,17 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A15ScannedDocumentV4fileAA0A13FileReferenceVvp 35:s:11RadrootsKit0A15ScannedDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A17OpaqueSignRequestV7purposeAA0acD7PurposeOvp 37:s:11RadrootsKit0A17OpaqueSignRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A17ShareTransferItemV3url10Foundation3URLVSgvp 37:s:11RadrootsKit0A17ShareTransferItemV 0: -12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A17UserPresenceErrorO13userCancelledyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18AppleSecurityErrorO16errorDescriptionSSSgvp 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18AppleSecurityErrorO16permanentFailureyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18AppleSecurityErrorO16permissionDeniedyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18AppleSecurityErrorO16transientFailureyA2CmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18CaptureIntakeErrorO16errorDescriptionSSSgvp 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18CaptureIntakeErrorO16permanentFailureyA2CmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18CaptureIntakeErrorO16permissionDeniedyA2CmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18CaptureIntakeErrorO16transientFailureyA2CmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18MultiplexTelemetryV6recordyyAA0aD5EventVYaF 38:s:11RadrootsKit0A18MultiplexTelemetryV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A18RedactingTelemetryV6recordyyAA0aD5EventVYaF 38:s:11RadrootsKit0A18RedactingTelemetryV 0: -12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A19BackgroundTaskErrorO11unavailableyACSScACmF 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A19ExternalActionErrorO11unavailableyACSScACmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A19ExternalActionErrorO15blockedByPolicyyA2CmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A19UserPresenceRequestV6policyAA0acD6PolicyOvp 39:s:11RadrootsKit0A19UserPresenceRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A20ExportDocumentResultV16exportedFilenameSSvp 40:s:11RadrootsKit0A20ExportDocumentResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A20IdentityCustodyErrorO14identityLockedyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: @@ -1116,6 +1168,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A21CurrentLocationResultV15servedFromCacheSbvp 41:s:11RadrootsKit0A21CurrentLocationResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A21LocationAuthorizationO13notDeterminedyA2CmF 41:s:11RadrootsKit0A21LocationAuthorizationO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A22CurrentLocationRequestV14timeoutSecondsSdvp 42:s:11RadrootsKit0A22CurrentLocationRequestV 0: +12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A23BackgroundTransferErrorO11unavailableyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A23BackgroundTransferStateO11interruptedyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A25BackgroundTransferRequestV7headersSDyS2SGvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 62:s:11RadrootsKit0A26BackgroundTransferResponseV10statusCodeSivp 46:s:11RadrootsKit0A26BackgroundTransferResponseV 0: @@ -1127,12 +1180,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A14SecureStoreKeyV11serviceName0F6PrefixS2S_tKF 34:s:11RadrootsKit0A14SecureStoreKeyV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A15LocationReadingV20speedMetersPerSecondSdSgvp 35:s:11RadrootsKit0A15LocationReadingV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A16ImportedDocumentV4fileAA0A13FileReferenceVvp 36:s:11RadrootsKit0A16ImportedDocumentV 0: -12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A17UserPresenceErrorO14invalidRequestyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A18AppleSecurityErrorO13userCancelledyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A18CaptureIntakeErrorO13userCancelledyACSScACmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A18UserPresenceStatusV7supportAA0acD7SupportOvp 38:s:11RadrootsKit0A18UserPresenceStatusV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19BackgroundTaskErrorO16errorDescriptionSSSgvp 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19BackgroundTaskErrorO16schedulerFailureyA2CmF 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19ExternalActionErrorO16errorDescriptionSSSgvp 39:s:11RadrootsKit0A19ExternalActionErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19ExternalActionErrorO16permanentFailureyA2CmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19ExternalActionErrorO16transientFailureyA2CmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19SecretAccessibilityO16afterFirstUnlockyA2CmF 39:s:11RadrootsKit0A19SecretAccessibilityO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19StagedBlobReferenceV010normalizedD2IDyS2SKFZ 39:s:11RadrootsKit0A19StagedBlobReferenceV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A19StagedBlobReferenceV4fromACs7Decoder_p_tKcfc 39:s:11RadrootsKit0A19StagedBlobReferenceV 0: @@ -1141,10 +1194,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A20ExportDocumentResultV9sizeBytess6UInt64VSgvp 40:s:11RadrootsKit0A20ExportDocumentResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A20IdentityCustodyErrorO15corruptMetadatayA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A20IdentityCustodyErrorO15invalidMetadatayA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A21LocationServicesErrorO14invalidRequestyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A23AppleTelemetryLogRecordV5levelAA0aD5LevelOvp 43:s:11RadrootsKit0A23AppleTelemetryLogRecordV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A23BackgroundTransferStateO8rawValueACSgSS_tcfc 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A24AppleKeychainSecureStoreC13servicePrefixSSvp 44:s:11RadrootsKit0A24AppleKeychainSecureStoreC 0: -12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A24DocumentInterchangeErrorO8notFoundyACSScACmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A25BackgroundTransferFailureO08platformE0yA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: +12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A25BackgroundTransferFailureO10httpStatusyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A25BackgroundTransferRequestV8metadataSDyS2SGvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A25ExternalActionDestinationV11appSettingsACvpZ 45:s:11RadrootsKit0A25ExternalActionDestinationV 0: 12:relationship 11:RadrootsKit 8:memberOf 63:s:11RadrootsKit0A25ExternalActionDestinationV9appleMapsyACSSKFZ 45:s:11RadrootsKit0A25ExternalActionDestinationV 0: @@ -1157,8 +1212,6 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A15OpaqueSignatureV7purposeAA0aC11SignPurposeOvp 35:s:11RadrootsKit0A15OpaqueSignatureV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A17OpaqueSignRequestV6digest10Foundation4DataVvp 37:s:11RadrootsKit0A17OpaqueSignRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A17ShareTransferItemV4text7subjectACSS_SSSgtKcfc 37:s:11RadrootsKit0A17ShareTransferItemV 0: -12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A18AppleSecurityErrorO14invalidRequestyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A18CaptureIntakeErrorO14invalidRequestyACSScACmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A18SecretAccessPolicyV20userPresenceRequiredSbvp 38:s:11RadrootsKit0A18SecretAccessPolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A20AppleLoggerTelemetryC6recordyyAA0aE5EventVYaF 40:s:11RadrootsKit0A20AppleLoggerTelemetryC 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A20IdentityCustodyErrorO16errorDescriptionSSSgvp 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: @@ -1170,12 +1223,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A21ExportDocumentRequestV17suggestedFilenameSSvp 41:s:11RadrootsKit0A21ExportDocumentRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A21ExportDocumentRequestV6sourceAA0acD6SourceOvp 41:s:11RadrootsKit0A21ExportDocumentRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A21ExportDocumentRequestV9sizeBytess6UInt64VSgvp 41:s:11RadrootsKit0A21ExportDocumentRequestV 0: -12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A21LocationServicesErrorO11unavailableyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A22IdentitySecretMaterialV10importTextACSS_tKcfc 42:s:11RadrootsKit0A22IdentitySecretMaterialV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A22IdentitySecretMaterialV16debugDescriptionSSvp 42:s:11RadrootsKit0A22IdentitySecretMaterialV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A23AppleTelemetryLogRecordV15renderedMessageSSvp 43:s:11RadrootsKit0A23AppleTelemetryLogRecordV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A24BackgroundTransferMethodO8rawValueACSgSS_tcfc 44:s:11RadrootsKit0A24BackgroundTransferMethodO 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A25AppLocalStateResetRequestV13appIdentifierSSvp 45:s:11RadrootsKit0A25AppLocalStateResetRequestV 0: +12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A25BackgroundTransferFailureO11interruptedyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A26VerifiedArtifactDescriptorV6heights6UInt32Vvp 46:s:11RadrootsKit0A26VerifiedArtifactDescriptorV 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A28AppleBackgroundTaskSchedulerC9cancelAllyyYaKF 48:s:11RadrootsKit0A28AppleBackgroundTaskSchedulerC 0: 12:relationship 11:RadrootsKit 8:memberOf 64:s:11RadrootsKit0A28BackgroundDownloadedArtifactV9mediaTypeSSSgvp 48:s:11RadrootsKit0A28BackgroundDownloadedArtifactV 0: @@ -1186,28 +1239,31 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A15LocationReadingV22verticalAccuracyMetersSdSgvp 35:s:11RadrootsKit0A15LocationReadingV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A15LocationReadingV24horizontalAccuracyMetersSdvp 35:s:11RadrootsKit0A15LocationReadingV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A15OpaqueSignatureV9signature10Foundation4DataVvp 35:s:11RadrootsKit0A15OpaqueSignatureV 0: -12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A17UserPresenceErrorO16permanentFailureyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A17UserPresenceErrorO16permissionDeniedyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A17UserPresenceErrorO16transientFailureyACSScACmF 37:s:11RadrootsKit0A17UserPresenceErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A18SecretAccessPolicyV017userPresenceLocalC0ACvpZ 38:s:11RadrootsKit0A18SecretAccessPolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A18UserPresenceStatusV21canEvaluateBiometricsSbvp 38:s:11RadrootsKit0A18UserPresenceStatusV 0: -12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A19BackgroundTaskErrorO14invalidRequestyACSScACmF 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A19ExternalActionErrorO14invalidRequestyACSScACmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A19TelemetryFieldValueO10stringListyACSaySSGcACmF 39:s:11RadrootsKit0A19TelemetryFieldValueO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A20IdentityCustodyErrorO17inconsistentStateyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A20IdentityCustodyErrorO17invalidPassphraseyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A20VerifiedArtifactFileV4data10Foundation4DataVvp 40:s:11RadrootsKit0A20VerifiedArtifactFileV 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21AppleMobileStoreErrorO16errorDescriptionSSSgvp 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21AppleMobileStoreErrorO16invalidPublicKeyyA2CmF 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21CurrentLocationResultV7readingAA0aD7ReadingVvp 41:s:11RadrootsKit0A21CurrentLocationResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21LocationAuthorizationO16authorizedAlwaysyA2CmF 41:s:11RadrootsKit0A21LocationAuthorizationO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21LocationServicesErrorO16errorDescriptionSSSgvp 41:s:11RadrootsKit0A21LocationServicesErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21LocationServicesErrorO16permanentFailureyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21LocationServicesErrorO16permissionDeniedyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A21LocationServicesErrorO16transientFailureyA2CmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A22PreparedExportDocumentV17suggestedFilenameSSvp 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A22PreparedExportDocumentV9sizeBytess6UInt64VSgvp 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A23AppLocalStateResetErrorO14invalidRequestyA2CmF 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A23BackgroundTransferErrorO14invalidRequestyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A24DocumentInterchangeErrorO13userCancelledyA2CmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A25BackgroundTransferFailureO8rawValueACSgSS_tcfc 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A25DocumentScannerOutputKindO8rawValueACSgSS_tcfc 45:s:11RadrootsKit0A25DocumentScannerOutputKindO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A25ExternalActionDestinationV4kindAA0acdE4KindOvp 45:s:11RadrootsKit0A25ExternalActionDestinationV 0: +12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A26AppleMediaPreparationErrorO11unavailableyA2CmF 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A26VerifiedArtifactDescriptorV13fileExtensionSSvp 46:s:11RadrootsKit0A26VerifiedArtifactDescriptorV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A29ExternalActionDestinationKindO9appleMapsyA2CmF 49:s:11RadrootsKit0A29ExternalActionDestinationKindO 0: -12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A29UnavailableBackgroundTransferV6reasonACSS_tcfc 49:s:11RadrootsKit0A29UnavailableBackgroundTransferV 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A30BackgroundTransferVerificationO8acceptedyA2CmF 50:s:11RadrootsKit0A30BackgroundTransferVerificationO 0: 12:relationship 11:RadrootsKit 8:memberOf 65:s:11RadrootsKit0A32BackgroundTransferResponsePolicyV7discardACvpZ 52:s:11RadrootsKit0A32BackgroundTransferResponsePolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A14AppleFileRootsV23normalizedAppIdentifieryS2SKFZ 34:s:11RadrootsKit0A14AppleFileRootsV 0: @@ -1217,14 +1273,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A17AppleUserPresenceC13currentStatusAA0adeG0VyYaKF 37:s:11RadrootsKit0A17AppleUserPresenceC 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A17ShareTransferItemV22transferRepresentationQrvpZ 37:s:11RadrootsKit0A17ShareTransferItemV 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18AppLocalStateResetO5resetyyAA0acdeF7RequestVKFZ 38:s:11RadrootsKit0A18AppLocalStateResetO 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18AppleSecurityErrorO16permanentFailureyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18AppleSecurityErrorO16permissionDeniedyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18AppleSecurityErrorO16transientFailureyACSScACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18CaptureIntakeErrorO16permanentFailureyACSScACmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18CaptureIntakeErrorO16permissionDeniedyACSScACmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18CaptureIntakeErrorO16transientFailureyACSScACmF 38:s:11RadrootsKit0A18CaptureIntakeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A18MediaPickerSupportV22cameraCaptureAvailableSbvp 38:s:11RadrootsKit0A18MediaPickerSupportV 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A19ExternalActionErrorO15blockedByPolicyyACSScACmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A20AppleLoggerTelemetryC18normalizedCategoryyS2SFZ 40:s:11RadrootsKit0A20AppleLoggerTelemetryC 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A20IdentityCustodyErrorO18cryptographyFailedyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A20IdentityCustodyErrorO18duplicateOperationyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: @@ -1235,10 +1284,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A21ProtectedDataProviderV12currentStateAA0acdG0OyF 41:s:11RadrootsKit0A21ProtectedDataProviderV 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A22DocumentScannerSupportV18multiPageSupportedSbvp 42:s:11RadrootsKit0A22DocumentScannerSupportV 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A22PreparedExportDocumentV010normalizedC2IDyS2SKFZ 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A23BackgroundTransferErrorO11unavailableyACSScACmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A23AppLocalStateResetErrorO15keychainFailureyA2CmF 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A23BackgroundTransferErrorO15transferFailureyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A24AppleKeychainSecureStoreC6deleteyyAA0aeF3KeyVKF 44:s:11RadrootsKit0A24AppleKeychainSecureStoreC 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A24BackgroundTransferHandleV16debugDescriptionSSvp 44:s:11RadrootsKit0A24BackgroundTransferHandleV 0: -12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A26BackgroundTransferSnapshotV12errorMessageSSSgvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: +12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A24DocumentInterchangeErrorO14invalidRequestyA2CmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A25BackgroundTransferFailureO13enqueueFailedyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A26VerifiedArtifactDescriptorV8byteSizes6UInt64Vvp 46:s:11RadrootsKit0A26VerifiedArtifactDescriptorV 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV7versions6UInt16Vvp 47:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV 0: 12:relationship 11:RadrootsKit 8:memberOf 66:s:11RadrootsKit0A30AppleProtectedDataAvailabilityO9availableyA2CmF 50:s:11RadrootsKit0A30AppleProtectedDataAvailabilityO 0: @@ -1250,14 +1301,12 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A15ScannedDocumentV10capturedAt10Foundation4DateVvp 35:s:11RadrootsKit0A15ScannedDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A17MediaImportResultV5itemsACSayAA0aC5AssetVG_tKcfc 37:s:11RadrootsKit0A17MediaImportResultV 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A18AppLocalStateResetO20clearKeychainServiceyySSKFZ 38:s:11RadrootsKit0A18AppLocalStateResetO 0: -12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A19BackgroundTaskErrorO16schedulerFailureyACSScACmF 39:s:11RadrootsKit0A19BackgroundTaskErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A19ExternalActionErrorO16permanentFailureyACSScACmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A19ExternalActionErrorO16transientFailureyACSScACmF 39:s:11RadrootsKit0A19ExternalActionErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A20AppleLoggerTelemetryC19normalizedSubsystemyS2SFZ 40:s:11RadrootsKit0A20AppleLoggerTelemetryC 0: -12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A21LocationServicesErrorO14invalidRequestyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A23AppLocalStateResetErrorO16errorDescriptionSSSgvp 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A23BackgroundTransferErrorO16errorDescriptionSSSgvp 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A24AppleKeychainSecureStoreC15deleteNamespaceyySSKF 44:s:11RadrootsKit0A24AppleKeychainSecureStoreC 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A24TelemetryRedactionPolicyV15shouldRedactKeyySbSSF 44:s:11RadrootsKit0A24TelemetryRedactionPolicyV 0: +12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A25BackgroundTransferFailureO011destinationE0yA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A25BackgroundTransferRequestV16debugDescriptionSSvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 67:s:11RadrootsKit0A26BackgroundTransferSnapshotV5stateAA0acD5StateOvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A14AppleFileRootsV13temporaryRoot10Foundation3URLVvp 34:s:11RadrootsKit0A14AppleFileRootsV 0: @@ -1267,10 +1316,17 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A21BackgroundTaskRequestV21requiresExternalPowerSbvp 41:s:11RadrootsKit0A21BackgroundTaskRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A21LocationAuthorizationO19authorizedWhenInUseyA2CmF 41:s:11RadrootsKit0A21LocationAuthorizationO 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A22PreparedExportDocumentV7fileURL10Foundation0G0Vvp 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A23AppLocalStateResetErrorO17fileSystemFailureyA2CmF 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A24AppleKeychainSecureStoreC13servicePrefixACSS_tcfc 44:s:11RadrootsKit0A24AppleKeychainSecureStoreC 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A24BackgroundTransferHandleV4fromACs7Decoder_p_tKcfc 44:s:11RadrootsKit0A24BackgroundTransferHandleV 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16errorDescriptionSSSgvp 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16permanentFailureyA2CmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16permissionDeniedyA2CmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16transientFailureyA2CmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A25BackgroundTransferFailureO15responseInvalidyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A25BackgroundTransferFailureO15responseMissingyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A25BackgroundTransferRequestV6methodAA0acD6MethodOvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: +12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A26AppleMediaPreparationErrorO14invalidRequestyA2CmF 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A26BackgroundTransferSnapshotV16debugDescriptionSSvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A28BackgroundDownloadedArtifactV8byteSizes6UInt64Vvp 48:s:11RadrootsKit0A28BackgroundDownloadedArtifactV 0: 12:relationship 11:RadrootsKit 8:memberOf 68:s:11RadrootsKit0A29ExternalActionDestinationKindO11appSettingsyA2CmF 49:s:11RadrootsKit0A29ExternalActionDestinationKindO 0: @@ -1279,20 +1335,16 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A17AppleUserPresenceC8adaptersAcA0acdE8AdaptersV_tcfc 37:s:11RadrootsKit0A17AppleUserPresenceC 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A18LocationCoordinateV8latitude9longitudeACSd_SdtKcfc 38:s:11RadrootsKit0A18LocationCoordinateV 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A20IdentityCustodyErrorO21identityAlreadyExistsyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A21LocationServicesErrorO16permanentFailureyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A21LocationServicesErrorO16permissionDeniedyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A21LocationServicesErrorO16transientFailureyACSScACmF 41:s:11RadrootsKit0A21LocationServicesErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A22BackgroundTaskSnapshotV21requiresExternalPowerSbvp 42:s:11RadrootsKit0A22BackgroundTaskSnapshotV 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A23AppLocalStateResetErrorO14invalidRequestyACSScACmF 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A23BackgroundTransferErrorO14invalidRequestyACSScACmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A23BackgroundTransferErrorO18persistenceFailureyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A24AppleKeychainSecureStoreC8containsySbAA0aeF3KeyVKF 44:s:11RadrootsKit0A24AppleKeychainSecureStoreC 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A24BackgroundTransferHandleV7requestAA0acD7RequestVvp 44:s:11RadrootsKit0A24BackgroundTransferHandleV 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A24DocumentInterchangeErrorO13userCancelledyACSScACmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A24TelemetryRedactionPolicyV19maximumStringLengthSivp 44:s:11RadrootsKit0A24TelemetryRedactionPolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A24TelemetryRedactionPolicyV8redactedyAA0aC5EventVAFF 44:s:11RadrootsKit0A24TelemetryRedactionPolicyV 0: +12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A25BackgroundTransferFailureO16responseTooLargeyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: +12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A25BackgroundTransferFailureO16transferTooLargeyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A25BackgroundTransferRequestV4fromACs7Decoder_p_tKcfc 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A26AppleIdentityMetadataStoreC6deleteyyAA0adE4SlotOKF 46:s:11RadrootsKit0A26AppleIdentityMetadataStoreC 0: -12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A26AppleMediaPreparationErrorO11unavailableyACSScACmF 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A26BackgroundTransferResponseV15contentEncodingSSSgvp 46:s:11RadrootsKit0A26BackgroundTransferResponseV 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV16debugDescriptionSSvp 47:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV 0: 12:relationship 11:RadrootsKit 8:memberOf 69:s:11RadrootsKit0A29ExternalActionDestinationKindO8rawValueACSgSS_tcfc 49:s:11RadrootsKit0A29ExternalActionDestinationKindO 0: @@ -1309,10 +1361,9 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A20AppleDocumentScannerC14currentSupportAA0adeG0VyYaKF 40:s:11RadrootsKit0A20AppleDocumentScannerC 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A21DocumentImportRequestV23allowsMultipleSelectionSbvp 41:s:11RadrootsKit0A21DocumentImportRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A21ProtectedDataProviderV9readStateAcA0acdG0OyYbc_tcfc 41:s:11RadrootsKit0A21ProtectedDataProviderV 0: -12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A23BackgroundTransferErrorO15transferFailureyACSScACmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A23CaptureIntakeValidationO18normalizedFilenameyS2SKFZ 43:s:11RadrootsKit0A23CaptureIntakeValidationO 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A24BackgroundTransferHandleV6encode2toys7Encoder_p_tKF 44:s:11RadrootsKit0A24BackgroundTransferHandleV 0: -12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A24DocumentInterchangeErrorO14invalidRequestyACSScACmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A25BackgroundTransferFailureO17responseMediaTypeyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A25ExternalActionDestinationV3url10Foundation3URLVSgvp 45:s:11RadrootsKit0A25ExternalActionDestinationV 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A26AppleMediaPreparationErrorO16errorDescriptionSSSgvp 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A26BackgroundTransferProgressV4fromACs7Decoder_p_tKcfc 46:s:11RadrootsKit0A26BackgroundTransferProgressV 0: @@ -1323,7 +1374,6 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A28BackgroundDownloadedArtifactV16debugDescriptionSSvp 48:s:11RadrootsKit0A28BackgroundDownloadedArtifactV 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A31AppleBackgroundTaskRegistrationV4kindAA0adE4KindOvp 51:s:11RadrootsKit0A31AppleBackgroundTaskRegistrationV 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO11publicHTTPSyA2CmF 51:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO 0: -12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerV6reasonACSS_tcfc 54:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerV 0: 12:relationship 11:RadrootsKit 8:memberOf 70:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerV9cancelAllyyYaKF 54:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerV 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A15LocationReadingV24normalizedOptionalCourseySdSgAEKFZ 35:s:11RadrootsKit0A15LocationReadingV 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A18ApplePreparedImageV4fileAA0A19StagedBlobReferenceVvp 38:s:11RadrootsKit0A18ApplePreparedImageV 0: @@ -1338,14 +1388,15 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A23BackgroundTransferStateO20awaitingVerificationyA2CmF 43:s:11RadrootsKit0A23BackgroundTransferStateO 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A23CaptureIntakeValidationO19normalizedMediaTypeyS2SKFZ 43:s:11RadrootsKit0A23CaptureIntakeValidationO 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A24TelemetryRedactionPolicyV19containsUnsafeValueySbSSF 44:s:11RadrootsKit0A24TelemetryRedactionPolicyV 0: +12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A25BackgroundTransferFailureO015downloadStagingE0yA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A25BackgroundTransferRequestV6encode2toys7Encoder_p_tKF 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A26BackgroundTransferSnapshotV7requestAA0acD7RequestVvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A27BackgroundTransferLocalFileO4fromACs7Decoder_p_tKcfc 47:s:11RadrootsKit0A27BackgroundTransferLocalFileO 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A27BackgroundTransferOperationO4fromACs7Decoder_p_tKcfc 47:s:11RadrootsKit0A27BackgroundTransferOperationO 0: +12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO16errorDescriptionSSSgvp 47:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A28AppleImagePreparationRequestV17maximumInputBytesSivp 48:s:11RadrootsKit0A28AppleImagePreparationRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A28AppleImagePreparationRequestV17maximumPixelCountSivp 48:s:11RadrootsKit0A28AppleImagePreparationRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A28LocationServicesAvailabilityV08locationD7EnabledSbvp 48:s:11RadrootsKit0A28LocationServicesAvailabilityV 0: -12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A30BackgroundTransferVerificationO8rejectedyACSS_tcACmF 50:s:11RadrootsKit0A30BackgroundTransferVerificationO 0: 12:relationship 11:RadrootsKit 8:memberOf 71:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO8rawValueACSgSS_tcfc 51:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A15ScannedDocumentV10outputKindAA0ad13ScannerOutputF0Ovp 35:s:11RadrootsKit0A15ScannedDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A20AppleExternalActionsC4openyyAA0aD13ActionRequestVYaKF 40:s:11RadrootsKit0A20AppleExternalActionsC 0: @@ -1354,11 +1405,8 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A20ExportDocumentSourceO4fileyAcA0A13FileReferenceVcACmF 40:s:11RadrootsKit0A20ExportDocumentSourceO 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A20IdentityCustodyErrorO24protectedDataUnavailableyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A22DocumentScannerSupportV24interactiveScanAvailableSbvp 42:s:11RadrootsKit0A22DocumentScannerSupportV 0: -12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A24DocumentInterchangeErrorO16permanentFailureyACSScACmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A24DocumentInterchangeErrorO16permissionDeniedyACSScACmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: -12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A24DocumentInterchangeErrorO16transientFailureyACSScACmF 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A25BackgroundTransferRequestV07maximumD5Bytess6UInt64Vvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: -12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A26AppleMediaPreparationErrorO14invalidRequestyACSScACmF 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A26AppleMediaPreparationErrorO18preparationFailureyA2CmF 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A26BackgroundTransferProgressV6encode2toys7Encoder_p_tKF 46:s:11RadrootsKit0A26BackgroundTransferProgressV 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A26BackgroundTransferResponseV6encode2toys7Encoder_p_tKF 46:s:11RadrootsKit0A26BackgroundTransferResponseV 0: 12:relationship 11:RadrootsKit 8:memberOf 72:s:11RadrootsKit0A26BackgroundTransferSnapshotV20possibleRemoteOrphanSbvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: @@ -1377,18 +1425,18 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A21AppleLocationServicesC8adaptersAcA0acdE8AdaptersV_tcfc 41:s:11RadrootsKit0A21AppleLocationServicesC 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A21AppleMobileStoreErrorO24protectedDataUnavailableyA2CmF 41:s:11RadrootsKit0A21AppleMobileStoreErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A21DocumentImportRequestV16destinationScopeAA0a4FileG0Ovp 41:s:11RadrootsKit0A21DocumentImportRequestV 0: -12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A23BackgroundTransferErrorO18persistenceFailureyACSScACmF 43:s:11RadrootsKit0A23BackgroundTransferErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A24BackgroundTaskValidationO20normalizedIdentifieryS2SKFZ 44:s:11RadrootsKit0A24BackgroundTaskValidationO 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A24BackgroundTransferHandleV7requestAcA0acD7RequestV_tcfc 44:s:11RadrootsKit0A24BackgroundTransferHandleV 0: +12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A25BackgroundTransferFailureO20verificationRejectedyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A25BackgroundTransferRequestV20expectedSourceSHA256SSSgvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A25BackgroundTransferRequestV9remoteURL10Foundation0G0Vvp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A26BackgroundTransferResponseV4body10Foundation4DataVSgvp 46:s:11RadrootsKit0A26BackgroundTransferResponseV 0: +12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A26BackgroundTransferSnapshotV7failureAA0acD7FailureOSgvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A26BackgroundTransferSnapshotV8progressAA0acD8ProgressVvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A26PreparedExportFileDocumentV08preparedD0AcA0acdF0V_tcfc 46:s:11RadrootsKit0A26PreparedExportFileDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV06cameraE0AA0adE0OyYbcvp 49:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV 0: 12:relationship 11:RadrootsKit 8:memberOf 73:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV06photosE0AA0adE0OyYbcvp 49:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV 0: 12:relationship 11:RadrootsKit 8:memberOf 74:s:11RadrootsKit0A17OpaqueSignRequestV24deadlineUnixMillisecondss6UInt64Vvp 37:s:11RadrootsKit0A17OpaqueSignRequestV 0: -12:relationship 11:RadrootsKit 8:memberOf 74:s:11RadrootsKit0A18AppleSecurityErrorO14keychainStatusyACs5Int32V_SStcACmF 38:s:11RadrootsKit0A18AppleSecurityErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 74:s:11RadrootsKit0A18OpaqueSignerBridgeC7custodyAcA0A15IdentityCustodyC_tcfc 38:s:11RadrootsKit0A18OpaqueSignerBridgeC 0: 12:relationship 11:RadrootsKit 8:memberOf 74:s:11RadrootsKit0A21BackgroundTaskRequestV10identifierAA0acD10IdentifierVvp 41:s:11RadrootsKit0A21BackgroundTaskRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 74:s:11RadrootsKit0A21BackgroundTaskRequestV27requiresNetworkConnectivitySbvp 41:s:11RadrootsKit0A21BackgroundTaskRequestV 0: @@ -1430,7 +1478,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A20ExportDocumentSourceO10inlineDatayAC10Foundation0G0VcACmF 40:s:11RadrootsKit0A20ExportDocumentSourceO 0: 12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A21ExternalActionRequestV11destinationAA0acD11DestinationVvp 41:s:11RadrootsKit0A21ExternalActionRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A23CaptureIntakeValidationO24normalizedSelectionLimityS2iKFZ 43:s:11RadrootsKit0A23CaptureIntakeValidationO 0: -12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A26AppleMediaPreparationErrorO18preparationFailureyACSScACmF 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 0: +12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A25BackgroundTransferFailureO23responseContentEncodingyA2CmF 45:s:11RadrootsKit0A25BackgroundTransferFailureO 0: 12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A26BackgroundTransferSnapshotV9updatedAt10Foundation4DateVvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: 12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A27AppleVerifiedArtifactAccessV10revalidateySbAA0adE4FileVKF 47:s:11RadrootsKit0A27AppleVerifiedArtifactAccessV 0: 12:relationship 11:RadrootsKit 8:memberOf 76:s:11RadrootsKit0A29AppleLocationServicesAdaptersV08locationE7EnabledSbyYbcvp 49:s:11RadrootsKit0A29AppleLocationServicesAdaptersV 0: @@ -1463,7 +1511,6 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 78:s:11RadrootsKit0A32BackgroundTransferResponsePolicyV6encode2toys7Encoder_p_tKF 52:s:11RadrootsKit0A32BackgroundTransferResponsePolicyV 0: 12:relationship 11:RadrootsKit 8:memberOf 79:s:11RadrootsKit0A20IdentityCustodyErrorO31portabilityAuthenticationFailedyA2CmF 40:s:11RadrootsKit0A20IdentityCustodyErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 79:s:11RadrootsKit0A21CurrentLocationResultV13authorizationAA0aD13AuthorizationOvp 41:s:11RadrootsKit0A21CurrentLocationResultV 0: -12:relationship 11:RadrootsKit 8:memberOf 79:s:11RadrootsKit0A23AppLocalStateResetErrorO14keychainStatusyACs5Int32V_SStcACmF 43:s:11RadrootsKit0A23AppLocalStateResetErrorO 0: 12:relationship 11:RadrootsKit 8:memberOf 79:s:11RadrootsKit0A24ExternalActionCapabilityV11destinationAA0acD11DestinationVvp 44:s:11RadrootsKit0A24ExternalActionCapabilityV 0: 12:relationship 11:RadrootsKit 8:memberOf 79:s:11RadrootsKit0A25BackgroundTransferRequestV13networkPolicyAA0acd7NetworkG0Ovp 45:s:11RadrootsKit0A25BackgroundTransferRequestV 0: 12:relationship 11:RadrootsKit 8:memberOf 79:s:11RadrootsKit0A26BackgroundTransferSnapshotV10identifierAA0acD10IdentifierVvp 46:s:11RadrootsKit0A26BackgroundTransferSnapshotV 0: @@ -1506,6 +1553,7 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 83:s:11RadrootsKit0A22DocumentScannerSupportV20supportedOutputKindsSayAA0acdG4KindOGvp 42:s:11RadrootsKit0A22DocumentScannerSupportV 0: 12:relationship 11:RadrootsKit 8:memberOf 83:s:11RadrootsKit0A22PreparedExportDocumentV17normalizedFileURLy10Foundation0H0VAGKFZ 42:s:11RadrootsKit0A22PreparedExportDocumentV 0: 12:relationship 11:RadrootsKit 8:memberOf 83:s:11RadrootsKit0A23AppleBackgroundTransferC5retryyAA0adE6HandleVAA0adE7RequestVYaKF 43:s:11RadrootsKit0A23AppleBackgroundTransferC 0: +12:relationship 11:RadrootsKit 8:memberOf 83:s:11RadrootsKit0A30BackgroundTransferVerificationO8rejectedyAcA0acD7FailureO_tcACmF 50:s:11RadrootsKit0A30BackgroundTransferVerificationO 0: 12:relationship 11:RadrootsKit 8:memberOf 84:s:11RadrootsKit0A15IdentityCustodyC06selectC014identityHandleAA0aC8SnapshotVSS_tYaKF 35:s:11RadrootsKit0A15IdentityCustodyC 0: 12:relationship 11:RadrootsKit 8:memberOf 84:s:11RadrootsKit0A24ExternalActionValidationO16normalizedWebURLy10Foundation0H0VSSKFZ 44:s:11RadrootsKit0A24ExternalActionValidationO 0: 12:relationship 11:RadrootsKit 8:memberOf 84:s:11RadrootsKit0A29AppleMobileStoreConfigurationV14ownerDirectory10Foundation3URLVvp 49:s:11RadrootsKit0A29AppleMobileStoreConfigurationV 0: @@ -1517,7 +1565,6 @@ radroots.apple-kit.public-api.v1 12:relationship 11:RadrootsKit 8:memberOf 85:s:11RadrootsKit0A18UserPresencePolicyO39deviceOwnerAuthenticationWithBiometricsyA2CmF 38:s:11RadrootsKit0A18UserPresencePolicyO 0: 12:relationship 11:RadrootsKit 8:memberOf 85:s:11RadrootsKit0A21AppleLocationServicesC29requestWhenInUseAuthorizationAA0adJ0OyYaKF 41:s:11RadrootsKit0A21AppleLocationServicesC 0: 12:relationship 11:RadrootsKit 8:memberOf 85:s:11RadrootsKit0A23AppleBackgroundTransferC7enqueueyAA0adE6HandleVAA0adE7RequestVYaKF 43:s:11RadrootsKit0A23AppleBackgroundTransferC 0: -12:relationship 11:RadrootsKit 8:memberOf 85:s:11RadrootsKit0A28BackgroundTransferValidationO25normalizedOptionalMessageySSSgAEKFZ 48:s:11RadrootsKit0A28BackgroundTransferValidationO 0: 12:relationship 11:RadrootsKit 8:memberOf 85:s:11RadrootsKit0A28IdentityCustodyConfigurationV12secretPolicyAA0a12SecretAccessG0Vvp 48:s:11RadrootsKit0A28IdentityCustodyConfigurationV 0: 12:relationship 11:RadrootsKit 8:memberOf 85:s:11RadrootsKit0A31AppleBackgroundTransferAdaptersV06handleD6EventsyySS_yyYbctYaYbcvp 51:s:11RadrootsKit0A31AppleBackgroundTransferAdaptersV 0: 12:relationship 11:RadrootsKit 8:memberOf 86:s:11RadrootsKit0A15AppleFileAccessC20preparedExportExistsySbAA0a8PreparedG8DocumentVKF 35:s:11RadrootsKit0A15AppleFileAccessC 0: @@ -1668,6 +1715,7 @@ radroots.apple-kit.public-api.v1 12:relationship 18:RadrootsKitTesting 8:memberOf 103:s:18RadrootsKitTesting0A29InMemoryIdentityMetadataStoreC7rawData3for10Foundation0J0VSg0aB00afG4SlotO_tF 56:s:18RadrootsKitTesting0A29InMemoryIdentityMetadataStoreC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 104:s:18RadrootsKitTesting0A19FakeExternalActionsC7canOpeny0aB00aE16ActionCapabilityVAE0aeI11DestinationVYaF 46:s:18RadrootsKitTesting0A19FakeExternalActionsC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 105:s:18RadrootsKitTesting0A15FakeMediaPickerC17setCaptureOutcomeyys6ResultOy0aB00aehJ0VAG0aH11IntakeErrorOGF 42:s:18RadrootsKitTesting0A15FakeMediaPickerC 0: +12:relationship 18:RadrootsKitTesting 8:memberOf 106:s:18RadrootsKitTesting0A22FakeBackgroundTransferC4fail_7failurey0aB00aeF10IdentifierV_AF0aeF7FailureOtYaKF 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 106:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC14removeSnapshot3fory0aB00afG10IdentifierV_tYaKF 58:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 106:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC8snapshot3for0aB00aF8SnapshotVAF0aF4KindO_tYaKF 59:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 107:s:18RadrootsKitTesting0A22FakeBackgroundTransferC8snapshot3for0aB00aeF8SnapshotVSgAF0aeF10IdentifierV_tYaKF 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0: @@ -1755,7 +1803,6 @@ radroots.apple-kit.public-api.v1 12:relationship 18:RadrootsKitTesting 8:memberOf 90:s:18RadrootsKitTesting0A22FakeBackgroundTransferC16enqueuedRequestsSay0aB00aeF7RequestVGvp 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 91:s:18RadrootsKitTesting0A22FakeBackgroundTransferC5retryy0aB00aeF6HandleVAE0aeF7RequestVYaKF 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 93:s:18RadrootsKitTesting0A20FakeLocationServicesC29requestWhenInUseAuthorization0aB00aeK0OyYaKF 47:s:18RadrootsKitTesting0A20FakeLocationServicesC 0: -12:relationship 18:RadrootsKitTesting 8:memberOf 93:s:18RadrootsKitTesting0A22FakeBackgroundTransferC4fail_7messagey0aB00aeF10IdentifierV_SStYaKF 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 93:s:18RadrootsKitTesting0A22FakeBackgroundTransferC7enqueuey0aB00aeF6HandleVAE0aeF7RequestVYaKF 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 94:s:18RadrootsKitTesting0A15FakeMediaPickerC06importE0y0aB00aE12ImportResultVAE0aeH7RequestVYaKF 42:s:18RadrootsKitTesting0A15FakeMediaPickerC 0: 12:relationship 18:RadrootsKitTesting 8:memberOf 94:s:18RadrootsKitTesting0A19FakeExternalActionsC25lastCapabilityDestination0aB00ae6ActionI0VSgvp 46:s:18RadrootsKitTesting0A19FakeExternalActionsC 0: @@ -1816,6 +1863,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 10:swift.enum 44:s:11RadrootsKit0A24BackgroundTransferMethodO 32:RadrootsBackgroundTransferMethod 37:enum RadrootsBackgroundTransferMethod 6:symbol 11:RadrootsKit 10:swift.enum 44:s:11RadrootsKit0A24DocumentInterchangeErrorO 32:RadrootsDocumentInterchangeError 37:enum RadrootsDocumentInterchangeError 6:symbol 11:RadrootsKit 10:swift.enum 44:s:11RadrootsKit0A24ExternalActionValidationO 32:RadrootsExternalActionValidation 37:enum RadrootsExternalActionValidation +6:symbol 11:RadrootsKit 10:swift.enum 45:s:11RadrootsKit0A25BackgroundTransferFailureO 33:RadrootsBackgroundTransferFailure 38:enum RadrootsBackgroundTransferFailure 6:symbol 11:RadrootsKit 10:swift.enum 45:s:11RadrootsKit0A25DocumentScannerOutputKindO 33:RadrootsDocumentScannerOutputKind 38:enum RadrootsDocumentScannerOutputKind 6:symbol 11:RadrootsKit 10:swift.enum 46:s:11RadrootsKit0A17ShareTransferItemV7PayloadO 33:RadrootsShareTransferItem.Payload 12:enum Payload 6:symbol 11:RadrootsKit 10:swift.enum 46:s:11RadrootsKit0A26AppleMediaPreparationErrorO 34:RadrootsAppleMediaPreparationError 39:enum RadrootsAppleMediaPreparationError @@ -1885,7 +1933,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 10:swift.init 220:s:11RadrootsKit0A15IdentityCustodyC13configuration11secureStore08metadataG012userPresence13protectedData3nowAcA0acD13ConfigurationV_AA0a6SecureG0_pAA0ac8MetadataG0_pAA0a4UserJ0_pAA0a9ProtectedL8ProviderVs6UInt64VyYbctcfc 77:init(configuration:secureStore:metadataStore:userPresence:protectedData:now:) 353:init(configuration: RadrootsIdentityCustodyConfiguration, secureStore: any RadrootsSecureStore, metadataStore: any RadrootsIdentityMetadataStore, userPresence: any RadrootsUserPresence, protectedData: RadrootsProtectedDataProvider = .available, now: @escaping @Sendable () -> UInt64 = {\n UInt64(Date().timeIntervalSince1970 * 1000)\n }) 6:symbol 11:RadrootsKit 10:swift.init 228:s:11RadrootsKit0A29AppleLocationServicesAdaptersV3now08locationE7Enabled19authorizationStatus29requestWhenInUseAuthorization0l7CurrentD0AC10Foundation4DateVyYbc_SbyYbcAA0adP0OyYbcAMSdYaYbKcAA0aD7ReadingVAA0aqD7RequestVYaYbKctcfc 107:init(now:locationServicesEnabled:authorizationStatus:requestWhenInUseAuthorization:requestCurrentLocation:) 419:init(now: @escaping @Sendable () -> Date = Date.init, locationServicesEnabled: @escaping @Sendable () -> Bool, authorizationStatus: @escaping @Sendable () -> RadrootsLocationAuthorization, requestWhenInUseAuthorization: @escaping @Sendable (TimeInterval) async throws -> RadrootsLocationAuthorization, requestCurrentLocation: @escaping @Sendable (RadrootsCurrentLocationRequest) async throws -> RadrootsLocationReading) 6:symbol 11:RadrootsKit 10:swift.init 243:s:11RadrootsKit0A36AppleBackgroundTaskSchedulerAdaptersV3now8register6submit6cancel0K3All12pendingTasksAC10Foundation4DateVyYbc_SbAA0acdE12RegistrationVYaYbKcyAA0adE7RequestVYaYbKcyAA0adE10IdentifierVYaYbcyyYaYbcSayAA0adE8SnapshotVGyYaYbKctcfc 56:init(now:register:submit:cancel:cancelAll:pendingTasks:) 443:init(now: @escaping @Sendable () -> Date = Date.init, register: @escaping @Sendable (RadrootsAppleBackgroundTaskRegistration) async throws -> Bool, submit: @escaping @Sendable (RadrootsBackgroundTaskRequest) async throws -> Void, cancel: @escaping @Sendable (RadrootsBackgroundTaskIdentifier) async -> Void, cancelAll: @escaping @Sendable () async -> Void, pendingTasks: @escaping @Sendable () async throws -> [RadrootsBackgroundTaskSnapshot]) -6:symbol 11:RadrootsKit 10:swift.init 259:s:11RadrootsKit0A26BackgroundTransferSnapshotV7request5state8progress12errorMessage8response18downloadedArtifact20possibleRemoteOrphan9updatedAtAcA0acD7RequestV_AA0acD5StateOAA0acD8ProgressVSSSgAA0acD8ResponseVSgAA0ac10DownloadedM0VSgSb10Foundation4DateVtKcfc 101:init(request:state:progress:errorMessage:response:downloadedArtifact:possibleRemoteOrphan:updatedAt:) 368:init(request: RadrootsBackgroundTransferRequest, state: RadrootsBackgroundTransferState = .queued, progress: RadrootsBackgroundTransferProgress = .zero, errorMessage: String? = nil, response: RadrootsBackgroundTransferResponse? = nil, downloadedArtifact: RadrootsBackgroundDownloadedArtifact? = nil, possibleRemoteOrphan: Bool = false, updatedAt: Date = Date()) throws +6:symbol 11:RadrootsKit 10:swift.init 266:s:11RadrootsKit0A26BackgroundTransferSnapshotV7request5state8progress7failure8response18downloadedArtifact20possibleRemoteOrphan9updatedAtAcA0acD7RequestV_AA0acD5StateOAA0acD8ProgressVAA0acD7FailureOSgAA0acD8ResponseVSgAA0ac10DownloadedL0VSgSb10Foundation4DateVtKcfc 96:init(request:state:progress:failure:response:downloadedArtifact:possibleRemoteOrphan:updatedAt:) 390:init(request: RadrootsBackgroundTransferRequest, state: RadrootsBackgroundTransferState = .queued, progress: RadrootsBackgroundTransferProgress = .zero, failure: RadrootsBackgroundTransferFailure? = nil, response: RadrootsBackgroundTransferResponse? = nil, downloadedArtifact: RadrootsBackgroundDownloadedArtifact? = nil, possibleRemoteOrphan: Bool = false, updatedAt: Date = Date()) throws 6:symbol 11:RadrootsKit 10:swift.init 295:s:11RadrootsKit0A25BackgroundTransferRequestV10identifier9remoteURL6method9operation7headers8metadata13networkPolicy08responseN020expectedSourceSHA25607maximumD5BytesAcA0acD10IdentifierV_10Foundation0H0VAA0acD6MethodOAA0acD9OperationOSDyS2SGAwA0acd7NetworkN0OAA0acd8ResponseN0VSSSgs6UInt64VtKcfc 132:init(identifier:remoteURL:method:operation:headers:metadata:networkPolicy:responsePolicy:expectedSourceSHA256:maximumTransferBytes:) 493:init(identifier: RadrootsBackgroundTransferIdentifier = .generated(), remoteURL: URL, method: RadrootsBackgroundTransferMethod, operation: RadrootsBackgroundTransferOperation, headers: [String : String] = [:], metadata: [String : String] = [:], networkPolicy: RadrootsBackgroundTransferNetworkPolicy = .publicHTTPS, responsePolicy: RadrootsBackgroundTransferResponsePolicy = .discard, expectedSourceSHA256: String? = nil, maximumTransferBytes: UInt64 = Self.defaultMaximumTransferBytes) throws 6:symbol 11:RadrootsKit 10:swift.init 39:s:11RadrootsKit0A13NoopTelemetryVACycfc 6:init() 6:init() 6:symbol 11:RadrootsKit 10:swift.init 47:s:11RadrootsKit0A18IdentityPassphraseVyACSSKcfc 8:init(_:) 28:init(_ value: String) throws @@ -1898,6 +1946,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 10:swift.init 53:s:11RadrootsKit0A24BackgroundTaskIdentifierVyACSSKcfc 8:init(_:) 28:init(_ value: String) throws 6:symbol 11:RadrootsKit 10:swift.init 54:s:11RadrootsKit0A14PermissionKindO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) 6:symbol 11:RadrootsKit 10:swift.init 54:s:11RadrootsKit0A14TelemetryLevelO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) +6:symbol 11:RadrootsKit 10:swift.init 55:s:11RadrootsKit0A29UnavailableBackgroundTransferVACycfc 6:init() 6:init() 6:symbol 11:RadrootsKit 10:swift.init 56:s:11RadrootsKit0A16PermissionStatusO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) 6:symbol 11:RadrootsKit 10:swift.init 56:s:11RadrootsKit0A18MultiplexTelemetryVyACSayAA0aD0_pGcfc 8:init(_:) 38:init(_ sinks: [any RadrootsTelemetry]) 6:symbol 11:RadrootsKit 10:swift.init 57:s:11RadrootsKit0A13FileReferenceV4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws @@ -1911,6 +1960,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 10:swift.init 60:s:11RadrootsKit0A14SecureStoreKeyV9namespace4nameACSS_SStcfc 21:init(namespace:name:) 37:init(namespace: String, name: String) 6:symbol 11:RadrootsKit 10:swift.init 60:s:11RadrootsKit0A20IdentityCustodyErrorO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) 6:symbol 11:RadrootsKit 10:swift.init 60:s:11RadrootsKit0A20IdentityMetadataSlotO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) +6:symbol 11:RadrootsKit 10:swift.init 60:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerVACycfc 6:init() 6:init() 6:symbol 11:RadrootsKit 10:swift.init 61:s:11RadrootsKit0A18MediaCaptureResultV4itemAcA0aC5AssetV_tcfc 11:init(item:) 30:init(item: RadrootsMediaAsset) 6:symbol 11:RadrootsKit 10:swift.init 61:s:11RadrootsKit0A21LocationAuthorizationO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) 6:symbol 11:RadrootsKit 10:swift.init 63:s:11RadrootsKit0A19StagedBlobReferenceV4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws @@ -1919,8 +1969,8 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 10:swift.init 64:s:11RadrootsKit0A20IdentityPublicRecordV4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws 6:symbol 11:RadrootsKit 10:swift.init 64:s:11RadrootsKit0A22IdentitySecretMaterialV10importTextACSS_tKcfc 17:init(importText:) 31:init(importText: String) throws 6:symbol 11:RadrootsKit 10:swift.init 64:s:11RadrootsKit0A24BackgroundTransferMethodO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) +6:symbol 11:RadrootsKit 10:swift.init 65:s:11RadrootsKit0A25BackgroundTransferFailureO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) 6:symbol 11:RadrootsKit 10:swift.init 65:s:11RadrootsKit0A25DocumentScannerOutputKindO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) -6:symbol 11:RadrootsKit 10:swift.init 65:s:11RadrootsKit0A29UnavailableBackgroundTransferV6reasonACSS_tcfc 13:init(reason:) 76:init(reason: String = "background transfer is unavailable on this platform") 6:symbol 11:RadrootsKit 10:swift.init 67:s:11RadrootsKit0A14TelemetryFieldV3key5valueACSS_AA0acD5ValueOtKcfc 16:init(key:value:) 60:init(key: String, value: RadrootsTelemetryFieldValue) throws 6:symbol 11:RadrootsKit 10:swift.init 67:s:11RadrootsKit0A17MediaImportResultV5itemsACSayAA0aC5AssetVG_tKcfc 12:init(items:) 40:init(items: [RadrootsMediaAsset]) throws 6:symbol 11:RadrootsKit 10:swift.init 68:s:11RadrootsKit0A24AppleKeychainSecureStoreC13servicePrefixACSS_tcfc 20:init(servicePrefix:) 61:init(servicePrefix: String = "org.radroots.kit.secure-store") @@ -1933,7 +1983,6 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 10:swift.init 70:s:11RadrootsKit0A26BackgroundTransferProgressV4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws 6:symbol 11:RadrootsKit 10:swift.init 70:s:11RadrootsKit0A26BackgroundTransferResponseV4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws 6:symbol 11:RadrootsKit 10:swift.init 70:s:11RadrootsKit0A26BackgroundTransferSnapshotV4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws -6:symbol 11:RadrootsKit 10:swift.init 70:s:11RadrootsKit0A34UnavailableBackgroundTaskSchedulerV6reasonACSS_tcfc 13:init(reason:) 83:init(reason: String = "background task scheduling is unavailable on this platform") 6:symbol 11:RadrootsKit 10:swift.init 71:s:11RadrootsKit0A27BackgroundTransferLocalFileO4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws 6:symbol 11:RadrootsKit 10:swift.init 71:s:11RadrootsKit0A27BackgroundTransferOperationO4fromACs7Decoder_p_tKcfc 11:init(from:) 38:init(from decoder: any Decoder) throws 6:symbol 11:RadrootsKit 10:swift.init 71:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO8rawValueACSgSS_tcfc 15:init(rawValue:) 23:init?(rawValue: String) @@ -2398,6 +2447,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A15OpaqueSignatureV9signature10Foundation4DataVvp 9:signature 19:let signature: Data 6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A18UserPresenceStatusV21canEvaluateBiometricsSbvp 21:canEvaluateBiometrics 31:let canEvaluateBiometrics: Bool 6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A20VerifiedArtifactFileV4data10Foundation4DataVvp 4:data 14:let data: Data +6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A21AppleMobileStoreErrorO16errorDescriptionSSSgvp 16:errorDescription 37:var errorDescription: String? { get } 6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A21CurrentLocationResultV7readingAA0aD7ReadingVvp 7:reading 36:let reading: RadrootsLocationReading 6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A21LocationServicesErrorO16errorDescriptionSSSgvp 16:errorDescription 37:var errorDescription: String? { get } 6:symbol 11:RadrootsKit 14:swift.property 65:s:11RadrootsKit0A22PreparedExportDocumentV17suggestedFilenameSSvp 17:suggestedFilename 29:let suggestedFilename: String @@ -2408,12 +2458,12 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 14:swift.property 66:s:11RadrootsKit0A18MediaPickerSupportV22cameraCaptureAvailableSbvp 22:cameraCaptureAvailable 32:let cameraCaptureAvailable: Bool 6:symbol 11:RadrootsKit 14:swift.property 66:s:11RadrootsKit0A22DocumentScannerSupportV18multiPageSupportedSbvp 18:multiPageSupported 28:let multiPageSupported: Bool 6:symbol 11:RadrootsKit 14:swift.property 66:s:11RadrootsKit0A24BackgroundTransferHandleV16debugDescriptionSSvp 16:debugDescription 36:var debugDescription: String { get } -6:symbol 11:RadrootsKit 14:swift.property 66:s:11RadrootsKit0A26BackgroundTransferSnapshotV12errorMessageSSSgvp 12:errorMessage 25:let errorMessage: String? 6:symbol 11:RadrootsKit 14:swift.property 66:s:11RadrootsKit0A26VerifiedArtifactDescriptorV8byteSizes6UInt64Vvp 8:byteSize 20:let byteSize: UInt64 6:symbol 11:RadrootsKit 14:swift.property 66:s:11RadrootsKit0A27IdentityPortabilityEnvelopeV7versions6UInt16Vvp 7:version 27:var version: UInt16 { get } 6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A15LocationReadingV10capturedAt10Foundation4DateVvp 10:capturedAt 20:let capturedAt: Date 6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A15LocationReadingV10coordinateAA0aC10CoordinateVvp 10:coordinate 42:let coordinate: RadrootsLocationCoordinate 6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A15ScannedDocumentV10capturedAt10Foundation4DateVvp 10:capturedAt 20:let capturedAt: Date +6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A23AppLocalStateResetErrorO16errorDescriptionSSSgvp 16:errorDescription 37:var errorDescription: String? { get } 6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A23BackgroundTransferErrorO16errorDescriptionSSSgvp 16:errorDescription 37:var errorDescription: String? { get } 6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A25BackgroundTransferRequestV16debugDescriptionSSvp 16:debugDescription 36:var debugDescription: String { get } 6:symbol 11:RadrootsKit 14:swift.property 67:s:11RadrootsKit0A26BackgroundTransferSnapshotV5stateAA0acD5StateOvp 5:state 42:let state: RadrootsBackgroundTransferState @@ -2450,6 +2500,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A19MediaCaptureRequestV16destinationScopeAA0a4FileG0Ovp 16:destinationScope 39:let destinationScope: RadrootsFileScope 6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A22CurrentLocationRequestV21desiredAccuracyMetersSdSgvp 21:desiredAccuracyMeters 34:let desiredAccuracyMeters: Double? 6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A26BackgroundTransferSnapshotV7requestAA0acD7RequestVvp 7:request 46:let request: RadrootsBackgroundTransferRequest +6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO16errorDescriptionSSSgvp 16:errorDescription 37:var errorDescription: String? { get } 6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A28AppleImagePreparationRequestV17maximumInputBytesSivp 17:maximumInputBytes 26:let maximumInputBytes: Int 6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A28AppleImagePreparationRequestV17maximumPixelCountSivp 17:maximumPixelCount 26:let maximumPixelCount: Int 6:symbol 11:RadrootsKit 14:swift.property 71:s:11RadrootsKit0A28LocationServicesAvailabilityV08locationD7EnabledSbvp 23:locationServicesEnabled 33:let locationServicesEnabled: Bool @@ -2465,6 +2516,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A25BackgroundTransferRequestV20expectedSourceSHA256SSSgvp 20:expectedSourceSHA256 33:let expectedSourceSHA256: String? 6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A25BackgroundTransferRequestV9remoteURL10Foundation0G0Vvp 9:remoteURL 18:let remoteURL: URL 6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A26BackgroundTransferResponseV4body10Foundation4DataVSgvp 4:body 15:let body: Data? +6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A26BackgroundTransferSnapshotV7failureAA0acD7FailureOSgvp 7:failure 47:let failure: RadrootsBackgroundTransferFailure? 6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A26BackgroundTransferSnapshotV8progressAA0acD8ProgressVvp 8:progress 48:let progress: RadrootsBackgroundTransferProgress 6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV06cameraE0AA0adE0OyYbcvp 12:cameraStatus 58:let cameraStatus: @Sendable () -> RadrootsPermissionStatus 6:symbol 11:RadrootsKit 14:swift.property 73:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV06photosE0AA0adE0OyYbcvp 12:photosStatus 58:let photosStatus: @Sendable () -> RadrootsPermissionStatus @@ -2576,6 +2628,7 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 15:swift.enum.case 47:s:11RadrootsKit0A14TelemetryLevelO6noticeyA2CmF 29:RadrootsTelemetryLevel.notice 11:case notice 6:symbol 11:RadrootsKit 15:swift.enum.case 48:s:11RadrootsKit0A13IdentityStateO8unlockedyA2CmF 30:RadrootsIdentityState.unlocked 13:case unlocked 6:symbol 11:RadrootsKit 15:swift.enum.case 48:s:11RadrootsKit0A14TelemetryLevelO7warningyA2CmF 30:RadrootsTelemetryLevel.warning 12:case warning +6:symbol 11:RadrootsKit 15:swift.enum.case 49:s:11RadrootsKit0A14AppleFileErrorO8notFoundyA2CmF 31:RadrootsAppleFileError.notFound 13:case notFound 6:symbol 11:RadrootsKit 15:swift.enum.case 49:s:11RadrootsKit0A14PermissionKindO8locationyA2CmF 31:RadrootsPermissionKind.location 13:case location 6:symbol 11:RadrootsKit 15:swift.enum.case 49:s:11RadrootsKit0A14TelemetryLevelO8criticalyA2CmF 31:RadrootsTelemetryLevel.critical 13:case critical 6:symbol 11:RadrootsKit 15:swift.enum.case 49:s:11RadrootsKit0A16PermissionStatusO6deniedyA2CmF 31:RadrootsPermissionStatus.denied 11:case denied @@ -2586,11 +2639,12 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 15:swift.enum.case 50:s:11RadrootsKit0A19DocumentContentKindO4jsonyA2CmF 32:RadrootsDocumentContentKind.json 9:case json 6:symbol 11:RadrootsKit 15:swift.enum.case 50:s:11RadrootsKit0A19UserPresenceSupportO4noneyA2CmF 32:RadrootsUserPresenceSupport.none 9:case none 6:symbol 11:RadrootsKit 15:swift.enum.case 51:s:11RadrootsKit0A12FileReadModeO6inlineyACSi_tcACmF 38:RadrootsFileReadMode.inline(maxBytes:) 26:case inline(maxBytes: Int) +6:symbol 11:RadrootsKit 15:swift.enum.case 51:s:11RadrootsKit0A17UserPresenceErrorO7timeoutyA2CmF 33:RadrootsUserPresenceError.timeout 12:case timeout 6:symbol 11:RadrootsKit 15:swift.enum.case 51:s:11RadrootsKit0A18ProtectedDataStateO6lockedyA2CmF 33:RadrootsProtectedDataState.locked 11:case locked 6:symbol 11:RadrootsKit 15:swift.enum.case 52:s:11RadrootsKit0A11MediaSourceO13cameraCaptureyA2CmF 33:RadrootsMediaSource.cameraCapture 18:case cameraCapture 6:symbol 11:RadrootsKit 15:swift.enum.case 52:s:11RadrootsKit0A11MediaSourceO13libraryImportyA2CmF 33:RadrootsMediaSource.libraryImport 18:case libraryImport 6:symbol 11:RadrootsKit 15:swift.enum.case 52:s:11RadrootsKit0A14PermissionKindO10microphoneyA2CmF 33:RadrootsPermissionKind.microphone 15:case microphone -6:symbol 11:RadrootsKit 15:swift.enum.case 53:s:11RadrootsKit0A14AppleFileErrorO8notFoundyACSScACmF 35:RadrootsAppleFileError.notFound(_:) 21:case notFound(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 53:s:11RadrootsKit0A18AppleSecurityErrorO8notFoundyA2CmF 35:RadrootsAppleSecurityError.notFound 13:case notFound 6:symbol 11:RadrootsKit 15:swift.enum.case 54:s:11RadrootsKit0A16PermissionStatusO10authorizedyA2CmF 35:RadrootsPermissionStatus.authorized 15:case authorized 6:symbol 11:RadrootsKit 15:swift.enum.case 54:s:11RadrootsKit0A16PermissionStatusO10restrictedyA2CmF 35:RadrootsPermissionStatus.restricted 15:case restricted 6:symbol 11:RadrootsKit 15:swift.enum.case 54:s:11RadrootsKit0A18ProtectedDataStateO9availableyA2CmF 36:RadrootsProtectedDataState.available 14:case available @@ -2602,11 +2656,14 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A16PermissionStatusO11unavailableyA2CmF 36:RadrootsPermissionStatus.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A16PermissionStatusO11unsupportedyA2CmF 36:RadrootsPermissionStatus.unsupported 16:case unsupported 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A17OpaqueSignPurposeO10nostrEventyA2CmF 36:RadrootsOpaqueSignPurpose.nostrEvent 15:case nostrEvent -6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A17UserPresenceErrorO7timeoutyACSScACmF 37:RadrootsUserPresenceError.timeout(_:) 20:case timeout(String) 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A19DocumentContentKindO9plainTextyA2CmF 37:RadrootsDocumentContentKind.plainText 14:case plainText 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A20IdentityCustodyErrorO8timedOutyA2CmF 37:RadrootsIdentityCustodyError.timedOut 13:case timedOut +6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A21LocationServicesErrorO7timeoutyA2CmF 37:RadrootsLocationServicesError.timeout 12:case timeout 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A24BackgroundTransferMethodO4postyA2CmF 37:RadrootsBackgroundTransferMethod.post 9:case post 6:symbol 11:RadrootsKit 15:swift.enum.case 55:s:11RadrootsKit0A25DocumentScannerOutputKindO3pdfyA2CmF 37:RadrootsDocumentScannerOutputKind.pdf 8:case pdf +6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A14AppleFileErrorO14invalidRequestyA2CmF 37:RadrootsAppleFileError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A14TelemetryErrorO14invalidRequestyA2CmF 37:RadrootsTelemetryError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A17UserPresenceErrorO11unavailableyA2CmF 37:RadrootsUserPresenceError.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A18BackgroundTaskKindO10appRefreshyA2CmF 37:RadrootsBackgroundTaskKind.appRefresh 15:case appRefresh 6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A18BackgroundTaskKindO10processingyA2CmF 37:RadrootsBackgroundTaskKind.processing 15:case processing 6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A19TelemetryFieldValueO6doubleyACSdcACmF 38:RadrootsTelemetryFieldValue.double(_:) 19:case double(Double) @@ -2617,142 +2674,152 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 15:swift.enum.case 56:s:11RadrootsKit0A23BackgroundTransferStateO6queuedyA2CmF 38:RadrootsBackgroundTransferState.queued 11:case queued 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A13IdentityStateO16recoveryRequiredyA2CmF 38:RadrootsIdentityState.recoveryRequired 21:case recoveryRequired 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A16PermissionStatusO13notDeterminedyA2CmF 38:RadrootsPermissionStatus.notDetermined 18:case notDetermined -6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A18AppleSecurityErrorO8notFoundyACSScACmF 39:RadrootsAppleSecurityError.notFound(_:) 21:case notFound(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A18AppleSecurityErrorO11unavailableyA2CmF 38:RadrootsAppleSecurityError.unavailable 16:case unavailable +6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A18CaptureIntakeErrorO11unavailableyA2CmF 38:RadrootsCaptureIntakeError.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A18ProtectedDataStateO11unavailableyA2CmF 38:RadrootsProtectedDataState.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A19DocumentContentKindO10stagedBlobyA2CmF 38:RadrootsDocumentContentKind.stagedBlob 15:case stagedBlob +6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A21LocationServicesErrorO9cancelledyA2CmF 39:RadrootsLocationServicesError.cancelled 14:case cancelled 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A23BackgroundTransferStateO7expiredyA2CmF 39:RadrootsBackgroundTransferState.expired 12:case expired 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A23BackgroundTransferStateO7runningyA2CmF 39:RadrootsBackgroundTransferState.running 12:case running 6:symbol 11:RadrootsKit 15:swift.enum.case 57:s:11RadrootsKit0A9ShareItemO3urlyAC10Foundation3URLVcACmF 25:RadrootsShareItem.url(_:) 13:case url(URL) 6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A12FileReadModeO12preferInlineyACSi_tcACmF 44:RadrootsFileReadMode.preferInline(maxBytes:) 32:case preferInline(maxBytes: Int) +6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A14AppleFileErrorO16permanentFailureyA2CmF 39:RadrootsAppleFileError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A14AppleFileErrorO16permissionDeniedyA2CmF 39:RadrootsAppleFileError.permissionDenied 21:case permissionDenied +6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A14AppleFileErrorO16transientFailureyA2CmF 39:RadrootsAppleFileError.transientFailure 21:case transientFailure 6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A17OpaqueSignPurposeO13blossomUploadyA2CmF 39:RadrootsOpaqueSignPurpose.blossomUpload 18:case blossomUpload +6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A17UserPresenceErrorO13userCancelledyA2CmF 39:RadrootsUserPresenceError.userCancelled 18:case userCancelled +6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A19BackgroundTaskErrorO11unavailableyA2CmF 39:RadrootsBackgroundTaskError.unavailable 16:case unavailable +6:symbol 11:RadrootsKit 15:swift.enum.case 58:s:11RadrootsKit0A19ExternalActionErrorO11unavailableyA2CmF 39:RadrootsExternalActionError.unavailable 16:case unavailable +6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A17UserPresenceErrorO14invalidRequestyA2CmF 40:RadrootsUserPresenceError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A18AppleSecurityErrorO13userCancelledyA2CmF 40:RadrootsAppleSecurityError.userCancelled 18:case userCancelled +6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A18CaptureIntakeErrorO13userCancelledyA2CmF 40:RadrootsCaptureIntakeError.userCancelled 18:case userCancelled 6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A19SecretAccessibilityO12whenUnlockedyA2CmF 40:RadrootsSecretAccessibility.whenUnlocked 17:case whenUnlocked 6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A20IdentityCustodyErrorO11staleSigneryA2CmF 40:RadrootsIdentityCustodyError.staleSigner 16:case staleSigner 6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A21LocationAuthorizationO10restrictedyA2CmF 40:RadrootsLocationAuthorization.restricted 15:case restricted -6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A21LocationServicesErrorO7timeoutyACSScACmF 41:RadrootsLocationServicesError.timeout(_:) 20:case timeout(String) 6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A23BackgroundTransferStateO9cancelledyA2CmF 41:RadrootsBackgroundTransferState.cancelled 14:case cancelled 6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A23BackgroundTransferStateO9completedyA2CmF 41:RadrootsBackgroundTransferState.completed 14:case completed +6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A24DocumentInterchangeErrorO8notFoundyA2CmF 41:RadrootsDocumentInterchangeError.notFound 13:case notFound +6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A25BackgroundTransferFailureO7expiredyA2CmF 41:RadrootsBackgroundTransferFailure.expired 12:case expired 6:symbol 11:RadrootsKit 15:swift.enum.case 59:s:11RadrootsKit0A29ExternalActionDestinationKindO3webyA2CmF 41:RadrootsExternalActionDestinationKind.web 8:case web -6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A14AppleFileErrorO14invalidRequestyACSScACmF 41:RadrootsAppleFileError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A14TelemetryErrorO14invalidRequestyACSScACmF 41:RadrootsTelemetryError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A17UserPresenceErrorO11unavailableyACSScACmF 41:RadrootsUserPresenceError.unavailable(_:) 24:case unavailable(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A18AppleSecurityErrorO14invalidRequestyA2CmF 41:RadrootsAppleSecurityError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A18CaptureIntakeErrorO14invalidRequestyA2CmF 41:RadrootsCaptureIntakeError.invalidRequest 19:case invalidRequest 6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A21LocationAuthorizationO11unavailableyA2CmF 41:RadrootsLocationAuthorization.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A21LocationAuthorizationO11unsupportedyA2CmF 41:RadrootsLocationAuthorization.unsupported 16:case unsupported +6:symbol 11:RadrootsKit 15:swift.enum.case 60:s:11RadrootsKit0A21LocationServicesErrorO11unavailableyA2CmF 41:RadrootsLocationServicesError.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A17ShareTransferItemV7PayloadO4textyAESScAEmF 42:RadrootsShareTransferItem.Payload.text(_:) 17:case text(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A18AppleSecurityErrorO11unavailableyACSScACmF 42:RadrootsAppleSecurityError.unavailable(_:) 24:case unavailable(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A18CaptureIntakeErrorO11unavailableyACSScACmF 42:RadrootsCaptureIntakeError.unavailable(_:) 24:case unavailable(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A17UserPresenceErrorO16permanentFailureyA2CmF 42:RadrootsUserPresenceError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A17UserPresenceErrorO16permissionDeniedyA2CmF 42:RadrootsUserPresenceError.permissionDenied 21:case permissionDenied +6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A17UserPresenceErrorO16transientFailureyA2CmF 42:RadrootsUserPresenceError.transientFailure 21:case transientFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A18AppleSecurityErrorO15keychainFailureyA2CmF 42:RadrootsAppleSecurityError.keychainFailure 20:case keychainFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A19BackgroundTaskErrorO14invalidRequestyA2CmF 42:RadrootsBackgroundTaskError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A19ExternalActionErrorO14invalidRequestyA2CmF 42:RadrootsExternalActionError.invalidRequest 19:case invalidRequest 6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A20IdentityCustodyErrorO13invalidSecretyA2CmF 42:RadrootsIdentityCustodyError.invalidSecret 18:case invalidSecret -6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A21LocationServicesErrorO9cancelledyACSScACmF 43:RadrootsLocationServicesError.cancelled(_:) 22:case cancelled(String) 6:symbol 11:RadrootsKit 15:swift.enum.case 61:s:11RadrootsKit0A29ExternalActionDestinationKindO5nostryA2CmF 43:RadrootsExternalActionDestinationKind.nostr 10:case nostr -6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A14AppleFileErrorO16permanentFailureyACSScACmF 43:RadrootsAppleFileError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A14AppleFileErrorO16permissionDeniedyACSScACmF 43:RadrootsAppleFileError.permissionDenied(_:) 29:case permissionDenied(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A14AppleFileErrorO16transientFailureyACSScACmF 43:RadrootsAppleFileError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A17UserPresenceErrorO13userCancelledyACSScACmF 43:RadrootsUserPresenceError.userCancelled(_:) 26:case userCancelled(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A19BackgroundTaskErrorO11unavailableyACSScACmF 43:RadrootsBackgroundTaskError.unavailable(_:) 24:case unavailable(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A19ExternalActionErrorO11unavailableyACSScACmF 43:RadrootsExternalActionError.unavailable(_:) 24:case unavailable(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A18AppleSecurityErrorO16permanentFailureyA2CmF 43:RadrootsAppleSecurityError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A18AppleSecurityErrorO16permissionDeniedyA2CmF 43:RadrootsAppleSecurityError.permissionDenied 21:case permissionDenied +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A18AppleSecurityErrorO16transientFailureyA2CmF 43:RadrootsAppleSecurityError.transientFailure 21:case transientFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A18CaptureIntakeErrorO16permanentFailureyA2CmF 43:RadrootsCaptureIntakeError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A18CaptureIntakeErrorO16permissionDeniedyA2CmF 43:RadrootsCaptureIntakeError.permissionDenied 21:case permissionDenied +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A18CaptureIntakeErrorO16transientFailureyA2CmF 43:RadrootsCaptureIntakeError.transientFailure 21:case transientFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A19ExternalActionErrorO15blockedByPolicyyA2CmF 43:RadrootsExternalActionError.blockedByPolicy 20:case blockedByPolicy 6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A20IdentityCustodyErrorO14identityLockedyA2CmF 43:RadrootsIdentityCustodyError.identityLocked 19:case identityLocked 6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A20IdentityMetadataSlotO011quarantinedD0yA2CmF 48:RadrootsIdentityMetadataSlot.quarantinedMetadata 24:case quarantinedMetadata 6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A21LocationAuthorizationO13notDeterminedyA2CmF 43:RadrootsLocationAuthorization.notDetermined 18:case notDetermined +6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A23BackgroundTransferErrorO11unavailableyA2CmF 43:RadrootsBackgroundTransferError.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 62:s:11RadrootsKit0A23BackgroundTransferStateO11interruptedyA2CmF 43:RadrootsBackgroundTransferState.interrupted 16:case interrupted -6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A17UserPresenceErrorO14invalidRequestyACSScACmF 44:RadrootsUserPresenceError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A18AppleSecurityErrorO13userCancelledyACSScACmF 44:RadrootsAppleSecurityError.userCancelled(_:) 26:case userCancelled(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A18CaptureIntakeErrorO13userCancelledyACSScACmF 44:RadrootsCaptureIntakeError.userCancelled(_:) 26:case userCancelled(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A19BackgroundTaskErrorO16schedulerFailureyA2CmF 44:RadrootsBackgroundTaskError.schedulerFailure 21:case schedulerFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A19ExternalActionErrorO16permanentFailureyA2CmF 44:RadrootsExternalActionError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A19ExternalActionErrorO16transientFailureyA2CmF 44:RadrootsExternalActionError.transientFailure 21:case transientFailure 6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A19SecretAccessibilityO16afterFirstUnlockyA2CmF 44:RadrootsSecretAccessibility.afterFirstUnlock 21:case afterFirstUnlock 6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A19TelemetryFieldValueO7integeryACs5Int64VcACmF 39:RadrootsTelemetryFieldValue.integer(_:) 19:case integer(Int64) 6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A19UserPresenceSupportO16deviceCredentialyA2CmF 44:RadrootsUserPresenceSupport.deviceCredential 21:case deviceCredential 6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A20IdentityCustodyErrorO15corruptMetadatayA2CmF 44:RadrootsIdentityCustodyError.corruptMetadata 20:case corruptMetadata 6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A20IdentityCustodyErrorO15invalidMetadatayA2CmF 44:RadrootsIdentityCustodyError.invalidMetadata 20:case invalidMetadata -6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A24DocumentInterchangeErrorO8notFoundyACSScACmF 45:RadrootsDocumentInterchangeError.notFound(_:) 21:case notFound(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A21LocationServicesErrorO14invalidRequestyA2CmF 44:RadrootsLocationServicesError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A25BackgroundTransferFailureO08platformE0yA2CmF 49:RadrootsBackgroundTransferFailure.platformFailure 20:case platformFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 63:s:11RadrootsKit0A25BackgroundTransferFailureO10httpStatusyA2CmF 44:RadrootsBackgroundTransferFailure.httpStatus 15:case httpStatus 6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A11FilePayloadO6inlineyAC10Foundation4DataVcACmF 30:RadrootsFilePayload.inline(_:) 17:case inline(Data) -6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A18AppleSecurityErrorO14invalidRequestyACSScACmF 45:RadrootsAppleSecurityError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A18CaptureIntakeErrorO14invalidRequestyACSScACmF 45:RadrootsCaptureIntakeError.invalidRequest(_:) 27:case invalidRequest(String) 6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A20IdentityCustodyErrorO16identityNotFoundyA2CmF 45:RadrootsIdentityCustodyError.identityNotFound 21:case identityNotFound 6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A20IdentityCustodyErrorO16invalidSignatureyA2CmF 45:RadrootsIdentityCustodyError.invalidSignature 21:case invalidSignature 6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A20IdentityCustodyErrorO16recoveryRequiredyA2CmF 45:RadrootsIdentityCustodyError.recoveryRequired 21:case recoveryRequired 6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A20IdentityCustodyErrorO16signingSaturatedyA2CmF 45:RadrootsIdentityCustodyError.signingSaturated 21:case signingSaturated -6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A21LocationServicesErrorO11unavailableyACSScACmF 45:RadrootsLocationServicesError.unavailable(_:) 24:case unavailable(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 64:s:11RadrootsKit0A25BackgroundTransferFailureO11interruptedyA2CmF 45:RadrootsBackgroundTransferFailure.interrupted 16:case interrupted 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A13IdentityStateO24protectedDataUnavailableyA2CmF 46:RadrootsIdentityState.protectedDataUnavailable 29:case protectedDataUnavailable -6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A17UserPresenceErrorO16permanentFailureyACSScACmF 46:RadrootsUserPresenceError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A17UserPresenceErrorO16permissionDeniedyACSScACmF 46:RadrootsUserPresenceError.permissionDenied(_:) 29:case permissionDenied(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A17UserPresenceErrorO16transientFailureyACSScACmF 46:RadrootsUserPresenceError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A19BackgroundTaskErrorO14invalidRequestyACSScACmF 46:RadrootsBackgroundTaskError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A19ExternalActionErrorO14invalidRequestyACSScACmF 46:RadrootsExternalActionError.invalidRequest(_:) 27:case invalidRequest(String) 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A19TelemetryFieldValueO10stringListyACSaySSGcACmF 42:RadrootsTelemetryFieldValue.stringList(_:) 25:case stringList([String]) 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A20IdentityCustodyErrorO17inconsistentStateyA2CmF 46:RadrootsIdentityCustodyError.inconsistentState 22:case inconsistentState 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A20IdentityCustodyErrorO17invalidPassphraseyA2CmF 46:RadrootsIdentityCustodyError.invalidPassphrase 22:case invalidPassphrase 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A21AppleMobileStoreErrorO16invalidPublicKeyyA2CmF 46:RadrootsAppleMobileStoreError.invalidPublicKey 21:case invalidPublicKey 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A21LocationAuthorizationO16authorizedAlwaysyA2CmF 46:RadrootsLocationAuthorization.authorizedAlways 21:case authorizedAlways +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A21LocationServicesErrorO16permanentFailureyA2CmF 46:RadrootsLocationServicesError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A21LocationServicesErrorO16permissionDeniedyA2CmF 46:RadrootsLocationServicesError.permissionDenied 21:case permissionDenied +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A21LocationServicesErrorO16transientFailureyA2CmF 46:RadrootsLocationServicesError.transientFailure 21:case transientFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A23AppLocalStateResetErrorO14invalidRequestyA2CmF 46:RadrootsAppLocalStateResetError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A23BackgroundTransferErrorO14invalidRequestyA2CmF 46:RadrootsBackgroundTransferError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A24DocumentInterchangeErrorO13userCancelledyA2CmF 46:RadrootsDocumentInterchangeError.userCancelled 18:case userCancelled +6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A26AppleMediaPreparationErrorO11unavailableyA2CmF 46:RadrootsAppleMediaPreparationError.unavailable 16:case unavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A29ExternalActionDestinationKindO9appleMapsyA2CmF 47:RadrootsExternalActionDestinationKind.appleMaps 14:case appleMaps 6:symbol 11:RadrootsKit 15:swift.enum.case 65:s:11RadrootsKit0A30BackgroundTransferVerificationO8acceptedyA2CmF 47:RadrootsBackgroundTransferVerification.accepted 13:case accepted -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A18AppleSecurityErrorO16permanentFailureyACSScACmF 47:RadrootsAppleSecurityError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A18AppleSecurityErrorO16permissionDeniedyACSScACmF 47:RadrootsAppleSecurityError.permissionDenied(_:) 29:case permissionDenied(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A18AppleSecurityErrorO16transientFailureyACSScACmF 47:RadrootsAppleSecurityError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A18CaptureIntakeErrorO16permanentFailureyACSScACmF 47:RadrootsCaptureIntakeError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A18CaptureIntakeErrorO16permissionDeniedyACSScACmF 47:RadrootsCaptureIntakeError.permissionDenied(_:) 29:case permissionDenied(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A18CaptureIntakeErrorO16transientFailureyACSScACmF 47:RadrootsCaptureIntakeError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A19ExternalActionErrorO15blockedByPolicyyACSScACmF 47:RadrootsExternalActionError.blockedByPolicy(_:) 28:case blockedByPolicy(String) 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A20IdentityCustodyErrorO18cryptographyFailedyA2CmF 47:RadrootsIdentityCustodyError.cryptographyFailed 23:case cryptographyFailed 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A20IdentityCustodyErrorO18duplicateOperationyA2CmF 47:RadrootsIdentityCustodyError.duplicateOperation 23:case duplicateOperation 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A20IdentityCustodyErrorO18invalidSignRequestyA2CmF 47:RadrootsIdentityCustodyError.invalidSignRequest 23:case invalidSignRequest 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A20IdentityCustodyErrorO18storageUnavailableyA2CmF 47:RadrootsIdentityCustodyError.storageUnavailable 23:case storageUnavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A20IdentityMetadataSlotO18transactionJournalyA2CmF 47:RadrootsIdentityMetadataSlot.transactionJournal 23:case transactionJournal 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A21AppleMobileStoreErrorO17fileSystemFailureyA2CmF 47:RadrootsAppleMobileStoreError.fileSystemFailure 22:case fileSystemFailure -6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A23BackgroundTransferErrorO11unavailableyACSScACmF 47:RadrootsBackgroundTransferError.unavailable(_:) 24:case unavailable(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A23AppLocalStateResetErrorO15keychainFailureyA2CmF 47:RadrootsAppLocalStateResetError.keychainFailure 20:case keychainFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A23BackgroundTransferErrorO15transferFailureyA2CmF 47:RadrootsBackgroundTransferError.transferFailure 20:case transferFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A24DocumentInterchangeErrorO14invalidRequestyA2CmF 47:RadrootsDocumentInterchangeError.invalidRequest 19:case invalidRequest +6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A25BackgroundTransferFailureO13enqueueFailedyA2CmF 47:RadrootsBackgroundTransferFailure.enqueueFailed 18:case enqueueFailed 6:symbol 11:RadrootsKit 15:swift.enum.case 66:s:11RadrootsKit0A30AppleProtectedDataAvailabilityO9availableyA2CmF 48:RadrootsAppleProtectedDataAvailability.available 14:case available 6:symbol 11:RadrootsKit 15:swift.enum.case 67:s:11RadrootsKit0A14FileReadResultO6inlineyAC10Foundation4DataVcACmF 33:RadrootsFileReadResult.inline(_:) 17:case inline(Data) -6:symbol 11:RadrootsKit 15:swift.enum.case 67:s:11RadrootsKit0A19BackgroundTaskErrorO16schedulerFailureyACSScACmF 48:RadrootsBackgroundTaskError.schedulerFailure(_:) 29:case schedulerFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 67:s:11RadrootsKit0A19ExternalActionErrorO16permanentFailureyACSScACmF 48:RadrootsExternalActionError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 67:s:11RadrootsKit0A19ExternalActionErrorO16transientFailureyACSScACmF 48:RadrootsExternalActionError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 67:s:11RadrootsKit0A21LocationServicesErrorO14invalidRequestyACSScACmF 48:RadrootsLocationServicesError.invalidRequest(_:) 27:case invalidRequest(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 67:s:11RadrootsKit0A25BackgroundTransferFailureO011destinationE0yA2CmF 52:RadrootsBackgroundTransferFailure.destinationFailure 23:case destinationFailure 6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A20IdentityCustodyErrorO20invalidConfigurationyA2CmF 49:RadrootsIdentityCustodyError.invalidConfiguration 25:case invalidConfiguration 6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A20IdentityCustodyErrorO20userPresenceRequiredyA2CmF 49:RadrootsIdentityCustodyError.userPresenceRequired 25:case userPresenceRequired 6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A21LocationAuthorizationO19authorizedWhenInUseyA2CmF 49:RadrootsLocationAuthorization.authorizedWhenInUse 24:case authorizedWhenInUse +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A23AppLocalStateResetErrorO17fileSystemFailureyA2CmF 49:RadrootsAppLocalStateResetError.fileSystemFailure 22:case fileSystemFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16permanentFailureyA2CmF 49:RadrootsDocumentInterchangeError.permanentFailure 21:case permanentFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16permissionDeniedyA2CmF 49:RadrootsDocumentInterchangeError.permissionDenied 21:case permissionDenied +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A24DocumentInterchangeErrorO16transientFailureyA2CmF 49:RadrootsDocumentInterchangeError.transientFailure 21:case transientFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A25BackgroundTransferFailureO15responseInvalidyA2CmF 49:RadrootsBackgroundTransferFailure.responseInvalid 20:case responseInvalid +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A25BackgroundTransferFailureO15responseMissingyA2CmF 49:RadrootsBackgroundTransferFailure.responseMissing 20:case responseMissing +6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A26AppleMediaPreparationErrorO14invalidRequestyA2CmF 49:RadrootsAppleMediaPreparationError.invalidRequest 19:case invalidRequest 6:symbol 11:RadrootsKit 15:swift.enum.case 68:s:11RadrootsKit0A29ExternalActionDestinationKindO11appSettingsyA2CmF 49:RadrootsExternalActionDestinationKind.appSettings 16:case appSettings 6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A20IdentityCustodyErrorO21identityAlreadyExistsyA2CmF 50:RadrootsIdentityCustodyError.identityAlreadyExists 26:case identityAlreadyExists -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A21LocationServicesErrorO16permanentFailureyACSScACmF 50:RadrootsLocationServicesError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A21LocationServicesErrorO16permissionDeniedyACSScACmF 50:RadrootsLocationServicesError.permissionDenied(_:) 29:case permissionDenied(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A21LocationServicesErrorO16transientFailureyACSScACmF 50:RadrootsLocationServicesError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A23AppLocalStateResetErrorO14invalidRequestyACSScACmF 50:RadrootsAppLocalStateResetError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A23BackgroundTransferErrorO14invalidRequestyACSScACmF 50:RadrootsBackgroundTransferError.invalidRequest(_:) 27:case invalidRequest(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A24DocumentInterchangeErrorO13userCancelledyACSScACmF 50:RadrootsDocumentInterchangeError.userCancelled(_:) 26:case userCancelled(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A26AppleMediaPreparationErrorO11unavailableyACSScACmF 50:RadrootsAppleMediaPreparationError.unavailable(_:) 24:case unavailable(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A23BackgroundTransferErrorO18persistenceFailureyA2CmF 50:RadrootsBackgroundTransferError.persistenceFailure 23:case persistenceFailure +6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A25BackgroundTransferFailureO16responseTooLargeyA2CmF 50:RadrootsBackgroundTransferFailure.responseTooLarge 21:case responseTooLarge +6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A25BackgroundTransferFailureO16transferTooLargeyA2CmF 50:RadrootsBackgroundTransferFailure.transferTooLarge 21:case transferTooLarge 6:symbol 11:RadrootsKit 15:swift.enum.case 69:s:11RadrootsKit0A30AppleProtectedDataAvailabilityO11unavailableyA2CmF 50:RadrootsAppleProtectedDataAvailability.unavailable 16:case unavailable -6:symbol 11:RadrootsKit 15:swift.enum.case 70:s:11RadrootsKit0A23BackgroundTransferErrorO15transferFailureyACSScACmF 51:RadrootsBackgroundTransferError.transferFailure(_:) 28:case transferFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 70:s:11RadrootsKit0A24DocumentInterchangeErrorO14invalidRequestyACSScACmF 51:RadrootsDocumentInterchangeError.invalidRequest(_:) 27:case invalidRequest(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 70:s:11RadrootsKit0A25BackgroundTransferFailureO17responseMediaTypeyA2CmF 51:RadrootsBackgroundTransferFailure.responseMediaType 22:case responseMediaType 6:symbol 11:RadrootsKit 15:swift.enum.case 70:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO15artifactCorruptyA2CmF 51:RadrootsVerifiedArtifactAccessError.artifactCorrupt 20:case artifactCorrupt 6:symbol 11:RadrootsKit 15:swift.enum.case 70:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO11publicHTTPSyA2CmF 51:RadrootsBackgroundTransferNetworkPolicy.publicHTTPS 16:case publicHTTPS 6:symbol 11:RadrootsKit 15:swift.enum.case 71:s:11RadrootsKit0A18UserPresencePolicyO25deviceOwnerAuthenticationyA2CmF 52:RadrootsUserPresencePolicy.deviceOwnerAuthentication 30:case deviceOwnerAuthentication 6:symbol 11:RadrootsKit 15:swift.enum.case 71:s:11RadrootsKit0A21AppleMobileStoreErrorO22invalidDirectoryLayoutyA2CmF 52:RadrootsAppleMobileStoreError.invalidDirectoryLayout 27:case invalidDirectoryLayout 6:symbol 11:RadrootsKit 15:swift.enum.case 71:s:11RadrootsKit0A23BackgroundTransferStateO20awaitingVerificationyA2CmF 52:RadrootsBackgroundTransferState.awaitingVerification 25:case awaitingVerification -6:symbol 11:RadrootsKit 15:swift.enum.case 71:s:11RadrootsKit0A30BackgroundTransferVerificationO8rejectedyACSS_tcACmF 54:RadrootsBackgroundTransferVerification.rejected(code:) 27:case rejected(code: String) +6:symbol 11:RadrootsKit 15:swift.enum.case 71:s:11RadrootsKit0A25BackgroundTransferFailureO015downloadStagingE0yA2CmF 56:RadrootsBackgroundTransferFailure.downloadStagingFailure 27:case downloadStagingFailure 6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A20ExportDocumentSourceO4fileyAcA0A13FileReferenceVcACmF 37:RadrootsExportDocumentSource.file(_:) 32:case file(RadrootsFileReference) 6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A20IdentityCustodyErrorO24protectedDataUnavailableyA2CmF 53:RadrootsIdentityCustodyError.protectedDataUnavailable 29:case protectedDataUnavailable -6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A24DocumentInterchangeErrorO16permanentFailureyACSScACmF 53:RadrootsDocumentInterchangeError.permanentFailure(_:) 29:case permanentFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A24DocumentInterchangeErrorO16permissionDeniedyACSScACmF 53:RadrootsDocumentInterchangeError.permissionDenied(_:) 29:case permissionDenied(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A24DocumentInterchangeErrorO16transientFailureyACSScACmF 53:RadrootsDocumentInterchangeError.transientFailure(_:) 29:case transientFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A26AppleMediaPreparationErrorO14invalidRequestyACSScACmF 53:RadrootsAppleMediaPreparationError.invalidRequest(_:) 27:case invalidRequest(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A26AppleMediaPreparationErrorO18preparationFailureyA2CmF 53:RadrootsAppleMediaPreparationError.preparationFailure 23:case preparationFailure 6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO17fileSystemFailureyA2CmF 53:RadrootsVerifiedArtifactAccessError.fileSystemFailure 22:case fileSystemFailure 6:symbol 11:RadrootsKit 15:swift.enum.case 72:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO17invalidDescriptoryA2CmF 53:RadrootsVerifiedArtifactAccessError.invalidDescriptor 22:case invalidDescriptor 6:symbol 11:RadrootsKit 15:swift.enum.case 73:s:11RadrootsKit0A11FilePayloadO10stagedBlobyAcA0a6StagedF9ReferenceVcACmF 34:RadrootsFilePayload.stagedBlob(_:) 44:case stagedBlob(RadrootsStagedBlobReference) 6:symbol 11:RadrootsKit 15:swift.enum.case 73:s:11RadrootsKit0A21AppleMobileStoreErrorO24protectedDataUnavailableyA2CmF 54:RadrootsAppleMobileStoreError.protectedDataUnavailable 29:case protectedDataUnavailable -6:symbol 11:RadrootsKit 15:swift.enum.case 73:s:11RadrootsKit0A23BackgroundTransferErrorO18persistenceFailureyACSScACmF 54:RadrootsBackgroundTransferError.persistenceFailure(_:) 31:case persistenceFailure(String) -6:symbol 11:RadrootsKit 15:swift.enum.case 74:s:11RadrootsKit0A18AppleSecurityErrorO14keychainStatusyACs5Int32V_SStcACmF 47:RadrootsAppleSecurityError.keychainStatus(_:_:) 34:case keychainStatus(Int32, String) +6:symbol 11:RadrootsKit 15:swift.enum.case 73:s:11RadrootsKit0A25BackgroundTransferFailureO20verificationRejectedyA2CmF 54:RadrootsBackgroundTransferFailure.verificationRejected 25:case verificationRejected 6:symbol 11:RadrootsKit 15:swift.enum.case 74:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO19artifactUnavailableyA2CmF 55:RadrootsVerifiedArtifactAccessError.artifactUnavailable 24:case artifactUnavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 75:s:11RadrootsKit0A17ShareTransferItemV7PayloadO3urlyAE10Foundation3URLVcAEmF 41:RadrootsShareTransferItem.Payload.url(_:) 13:case url(URL) 6:symbol 11:RadrootsKit 15:swift.enum.case 75:s:11RadrootsKit0A19UserPresenceSupportO28biometricsOrDeviceCredentialyA2CmF 56:RadrootsUserPresenceSupport.biometricsOrDeviceCredential 33:case biometricsOrDeviceCredential 6:symbol 11:RadrootsKit 15:swift.enum.case 75:s:11RadrootsKit0A27BackgroundTransferLocalFileO4fileyAcA0aF9ReferenceVcACmF 44:RadrootsBackgroundTransferLocalFile.file(_:) 32:case file(RadrootsFileReference) 6:symbol 11:RadrootsKit 15:swift.enum.case 76:s:11RadrootsKit0A14FileReadResultO10stagedBlobyAcA0a6StagedG9ReferenceVcACmF 37:RadrootsFileReadResult.stagedBlob(_:) 44:case stagedBlob(RadrootsStagedBlobReference) 6:symbol 11:RadrootsKit 15:swift.enum.case 76:s:11RadrootsKit0A20ExportDocumentSourceO10inlineDatayAC10Foundation0G0VcACmF 43:RadrootsExportDocumentSource.inlineData(_:) 21:case inlineData(Data) -6:symbol 11:RadrootsKit 15:swift.enum.case 76:s:11RadrootsKit0A26AppleMediaPreparationErrorO18preparationFailureyACSScACmF 57:RadrootsAppleMediaPreparationError.preparationFailure(_:) 31:case preparationFailure(String) +6:symbol 11:RadrootsKit 15:swift.enum.case 76:s:11RadrootsKit0A25BackgroundTransferFailureO23responseContentEncodingyA2CmF 57:RadrootsBackgroundTransferFailure.responseContentEncoding 28:case responseContentEncoding 6:symbol 11:RadrootsKit 15:swift.enum.case 76:s:11RadrootsKit0A9ShareItemO10stagedBlobyAcA0a6StagedF9ReferenceV_SSSgtcACmF 50:RadrootsShareItem.stagedBlob(_:suggestedFilename:) 72:case stagedBlob(RadrootsStagedBlobReference, suggestedFilename: String?) 6:symbol 11:RadrootsKit 15:swift.enum.case 78:s:11RadrootsKit0A20IdentityCustodyErrorO30unsupportedPortabilityEnvelopeyA2CmF 59:RadrootsIdentityCustodyError.unsupportedPortabilityEnvelope 35:case unsupportedPortabilityEnvelope 6:symbol 11:RadrootsKit 15:swift.enum.case 79:s:11RadrootsKit0A20IdentityCustodyErrorO31portabilityAuthenticationFailedyA2CmF 60:RadrootsIdentityCustodyError.portabilityAuthenticationFailed 36:case portabilityAuthenticationFailed -6:symbol 11:RadrootsKit 15:swift.enum.case 79:s:11RadrootsKit0A23AppLocalStateResetErrorO14keychainStatusyACs5Int32V_SStcACmF 52:RadrootsAppLocalStateResetError.keychainStatus(_:_:) 34:case keychainStatus(Int32, String) 6:symbol 11:RadrootsKit 15:swift.enum.case 79:s:11RadrootsKit0A27VerifiedArtifactAccessErrorO24protectedDataUnavailableyA2CmF 60:RadrootsVerifiedArtifactAccessError.protectedDataUnavailable 29:case protectedDataUnavailable 6:symbol 11:RadrootsKit 15:swift.enum.case 79:s:11RadrootsKit0A9ShareItemO4fileyAcA0A13FileReferenceV_SSSgAGs6UInt64VSgtcACmF 64:RadrootsShareItem.file(_:suggestedFilename:mediaType:sizeBytes:) 100:case file(RadrootsFileReference, suggestedFilename: String?, mediaType: String?, sizeBytes: UInt64?) 6:symbol 11:RadrootsKit 15:swift.enum.case 80:s:11RadrootsKit0A27BackgroundTransferOperationO6uploadyAcA0acD9LocalFileO_tcACmF 51:RadrootsBackgroundTransferOperation.upload(source:) 56:case upload(source: RadrootsBackgroundTransferLocalFile) 6:symbol 11:RadrootsKit 15:swift.enum.case 80:s:11RadrootsKit0A31BackgroundTransferNetworkPolicyO21simulatorLoopbackHTTPyA2CmF 61:RadrootsBackgroundTransferNetworkPolicy.simulatorLoopbackHTTP 26:case simulatorLoopbackHTTP 6:symbol 11:RadrootsKit 15:swift.enum.case 82:s:11RadrootsKit0A20ExportDocumentSourceO10stagedBlobyAcA0a6StagedG9ReferenceVcACmF 43:RadrootsExportDocumentSource.stagedBlob(_:) 44:case stagedBlob(RadrootsStagedBlobReference) 6:symbol 11:RadrootsKit 15:swift.enum.case 82:s:11RadrootsKit0A27BackgroundTransferOperationO8downloadyAcA0acD9LocalFileO_tcACmF 58:RadrootsBackgroundTransferOperation.download(destination:) 63:case download(destination: RadrootsBackgroundTransferLocalFile) +6:symbol 11:RadrootsKit 15:swift.enum.case 83:s:11RadrootsKit0A30BackgroundTransferVerificationO8rejectedyAcA0acD7FailureO_tcACmF 57:RadrootsBackgroundTransferVerification.rejected(failure:) 57:case rejected(failure: RadrootsBackgroundTransferFailure) 6:symbol 11:RadrootsKit 15:swift.enum.case 85:s:11RadrootsKit0A18UserPresencePolicyO39deviceOwnerAuthenticationWithBiometricsyA2CmF 66:RadrootsUserPresencePolicy.deviceOwnerAuthenticationWithBiometrics 44:case deviceOwnerAuthenticationWithBiometrics 6:symbol 11:RadrootsKit 15:swift.enum.case 87:s:11RadrootsKit0A17ShareTransferItemV7PayloadO4fileyAeA0A22PreparedExportDocumentVcAEmF 42:RadrootsShareTransferItem.Payload.file(_:) 41:case file(RadrootsPreparedExportDocument) 6:symbol 11:RadrootsKit 15:swift.enum.case 89:s:11RadrootsKit0A27BackgroundTransferLocalFileO10stagedBlobyAcA0a6StagedH9ReferenceVcACmF 50:RadrootsBackgroundTransferLocalFile.stagedBlob(_:) 44:case stagedBlob(RadrootsStagedBlobReference) @@ -2823,7 +2890,6 @@ radroots.apple-kit.public-api.v1 6:symbol 11:RadrootsKit 17:swift.type.method 83:s:11RadrootsKit0A22PreparedExportDocumentV17normalizedFileURLy10Foundation0H0VAGKFZ 21:normalizedFileURL(_:) 59:static func normalizedFileURL(_ fileURL: URL) throws -> URL 6:symbol 11:RadrootsKit 17:swift.type.method 84:s:11RadrootsKit0A24ExternalActionValidationO16normalizedWebURLy10Foundation0H0VSSKFZ 20:normalizedWebURL(_:) 59:static func normalizedWebURL(_ value: String) throws -> URL 6:symbol 11:RadrootsKit 17:swift.type.method 85:s:11RadrootsKit0A14AppleFileRootsV17normalizedRootURL_5field10Foundation0H0VAH_SStKFZ 27:normalizedRootURL(_:field:) 74:static func normalizedRootURL(_ rootURL: URL, field: String) throws -> URL -6:symbol 11:RadrootsKit 17:swift.type.method 85:s:11RadrootsKit0A28BackgroundTransferValidationO25normalizedOptionalMessageySSSgAEKFZ 29:normalizedOptionalMessage(_:) 73:static func normalizedOptionalMessage(_ value: String?) throws -> String? 6:symbol 11:RadrootsKit 17:swift.type.method 86:s:11RadrootsKit0A29ApplePermissionStatusAdaptersV019currentNotificationE0AA0adE0OyYaFZ 27:currentNotificationStatus() 73:static func currentNotificationStatus() async -> RadrootsPermissionStatus 6:symbol 11:RadrootsKit 17:swift.type.method 87:s:11RadrootsKit0A21DocumentImportRequestV22normalizedContentKindsySayAA0acG4KindOGAGKFZ 26:normalizedContentKinds(_:) 128:static func normalizedContentKinds(_ allowedContentKinds: [RadrootsDocumentContentKind]) throws -> [RadrootsDocumentContentKind] 6:symbol 11:RadrootsKit 17:swift.type.method 87:s:11RadrootsKit0A24ExternalActionValidationO18normalizedNostrURIy10Foundation3URLVSSKFZ 22:normalizedNostrURI(_:) 61:static func normalizedNostrURI(_ value: String) throws -> URL @@ -2900,6 +2966,7 @@ radroots.apple-kit.public-api.v1 6:symbol 18:RadrootsKitTesting 12:swift.method 103:s:18RadrootsKitTesting0A29InMemoryIdentityMetadataStoreC7rawData3for10Foundation0J0VSg0aB00afG4SlotO_tF 13:rawData(for:) 61:func rawData(for slot: RadrootsIdentityMetadataSlot) -> Data? 6:symbol 18:RadrootsKitTesting 12:swift.method 104:s:18RadrootsKitTesting0A19FakeExternalActionsC7canOpeny0aB00aE16ActionCapabilityVAE0aeI11DestinationVYaF 11:canOpen(_:) 104:func canOpen(_ destination: RadrootsExternalActionDestination) async -> RadrootsExternalActionCapability 6:symbol 18:RadrootsKitTesting 12:swift.method 105:s:18RadrootsKitTesting0A15FakeMediaPickerC17setCaptureOutcomeyys6ResultOy0aB00aehJ0VAG0aH11IntakeErrorOGF 21:setCaptureOutcome(_:) 97:func setCaptureOutcome(_ outcome: Result<RadrootsMediaCaptureResult, RadrootsCaptureIntakeError>) +6:symbol 18:RadrootsKitTesting 12:swift.method 106:s:18RadrootsKitTesting0A22FakeBackgroundTransferC4fail_7failurey0aB00aeF10IdentifierV_AF0aeF7FailureOtYaKF 16:fail(_:failure:) 118:func fail(_ identifier: RadrootsBackgroundTransferIdentifier, failure: RadrootsBackgroundTransferFailure) async throws 6:symbol 18:RadrootsKitTesting 12:swift.method 106:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC14removeSnapshot3fory0aB00afG10IdentifierV_tYaKF 20:removeSnapshot(for:) 86:func removeSnapshot(for identifier: RadrootsBackgroundTransferIdentifier) async throws 6:symbol 18:RadrootsKitTesting 12:swift.method 106:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC8snapshot3for0aB00aF8SnapshotVAF0aF4KindO_tYaKF 14:snapshot(for:) 90:func snapshot(for kind: RadrootsPermissionKind) async throws -> RadrootsPermissionSnapshot 6:symbol 18:RadrootsKitTesting 12:swift.method 107:s:18RadrootsKitTesting0A22FakeBackgroundTransferC8snapshot3for0aB00aeF8SnapshotVSgAF0aeF10IdentifierV_tYaKF 14:snapshot(for:) 119:func snapshot(for identifier: RadrootsBackgroundTransferIdentifier) async throws -> RadrootsBackgroundTransferSnapshot? @@ -2943,7 +3010,6 @@ radroots.apple-kit.public-api.v1 6:symbol 18:RadrootsKitTesting 12:swift.method 90:s:18RadrootsKitTesting0A20FakeLocationServicesC28setAuthorizationAfterRequestyy0aB00aeH0OF 32:setAuthorizationAfterRequest(_:) 81:func setAuthorizationAfterRequest(_ authorization: RadrootsLocationAuthorization) 6:symbol 18:RadrootsKitTesting 12:swift.method 91:s:18RadrootsKitTesting0A22FakeBackgroundTransferC5retryy0aB00aeF6HandleVAE0aeF7RequestVYaKF 9:retry(_:) 105:func retry(_ request: RadrootsBackgroundTransferRequest) async throws -> RadrootsBackgroundTransferHandle 6:symbol 18:RadrootsKitTesting 12:swift.method 93:s:18RadrootsKitTesting0A20FakeLocationServicesC29requestWhenInUseAuthorization0aB00aeK0OyYaKF 31:requestWhenInUseAuthorization() 82:func requestWhenInUseAuthorization() async throws -> RadrootsLocationAuthorization -6:symbol 18:RadrootsKitTesting 12:swift.method 93:s:18RadrootsKitTesting0A22FakeBackgroundTransferC4fail_7messagey0aB00aeF10IdentifierV_SStYaKF 16:fail(_:message:) 91:func fail(_ identifier: RadrootsBackgroundTransferIdentifier, message: String) async throws 6:symbol 18:RadrootsKitTesting 12:swift.method 93:s:18RadrootsKitTesting0A22FakeBackgroundTransferC7enqueuey0aB00aeF6HandleVAE0aeF7RequestVYaKF 11:enqueue(_:) 107:func enqueue(_ request: RadrootsBackgroundTransferRequest) async throws -> RadrootsBackgroundTransferHandle 6:symbol 18:RadrootsKitTesting 12:swift.method 94:s:18RadrootsKitTesting0A15FakeMediaPickerC06importE0y0aB00aE12ImportResultVAE0aeH7RequestVYaKF 15:importMedia(_:) 97:func importMedia(_ request: RadrootsMediaImportRequest) async throws -> RadrootsMediaImportResult 6:symbol 18:RadrootsKitTesting 12:swift.method 95:s:18RadrootsKitTesting0A19FakeDocumentScannerC04scanE0y0aB00a7ScannedE0VAE0aE11ScanRequestVYaKF 16:scanDocument(_:) 97:func scanDocument(_ request: RadrootsDocumentScanRequest) async throws -> RadrootsScannedDocument diff --git a/tools/verify-boundaries.sh b/tools/verify-boundaries.sh @@ -17,6 +17,15 @@ if git grep -I -n -E -e '-----BEGIN ([A-Z0-9 ]+ )?PRIVATE KEY-----|AKIA[0-9A-Z]{ echo "boundary_invalid: production source contains credential material" >&2 exit 1 fi +if git grep -I -n -E \ + -e 'localizedDescription' \ + -e 'public let errorMessage: String' \ + -e 'case rejected\(code: String\)' \ + -e 'case (invalidRequest|unavailable|permissionDenied|userCancelled|transientFailure|permanentFailure|transferFailure|persistenceFailure|notFound|blockedByPolicy|timeout|cancelled|schedulerFailure|preparationFailure|keychainStatus)\([^)]*String' \ + -- Sources >/dev/null; then + echo "boundary_invalid: public error surface accepts arbitrary diagnostic text" >&2 + exit 1 +fi swift build bin_path="$(swift build --show-bin-path)"