commit 68135576af9ac34126195178dabe82f02ff69497
parent 75a4a63149adb55b6a07f94686ef74b5446f1136
Author: triesap <tyson@radroots.org>
Date: Sun, 20 Sep 2026 15:06:37 +0000
transfer: fence renewal behind inactive native execution
- Hold existing identifier admission across explicit caller work
- Reject active unknown and unreconciled native outcomes
- Preserve prior snapshots and late receipt persistence
- Verify competing owners and cancellation on macOS and iOS
Diffstat:
8 files changed, 280 insertions(+), 43 deletions(-)
diff --git a/Sources/RadrootsKit/RadrootsAppleBackgroundTransfer.swift b/Sources/RadrootsKit/RadrootsAppleBackgroundTransfer.swift
@@ -5,6 +5,9 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer {
private let adapters: RadrootsAppleBackgroundTransferAdapters
private var admissions: Set<RadrootsBackgroundTransferIdentifier> = []
private var stoppedAdmissions: [RadrootsBackgroundTransferIdentifier: RadrootsBackgroundTransferState] = [:]
+ private struct InactiveExecutionBodyError: Error {
+ let underlying: any Error
+ }
public init(store: any RadrootsBackgroundTransferStore, adapters: RadrootsAppleBackgroundTransferAdapters) {
self.store = store
@@ -35,6 +38,48 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer {
return try await admission(request, retry: true)
}
+ public func withInactiveExecution<Result: Sendable>(
+ for identifier: RadrootsBackgroundTransferIdentifier,
+ operation: @escaping @Sendable (RadrootsBackgroundTransferSnapshot?) async throws -> Result
+ ) async throws -> Result {
+ try Task.checkCancellation()
+ try reserve(identifier)
+ defer { release(identifier) }
+ do {
+ return try await store.withAdmission(for: identifier) {
+ let snapshot = try await self.inactiveSnapshot(identifier)
+ try Task.checkCancellation()
+ do { return try await operation(snapshot) }
+ catch { throw InactiveExecutionBodyError(underlying: error) }
+ }
+ } catch let error as InactiveExecutionBodyError {
+ throw error.underlying
+ } catch is CancellationError {
+ throw CancellationError()
+ } catch let error as RadrootsBackgroundTransferError {
+ throw error
+ } catch {
+ throw RadrootsBackgroundTransferError.persistenceFailure
+ }
+ }
+
+ private func inactiveSnapshot(
+ _ identifier: RadrootsBackgroundTransferIdentifier
+ ) async throws -> RadrootsBackgroundTransferSnapshot? {
+ guard try await !activeIdentifiers().contains(identifier), stoppedAdmissions[identifier] == nil else {
+ throw RadrootsBackgroundTransferError.transferFailure
+ }
+ let snapshot = try await load(identifier)
+ if let snapshot {
+ guard [.failed, .interrupted, .cancelled, .expired].contains(snapshot.state),
+ snapshot.response == nil, snapshot.downloadedArtifact == nil
+ else { throw RadrootsBackgroundTransferError.transferFailure }
+ }
+ // Do not rewrite state, drop a receipt or cancel an OS task. Completion
+ // callbacks may still persist late evidence while admission is held.
+ return snapshot
+ }
+
private func admission(
_ request: RadrootsBackgroundTransferRequest, retry: Bool
) async throws -> RadrootsBackgroundTransferHandle {
@@ -104,7 +149,8 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer {
try? await adapters.cancel(request.identifier)
}
if let current = try await load(request.identifier), current.executionID == queued.executionID,
- current.state == .queued || current.state == .running {
+ current.state == .queued || current.state == .running
+ {
_ = try await exchange(current, current.transitioned(to: .failed, at: adapters.now(),
failure: .enqueueFailed,
possibleRemoteOrphan: request.isUpload))
@@ -114,7 +160,8 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer {
if stoppedAdmissions[request.identifier] != nil || Task.isCancelled {
try await stop(request.identifier, state: stoppedAdmissions[request.identifier] ?? .cancelled)
} else if let current = try await load(request.identifier), current.executionID == queued.executionID,
- current.state == .queued {
+ current.state == .queued
+ {
_ = try await exchange(current, current.transitioned(to: .running, at: adapters.now()))
}
return RadrootsBackgroundTransferHandle(request: request)
@@ -177,7 +224,8 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer {
}
public func snapshot(for identifier: RadrootsBackgroundTransferIdentifier) async throws
- -> RadrootsBackgroundTransferSnapshot? {
+ -> RadrootsBackgroundTransferSnapshot?
+ {
try await snapshots().first { $0.identifier == identifier }
}
@@ -216,7 +264,8 @@ public actor RadrootsAppleBackgroundTransfer: RadrootsBackgroundTransfer {
}
private func load(_ identifier: RadrootsBackgroundTransferIdentifier) async throws
- -> RadrootsBackgroundTransferSnapshot? {
+ -> RadrootsBackgroundTransferSnapshot?
+ {
try await loadAll().first { $0.identifier == identifier }
}
diff --git a/Sources/RadrootsKit/RadrootsAppleBackgroundTransferStore.swift b/Sources/RadrootsKit/RadrootsAppleBackgroundTransferStore.swift
@@ -42,10 +42,10 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto
try withStoreLock { try loadSnapshotsSynchronously() }
}
- public func withAdmission(
+ public func withAdmission<Result: Sendable>(
for identifier: RadrootsBackgroundTransferIdentifier,
- operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle
- ) async throws -> RadrootsBackgroundTransferHandle {
+ operation: @escaping @Sendable () async throws -> Result
+ ) async throws -> Result {
guard let admission = try await acquireAdmission(identifier) else {
throw RadrootsBackgroundTransferError.invalidRequest
}
@@ -127,14 +127,20 @@ public actor RadrootsAppleBackgroundTransferStore: RadrootsBackgroundTransferSto
defer { Darwin.close(coordination) }
do {
let directory = try admissionCoordinationURL().deletingLastPathComponent()
- if admissionScan == nil { admissionScan = try RadrootsAdmissionFileScan(url: directory) }
+ if admissionScan == nil {
+ admissionScan = try RadrootsAdmissionFileScan(url: directory)
+ }
guard let admissionScan else { throw RadrootsBackgroundTransferError.persistenceFailure }
let batch = try admissionScan.next(limit: limit)
var removed = 0
for name in batch.names where Self.isAdmissionFilename(name) {
- if try admissionScan.removeInactive(name: name, coordination: coordination) { removed += 1 }
+ if try admissionScan.removeInactive(name: name, coordination: coordination) {
+ removed += 1
+ }
+ }
+ if batch.reachedEnd {
+ self.admissionScan = nil
}
- if batch.reachedEnd { self.admissionScan = nil }
return RadrootsAdmissionCleanupResult(scannedEntries: batch.scanned, removedFiles: removed, reachedEnd: batch.reachedEnd)
} catch {
admissionScan = nil
diff --git a/Sources/RadrootsKit/RadrootsBackgroundTransfer.swift b/Sources/RadrootsKit/RadrootsBackgroundTransfer.swift
@@ -19,7 +19,8 @@ extension RadrootsBackgroundTransferError: LocalizedError {
}
public struct RadrootsBackgroundTransferIdentifier: Sendable, Equatable, Hashable, Comparable,
- Codable {
+ Codable
+{
public let rawValue: String
public init(_ value: String) throws {
@@ -52,7 +53,8 @@ public struct RadrootsBackgroundTransferIdentifier: Sendable, Equatable, Hashabl
}
public enum RadrootsBackgroundTransferMethod: String, Sendable, Equatable, Hashable, Codable,
- CaseIterable {
+ CaseIterable
+{
case get = "GET"
case post = "POST"
case put = "PUT"
@@ -69,7 +71,8 @@ public enum RadrootsBackgroundTransferOperation: Sendable, Equatable, Hashable,
}
public enum RadrootsBackgroundTransferState: String, Sendable, Equatable, Hashable, Codable,
- CaseIterable {
+ CaseIterable
+{
case queued
case running
case awaitingVerification
@@ -81,7 +84,8 @@ public enum RadrootsBackgroundTransferState: String, Sendable, Equatable, Hashab
}
public enum RadrootsBackgroundTransferFailure: String, Sendable, Equatable, Hashable, Codable,
- CaseIterable {
+ CaseIterable
+{
case enqueueFailed = "background_transfer_enqueue_failed"
case expired = "background_transfer_expired"
case interrupted = "background_transfer_interrupted"
@@ -106,10 +110,10 @@ public enum RadrootsBackgroundTransferNetworkPolicy: String, Sendable, Equatable
public protocol RadrootsBackgroundTransferStore: Sendable {
/// Serializes admission across suspension and independent owners. This is a
/// nonblocking reservation, separate from short persistence transactions.
- func withAdmission(
+ func withAdmission<Result: Sendable>(
for identifier: RadrootsBackgroundTransferIdentifier,
- operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle
- ) async throws -> RadrootsBackgroundTransferHandle
+ operation: @escaping @Sendable () async throws -> Result
+ ) async throws -> Result
func admissionIsActive(for identifier: RadrootsBackgroundTransferIdentifier) async throws -> Bool
/// Atomically installs a redacted snapshot only if the exact expected value
/// still owns the identifier. Nil reserves a previously absent identifier.
@@ -123,6 +127,16 @@ public protocol RadrootsBackgroundTransferStore: Sendable {
}
public protocol RadrootsBackgroundTransfer: Sendable {
+ /// Explicitly reconcile the OS and retain exclusive admission for this prior
+ /// identifier during bounded caller work. Only absent or inactive failed
+ /// execution without a retained response is admitted. Unknown state throws.
+ /// This does not establish absence of remote effects or grant retry/signing
+ /// authority. Preserve prior lineage and use a new identifier for renewal;
+ /// enqueue/retry of the reserved identifier is rejected until the body exits.
+ func withInactiveExecution<Result: Sendable>(
+ for identifier: RadrootsBackgroundTransferIdentifier,
+ operation: @escaping @Sendable (RadrootsBackgroundTransferSnapshot?) async throws -> Result
+ ) async throws -> Result
func enqueue(_ request: RadrootsBackgroundTransferRequest) async throws
-> RadrootsBackgroundTransferHandle
func retry(_ request: RadrootsBackgroundTransferRequest) async throws
@@ -141,6 +155,15 @@ public protocol RadrootsBackgroundTransfer: Sendable {
) async
}
+public extension RadrootsBackgroundTransfer {
+ func withInactiveExecution<Result: Sendable>(
+ for _: RadrootsBackgroundTransferIdentifier,
+ operation _: @escaping @Sendable (RadrootsBackgroundTransferSnapshot?) async throws -> Result
+ ) async throws -> Result {
+ throw RadrootsBackgroundTransferError.unavailable
+ }
+}
+
public protocol RadrootsBackgroundTransferFileResolver: Sendable {
func resolve(_ file: RadrootsBackgroundTransferLocalFile) throws -> URL
func read(_ file: RadrootsBackgroundTransferLocalFile, maximumBytes: Int) throws -> Data
@@ -155,12 +178,14 @@ public struct RadrootsUnavailableBackgroundTransfer: RadrootsBackgroundTransfer,
public init() {}
public func enqueue(_: RadrootsBackgroundTransferRequest) async throws
- -> RadrootsBackgroundTransferHandle {
+ -> RadrootsBackgroundTransferHandle
+ {
throw RadrootsBackgroundTransferError.unavailable
}
public func retry(_: RadrootsBackgroundTransferRequest) async throws
- -> RadrootsBackgroundTransferHandle {
+ -> RadrootsBackgroundTransferHandle
+ {
throw RadrootsBackgroundTransferError.unavailable
}
@@ -180,7 +205,8 @@ public struct RadrootsUnavailableBackgroundTransfer: RadrootsBackgroundTransfer,
}
public func snapshot(for _: RadrootsBackgroundTransferIdentifier) async throws
- -> RadrootsBackgroundTransferSnapshot? {
+ -> RadrootsBackgroundTransferSnapshot?
+ {
throw RadrootsBackgroundTransferError.unavailable
}
diff --git a/Sources/RadrootsKitTesting/RadrootsBackgroundTransferTesting.swift b/Sources/RadrootsKitTesting/RadrootsBackgroundTransferTesting.swift
@@ -4,10 +4,10 @@ import RadrootsKit
public actor RadrootsInMemoryBackgroundTransferStore: RadrootsBackgroundTransferStore {
private var admissions: Set<RadrootsBackgroundTransferIdentifier> = []
- public func withAdmission(
+ public func withAdmission<Result: Sendable>(
for identifier: RadrootsBackgroundTransferIdentifier,
- operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle
- ) async throws -> RadrootsBackgroundTransferHandle {
+ operation: @escaping @Sendable () async throws -> Result
+ ) async throws -> Result {
guard admissions.insert(identifier).inserted else { throw RadrootsBackgroundTransferError.invalidRequest }
defer { admissions.remove(identifier) }
return try await operation()
@@ -79,7 +79,8 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer {
}
public func enqueue(_ request: RadrootsBackgroundTransferRequest) async throws
- -> RadrootsBackgroundTransferHandle {
+ -> RadrootsBackgroundTransferHandle
+ {
enqueuedRequestsValue.append(request)
switch enqueueOutcome {
case .success:
@@ -96,7 +97,8 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer {
}
public func retry(_ request: RadrootsBackgroundTransferRequest) async throws
- -> RadrootsBackgroundTransferHandle {
+ -> RadrootsBackgroundTransferHandle
+ {
try await enqueue(request)
}
@@ -197,7 +199,8 @@ public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer {
}
public func snapshot(for identifier: RadrootsBackgroundTransferIdentifier) async throws
- -> RadrootsBackgroundTransferSnapshot? {
+ -> RadrootsBackgroundTransferSnapshot?
+ {
try await store.loadSnapshots().first { $0.identifier == identifier }
}
diff --git a/Tests/RadrootsKitTests/RadrootsAppleErrorSafetyTests.swift b/Tests/RadrootsKitTests/RadrootsAppleErrorSafetyTests.swift
@@ -8,14 +8,14 @@ import Testing
let forbidden = [
"/Users/example/private.sqlite",
"https://secret.example.invalid/token",
- "nsec1secretcanary"
+ "nsec1secretcanary",
]
for error in errors {
let renderings = [
String(describing: error),
String(reflecting: error),
- (error as NSError).localizedDescription
+ (error as NSError).localizedDescription,
]
#expect(renderings.allSatisfy { !$0.isEmpty })
#expect(
@@ -32,7 +32,7 @@ import Testing
code: 7,
userInfo: [
NSLocalizedDescriptionKey:
- "https://secret.example.invalid/token nsec1secretcanary"
+ "https://secret.example.invalid/token nsec1secretcanary",
]
)
@@ -93,10 +93,10 @@ private func dependencyCanaryError() -> NSError {
}
private actor ThrowingBackgroundTransferStore: RadrootsBackgroundTransferStore {
- func withAdmission(
+ func withAdmission<Result: Sendable>(
for _: RadrootsBackgroundTransferIdentifier,
- operation _: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle
- ) async throws -> RadrootsBackgroundTransferHandle {
+ operation _: @escaping @Sendable () async throws -> Result
+ ) async throws -> Result {
throw dependencyCanaryError()
}
@@ -194,7 +194,7 @@ private let publicAppleErrorExamples: [any Error] = [
RadrootsVerifiedArtifactAccessError.protectedDataUnavailable,
RadrootsVerifiedArtifactAccessError.artifactUnavailable,
RadrootsVerifiedArtifactAccessError.artifactCorrupt,
- RadrootsVerifiedArtifactAccessError.fileSystemFailure
+ RadrootsVerifiedArtifactAccessError.fileSystemFailure,
]
private func verifyBackgroundTaskErrorMapping() async throws {
diff --git a/Tests/RadrootsKitTests/RadrootsInactiveExecutionTests.swift b/Tests/RadrootsKitTests/RadrootsInactiveExecutionTests.swift
@@ -0,0 +1,145 @@
+import Foundation
+@testable import RadrootsKit
+import RadrootsKitTesting
+import Testing
+
+@Test func inactiveExecutionRefusesActiveTerminalSnapshots() async throws {
+ let request = try appleUploadRequest(identifier: "renewal.active")
+ for state: RadrootsBackgroundTransferState in [.failed, .expired, .cancelled, .interrupted] {
+ let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: state, executionID: UUID())
+ let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot])
+ let probe = RadrootsAppleBackgroundTransferProbe(activeIdentifiers: [request.identifier])
+ let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters())
+ let called = RadrootsCompletionProbe()
+ await #expect(throws: RadrootsBackgroundTransferError.transferFailure) {
+ try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() }
+ }
+ #expect(!called.completed)
+ #expect(try await store.loadSnapshots() == [snapshot])
+ #expect(await probe.enqueuedRequests.isEmpty)
+ #expect(await probe.cancelledIdentifiers.isEmpty)
+ #expect(try await !store.admissionIsActive(for: request.identifier))
+ }
+}
+
+@Test func inactiveExecutionRefusesUnreconciledOrReceiptedSnapshots() async throws {
+ let request = try appleUploadRequest(identifier: "renewal.receipt")
+ for state: RadrootsBackgroundTransferState in [.queued, .running, .awaitingVerification, .completed] {
+ let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: state)
+ let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot])
+ let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: RadrootsAppleBackgroundTransferProbe().adapters())
+ let called = RadrootsCompletionProbe()
+ await #expect(throws: RadrootsBackgroundTransferError.transferFailure) {
+ try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() }
+ }
+ #expect(!called.completed)
+ #expect(try await store.loadSnapshots() == [snapshot])
+ }
+}
+
+@Test func inactiveExecutionUnknownInventoryPreservesEvidence() async throws {
+ let request = try appleUploadRequest(identifier: "renewal.unknown")
+ let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .expired)
+ let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot])
+ let called = RadrootsCompletionProbe()
+ let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: .unavailable)
+ await #expect(throws: RadrootsBackgroundTransferError.unavailable) {
+ try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() }
+ }
+ #expect(!called.completed)
+ #expect(try await store.loadSnapshots() == [snapshot])
+ #expect(try await !store.admissionIsActive(for: request.identifier))
+}
+
+@Test func inactiveExecutionSuppliesExactRedactedSnapshotOrAbsenceThroughExistential() async throws {
+ let request = try appleUploadRequest(identifier: "renewal.inactive")
+ let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .expired, executionID: UUID())
+ for snapshots in [[], [snapshot]] {
+ let store = RadrootsInMemoryBackgroundTransferStore(snapshots: snapshots)
+ let probe = RadrootsAppleBackgroundTransferProbe()
+ let transfer: any RadrootsBackgroundTransfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters())
+ let result = try await transfer.withInactiveExecution(for: request.identifier) { observed in
+ #expect(observed == snapshots.first)
+ let reserved = try await store.admissionIsActive(for: request.identifier)
+ #expect(reserved)
+ return "caller-result"
+ }
+ #expect(result == "caller-result")
+ #expect(try await store.loadSnapshots() == snapshots)
+ #expect(await probe.enqueuedRequests.isEmpty)
+ #expect(await probe.cancelledIdentifiers.isEmpty)
+ #expect(try await !store.admissionIsActive(for: request.identifier))
+ }
+}
+
+@Test func inactiveExecutionFencesIndependentOwnersAndRetainsLateReceipt() async throws {
+ let roots = try appleTransferRoots()
+ defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) }
+ let request = try appleUploadRequest(identifier: "renewal.exclusive")
+ let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .interrupted, executionID: UUID())
+ let late = try snapshot.transitioned(to: .awaitingVerification, at: Date())
+ let firstStore = RadrootsAppleBackgroundTransferStore(roots: roots)
+ let otherStore = RadrootsAppleBackgroundTransferStore(roots: roots)
+ try await firstStore.saveSnapshot(snapshot)
+ let probe = RadrootsAppleBackgroundTransferProbe()
+ let first = RadrootsAppleBackgroundTransfer(store: firstStore, adapters: probe.adapters())
+ let other = RadrootsAppleBackgroundTransfer(store: otherStore, adapters: probe.adapters())
+ let result = try await first.withInactiveExecution(for: request.identifier) { observed in
+ #expect(observed == snapshot)
+ await #expect(throws: RadrootsBackgroundTransferError.invalidRequest) { _ = try await other.retry(request) }
+ await #expect(throws: RadrootsBackgroundTransferError.invalidRequest) {
+ try await other.withInactiveExecution(for: request.identifier) { _ in Issue.record("Competing renewal admitted") }
+ }
+ await #expect(throws: RadrootsBackgroundTransferError.invalidRequest) { _ = try await first.retry(request) }
+ // Delegate persistence remains available during the long reservation.
+ let retained = try await otherStore.compareExchangeSnapshot(expected: snapshot, desired: late)
+ #expect(retained)
+ return 42
+ }
+ #expect(result == 42)
+ #expect(try await otherStore.loadSnapshots() == [late])
+ #expect(try await !otherStore.admissionIsActive(for: request.identifier))
+ #expect(await probe.enqueuedRequests.isEmpty)
+ await #expect(throws: RadrootsBackgroundTransferError.transferFailure) {
+ try await other.withInactiveExecution(for: request.identifier) { _ in Issue.record("Receipt discarded") }
+ }
+}
+
+@Test func inactiveExecutionCallerDenialReleasesAdmissionAndPreservesError() async throws {
+ let request = try appleUploadRequest(identifier: "renewal.denied")
+ let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .expired)
+ let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot])
+ let probe = RadrootsAppleBackgroundTransferProbe()
+ let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters())
+ await #expect(throws: RenewalTestDenial.denied) {
+ try await transfer.withInactiveExecution(for: request.identifier) { _ in throw RenewalTestDenial.denied }
+ }
+ #expect(try await store.loadSnapshots() == [snapshot])
+ #expect(try await !store.admissionIsActive(for: request.identifier))
+ _ = try await transfer.retry(request)
+ #expect(await probe.enqueuedRequests == [request])
+}
+
+@Test func inactiveExecutionCancellationDuringQueryNeverCallsBody() async throws {
+ let request = try appleUploadRequest(identifier: "renewal.cancelled-query")
+ let store = RadrootsInMemoryBackgroundTransferStore()
+ let called = RadrootsCompletionProbe()
+ let (entered, signal) = AsyncStream<Void>.makeStream()
+ let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: RadrootsAppleBackgroundTransferAdapters(
+ enqueue: { _, _ in Issue.record("Unexpected enqueue") }, cancel: { _ in Issue.record("Unexpected cancel") },
+ activeTransferIdentifiers: {
+ try await RadrootsBackgroundTaskQuery.read { _ in signal.yield(); signal.finish() }
+ }, handleBackgroundEvents: { _, completion in completion() }
+ ))
+ let task = Task { try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() } }
+ for await _ in entered {
+ break
+ }
+ task.cancel()
+ await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { try await task.value }
+ #expect(!called.completed)
+ #expect(try await !store.admissionIsActive(for: request.identifier))
+ #expect(try await store.loadSnapshots().isEmpty)
+}
+
+private enum RenewalTestDenial: Error, Equatable { case denied }
diff --git a/Tests/RadrootsKitTests/RadrootsReceiptBarrierTests.swift b/Tests/RadrootsKitTests/RadrootsReceiptBarrierTests.swift
@@ -116,7 +116,8 @@ private actor ReceiptBarrierStore: RadrootsBackgroundTransferStore {
}
func compareExchangeSnapshot(expected: RadrootsBackgroundTransferSnapshot?,
- desired: RadrootsBackgroundTransferSnapshot) async throws -> Bool {
+ desired: RadrootsBackgroundTransferSnapshot) async throws -> Bool
+ {
if desired.state == .awaitingVerification {
terminalAttempts += 1
if terminalAttempts == 1 {
@@ -142,9 +143,10 @@ private actor ReceiptBarrierStore: RadrootsBackgroundTransferStore {
return exchanged
}
- func withAdmission(for identifier: RadrootsBackgroundTransferIdentifier,
- operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle)
- async throws -> RadrootsBackgroundTransferHandle {
+ func withAdmission<Result: Sendable>(for identifier: RadrootsBackgroundTransferIdentifier,
+ operation: @escaping @Sendable () async throws -> Result)
+ async throws -> Result
+ {
try await storage.withAdmission(for: identifier, operation: operation)
}
diff --git a/contracts/api_baselines/apple_kit.txt b/contracts/api_baselines/apple_kit.txt
@@ -742,9 +742,10 @@ radroots.apple-kit.public-api.v1
12:relationship 11:RadrootsKit 13:requirementOf 109:s:11RadrootsKit0A30BackgroundTransferFileResolverP4read_12maximumBytes10Foundation4DataVAA0acd5LocalE0O_SitKF 50:s:11RadrootsKit0A30BackgroundTransferFileResolverP 0:
12:relationship 11:RadrootsKit 13:requirementOf 113:s:11RadrootsKit0A18BackgroundTransferP015handleEventsForC10URLSession10identifier17completionHandlerySS_yyYbctYaF 38:s:11RadrootsKit0A18BackgroundTransferP 0:
12:relationship 11:RadrootsKit 13:requirementOf 116:s:11RadrootsKit0A10FileAccessP9stageBlob_9mediaType12filenameHintAA0a6StagedF9ReferenceV10Foundation4DataV_SSSgALtKF 30:s:11RadrootsKit0A10FileAccessP 0:
-12:relationship 11:RadrootsKit 13:requirementOf 120:s:11RadrootsKit0A23BackgroundTransferStoreP13withAdmission3for9operationAA0acD6HandleVAA0acD10IdentifierV_AHyYaYbKctYaKF 43:s:11RadrootsKit0A23BackgroundTransferStoreP 0:
12:relationship 11:RadrootsKit 13:requirementOf 126:s:11RadrootsKit0A10FileAccessP013stageExternalC0_9mediaType12filenameHintAA0A19StagedBlobReferenceV10Foundation3URLV_SSSgALtKF 30:s:11RadrootsKit0A10FileAccessP 0:
+12:relationship 11:RadrootsKit 13:requirementOf 127:s:11RadrootsKit0A23BackgroundTransferStoreP13withAdmission3for9operationqd__AA0acD10IdentifierV_qd__yYaYbKctYaKs8SendableRd__lF 43:s:11RadrootsKit0A23BackgroundTransferStoreP 0:
12:relationship 11:RadrootsKit 13:requirementOf 146:s:11RadrootsKit0A10FileAccessP012copyExternalC0_2to9mediaType17suggestedFilenameAA0A16ImportedDocumentV10Foundation3URLV_AA0aC9ReferenceVSSSgAOtKF 30:s:11RadrootsKit0A10FileAccessP 0:
+12:relationship 11:RadrootsKit 13:requirementOf 147:s:11RadrootsKit0A18BackgroundTransferP21withInactiveExecution3for9operationqd__AA0acD10IdentifierV_qd__AA0acD8SnapshotVSgYaYbKctYaKs8SendableRd__lF 38:s:11RadrootsKit0A18BackgroundTransferP 0:
12:relationship 11:RadrootsKit 13:requirementOf 171:s:11RadrootsKit0A30BackgroundTransferFileResolverP18prepareUploadLease3for11executionID8existingAA0a10StagedBlobI0VAA0acD7RequestV_10Foundation4UUIDVAA0anO9ReferenceVSgtKF 50:s:11RadrootsKit0A30BackgroundTransferFileResolverP 0:
12:relationship 11:RadrootsKit 13:requirementOf 52:s:11RadrootsKit0A10FileAccessP16resetStagedBlobsyyKF 30:s:11RadrootsKit0A10FileAccessP 0:
12:relationship 11:RadrootsKit 13:requirementOf 52:s:11RadrootsKit0A9TelemetryP6recordyyAA0aC5EventVYaF 28:s:11RadrootsKit0A9TelemetryP 0:
@@ -796,6 +797,7 @@ radroots.apple-kit.public-api.v1
12:relationship 11:RadrootsKit 13:requirementOf 94:s:11RadrootsKit0A18BackgroundTransferP8snapshot3forAA0acD8SnapshotVSgAA0acD10IdentifierV_tYaKF 38:s:11RadrootsKit0A18BackgroundTransferP 0:
12:relationship 11:RadrootsKit 13:requirementOf 98:s:11RadrootsKit0A24PermissionStatusProviderP9snapshots3forSayAA0aC8SnapshotVGSayAA0aC4KindOG_tYaKF 44:s:11RadrootsKit0A24PermissionStatusProviderP 0:
12:relationship 11:RadrootsKit 23:defaultImplementationOf 101:s:11RadrootsKit0A24PermissionStatusProviderPAAE9snapshots3forSayAA0aC8SnapshotVGSayAA0aC4KindOG_tYaKF 98:s:11RadrootsKit0A24PermissionStatusProviderP9snapshots3forSayAA0aC8SnapshotVGSayAA0aC4KindOG_tYaKF 0:
+12:relationship 11:RadrootsKit 23:defaultImplementationOf 150:s:11RadrootsKit0A18BackgroundTransferPAAE21withInactiveExecution3for9operationqd__AA0acD10IdentifierV_qd__AA0acD8SnapshotVSgYaYbKctYaKs8SendableRd__lF 147:s:11RadrootsKit0A18BackgroundTransferP21withInactiveExecution3for9operationqd__AA0acD10IdentifierV_qd__AA0acD8SnapshotVSgYaYbKctYaKs8SendableRd__lF 0:
12:relationship 11:RadrootsKit 8:memberOf 100:s:11RadrootsKit0A15LocationReadingV7isFresh10relativeTo17maximumAgeSecondsSb10Foundation4DateV_SdtKF 35:s:11RadrootsKit0A15LocationReadingV 0:
12:relationship 11:RadrootsKit 8:memberOf 100:s:11RadrootsKit0A26PreparedExportFileDocumentV13configurationAC7SwiftUI0eF17ReadConfigurationV_tKcfc 46:s:11RadrootsKit0A26PreparedExportFileDocumentV 0:
12:relationship 11:RadrootsKit 8:memberOf 101:s:11RadrootsKit0A18AppleMediaPreparerC12prepareImageyAA0ac8PreparedG0VAA0acG18PreparationRequestVYaKF 38:s:11RadrootsKit0A18AppleMediaPreparerC 0:
@@ -857,12 +859,12 @@ radroots.apple-kit.public-api.v1
12:relationship 11:RadrootsKit 8:memberOf 123:s:11RadrootsKit0A9FileEntryV4file4name11isDirectory9sizeBytes10modifiedAtAcA0aC9ReferenceV_SSSbSiSg10Foundation4DateVSgtcfc 28:s:11RadrootsKit0A9FileEntryV 0:
12:relationship 11:RadrootsKit 8:memberOf 124:s:11RadrootsKit0A21ExportDocumentRequestV6source17suggestedFilename9mediaType9sizeBytesAcA0acD6SourceO_S2SSgs6UInt64VSgtKcfc 41:s:11RadrootsKit0A21ExportDocumentRequestV 0:
12:relationship 11:RadrootsKit 8:memberOf 124:s:11RadrootsKit0A25AppleUserPresenceAdaptersV13currentStatus6verifyAcA0adeH0VyYaYbKc_AA0adE6ResultVAA0adE7RequestVYaYbKctcfc 45:s:11RadrootsKit0A25AppleUserPresenceAdaptersV 0:
+12:relationship 11:RadrootsKit 8:memberOf 124:s:11RadrootsKit0A28AppleBackgroundTransferStoreC13withAdmission3for9operationxAA0adE10IdentifierV_xyYaYbKctYaKs8SendableRzlF 48:s:11RadrootsKit0A28AppleBackgroundTransferStoreC 0:
12:relationship 11:RadrootsKit 8:memberOf 124:s:11RadrootsKit0A29UnavailableBackgroundTransferV015handleEventsForD10URLSession10identifier17completionHandlerySS_yyYbctYaF 49:s:11RadrootsKit0A29UnavailableBackgroundTransferV 0:
12:relationship 11:RadrootsKit 8:memberOf 125:s:11RadrootsKit0A16IdentitySnapshotV5state8identity12signerHandle12recoveryCodeAcA0aC5StateO_AA0aC12PublicRecordVSgSSSgAMtcfc 36:s:11RadrootsKit0A16IdentitySnapshotV 0:
12:relationship 11:RadrootsKit 8:memberOf 125:s:11RadrootsKit0A18MediaImportRequestV07allowedC5Kinds14selectionLimit16destinationScopeACSayAA0aC4KindOG_SiAA0a4FileK0OtKcfc 38:s:11RadrootsKit0A18MediaImportRequestV 0:
12:relationship 11:RadrootsKit 8:memberOf 125:s:11RadrootsKit0A21CurrentLocationResultV7reading13authorization15servedFromCacheAcA0aD7ReadingV_AA0aD13AuthorizationOSbtKcfc 41:s:11RadrootsKit0A21CurrentLocationResultV 0:
12:relationship 11:RadrootsKit 8:memberOf 125:s:11RadrootsKit0A22CurrentLocationRequestV14timeoutSeconds21desiredAccuracyMeters023maximumCachedReadingAgeG0ACSd_SdSgAGtKcfc 42:s:11RadrootsKit0A22CurrentLocationRequestV 0:
-12:relationship 11:RadrootsKit 8:memberOf 125:s:11RadrootsKit0A28AppleBackgroundTransferStoreC13withAdmission3for9operationAA0adE6HandleVAA0adE10IdentifierV_AHyYaYbKctYaKF 48:s:11RadrootsKit0A28AppleBackgroundTransferStoreC 0:
12:relationship 11:RadrootsKit 8:memberOf 126:s:11RadrootsKit0A24ExternalActionValidationO12appleMapsURL10coordinate5label10Foundation0H0VAA0A18LocationCoordinateV_SSSgtKFZ 44:s:11RadrootsKit0A24ExternalActionValidationO 0:
12:relationship 11:RadrootsKit 8:memberOf 126:s:11RadrootsKit0A26BackgroundTransferResponseV10statusCode9mediaType15contentEncoding4bodyACSi_SSSgAH10Foundation4DataVSgtKcfc 46:s:11RadrootsKit0A26BackgroundTransferResponseV 0:
12:relationship 11:RadrootsKit 8:memberOf 126:s:11RadrootsKit0A27DocumentPresentationAdapterO12contentTypes3forSay22UniformTypeIdentifiers6UTTypeVGAA0aC13ImportRequestV_tFZ 47:s:11RadrootsKit0A27DocumentPresentationAdapterO 0:
@@ -882,6 +884,7 @@ radroots.apple-kit.public-api.v1
12:relationship 11:RadrootsKit 8:memberOf 143:s:11RadrootsKit0A21DocumentImportRequestV19allowedContentKinds23allowsMultipleSelection16destinationScopeACSayAA0acG4KindOG_SbAA0a4FileM0OtKcfc 41:s:11RadrootsKit0A21DocumentImportRequestV 0:
12:relationship 11:RadrootsKit 8:memberOf 143:s:11RadrootsKit0A22PreparedExportDocumentV10preparedID7fileURL17suggestedFilename9mediaType9sizeBytesACSS_10Foundation0I0VS2SSgs6UInt64VSgtKcfc 42:s:11RadrootsKit0A22PreparedExportDocumentV 0:
12:relationship 11:RadrootsKit 8:memberOf 144:s:11RadrootsKit0A18UserPresenceStatusV7support12biometryKind27canEvaluateDeviceCredential0iJ10BiometricsAcA0acD7SupportO_AA0a8BiometryH0OS2btcfc 38:s:11RadrootsKit0A18UserPresenceStatusV 0:
+12:relationship 11:RadrootsKit 8:memberOf 144:s:11RadrootsKit0A23AppleBackgroundTransferC21withInactiveExecution3for9operationxAA0adE10IdentifierV_xAA0adE8SnapshotVSgYaYbKctYaKs8SendableRzlF 43:s:11RadrootsKit0A23AppleBackgroundTransferC 0:
12:relationship 11:RadrootsKit 8:memberOf 151:s:11RadrootsKit0A15AppleFileAccessC012copyExternalD0_2to9mediaType17suggestedFilenameAA0A16ImportedDocumentV10Foundation3URLV_AA0aD9ReferenceVSSSgAOtKF 35:s:11RadrootsKit0A15AppleFileAccessC 0:
12:relationship 11:RadrootsKit 8:memberOf 152:s:11RadrootsKit0A15IdentityCustodyC014importPortableC0_10passphrase15replaceExistingAA0aC8SnapshotVAA0aC19PortabilityEnvelopeV_AA0aC10PassphraseVSbtYaKF 35:s:11RadrootsKit0A15IdentityCustodyC 0:
12:relationship 11:RadrootsKit 8:memberOf 152:s:11RadrootsKit0A16ImportedDocumentV4file11originalURL17suggestedFilename9mediaType9sizeBytesAcA0A13FileReferenceV_10Foundation0G0VSgS2SSgs6UInt64VtKcfc 36:s:11RadrootsKit0A16ImportedDocumentV 0:
@@ -1800,7 +1803,7 @@ radroots.apple-kit.public-api.v1
12:relationship 18:RadrootsKitTesting 8:memberOf 124:s:18RadrootsKitTesting0A22FakeBackgroundTransferC015handleEventsForE10URLSession10identifier17completionHandlerySS_yyYbctYaF 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0:
12:relationship 18:RadrootsKitTesting 8:memberOf 128:s:18RadrootsKitTesting0A25UITestLaunchConfigurationV13deterministic11environment9arguments8language6localeACSDyS2SG_SaySSGS2StFZ 52:s:18RadrootsKitTesting0A25UITestLaunchConfigurationV 0:
12:relationship 18:RadrootsKitTesting 8:memberOf 128:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC03setG0_3for10observedAty0aB00afG0O_AG0aF4KindO10Foundation4DateVSgtF 59:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC 0:
-12:relationship 18:RadrootsKitTesting 8:memberOf 137:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC13withAdmission3for9operation0aB00afG6HandleVAG0afG10IdentifierV_AIyYaYbKctYaKF 58:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC 0:
+12:relationship 18:RadrootsKitTesting 8:memberOf 136:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC13withAdmission3for9operationx0aB00afG10IdentifierV_xyYaYbKctYaKs8SendableRzlF 58:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC 0:
12:relationship 18:RadrootsKitTesting 8:memberOf 142:s:18RadrootsKitTesting0A19FakeDocumentScannerC7support11scanOutcomeAC0aB00aeF7SupportV_s6ResultOyAF0a7ScannedE0VAF0A18CaptureIntakeErrorOGtcfc 46:s:18RadrootsKitTesting0A19FakeDocumentScannerC 0:
12:relationship 18:RadrootsKitTesting 8:memberOf 143:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC8statuses07defaultG010observedAtACSDy0aB00aF4KindOAG0afG0OG_AK10Foundation4DateVtcfc 59:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC 0:
12:relationship 18:RadrootsKitTesting 8:memberOf 148:s:18RadrootsKitTesting0A22FakeBackgroundTransferC5store14enqueueOutcome9updatedAtAC0aB00aeF5Store_p_s6ResultOyytAG0aeF5ErrorOG10Foundation4DateVtcfc 49:s:18RadrootsKitTesting0A22FakeBackgroundTransferC 0:
@@ -2144,14 +2147,17 @@ radroots.apple-kit.public-api.v1
6:symbol 11:RadrootsKit 12:swift.method 118:s:11RadrootsKit0A28AppleBackgroundTransferStoreC25collectInactiveAdmissions5limitAA0A22AdmissionCleanupResultVSi_tYaKF 33:collectInactiveAdmissions(limit:) 94:func collectInactiveAdmissions(limit: Int = 64) async throws -> RadrootsAdmissionCleanupResult
6:symbol 11:RadrootsKit 12:swift.method 118:s:11RadrootsKit0A29UnavailableBackgroundTransferV6settle_12verificationyAA0adE10IdentifierV_AA0adE12VerificationOtYaKF 23:settle(_:verification:) 121:func settle(_: RadrootsBackgroundTransferIdentifier, verification _: RadrootsBackgroundTransferVerification) async throws
6:symbol 11:RadrootsKit 12:swift.method 119:s:11RadrootsKit0A24AppleKeychainSecureStoreC3put_3for6policyy10Foundation4DataV_AA0aeF3KeyVAA0A18SecretAccessPolicyVtKF 18:put(_:for:policy:) 120:func put(_ value: Data, for key: RadrootsSecureStoreKey, policy: RadrootsSecretAccessPolicy = .secureLocalSecret) throws
-6:symbol 11:RadrootsKit 12:swift.method 120:s:11RadrootsKit0A23BackgroundTransferStoreP13withAdmission3for9operationAA0acD6HandleVAA0acD10IdentifierV_AHyYaYbKctYaKF 29:withAdmission(for:operation:) 205:func withAdmission(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle) async throws -> RadrootsBackgroundTransferHandle
6:symbol 11:RadrootsKit 12:swift.method 121:s:11RadrootsKit0A15AppleFileAccessC9stageBlob_9mediaType12filenameHintAA0a6StagedG9ReferenceV10Foundation4DataV_SSSgALtKF 36:stageBlob(_:mediaType:filenameHint:) 140:@discardableResult func stageBlob(_ data: Data, mediaType: String? = nil, filenameHint: String? = nil) throws -> RadrootsStagedBlobReference
6:symbol 11:RadrootsKit 12:swift.method 123:s:11RadrootsKit0A26PreparedExportFileDocumentV11fileWrapper13configurationSo06NSFileH0C7SwiftUI0eF18WriteConfigurationV_tKF 27:fileWrapper(configuration:) 110:func fileWrapper(configuration _: RadrootsPreparedExportFileDocument.WriteConfiguration) throws -> FileWrapper
+6:symbol 11:RadrootsKit 12:swift.method 124:s:11RadrootsKit0A28AppleBackgroundTransferStoreC13withAdmission3for9operationxAA0adE10IdentifierV_xyYaYbKctYaKs8SendableRzlF 29:withAdmission(for:operation:) 185:func withAdmission<Result>(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable () async throws -> Result) async throws -> Result where Result : Sendable
6:symbol 11:RadrootsKit 12:swift.method 124:s:11RadrootsKit0A29UnavailableBackgroundTransferV015handleEventsForD10URLSession10identifier17completionHandlerySS_yyYbctYaF 66:handleEventsForBackgroundURLSession(identifier:completionHandler:) 119:func handleEventsForBackgroundURLSession(identifier _: String, completionHandler: @escaping @Sendable () -> Void) async
-6:symbol 11:RadrootsKit 12:swift.method 125:s:11RadrootsKit0A28AppleBackgroundTransferStoreC13withAdmission3for9operationAA0adE6HandleVAA0adE10IdentifierV_AHyYaYbKctYaKF 29:withAdmission(for:operation:) 205:func withAdmission(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle) async throws -> RadrootsBackgroundTransferHandle
6:symbol 11:RadrootsKit 12:swift.method 126:s:11RadrootsKit0A10FileAccessP013stageExternalC0_9mediaType12filenameHintAA0A19StagedBlobReferenceV10Foundation3URLV_SSSgALtKF 44:stageExternalFile(_:mediaType:filenameHint:) 140:@discardableResult func stageExternalFile(_ sourceURL: URL, mediaType: String?, filenameHint: String?) throws -> RadrootsStagedBlobReference
+6:symbol 11:RadrootsKit 12:swift.method 127:s:11RadrootsKit0A23BackgroundTransferStoreP13withAdmission3for9operationqd__AA0acD10IdentifierV_qd__yYaYbKctYaKs8SendableRd__lF 29:withAdmission(for:operation:) 185:func withAdmission<Result>(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable () async throws -> Result) async throws -> Result where Result : Sendable
6:symbol 11:RadrootsKit 12:swift.method 131:s:11RadrootsKit0A15AppleFileAccessC013stageExternalD0_9mediaType12filenameHintAA0A19StagedBlobReferenceV10Foundation3URLV_SSSgALtKF 44:stageExternalFile(_:mediaType:filenameHint:) 152:@discardableResult func stageExternalFile(_ sourceURL: URL, mediaType: String? = nil, filenameHint: String? = nil) throws -> RadrootsStagedBlobReference
+6:symbol 11:RadrootsKit 12:swift.method 144:s:11RadrootsKit0A23AppleBackgroundTransferC21withInactiveExecution3for9operationxAA0adE10IdentifierV_xAA0adE8SnapshotVSgYaYbKctYaKs8SendableRzlF 37:withInactiveExecution(for:operation:) 228:func withInactiveExecution<Result>(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable (RadrootsBackgroundTransferSnapshot?) async throws -> Result) async throws -> Result where Result : Sendable
6:symbol 11:RadrootsKit 12:swift.method 146:s:11RadrootsKit0A10FileAccessP012copyExternalC0_2to9mediaType17suggestedFilenameAA0A16ImportedDocumentV10Foundation3URLV_AA0aC9ReferenceVSSSgAOtKF 51:copyExternalFile(_:to:mediaType:suggestedFilename:) 173:@discardableResult func copyExternalFile(_ sourceURL: URL, to file: RadrootsFileReference, mediaType: String?, suggestedFilename: String?) throws -> RadrootsImportedDocument
+6:symbol 11:RadrootsKit 12:swift.method 147:s:11RadrootsKit0A18BackgroundTransferP21withInactiveExecution3for9operationqd__AA0acD10IdentifierV_qd__AA0acD8SnapshotVSgYaYbKctYaKs8SendableRd__lF 37:withInactiveExecution(for:operation:) 228:func withInactiveExecution<Result>(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable (RadrootsBackgroundTransferSnapshot?) async throws -> Result) async throws -> Result where Result : Sendable
+6:symbol 11:RadrootsKit 12:swift.method 150:s:11RadrootsKit0A18BackgroundTransferPAAE21withInactiveExecution3for9operationqd__AA0acD10IdentifierV_qd__AA0acD8SnapshotVSgYaYbKctYaKs8SendableRd__lF 37:withInactiveExecution(for:operation:) 221:func withInactiveExecution<Result>(for _: RadrootsBackgroundTransferIdentifier, operation _: @escaping @Sendable (RadrootsBackgroundTransferSnapshot?) async throws -> Result) async throws -> Result where Result : Sendable
6:symbol 11:RadrootsKit 12:swift.method 151:s:11RadrootsKit0A15AppleFileAccessC012copyExternalD0_2to9mediaType17suggestedFilenameAA0A16ImportedDocumentV10Foundation3URLV_AA0aD9ReferenceVSSSgAOtKF 51:copyExternalFile(_:to:mediaType:suggestedFilename:) 185:@discardableResult func copyExternalFile(_ sourceURL: URL, to file: RadrootsFileReference, mediaType: String? = nil, suggestedFilename: String? = nil) throws -> RadrootsImportedDocument
6:symbol 11:RadrootsKit 12:swift.method 152:s:11RadrootsKit0A15IdentityCustodyC014importPortableC0_10passphrase15replaceExistingAA0aC8SnapshotVAA0aC19PortabilityEnvelopeV_AA0aC10PassphraseVSbtYaKF 53:importPortableIdentity(_:passphrase:replaceExisting:) 207:@discardableResult func importPortableIdentity(_ envelope: RadrootsIdentityPortabilityEnvelope, passphrase: RadrootsIdentityPassphrase, replaceExisting: Bool = false) async throws -> RadrootsIdentitySnapshot
6:symbol 11:RadrootsKit 12:swift.method 154:s:7SwiftUI4ViewP11RadrootsKitE24radrootsDocumentExporter14preparedExport12onCompletionQrAA7BindingVyAD0d8PreparedjG0VSgG_ys6ResultOyAD0djgO0Vs5Error_pGctF 54:radrootsDocumentExporter(preparedExport:onCompletion:) 195:@MainActor func radrootsDocumentExporter(preparedExport: Binding<RadrootsPreparedExportDocument?>, onCompletion: @escaping (Result<RadrootsExportDocumentResult, any Error>) -> Void) -> some View\n
@@ -3105,7 +3111,7 @@ radroots.apple-kit.public-api.v1
6:symbol 18:RadrootsKitTesting 12:swift.method 123:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC23compareExchangeSnapshot8expected7desiredSb0aB00afgK0VSg_AItYaKF 42:compareExchangeSnapshot(expected:desired:) 141:func compareExchangeSnapshot(expected: RadrootsBackgroundTransferSnapshot?, desired: RadrootsBackgroundTransferSnapshot) async throws -> Bool
6:symbol 18:RadrootsKitTesting 12:swift.method 124:s:18RadrootsKitTesting0A22FakeBackgroundTransferC015handleEventsForE10URLSession10identifier17completionHandlerySS_yyYbctYaF 66:handleEventsForBackgroundURLSession(identifier:completionHandler:) 117:func handleEventsForBackgroundURLSession(identifier: String, completionHandler: @escaping @Sendable () -> Void) async
6:symbol 18:RadrootsKitTesting 12:swift.method 128:s:18RadrootsKitTesting0A32InMemoryPermissionStatusProviderC03setG0_3for10observedAty0aB00afG0O_AG0aF4KindO10Foundation4DateVSgtF 28:setStatus(_:for:observedAt:) 109:func setStatus(_ status: RadrootsPermissionStatus, for kind: RadrootsPermissionKind, observedAt: Date? = nil)
-6:symbol 18:RadrootsKitTesting 12:swift.method 137:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC13withAdmission3for9operation0aB00afG6HandleVAG0afG10IdentifierV_AIyYaYbKctYaKF 29:withAdmission(for:operation:) 205:func withAdmission(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable () async throws -> RadrootsBackgroundTransferHandle) async throws -> RadrootsBackgroundTransferHandle
+6:symbol 18:RadrootsKitTesting 12:swift.method 136:s:18RadrootsKitTesting0A31InMemoryBackgroundTransferStoreC13withAdmission3for9operationx0aB00afG10IdentifierV_xyYaYbKctYaKs8SendableRzlF 29:withAdmission(for:operation:) 185:func withAdmission<Result>(for identifier: RadrootsBackgroundTransferIdentifier, operation: @escaping @Sendable () async throws -> Result) async throws -> Result where Result : Sendable
6:symbol 18:RadrootsKitTesting 12:swift.method 54:s:18RadrootsKitTesting0A18RecordingTelemetryC5resetyyF 7:reset() 12:func reset()
6:symbol 18:RadrootsKitTesting 12:swift.method 67:s:18RadrootsKitTesting0A16FakeUserPresenceC9setStatusyy0aB00aefH0VF 13:setStatus(_:) 52:func setStatus(_ status: RadrootsUserPresenceStatus)
6:symbol 18:RadrootsKitTesting 12:swift.method 68:s:18RadrootsKitTesting0A15FakeMediaPickerC10setSupportyy0aB00aefH0VF 14:setSupport(_:) 54:func setSupport(_ support: RadrootsMediaPickerSupport)