commit c605b4e43846cc9fee5f76e3b8b856cb9a545c95
parent 47617a25ad1c38c3e05c3fe7d2680f6c0713559f
Author: triesap <tyson@radroots.org>
Date: Wed, 9 Sep 2026 07:27:49 +0000
runtime: settle abandoned resource creation
- Separate creation from adoption with one bounded resource cleanup owner.
- Preserve shared startup and remove stale session-wide stop authority.
- Regenerate exact producer receipts while retaining native APIs and bytes.
- Verify fourteen new native tests and reproduce the prior ownership failures.
Diffstat:
21 files changed, 872 insertions(+), 204 deletions(-)
diff --git a/Package.swift b/Package.swift
@@ -61,6 +61,8 @@ let package = Package(
"Runtime/TeraRuntimeBoundedTask.swift",
"Runtime/TeraRuntimeClient.swift",
"Runtime/TeraRuntimeModels.swift",
+ "Runtime/TeraRuntimeResourceCreation.swift",
+ "Runtime/TeraRuntimeResourceTask.swift",
"Runtime/TeraSessionGeneration.swift",
"Runtime/TeraUserMessageClassifier.swift",
"Runtime/TeraUserMessages.swift",
diff --git a/Tera.xcodeproj/project.pbxproj b/Tera.xcodeproj/project.pbxproj
@@ -11,10 +11,12 @@
229C9AC991E67663DA53D339 /* TeraRuntimeBoundedTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4FFC0023C620B68D0EC0ED /* TeraRuntimeBoundedTaskTests.swift */; };
2479571FA99A7962794999C4 /* TeraSupportingStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0636AEDEB50DDDD328374A9 /* TeraSupportingStoreTests.swift */; };
2C6DDB502DFA11C2CBCA49C4 /* TeraApp in Frameworks */ = {isa = PBXBuildFile; productRef = A1979630FE6BC343F0E59F24 /* TeraApp */; };
+ 31CBA09815D972CCE187009F /* TeraSessionResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B870D20178E633610113 /* TeraSessionResourceTests.swift */; };
38EF7FA8874E82B28110A232 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6967B3EAC4CF1A410B26DD1A /* PrivacyInfo.xcprivacy */; };
4734A842C366608EC1BD31C5 /* TeraUserMessagesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46DDEE781FDD92A292FD847B /* TeraUserMessagesTests.swift */; };
64D49ADFD3FDD54CBA509DC5 /* TeraTodayStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794D475C70BA8ED994D1CA66 /* TeraTodayStoreTests.swift */; };
671EF50DB4EFBC25AABF26DC /* TeraLifecycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C890BDE885536BC2AB215393 /* TeraLifecycleTests.swift */; };
+ 6B0BE8E92892EF60B8E996A5 /* TeraRuntimeResourceFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A51F9AF2E0803264E07FBB /* TeraRuntimeResourceFixtures.swift */; };
70417FB635F06CC9829FB38C /* TeraClockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506546B0EC1792240C36F7 /* TeraClockTests.swift */; };
772A9B1AD8DE1D2E0969F285 /* TeraCalendarFFICompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B808E67E85CD36E49C38B6F /* TeraCalendarFFICompatibilityTests.swift */; };
79D06C6BF2B55C0C4104FB91 /* TeraSessionGenerationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0836F5D5F71F9A54795C57 /* TeraSessionGenerationTests.swift */; };
@@ -22,7 +24,9 @@
852FF7514FF7F682A246FB47 /* TeraStateMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E969BCFF7CED33A1D4A040 /* TeraStateMigrationTests.swift */; };
912C8743116D929BB6A02778 /* TeraRuntimeClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */; };
9AD968F0B37C64B5AF8D9A68 /* TeraRootShellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B8677670E203FF68EC88983 /* TeraRootShellTests.swift */; };
+ 9B37084901EC52EB44969304 /* TeraRuntimeResourceLifecycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226F5C2370522AAFAD9D1C3D /* TeraRuntimeResourceLifecycleTests.swift */; };
A1AD2A380F09994786889AF1 /* TeraAddStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F22CFABCF27A28408F68D25 /* TeraAddStoreTests.swift */; };
+ B1B83B8673660E0EC109F74C /* TeraStateTestFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD45A3134A16704CBE4B1A5 /* TeraStateTestFixtures.swift */; };
B2DAAE01D16E1FAC35816DED /* RadrootsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 1A9F6E32DE0C3728D4DB8E60 /* RadrootsKit */; };
B4AE86BE0A9F7FBE0DEBD9AC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = ADFD21D28C5F9E58B70E76CA /* Localizable.strings */; };
B64A836C5E84924D28CB5D47 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB8A30A75EFD65EFD938D248 /* Assets.xcassets */; };
@@ -33,6 +37,7 @@
DF97F408D7D32F72C5AF5939 /* TeraRootShellUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5238B5F236C392C82CEF9838 /* TeraRootShellUITests.swift */; };
E508A7061082B091B733D62B /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67ACCE25979BA86C1EC5BFD6 /* App.swift */; };
EF7293C553BF1DB45EEEBD49 /* local-social-personas.v1.json in Resources */ = {isa = PBXBuildFile; fileRef = 27D9D40699A01FFB02F30B11 /* local-social-personas.v1.json */; };
+ F8C87782857DC930D47E6A90 /* TeraRuntimeResourceTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D01C023E258AC3B3B1C27D /* TeraRuntimeResourceTaskTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -54,11 +59,13 @@
/* Begin PBXFileReference section */
0CDE3D76E1EDF909C19D0506 /* TeraUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = TeraUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 226F5C2370522AAFAD9D1C3D /* TeraRuntimeResourceLifecycleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeResourceLifecycleTests.swift; sourceTree = "<group>"; };
27D9D40699A01FFB02F30B11 /* local-social-personas.v1.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "local-social-personas.v1.json"; sourceTree = "<group>"; };
2A4FFC0023C620B68D0EC0ED /* TeraRuntimeBoundedTaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeBoundedTaskTests.swift; sourceTree = "<group>"; };
2A506546B0EC1792240C36F7 /* TeraClockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraClockTests.swift; sourceTree = "<group>"; };
32CBDAE974FB8D7408108E69 /* TeraRemoteQualificationUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRemoteQualificationUITests.swift; sourceTree = "<group>"; };
3B808E67E85CD36E49C38B6F /* TeraCalendarFFICompatibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraCalendarFFICompatibilityTests.swift; sourceTree = "<group>"; };
+ 4383B870D20178E633610113 /* TeraSessionResourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraSessionResourceTests.swift; sourceTree = "<group>"; };
46DDEE781FDD92A292FD847B /* TeraUserMessagesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraUserMessagesTests.swift; sourceTree = "<group>"; };
5238B5F236C392C82CEF9838 /* TeraRootShellUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRootShellUITests.swift; sourceTree = "<group>"; };
57CA176D0A7566A4ADD2CEA4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
@@ -76,11 +83,14 @@
9F54FC4930051FC4611B37D3 /* tera */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tera; path = .; sourceTree = SOURCE_ROOT; };
AB8A30A75EFD65EFD938D248 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
ADFD21D28C5F9E58B70E76CA /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
+ BAD45A3134A16704CBE4B1A5 /* TeraStateTestFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraStateTestFixtures.swift; sourceTree = "<group>"; };
C0636AEDEB50DDDD328374A9 /* TeraSupportingStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraSupportingStoreTests.swift; sourceTree = "<group>"; };
C1DBB5D9AC2FE8EA3AE68458 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+ C4D01C023E258AC3B3B1C27D /* TeraRuntimeResourceTaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeResourceTaskTests.swift; sourceTree = "<group>"; };
C890BDE885536BC2AB215393 /* TeraLifecycleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraLifecycleTests.swift; sourceTree = "<group>"; };
DE9E724A23014C6EA9042123 /* Tera.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Tera.app; sourceTree = BUILT_PRODUCTS_DIR; };
E351F52E77C74A4E99095E45 /* TeraRemoteQualificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRemoteQualificationTests.swift; sourceTree = "<group>"; };
+ F0A51F9AF2E0803264E07FBB /* TeraRuntimeResourceFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeResourceFixtures.swift; sourceTree = "<group>"; };
FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeClientTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -155,8 +165,13 @@
2A4FFC0023C620B68D0EC0ED /* TeraRuntimeBoundedTaskTests.swift */,
88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */,
FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */,
+ F0A51F9AF2E0803264E07FBB /* TeraRuntimeResourceFixtures.swift */,
+ 226F5C2370522AAFAD9D1C3D /* TeraRuntimeResourceLifecycleTests.swift */,
+ C4D01C023E258AC3B3B1C27D /* TeraRuntimeResourceTaskTests.swift */,
7C0836F5D5F71F9A54795C57 /* TeraSessionGenerationTests.swift */,
+ 4383B870D20178E633610113 /* TeraSessionResourceTests.swift */,
98E969BCFF7CED33A1D4A040 /* TeraStateMigrationTests.swift */,
+ BAD45A3134A16704CBE4B1A5 /* TeraStateTestFixtures.swift */,
C0636AEDEB50DDDD328374A9 /* TeraSupportingStoreTests.swift */,
794D475C70BA8ED994D1CA66 /* TeraTodayStoreTests.swift */,
46DDEE781FDD92A292FD847B /* TeraUserMessagesTests.swift */,
@@ -378,8 +393,13 @@
229C9AC991E67663DA53D339 /* TeraRuntimeBoundedTaskTests.swift in Sources */,
C219B5F4E24188E1116BF45B /* TeraRuntimeClientFixtures.swift in Sources */,
912C8743116D929BB6A02778 /* TeraRuntimeClientTests.swift in Sources */,
+ 6B0BE8E92892EF60B8E996A5 /* TeraRuntimeResourceFixtures.swift in Sources */,
+ 9B37084901EC52EB44969304 /* TeraRuntimeResourceLifecycleTests.swift in Sources */,
+ F8C87782857DC930D47E6A90 /* TeraRuntimeResourceTaskTests.swift in Sources */,
79D06C6BF2B55C0C4104FB91 /* TeraSessionGenerationTests.swift in Sources */,
+ 31CBA09815D972CCE187009F /* TeraSessionResourceTests.swift in Sources */,
852FF7514FF7F682A246FB47 /* TeraStateMigrationTests.swift in Sources */,
+ B1B83B8673660E0EC109F74C /* TeraStateTestFixtures.swift in Sources */,
2479571FA99A7962794999C4 /* TeraSupportingStoreTests.swift in Sources */,
64D49ADFD3FDD54CBA509DC5 /* TeraTodayStoreTests.swift in Sources */,
4734A842C366608EC1BD31C5 /* TeraUserMessagesTests.swift in Sources */,
diff --git a/Tera/Runtime/TeraRuntimeClient.swift b/Tera/Runtime/TeraRuntimeClient.swift
@@ -292,7 +292,8 @@ actor TeraRuntimeClient {
private struct StartupOperation: Sendable {
let identity: TeraRuntimeOperationIdentity
let configuration: TeraRuntimeLaunchConfiguration
- let task: TeraRuntimeBoundedTask<TeraRuntimeBackendStart>
+ let task: TeraRuntimeResourceTask<TeraRuntimeBackendStart>
+ var waiters = 0
}
private struct ShutdownOperation: Sendable {
@@ -385,33 +386,9 @@ actor TeraRuntimeClient {
lifecycleState = .starting(generation: operationGeneration)
let identity = nextIdentity(kind: kind)
- let factory = factory
- let cleanupDeadline = deadlines.shutdownNanoseconds
- let task = TeraRuntimeBoundedTask<TeraRuntimeBackendStart>(
- deadlineNanoseconds: deadlines.startupNanoseconds,
- operation: {
- do {
- return try await .success(factory(requestedConfiguration))
- } catch {
- return .failure(Self.failure(from: error, operation: identity.rawValue))
- }
- },
- onAbandonedResult: { result in
- guard case let .success(started) = result else { return }
- let cleanup = TeraRuntimeBoundedTask<TeraRuntimeShutdownReceipt>(
- deadlineNanoseconds: cleanupDeadline,
- operation: {
- do {
- return try await .success(started.backend.shutdown())
- } catch {
- return .failure(
- Self.failure(from: error, operation: "runtime.abandoned_startup")
- )
- }
- }
- )
- _ = await cleanup.value()
- }
+ let task = TeraRuntimeResourceCreation.startup(
+ configuration: requestedConfiguration, factory: factory,
+ identity: identity, deadlines: deadlines
)
let operation = StartupOperation(
identity: identity,
@@ -754,36 +731,12 @@ actor TeraRuntimeClient {
token: nil
)
- let subscriptionDeadline = deadlines.subscriptionNanoseconds
- let task = TeraRuntimeBoundedTask<any TeraRuntimeSubscriptionToken>(
- deadlineNanoseconds: subscriptionDeadline,
- operation: {
- do {
- return try await .success(
- backend.subscribe(bufferCapacity: bufferCapacity) { [weak self] change in
- await self?.receive(
- change,
- subscriptionID: id,
- generation: subscriptionGeneration
- )
- }
- )
- } catch {
- return .failure(Self.failure(from: error, operation: identity.rawValue))
- }
- },
- onAbandonedResult: { result in
- guard case let .success(token) = result else { return }
- let cancellation = TeraRuntimeBoundedTask<Void>(
- deadlineNanoseconds: subscriptionDeadline,
- operation: {
- await token.cancel()
- return .success(())
- }
- )
- _ = await cancellation.value()
- }
- )
+ let task = TeraRuntimeResourceCreation.subscription(
+ backend: backend, bufferCapacity: bufferCapacity,
+ identity: identity, deadline: deadlines.subscriptionNanoseconds
+ ) { [weak self] change in
+ await self?.receive(change, subscriptionID: id, generation: subscriptionGeneration)
+ }
activeOperations[identity.sequence] = ActiveOperation(
identity: identity,
cancel: { task.cancel() }
@@ -796,14 +749,22 @@ actor TeraRuntimeClient {
var subscription = subscriptions[id]
else {
subscriptions.removeValue(forKey: id)?.continuation.finish()
- if case let .completed(.success(token)) = outcome {
- cancelTokenDetached(token)
- }
+ task.cancel()
throw TeraRuntimeClientError.superseded
}
+ if Task.isCancelled {
+ subscriptions.removeValue(forKey: id)?.continuation.finish()
+ task.cancel()
+ throw TeraRuntimeClientError.subscription(Self.cancellationFailure(identity: identity))
+ }
+
switch outcome {
case let .completed(.success(token)):
+ guard task.adopt() else {
+ subscriptions.removeValue(forKey: id)?.continuation.finish()
+ throw TeraRuntimeClientError.superseded
+ }
subscription.token = token
subscriptions[id] = subscription
return pair.stream
@@ -864,17 +825,18 @@ actor TeraRuntimeClient {
private func finishStartup(
_ operation: StartupOperation
) async throws -> TeraRuntimeSnapshot {
+ if startupOperation?.identity == operation.identity {
+ startupOperation?.waiters += 1
+ }
+ defer { releaseStartupWaiter(operation) }
let outcome = await operation.task.value(cancelsOperationWhenWaiterCancelled: false)
guard generation == operation.identity.generation else {
+ operation.task.cancel()
throw TeraRuntimeClientError.superseded
}
- if case .cancelled = outcome,
- startupOperation?.identity == operation.identity
- {
- throw TeraRuntimeClientError.startup(
- Self.cancellationFailure(identity: operation.identity)
- )
+ if Task.isCancelled {
+ throw TeraRuntimeClientError.startup(Self.cancellationFailure(identity: operation.identity))
}
if startupOperation?.identity == operation.identity {
@@ -890,6 +852,7 @@ actor TeraRuntimeClient {
switch outcome {
case let .completed(.success(started)):
+ guard operation.task.adopt() else { throw TeraRuntimeClientError.superseded }
backend = started.backend
configuration = operation.configuration
lifecycleState = .running(generation: operation.identity.generation)
@@ -908,6 +871,19 @@ actor TeraRuntimeClient {
}
}
+ private func releaseStartupWaiter(_ operation: StartupOperation) {
+ guard var pending = startupOperation, pending.identity == operation.identity else { return }
+ pending.waiters -= 1
+ guard pending.waiters == 0 else {
+ startupOperation = pending
+ return
+ }
+ pending.task.cancel()
+ startupOperation = nil
+ configuration = nil
+ lifecycleState = .stopped
+ }
+
private func beginShutdown() -> ShutdownOperation {
generation = generation.invalidated()
let operationGeneration = generation
@@ -1180,7 +1156,7 @@ actor TeraRuntimeClient {
)
}
- private static func failure(from error: Error, operation: String) -> TeraRuntimeFailure {
+ static func failure(from error: Error, operation: String) -> TeraRuntimeFailure {
if let failure = error as? TeraRuntimeFailure {
return failure
}
diff --git a/Tera/Runtime/TeraRuntimeResourceCreation.swift b/Tera/Runtime/TeraRuntimeResourceCreation.swift
@@ -0,0 +1,44 @@
+enum TeraRuntimeResourceCreation {
+ static func startup(
+ configuration: TeraRuntimeLaunchConfiguration,
+ factory: @escaping TeraRuntimeBackendFactory,
+ identity: TeraRuntimeOperationIdentity,
+ deadlines: TeraRuntimeDeadlinePolicy
+ ) -> TeraRuntimeResourceTask<TeraRuntimeBackendStart> {
+ TeraRuntimeResourceTask(
+ deadlineNanoseconds: deadlines.startupNanoseconds,
+ cleanupDeadlineNanoseconds: deadlines.shutdownNanoseconds,
+ operation: {
+ do {
+ return try await .success(factory(configuration))
+ } catch {
+ return .failure(TeraRuntimeClient.failure(from: error, operation: identity.rawValue))
+ }
+ },
+ cleanup: { started in
+ _ = try? await started.backend.shutdown()
+ }
+ )
+ }
+
+ static func subscription(
+ backend: any TeraRuntimeBackend,
+ bufferCapacity: Int,
+ identity: TeraRuntimeOperationIdentity,
+ deadline: UInt64,
+ receive: @escaping @Sendable (TeraRuntimeChange) async -> Void
+ ) -> TeraRuntimeResourceTask<any TeraRuntimeSubscriptionToken> {
+ TeraRuntimeResourceTask(
+ deadlineNanoseconds: deadline,
+ cleanupDeadlineNanoseconds: deadline,
+ operation: {
+ do {
+ return try await .success(backend.subscribe(bufferCapacity: bufferCapacity, receive: receive))
+ } catch {
+ return .failure(TeraRuntimeClient.failure(from: error, operation: identity.rawValue))
+ }
+ },
+ cleanup: { token in await token.cancel() }
+ )
+ }
+}
diff --git a/Tera/Runtime/TeraRuntimeResourceTask.swift b/Tera/Runtime/TeraRuntimeResourceTask.swift
@@ -0,0 +1,118 @@
+import Foundation
+
+/// Creation completion and adoption are separate events. Only the transition
+/// to abandoned may start cleanup; adoption permanently transfers that duty.
+final class TeraRuntimeResourceTask<Value: Sendable>: Sendable {
+ private enum State {
+ case pending
+ case created(Value)
+ case adopted
+ case abandoned
+ }
+
+ private let ownership: Ownership
+ private let task: TeraRuntimeBoundedTask<Value>
+
+ init(
+ deadlineNanoseconds: UInt64,
+ cleanupDeadlineNanoseconds: UInt64,
+ operation: @escaping @Sendable () async -> Result<Value, TeraRuntimeFailure>,
+ cleanup: @escaping @Sendable (Value) async -> Void
+ ) {
+ let ownership = Ownership(deadline: cleanupDeadlineNanoseconds, cleanup: cleanup)
+ self.ownership = ownership
+ task = TeraRuntimeBoundedTask(deadlineNanoseconds: deadlineNanoseconds) {
+ let result = await operation()
+ if case let .success(value) = result {
+ ownership.created(value)
+ }
+ return result
+ } onAbandonedResult: { _ in
+ ownership.abandon()
+ }
+ }
+
+ func value(cancelsOperationWhenWaiterCancelled: Bool = true) async -> TeraRuntimeBoundedOutcome<Value> {
+ let outcome = await task.value(cancelsOperationWhenWaiterCancelled: cancelsOperationWhenWaiterCancelled)
+ switch outcome {
+ case .timedOut:
+ ownership.abandon()
+ case .cancelled where cancelsOperationWhenWaiterCancelled:
+ ownership.abandon()
+ default:
+ break
+ }
+ return outcome
+ }
+
+ func adopt() -> Bool {
+ ownership.adopt()
+ }
+
+ func cancel() {
+ ownership.abandon()
+ task.cancel()
+ }
+
+ /// Every mutable field is lock protected. Cleanup starts outside the lock,
+ /// has the existing host deadline, and retains its resource until it returns.
+ private final class Ownership: @unchecked Sendable {
+ private let lock = NSLock()
+ private var state = State.pending
+ private var cleanupTask: TeraRuntimeBoundedTask<Void>?
+ private let deadline: UInt64
+ private let cleanup: @Sendable (Value) async -> Void
+
+ init(deadline: UInt64, cleanup: @escaping @Sendable (Value) async -> Void) {
+ self.deadline = deadline
+ self.cleanup = cleanup
+ }
+
+ func created(_ value: Value) {
+ let abandoned = lock.withLock {
+ if case .abandoned = state {
+ return true
+ }
+ state = .created(value)
+ return false
+ }
+ if abandoned {
+ startCleanup(value)
+ }
+ }
+
+ func adopt() -> Bool {
+ lock.withLock {
+ guard case .created = state else { return false }
+ state = .adopted
+ return true
+ }
+ }
+
+ func abandon() {
+ let value: Value? = lock.withLock {
+ switch state {
+ case let .created(value):
+ state = .abandoned
+ return value
+ case .pending:
+ state = .abandoned
+ return nil
+ case .adopted, .abandoned:
+ return nil
+ }
+ }
+ if let value {
+ startCleanup(value)
+ }
+ }
+
+ private func startCleanup(_ value: Value) {
+ let task = TeraRuntimeBoundedTask<Void>(deadlineNanoseconds: deadline) { [cleanup] in
+ await cleanup(value)
+ return .success(())
+ }
+ lock.withLock { cleanupTask = task }
+ }
+ }
+}
diff --git a/Tera/State/TeraSessionStore.swift b/Tera/State/TeraSessionStore.swift
@@ -347,6 +347,7 @@ actor TeraSessionStore {
)
let sourceGeneration = try await configurationStore.sourceGeneration()
let signer = try await identityStore.signer(for: identity)
+ guard generation == requestedGeneration else { throw TeraRuntimeClientError.superseded }
let launchConfiguration = TeraRuntimeLaunchConfiguration(
applicationSupportDirectory: mobileStore.applicationSupportDirectory.path,
publicKeyHex: publicKeyHex,
@@ -368,7 +369,6 @@ actor TeraSessionStore {
try await runtimeClient.start(configuration: launchConfiguration)
}
guard generation == requestedGeneration else {
- _ = try? await runtimeClient.stop()
throw TeraRuntimeClientError.superseded
}
try await reconcileIdentity(identity)
@@ -380,7 +380,6 @@ actor TeraSessionStore {
)
}
guard generation == requestedGeneration else {
- _ = try? await runtimeClient.stop()
throw TeraRuntimeClientError.superseded
}
return .running(snapshot)
diff --git a/TeraFFI/provenance.json b/TeraFFI/provenance.json
@@ -94,17 +94,17 @@
{
"bytes": 55357,
"path": "source/aarch64-apple-darwin.json",
- "sha256": "238afaca3ed63bc11a6cb725d959ea07adadbe65702b4513f4d922d737ea7345"
+ "sha256": "bd0f9c9ef8f74f381bec5b3102c2103d76ab0a013ebeb1cfacfc2e46db8d28b7"
},
{
"bytes": 55201,
"path": "source/aarch64-apple-ios-sim.json",
- "sha256": "8d1b2dfcbeeeadb49c8b3711d5c23e1eb042c5f3fc1ce45012e35b0eacb5327d"
+ "sha256": "bc13a2c56369c028d16c766928f932ff6251b01d213676c3c44fa33a2e42f4f3"
},
{
"bytes": 55197,
"path": "source/aarch64-apple-ios.json",
- "sha256": "99acf9fc1abb5514e5bec3a73891dfb829f223d031c618df25fd564ce69214be"
+ "sha256": "c52645bf421065857e8638b5f6736a53e13f16e47a1c021c14f84ec1898d1f3f"
}
],
"language": "swift",
@@ -112,7 +112,7 @@
"schema": "radroots.artifact-manifest.v2",
"source": {
"repository": "https://github.com/radrootslabs/tera",
- "tree": "da2fa1a2912ebae4fe1cddf0423f1565e676d9a3"
+ "tree": "ee3a2c5daa71f0fb972496b44ca3be606a887c16"
},
"source_records": {
"aarch64-apple-darwin": "source/aarch64-apple-darwin.json",
@@ -176,17 +176,17 @@
{
"bytes": 55357,
"path": "TeraFFI/source/aarch64-apple-darwin.json",
- "sha256": "238afaca3ed63bc11a6cb725d959ea07adadbe65702b4513f4d922d737ea7345"
+ "sha256": "bd0f9c9ef8f74f381bec5b3102c2103d76ab0a013ebeb1cfacfc2e46db8d28b7"
},
{
"bytes": 55201,
"path": "TeraFFI/source/aarch64-apple-ios-sim.json",
- "sha256": "8d1b2dfcbeeeadb49c8b3711d5c23e1eb042c5f3fc1ce45012e35b0eacb5327d"
+ "sha256": "bc13a2c56369c028d16c766928f932ff6251b01d213676c3c44fa33a2e42f4f3"
},
{
"bytes": 55197,
"path": "TeraFFI/source/aarch64-apple-ios.json",
- "sha256": "99acf9fc1abb5514e5bec3a73891dfb829f223d031c618df25fd564ce69214be"
+ "sha256": "c52645bf421065857e8638b5f6736a53e13f16e47a1c021c14f84ec1898d1f3f"
}
],
"schema": "tera.installed-native-artifacts.v1"
diff --git a/TeraFFI/source.lock b/TeraFFI/source.lock
@@ -1,7 +1,7 @@
schema = "tera.installed-source.v1"
repository = "https://github.com/radrootslabs/tera"
-source_tree = "da2fa1a2912ebae4fe1cddf0423f1565e676d9a3"
-manifest_sha256 = "2e47dd8016a3980af7df78961e0a105cbc542cd92d372539d4ed1105c6754d55"
+source_tree = "ee3a2c5daa71f0fb972496b44ca3be606a887c16"
+manifest_sha256 = "d8a97dce55ee2f58e56cc6bebc5870956df106ed7715e144f13c617be94c9d2a"
source_date_epoch = 1787871027
[foundation]
diff --git a/TeraFFI/source/aarch64-apple-darwin.json b/TeraFFI/source/aarch64-apple-darwin.json
@@ -1189,13 +1189,13 @@
"sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d"
},
"test-fixtures/legacy-identifiers.v1.json": {
- "bytes": 110025,
- "git_blob": "686dcad1b181044b69599fa90f27b35fb8f8c61c",
+ "bytes": 110621,
+ "git_blob": "14d5342029968785335a2466d25832fcc8b85e9d",
"mode": "100644",
- "sha256": "27fd4299dc34e40bf719f059762401d79df44de339d657c29afd5ea290f36eed"
+ "sha256": "e45baeb16a4b77dd90962651ee9b8398040e3685b38feb298b8b5d5fdc437d67"
}
},
"policy": "staged_inputs",
- "tree": "da2fa1a2912ebae4fe1cddf0423f1565e676d9a3"
+ "tree": "ee3a2c5daa71f0fb972496b44ca3be606a887c16"
}
}
diff --git a/TeraFFI/source/aarch64-apple-ios-sim.json b/TeraFFI/source/aarch64-apple-ios-sim.json
@@ -1185,13 +1185,13 @@
"sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d"
},
"test-fixtures/legacy-identifiers.v1.json": {
- "bytes": 110025,
- "git_blob": "686dcad1b181044b69599fa90f27b35fb8f8c61c",
+ "bytes": 110621,
+ "git_blob": "14d5342029968785335a2466d25832fcc8b85e9d",
"mode": "100644",
- "sha256": "27fd4299dc34e40bf719f059762401d79df44de339d657c29afd5ea290f36eed"
+ "sha256": "e45baeb16a4b77dd90962651ee9b8398040e3685b38feb298b8b5d5fdc437d67"
}
},
"policy": "staged_inputs",
- "tree": "da2fa1a2912ebae4fe1cddf0423f1565e676d9a3"
+ "tree": "ee3a2c5daa71f0fb972496b44ca3be606a887c16"
}
}
diff --git a/TeraFFI/source/aarch64-apple-ios.json b/TeraFFI/source/aarch64-apple-ios.json
@@ -1185,13 +1185,13 @@
"sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d"
},
"test-fixtures/legacy-identifiers.v1.json": {
- "bytes": 110025,
- "git_blob": "686dcad1b181044b69599fa90f27b35fb8f8c61c",
+ "bytes": 110621,
+ "git_blob": "14d5342029968785335a2466d25832fcc8b85e9d",
"mode": "100644",
- "sha256": "27fd4299dc34e40bf719f059762401d79df44de339d657c29afd5ea290f36eed"
+ "sha256": "e45baeb16a4b77dd90962651ee9b8398040e3685b38feb298b8b5d5fdc437d67"
}
},
"policy": "staged_inputs",
- "tree": "da2fa1a2912ebae4fe1cddf0423f1565e676d9a3"
+ "tree": "ee3a2c5daa71f0fb972496b44ca3be606a887c16"
}
}
diff --git a/TeraTests/TeraRuntimeResourceFixtures.swift b/TeraTests/TeraRuntimeResourceFixtures.swift
@@ -0,0 +1,153 @@
+import Foundation
+@testable import TeraApp
+
+actor ResourceTestGate {
+ private var opened = false
+ private var waiters: [CheckedContinuation<Void, Never>] = []
+
+ func wait() async {
+ guard !opened else { return }
+ await withCheckedContinuation { waiters.append($0) }
+ }
+
+ func open() {
+ guard !opened else { return }
+ opened = true
+ let pending = waiters
+ waiters.removeAll()
+ for waiter in pending {
+ waiter.resume()
+ }
+ }
+}
+
+struct ResourceTestPause: Sendable {
+ let entered = ResourceTestGate()
+ let resume = ResourceTestGate()
+
+ func wait() async {
+ await entered.open()
+ await resume.wait()
+ }
+}
+
+actor ResourceTestToken: TeraRuntimeSubscriptionToken {
+ let cancelled = ResourceTestGate()
+ private(set) var cancelCount = 0
+
+ func cancel() async {
+ cancelCount += 1
+ await cancelled.open()
+ }
+}
+
+actor ResourceTestBackend: TeraRuntimeBackend {
+ let token = ResourceTestToken()
+ let closed = ResourceTestGate()
+ private(set) var shutdownCount = 0
+ private let value: TeraRuntimeSnapshot
+ private var subscriptionPause: ResourceTestPause?
+ private var snapshotPause: ResourceTestPause?
+ private var settingsPause: ResourceTestPause?
+ private var receive: (@Sendable (TeraRuntimeChange) async -> Void)?
+
+ init(publicKeyHex: String) {
+ value = TeraRuntimeSnapshot(
+ identity: TeraRuntimeIdentity(publicKeyHex: publicKeyHex, hostSignerConfigured: true),
+ relay: nil, blossomConfiguration: nil, blossomEvidence: nil,
+ crateName: "tera_ffi", crateVersion: "0.1.0-alpha", isClosed: false
+ )
+ }
+
+ func start() -> TeraRuntimeBackendStart {
+ TeraRuntimeBackendStart(backend: self, snapshot: value)
+ }
+
+ func pauseSubscription(_ pause: ResourceTestPause) {
+ subscriptionPause = pause
+ }
+
+ func pauseSnapshot(_ pause: ResourceTestPause) {
+ snapshotPause = pause
+ }
+
+ func pauseSettings(_ pause: ResourceTestPause) {
+ settingsPause = pause
+ }
+
+ func snapshot() async throws -> TeraRuntimeSnapshot {
+ let pause = snapshotPause
+ snapshotPause = nil
+ await pause?.wait()
+ guard shutdownCount == 0 else { throw unsupported() }
+ return value
+ }
+
+ func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage {
+ throw unsupported()
+ }
+
+ func refreshToday(
+ context _: TeraLocalNetwork, nowUnixSeconds _: UInt64, update _: TeraTodayProjectionUpdate
+ ) throws -> TeraTodayRefreshReceipt {
+ throw unsupported()
+ }
+
+ func subscribe(
+ bufferCapacity _: Int, receive: @escaping @Sendable (TeraRuntimeChange) async -> Void
+ ) async -> any TeraRuntimeSubscriptionToken {
+ self.receive = receive
+ let pause = subscriptionPause
+ subscriptionPause = nil
+ await pause?.wait()
+ return token
+ }
+
+ func emit(_ revision: UInt64) async {
+ await receive?(TeraRuntimeChange(
+ schemaVersion: 1, generation: TeraProjectionRevision(rawValue: revision), kind: .today, entityID: nil
+ ))
+ }
+
+ func shutdown() async -> TeraRuntimeShutdownReceipt {
+ shutdownCount += 1
+ await closed.open()
+ return TeraRuntimeShutdownReceipt(state: "closed", alreadyClosed: shutdownCount > 1)
+ }
+
+ func mobileSettings() async -> TeraMobileSettings {
+ let pause = settingsPause
+ settingsPause = nil
+ await pause?.wait()
+ return settings()
+ }
+
+ func applyIdentityCommand(
+ expectedRevision _: UInt64, command _: TeraIdentityCommand
+ ) -> TeraSettingsTransition {
+ TeraSettingsTransition(
+ settings: settings(), runtimeRestartRequired: false,
+ outboxRequeueRequired: false, mediaCacheInvalidationRequired: false
+ )
+ }
+
+ private func settings() -> TeraMobileSettings {
+ TeraMobileSettings(
+ revision: 1,
+ identity: TeraSettingsIdentityState(
+ identities: [TeraSettingsIdentity(id: "identity", publicKeyHex: value.identity.publicKeyHex)],
+ activeIdentityID: "identity", lockState: .unlocked, pendingImportOperationID: nil
+ ),
+ networkEnvironment: .simulator,
+ relays: [TeraRelayPreference(url: "ws://127.0.0.1:7447", access: .readWrite)],
+ blossomAuthority: .loopbackDevelopment, blossomPrimaryOrigin: "http://127.0.0.1:3000",
+ blossomFallbackOrigins: [], allowCellularDownloads: true,
+ allowCellularUploads: true, allowBackgroundTransfers: true,
+ mediaCacheBytes: 256 * 1_048_576, mediaCacheArtifacts: 1024
+ )
+ }
+
+ private func unsupported() -> TeraRuntimeFailure {
+ .local(operation: "test.resource", code: "test.unavailable", safeMessage: "Test operation unavailable")
+ }
+}
diff --git a/TeraTests/TeraRuntimeResourceLifecycleTests.swift b/TeraTests/TeraRuntimeResourceLifecycleTests.swift
@@ -0,0 +1,163 @@
+@testable import TeraApp
+import XCTest
+
+final class TeraRuntimeResourceLifecycleTests: XCTestCase {
+ func testStartupTimeoutClosesLateBackendWithoutResurrection() async throws {
+ let configuration = TeraRuntimeClientTests().makeConfiguration(generation: "30")
+ let backend = ResourceTestBackend(publicKeyHex: configuration.publicKeyHex)
+ let pause = ResourceTestPause()
+ let deadlines = TeraRuntimeDeadlinePolicy(startupNanoseconds: 1_000_000)
+ let client = TeraRuntimeClient(
+ factory: { _ in await pause.wait(); return await backend.start() }, deadlines: deadlines
+ )
+ let start = Task { try await client.start(configuration: configuration) }
+ await pause.entered.wait()
+ do {
+ _ = try await start.value
+ XCTFail("Startup deadline must end the wait")
+ } catch let TeraRuntimeClientError.startup(failure) {
+ XCTAssertEqual(failure.code, "ios.runtime.deadline_exceeded")
+ }
+ await pause.resume.open()
+ await assertClosed(backend)
+ guard case .failed = await client.lifecycle() else { return XCTFail("Late success cannot revive the runtime") }
+ _ = try await client.stop()
+ }
+
+ func testLastCancelledStartupWaiterClosesItsLateBackend() async throws {
+ let configuration = TeraRuntimeClientTests().makeConfiguration(generation: "31")
+ let backend = ResourceTestBackend(publicKeyHex: configuration.publicKeyHex)
+ let pause = ResourceTestPause()
+ let client = TeraRuntimeClient(factory: { _ in await pause.wait(); return await backend.start() })
+ let start = Task { try await client.start(configuration: configuration) }
+ await pause.entered.wait()
+ start.cancel()
+ do {
+ _ = try await start.value
+ XCTFail("A cancelled sole waiter must not adopt the runtime")
+ } catch let TeraRuntimeClientError.startup(failure) {
+ XCTAssertEqual(failure.code, "ios.runtime.cancelled")
+ }
+ let lifecycle = await client.lifecycle()
+ XCTAssertEqual(lifecycle, .stopped)
+ await pause.resume.open()
+ await assertClosed(backend)
+ _ = try await client.stop()
+ let count = await backend.shutdownCount
+ XCTAssertEqual(count, 1)
+ }
+
+ func testLateSupersededStartupCannotCloseNewRuntime() async throws {
+ let firstConfig = TeraRuntimeClientTests().makeConfiguration(generation: "32")
+ let nextConfig = TeraRuntimeClientTests().makeConfiguration(generation: "33")
+ let old = ResourceTestBackend(publicKeyHex: firstConfig.publicKeyHex)
+ let current = ResourceTestBackend(publicKeyHex: nextConfig.publicKeyHex)
+ let pause = ResourceTestPause()
+ let client = TeraRuntimeClient(factory: { configuration in
+ if configuration == firstConfig {
+ await pause.wait(); return await old.start()
+ }
+ return await current.start()
+ })
+ let first = Task { try await client.start(configuration: firstConfig) }
+ await pause.entered.wait()
+ _ = try await client.start(configuration: nextConfig)
+ do {
+ _ = try await first.value
+ XCTFail("Old startup must be superseded")
+ } catch {
+ XCTAssertEqual(error as? TeraRuntimeClientError, .superseded)
+ }
+ await pause.resume.open()
+ await assertClosed(old)
+ let snapshot = try await client.snapshot()
+ let activeCloses = await current.shutdownCount
+ XCTAssertEqual(snapshot.identity.publicKeyHex, nextConfig.publicKeyHex)
+ XCTAssertEqual(activeCloses, 0)
+ _ = try await client.stop()
+ }
+
+ func testLateSubscriptionAfterCancellationDetachesOnce() async throws {
+ try await assertLateSubscription(termination: .cancel)
+ }
+
+ func testLateSubscriptionAfterTimeoutDetachesOnce() async throws {
+ try await assertLateSubscription(termination: .timeout)
+ }
+
+ func testLateSubscriptionAfterStopDetachesOnce() async throws {
+ try await assertLateSubscription(termination: .stop)
+ }
+
+ func testAdoptedSubscriptionFinishesAndDetachesOnceOnRepeatedStop() async throws {
+ let configuration = TeraRuntimeClientTests().makeConfiguration(generation: "34")
+ let backend = ResourceTestBackend(publicKeyHex: configuration.publicKeyHex)
+ let client = TeraRuntimeClient(factory: { _ in await backend.start() })
+ _ = try await client.start(configuration: configuration)
+ let stream = try await client.changes()
+ await backend.emit(1)
+ var iterator = stream.makeAsyncIterator()
+ let first = await iterator.next()
+ XCTAssertEqual(first?.generation.rawValue, 1)
+ _ = try await client.stop()
+ _ = try await client.stop()
+ await backend.emit(2)
+ let final = await iterator.next()
+ let cancels = await backend.token.cancelCount
+ let closes = await backend.shutdownCount
+ XCTAssertNil(final)
+ XCTAssertEqual(cancels, 1)
+ XCTAssertEqual(closes, 1)
+ }
+
+ private enum Termination { case cancel, timeout, stop }
+
+ private func assertLateSubscription(termination: Termination) async throws {
+ let configuration = TeraRuntimeClientTests().makeConfiguration(generation: "35")
+ let backend = ResourceTestBackend(publicKeyHex: configuration.publicKeyHex)
+ let pause = ResourceTestPause()
+ await backend.pauseSubscription(pause)
+ let deadlines = TeraRuntimeDeadlinePolicy(
+ subscriptionNanoseconds: termination == .timeout ? 1_000_000 : 10_000_000_000
+ )
+ let client = TeraRuntimeClient(factory: { _ in await backend.start() }, deadlines: deadlines)
+ _ = try await client.start(configuration: configuration)
+ let creation = Task { try await client.changes() }
+ await pause.entered.wait()
+ switch termination {
+ case .cancel: creation.cancel()
+ case .stop: _ = try await client.stop()
+ case .timeout: break
+ }
+ do {
+ _ = try await creation.value
+ XCTFail("Abandoned creation must not return an observer")
+ } catch {
+ if termination == .stop {
+ XCTAssertEqual(error as? TeraRuntimeClientError, .superseded)
+ } else {
+ guard case let TeraRuntimeClientError.subscription(failure) = error else { throw error }
+ XCTAssertEqual(failure.code, termination == .timeout ? "ios.runtime.deadline_exceeded" : "ios.runtime.cancelled")
+ }
+ }
+ await pause.resume.open()
+ let detached = expectation(description: "Late token detached")
+ let wait = Task { await backend.token.cancelled.wait(); detached.fulfill() }
+ await fulfillment(of: [detached], timeout: 2)
+ await backend.token.cancelled.open()
+ await wait.value
+ _ = try await client.stop()
+ let count = await backend.token.cancelCount
+ XCTAssertEqual(count, 1)
+ }
+
+ private func assertClosed(_ backend: ResourceTestBackend) async {
+ let closed = expectation(description: "Late backend closed")
+ let wait = Task { await backend.closed.wait(); closed.fulfill() }
+ await fulfillment(of: [closed], timeout: 2)
+ await backend.closed.open()
+ await wait.value
+ let count = await backend.shutdownCount
+ XCTAssertEqual(count, 1)
+ }
+}
diff --git a/TeraTests/TeraRuntimeResourceTaskTests.swift b/TeraTests/TeraRuntimeResourceTaskTests.swift
@@ -0,0 +1,81 @@
+@testable import TeraApp
+import XCTest
+
+final class TeraRuntimeResourceTaskTests: XCTestCase {
+ func testCompletedResourceIsCleanedOnceWhenAbandonedBeforeAdoption() async {
+ let cleaned = expectation(description: "Completed but unadopted resource is closed")
+ cleaned.assertForOverFulfill = true
+ let task = TeraRuntimeResourceTask<Int>(
+ deadlineNanoseconds: .max, cleanupDeadlineNanoseconds: .max,
+ operation: { .success(42) }, cleanup: { value in
+ XCTAssertEqual(value, 42)
+ cleaned.fulfill()
+ }
+ )
+ guard case .completed(.success(42)) = await task.value() else { return XCTFail("Creation must complete") }
+ task.cancel()
+ task.cancel()
+ XCTAssertFalse(task.adopt())
+ await fulfillment(of: [cleaned], timeout: 2)
+ }
+
+ func testCancellationBeforeCreationCleansLateSuccessOnce() async {
+ let pause = ResourceTestPause()
+ let cleaned = expectation(description: "Late resource is closed")
+ cleaned.assertForOverFulfill = true
+ let task = TeraRuntimeResourceTask<Int>(
+ deadlineNanoseconds: .max, cleanupDeadlineNanoseconds: .max,
+ operation: { await pause.wait(); return .success(42) },
+ cleanup: { _ in cleaned.fulfill() }
+ )
+ await pause.entered.wait()
+ task.cancel()
+ task.cancel()
+ guard case .cancelled = await task.value() else { return XCTFail("Caller must stop waiting") }
+ await pause.resume.open()
+ await fulfillment(of: [cleaned], timeout: 2)
+ XCTAssertFalse(task.adopt())
+ }
+
+ func testTimeoutDuringCreationCleansLateSuccessOnce() async {
+ let pause = ResourceTestPause()
+ let cleaned = expectation(description: "Timed out resource is closed")
+ cleaned.assertForOverFulfill = true
+ let task = TeraRuntimeResourceTask<Int>(
+ deadlineNanoseconds: 1_000_000, cleanupDeadlineNanoseconds: .max,
+ operation: { await pause.wait(); return .success(42) },
+ cleanup: { _ in cleaned.fulfill() }
+ )
+ await pause.entered.wait()
+ guard case .timedOut = await task.value() else { return XCTFail("Deadline must end the wait") }
+ task.cancel()
+ await pause.resume.open()
+ await fulfillment(of: [cleaned], timeout: 2)
+ XCTAssertFalse(task.adopt())
+ }
+
+ func testAdoptedResourceCannotBeCleanedByAnOldWaiter() async {
+ let task = TeraRuntimeResourceTask<Int>(
+ deadlineNanoseconds: .max, cleanupDeadlineNanoseconds: .max,
+ operation: { .success(42) }, cleanup: { _ in XCTFail("The active owner holds this resource") }
+ )
+ guard case .completed(.success(42)) = await task.value() else { return XCTFail("Creation must complete") }
+ XCTAssertTrue(task.adopt())
+ XCTAssertFalse(task.adopt())
+ task.cancel()
+ task.cancel()
+ guard case .completed(.success(42)) = await task.value() else { return XCTFail("Keep the useful result") }
+ }
+
+ func testCreationFailureHasNoResourceToClean() async {
+ let failure = TeraRuntimeFailure.local(operation: "test", code: "test.failure", safeMessage: "Test failure")
+ let task = TeraRuntimeResourceTask<Int>(
+ deadlineNanoseconds: .max, cleanupDeadlineNanoseconds: .max,
+ operation: { .failure(failure) }, cleanup: { _ in XCTFail("No resource was created") }
+ )
+ guard case let .completed(.failure(observed)) = await task.value() else { return XCTFail("Failure must survive") }
+ XCTAssertEqual(observed, failure)
+ task.cancel()
+ XCTAssertFalse(task.adopt())
+ }
+}
diff --git a/TeraTests/TeraSessionResourceTests.swift b/TeraTests/TeraSessionResourceTests.swift
@@ -0,0 +1,80 @@
+import Foundation
+import RadrootsKit
+@testable import TeraApp
+import XCTest
+
+final class TeraSessionResourceTests: XCTestCase {
+ func testStaleStartupSnapshotDoesNotStopNewSharedSession() async throws {
+ let fixture = try await Fixture()
+ defer { fixture.state.remove() }
+ let initial = await fixture.session.start()
+ guard case .running = initial else { return XCTFail("Initial session must run") }
+ let pause = ResourceTestPause()
+ await fixture.backend.pauseSnapshot(pause)
+ let old = Task { await fixture.session.start() }
+ await pause.entered.wait()
+ let current = await fixture.session.start()
+ guard case .running = current else { return XCTFail("New session must adopt the shared runtime") }
+ await pause.resume.open()
+ _ = await old.value
+ try await assertRunning(fixture, phase: current)
+ _ = await fixture.session.stop()
+ }
+
+ func testStaleIdentityReconciliationDoesNotStopNewSharedSession() async throws {
+ let fixture = try await Fixture()
+ defer { fixture.state.remove() }
+ let pause = ResourceTestPause()
+ await fixture.backend.pauseSettings(pause)
+ let old = Task { await fixture.session.start() }
+ await pause.entered.wait()
+ let current = await fixture.session.start()
+ guard case .running = current else { return XCTFail("New session must adopt the shared runtime") }
+ await pause.resume.open()
+ _ = await old.value
+ try await assertRunning(fixture, phase: current)
+ _ = await fixture.session.stop()
+ }
+
+ private func assertRunning(_ fixture: Fixture, phase: TeraSessionPhase) async throws {
+ let observed = await fixture.session.currentPhase()
+ let closes = await fixture.backend.shutdownCount
+ let snapshot = try await fixture.client.snapshot()
+ XCTAssertEqual(observed, phase)
+ XCTAssertFalse(snapshot.isClosed)
+ XCTAssertEqual(closes, 0)
+ }
+
+ private struct Fixture {
+ let state: StateFixture
+ let backend: ResourceTestBackend
+ let client: TeraRuntimeClient
+ let session: TeraSessionStore
+
+ init() async throws {
+ state = try StateFixture()
+ let secure = InMemorySecureStore()
+ let custody = try RadrootsIdentityCustody(
+ configuration: RadrootsIdentityCustodyConfiguration(
+ namespace: "radroots_identity_v1", secretPolicy: .secureLocalSecret
+ ),
+ secureStore: secure, metadataStore: InMemoryIdentityMetadataStore(),
+ userPresence: AllowingUserPresence()
+ )
+ let identity = TeraIdentityStore(
+ custody: custody, secureStore: secure,
+ servicePrefix: "org.tera.tests.session.\(UUID().uuidString.lowercased())"
+ )
+ let created = try await identity.create()
+ let backend = try ResourceTestBackend(publicKeyHex: XCTUnwrap(created.publicKeyHex))
+ self.backend = backend
+ let client = TeraRuntimeClient(factory: { _ in await backend.start() })
+ self.client = client
+ session = TeraSessionStore(
+ configurationStore: TeraConfigurationStore(bootstrap: state.bootstrap, roots: state.roots),
+ identityStore: identity, runtimeClient: client, roots: state.roots,
+ protectedData: TeraProtectedDataMonitor(available: true)
+ )
+ }
+ }
+}
diff --git a/TeraTests/TeraStateMigrationTests.swift b/TeraTests/TeraStateMigrationTests.swift
@@ -620,99 +620,3 @@ final class TeraStateMigrationTests: XCTestCase {
XCTAssertEqual(signatureHex.count, 128)
}
}
-
-private struct StateFixture {
- let root: URL
- let roots: RadrootsAppleFileRoots
- let bootstrap: TeraConfigurationBootstrap
-
- init() throws {
- root = FileManager.default.temporaryDirectory
- .appendingPathComponent("radroots-state-tests-\(UUID().uuidString.lowercased())")
- try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false)
- roots = try RadrootsAppleFileRoots(
- appIdentifier: "org.radroots.tests",
- dataRoot: root.appendingPathComponent("data"),
- cacheRoot: root.appendingPathComponent("cache"),
- temporaryRoot: root.appendingPathComponent("tmp")
- )
- bootstrap = TeraConfigurationBootstrap(
- runtimeMode: "localhost-dev",
- relayURLs: ["ws://127.0.0.1:8080"],
- blossomOrigins: ["http://127.0.0.1:3000"],
- keychainServicePrefix: "org.radroots.tests",
- bundleIdentifier: "org.radroots.tests",
- appMetadata: TeraRuntimeAppMetadata(
- bundleIdentifier: "org.radroots.tests",
- version: "0.1.0-alpha",
- buildNumber: "1",
- buildSHA: nil
- )
- )
- }
-
- func remove() {
- try? FileManager.default.removeItem(at: root)
- }
-}
-
-private final class InMemorySecureStore: RadrootsSecureStore, @unchecked Sendable {
- private let lock = NSLock()
- private var values: [RadrootsSecureStoreKey: Data] = [:]
-
- func put(
- _ value: Data,
- for key: RadrootsSecureStoreKey,
- policy _: RadrootsSecretAccessPolicy
- ) throws {
- lock.withLock { values[key] = value }
- }
-
- func contains(_ key: RadrootsSecureStoreKey) throws -> Bool {
- lock.withLock { values[key] != nil }
- }
-
- func get(_ key: RadrootsSecureStoreKey) throws -> Data? {
- lock.withLock { values[key] }
- }
-
- func delete(_ key: RadrootsSecureStoreKey) throws {
- lock.withLock { _ = values.removeValue(forKey: key) }
- }
-
- func deleteNamespace(_ namespace: String) throws {
- lock.withLock { values = values.filter { $0.key.namespace != namespace } }
- }
-}
-
-private final class InMemoryIdentityMetadataStore: RadrootsIdentityMetadataStore, @unchecked Sendable {
- private let lock = NSLock()
- private var values: [RadrootsIdentityMetadataSlot: Data] = [:]
-
- func data(for slot: RadrootsIdentityMetadataSlot) throws -> Data? {
- lock.withLock { values[slot] }
- }
-
- func put(_ data: Data, for slot: RadrootsIdentityMetadataSlot) throws {
- lock.withLock { values[slot] = data }
- }
-
- func delete(_ slot: RadrootsIdentityMetadataSlot) throws {
- lock.withLock { _ = values.removeValue(forKey: slot) }
- }
-}
-
-private struct AllowingUserPresence: RadrootsUserPresence {
- func currentStatus() async throws -> RadrootsUserPresenceStatus {
- RadrootsUserPresenceStatus(
- support: .deviceCredential,
- biometryKind: .none,
- canEvaluateDeviceCredential: true,
- canEvaluateBiometrics: false
- )
- }
-
- func verify(_ request: RadrootsUserPresenceRequest) async throws -> RadrootsUserPresenceResult {
- RadrootsUserPresenceResult(policy: request.policy, verified: true)
- }
-}
diff --git a/TeraTests/TeraStateTestFixtures.swift b/TeraTests/TeraStateTestFixtures.swift
@@ -0,0 +1,99 @@
+import Foundation
+import RadrootsKit
+@testable import TeraApp
+
+struct StateFixture {
+ let root: URL
+ let roots: RadrootsAppleFileRoots
+ let bootstrap: TeraConfigurationBootstrap
+
+ init() throws {
+ root = FileManager.default.temporaryDirectory
+ .appendingPathComponent("radroots-state-tests-\(UUID().uuidString.lowercased())")
+ try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false)
+ roots = try RadrootsAppleFileRoots(
+ appIdentifier: "org.radroots.tests",
+ dataRoot: root.appendingPathComponent("data"),
+ cacheRoot: root.appendingPathComponent("cache"),
+ temporaryRoot: root.appendingPathComponent("tmp")
+ )
+ bootstrap = TeraConfigurationBootstrap(
+ runtimeMode: "localhost-dev",
+ relayURLs: ["ws://127.0.0.1:8080"],
+ blossomOrigins: ["http://127.0.0.1:3000"],
+ keychainServicePrefix: "org.radroots.tests",
+ bundleIdentifier: "org.radroots.tests",
+ appMetadata: TeraRuntimeAppMetadata(
+ bundleIdentifier: "org.radroots.tests",
+ version: "0.1.0-alpha",
+ buildNumber: "1",
+ buildSHA: nil
+ )
+ )
+ }
+
+ func remove() {
+ try? FileManager.default.removeItem(at: root)
+ }
+}
+
+final class InMemorySecureStore: RadrootsSecureStore, @unchecked Sendable {
+ private let lock = NSLock()
+ private var values: [RadrootsSecureStoreKey: Data] = [:]
+
+ func put(
+ _ value: Data,
+ for key: RadrootsSecureStoreKey,
+ policy _: RadrootsSecretAccessPolicy
+ ) throws {
+ lock.withLock { values[key] = value }
+ }
+
+ func contains(_ key: RadrootsSecureStoreKey) throws -> Bool {
+ lock.withLock { values[key] != nil }
+ }
+
+ func get(_ key: RadrootsSecureStoreKey) throws -> Data? {
+ lock.withLock { values[key] }
+ }
+
+ func delete(_ key: RadrootsSecureStoreKey) throws {
+ lock.withLock { _ = values.removeValue(forKey: key) }
+ }
+
+ func deleteNamespace(_ namespace: String) throws {
+ lock.withLock { values = values.filter { $0.key.namespace != namespace } }
+ }
+}
+
+final class InMemoryIdentityMetadataStore: RadrootsIdentityMetadataStore, @unchecked Sendable {
+ private let lock = NSLock()
+ private var values: [RadrootsIdentityMetadataSlot: Data] = [:]
+
+ func data(for slot: RadrootsIdentityMetadataSlot) throws -> Data? {
+ lock.withLock { values[slot] }
+ }
+
+ func put(_ data: Data, for slot: RadrootsIdentityMetadataSlot) throws {
+ lock.withLock { values[slot] = data }
+ }
+
+ func delete(_ slot: RadrootsIdentityMetadataSlot) throws {
+ lock.withLock { _ = values.removeValue(forKey: slot) }
+ }
+}
+
+struct AllowingUserPresence: RadrootsUserPresence {
+ func currentStatus() async throws -> RadrootsUserPresenceStatus {
+ RadrootsUserPresenceStatus(
+ support: .deviceCredential,
+ biometryKind: .none,
+ canEvaluateDeviceCredential: true,
+ canEvaluateBiometrics: false
+ )
+ }
+
+ func verify(_ request: RadrootsUserPresenceRequest) async throws -> RadrootsUserPresenceResult {
+ RadrootsUserPresenceResult(policy: request.policy, verified: true)
+ }
+}
diff --git a/project.yml b/project.yml
@@ -50,6 +50,11 @@ targets:
- path: TeraTests/TeraSessionGenerationTests.swift
- path: TeraTests/TeraErrorRecoveryTests.swift
- path: TeraTests/TeraRuntimeBoundedTaskTests.swift
+ - path: TeraTests/TeraRuntimeResourceFixtures.swift
+ - path: TeraTests/TeraRuntimeResourceTaskTests.swift
+ - path: TeraTests/TeraRuntimeResourceLifecycleTests.swift
+ - path: TeraTests/TeraSessionResourceTests.swift
+ - path: TeraTests/TeraStateTestFixtures.swift
- path: TeraTests/TeraRootShellTests.swift
- path: TeraTests/TeraStateMigrationTests.swift
- path: TeraTests/TeraTodayStoreTests.swift
diff --git a/release/provenance.json b/release/provenance.json
@@ -2,11 +2,11 @@
"artifacts": {
"app_api_sha256": "020924097c0d7efc33128cb8fd3d3b2026d95f57c44da71880e585aff80f070b",
"ffi_api_sha256": "37ba223303e6268d12d8581ba9a1a77a327b132122e29284bec715cca3c19369",
- "ffi_provenance_sha256": "2e47dd8016a3980af7df78961e0a105cbc542cd92d372539d4ed1105c6754d55",
+ "ffi_provenance_sha256": "d8a97dce55ee2f58e56cc6bebc5870956df106ed7715e144f13c617be94c9d2a",
"info_plist_sha256": "15ef08b1cdd1096cfb9eeaf5be5bf8f814807a7ca9350bbbb47860fa72ec13ef",
"privacy_manifest_sha256": "a331d51864743ebe4e00dd22360b4a538b6b3ac26a6b3eb54094e60a36959a12",
"sbom_sha256": "6b5ce897d5273290febc8b831663e12dea759cb0084f182ba7949e1c593fc410",
- "xcode_project_sha256": "4d8248344c4728688df5b51fc9127b08a50aab252b177956ea76a8ab31ecfb31"
+ "xcode_project_sha256": "33057e3e83dacfa3c9a41c270347485339da5101c217a005770022f881c1596c"
},
"disposition": "unsigned",
"platforms": [
@@ -22,7 +22,7 @@
"lib_revision": "ad17b7d3455a7147cfa303d976fc5c70c3a4c0cb",
"source_date_epoch": 1787871027,
"swift_package_lock_sha256": "94ae067a374726cdaf6b4ca0a5e44663c57fcdc5334060c5ffef5e79cfbf04c0",
- "tera_ffi_source_tree": "da2fa1a2912ebae4fe1cddf0423f1565e676d9a3",
+ "tera_ffi_source_tree": "ee3a2c5daa71f0fb972496b44ca3be606a887c16",
"xcode_package_lock_sha256": "c7f41934ea25f7a287bdc4f3a6ecabbf09a3a0bdd0f5a3e58183f355ca814096"
},
"version": "0.1.0-alpha"
diff --git a/test-fixtures/legacy-identifiers.v1.json b/test-fixtures/legacy-identifiers.v1.json
@@ -257,7 +257,7 @@
"count": 1
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 2
}
]
@@ -861,6 +861,10 @@
"count": 5
},
{
+ "path": "TeraTests/TeraSessionResourceTests.swift",
+ "count": 1
+ },
+ {
"path": "TeraTests/TeraStateMigrationTests.swift",
"count": 3
}
@@ -875,6 +879,10 @@
"count": 4
},
{
+ "path": "TeraTests/TeraSessionResourceTests.swift",
+ "count": 1
+ },
+ {
"path": "TeraTests/TeraStateMigrationTests.swift",
"count": 3
}
@@ -909,7 +917,7 @@
"category": "shared_apple_api",
"occurrences": [
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 4
}
]
@@ -919,7 +927,7 @@
"category": "shared_apple_api",
"occurrences": [
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 1
}
]
@@ -1051,10 +1059,18 @@
"count": 1
},
{
+ "path": "TeraTests/TeraSessionResourceTests.swift",
+ "count": 1
+ },
+ {
"path": "TeraTests/TeraStateMigrationTests.swift",
"count": 1
},
{
+ "path": "TeraTests/TeraStateTestFixtures.swift",
+ "count": 1
+ },
+ {
"path": "TeraTests/TeraUserMessagesTests.swift",
"count": 1
},
@@ -1299,7 +1315,7 @@
"category": "shared_apple_api",
"occurrences": [
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 1
}
]
@@ -1313,7 +1329,7 @@
"count": 2
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 1
}
]
@@ -1328,7 +1344,11 @@
},
{
"path": "TeraTests/TeraStateMigrationTests.swift",
- "count": 6
+ "count": 1
+ },
+ {
+ "path": "TeraTests/TeraStateTestFixtures.swift",
+ "count": 5
}
]
},
@@ -1567,7 +1587,7 @@
"count": 1
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 1
}
]
@@ -1615,7 +1635,7 @@
"count": 1
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 1
}
]
@@ -1629,7 +1649,7 @@
"count": 2
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 2
}
]
@@ -1643,7 +1663,7 @@
"count": 2
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 2
}
]
@@ -1901,7 +1921,7 @@
"category": "verification_compatibility",
"occurrences": [
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 1
}
]
@@ -3501,7 +3521,7 @@
"count": 1
},
{
- "path": "TeraTests/TeraStateMigrationTests.swift",
+ "path": "TeraTests/TeraStateTestFixtures.swift",
"count": 4
}
]
@@ -4047,6 +4067,10 @@
"count": 1
},
{
+ "path": "TeraTests/TeraSessionResourceTests.swift",
+ "count": 1
+ },
+ {
"path": "TeraTests/TeraStateMigrationTests.swift",
"count": 3
}
diff --git a/test-fixtures/swiftlint-maintainability-baseline.v1.json b/test-fixtures/swiftlint-maintainability-baseline.v1.json
@@ -1 +1 @@
-[{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/App\/TeraAppModel.swift","line":55,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 255 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraAppModel: ObservableObject {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/App\/TeraAppModel.swift","line":71,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" init("},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":73,"character":1},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 427 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":192,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 55 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func uploadInBackground("},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":580,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 580","ruleDescription":"Files should not span too many lines."},"text":"#endif"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":134,"character":15},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 545 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":1696,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1696","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraLifecycleCoordinator.swift","line":434,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 434","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":291,"character":1},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 847 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"actor TeraRuntimeClient {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":353,"character":11},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 60 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" private func start("},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":731,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 88 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":911,"character":11},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" private func beginShutdown() -> ShutdownOperation {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":1221,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1221","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraRuntimeModels.swift","line":1234,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1234","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/State\/TeraAddStore.swift","line":11,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 741 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraAddStore.swift","line":280,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func submit() async {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraAddStore.swift","line":438,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 51 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func retract(_ card: TeraTodayCard) async {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraAddStore.swift","line":524,"character":11},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 60 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" private func uploadPendingMedia(_ initial: TeraDraftStatus) async throws"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/State\/TeraAddStore.swift","line":810,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 810","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":78,"character":1},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"actor TeraConfigurationStore {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":148,"character":5},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func load() throws -> TeraAppConfiguration {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":759,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 759","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":40,"character":1},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 431 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"actor TeraSessionStore {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":70,"character":10},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" static func production("},{"violation":{"ruleIdentifier":"cyclomatic_complexity","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":182,"character":11},"ruleName":"Cyclomatic Complexity","severity":"error","reason":"Function should have complexity 10 or less; currently complexity is 14","ruleDescription":"Complexity of function bodies should be limited."},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":182,"character":11},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 64 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":389,"character":11},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" private func reconcileIdentity(_ identity: TeraAppIdentity) async throws {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":493,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 493","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/State\/TeraSupportingStores.swift","line":451,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 451","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/Views\/TeraAddView.swift","line":4,"character":1},"ruleName":"Type Body Length","severity":"error","reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 454 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"struct TeraAddView: View {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Views\/TeraAddView.swift","line":560,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 560","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","line":365,"character":1},"ruleName":"Type Body Length","severity":"error","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"struct TeraSettingsView: View {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","line":629,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 629","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"Tera\/Views\/TeraTodayView.swift","line":493,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 493","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":6,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 665 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraAddStoreTests: XCTestCase {"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":847,"character":9},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 579 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"private actor AddBackend: TeraRuntimeBackend {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":934,"character":22},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 89 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" nonisolated static func schemas() -> [TeraAddSchema] {"},{"violation":{"ruleIdentifier":"function_parameter_count","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1371,"character":11},"ruleName":"Function Parameter Count","severity":"error","reason":"Function should have 5 parameters or less: it currently has 8","ruleDescription":"Number of function parameters should be low."},"text":" private func makeStatus("},{"violation":{"ruleIdentifier":"large_tuple","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1635,"character":15},"ruleName":"Large Tuple","severity":"error","reason":"Tuples should have at most 2 members","ruleDescription":"Tuples shouldn't have too many members. Create a custom type instead."},"text":" var counts: (enqueue: Int, retry: Int, cancel: Int, acceptedSettlement: Int) {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1784,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1784","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":7,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraRemoteQualificationTests: XCTestCase {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":45,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":212,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraRuntimeClientTests.swift","line":4,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 334 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraRuntimeClientTests: XCTestCase {"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","line":6,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","line":7,"character":5},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","line":718,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 718","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","line":207,"character":9},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 253 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"private actor SupportingBackend: TeraRuntimeBackend {"},{"violation":{"ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","line":487,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 400 lines or less: currently contains 487","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":4,"character":7},"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraRemoteQualificationUITests: XCTestCase {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":62,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func testLocalSocialFiveFlowScenario() throws {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":174,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func testLocalSocialDeterministicPersonas() throws {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":247,"character":3},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" func testRemoteBlossomUploadAndRecovery() throws {"},{"violation":{"ruleIdentifier":"cyclomatic_complexity","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":573,"character":11},"ruleName":"Cyclomatic Complexity","severity":"error","reason":"Function should have complexity 10 or less; currently complexity is 18","ruleDescription":"Complexity of function bodies should be limited."},"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {"},{"violation":{"ruleIdentifier":"cyclomatic_complexity","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":767,"character":11},"ruleName":"Cyclomatic Complexity","severity":"error","reason":"Function should have complexity 10 or less; currently complexity is 11","ruleDescription":"Complexity of function bodies should be limited."},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":767,"character":11},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"violation":{"ruleIdentifier":"function_body_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":1736,"character":10},"ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","ruleDescription":"Function bodies should not span too many lines"},"text":" static func environment("},{"violation":{"ruleIdentifier":"file_length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":2212,"character":1},"ruleName":"File Length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 2212","ruleDescription":"Files should not span too many lines."},"text":"}"}]
-\ No newline at end of file
+[{"text":"final class TeraAppModel: ObservableObject {","violation":{"severity":"error","ruleDescription":"Type bodies should not span too many lines","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 255 lines","ruleName":"Type Body Length","location":{"file":"Tera\/App\/TeraAppModel.swift","character":7,"line":55},"ruleIdentifier":"type_body_length"}},{"text":" init(","violation":{"ruleDescription":"Function bodies should not span too many lines","severity":"error","ruleName":"Function Body Length","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","ruleIdentifier":"function_body_length","location":{"character":3,"file":"Tera\/App\/TeraAppModel.swift","line":71}}},{"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {","violation":{"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 427 lines","ruleName":"Type Body Length","severity":"error","ruleDescription":"Type bodies should not span too many lines","location":{"line":73,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1},"ruleIdentifier":"type_body_length"}},{"text":" func uploadInBackground(","violation":{"location":{"line":192,"character":3,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift"},"severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 55 lines","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length"}},{"text":"#endif","violation":{"location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":580,"character":1},"ruleDescription":"Files should not span too many lines.","reason":"File should contain 400 lines or less: currently contains 580","severity":"error","ruleIdentifier":"file_length","ruleName":"File Length"}},{"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {","violation":{"ruleDescription":"Type bodies should not span too many lines","location":{"character":15,"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":134},"reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 545 lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","severity":"error"}},{"text":"}","violation":{"ruleName":"File Length","severity":"error","ruleDescription":"Files should not span too many lines.","location":{"character":1,"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":1696},"ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1696"}},{"text":"}","violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 434","location":{"character":1,"line":434,"file":"Tera\/Runtime\/TeraLifecycleCoordinator.swift"}}},{"violation":{"location":{"character":1,"line":291,"file":"Tera\/Runtime\/TeraRuntimeClient.swift"},"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 821 lines","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","severity":"error","ruleName":"Type Body Length"},"text":"actor TeraRuntimeClient {"},{"violation":{"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 71 lines","ruleName":"Function Body Length","location":{"character":3,"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":708},"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","severity":"error"},"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {"},{"violation":{"severity":"error","ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","ruleIdentifier":"function_body_length","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":11,"line":887},"ruleName":"Function Body Length"},"text":" private func beginShutdown() -> ShutdownOperation {"},{"violation":{"reason":"File should contain 1000 lines or less: currently contains 1197","ruleName":"File Length","severity":"error","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"line":1197,"character":1,"file":"Tera\/Runtime\/TeraRuntimeClient.swift"}},"text":"}"},{"violation":{"severity":"error","ruleName":"File Length","location":{"character":1,"line":1234,"file":"Tera\/Runtime\/TeraRuntimeModels.swift"},"reason":"File should contain 1000 lines or less: currently contains 1234","ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length"},"text":"}"},{"violation":{"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 741 lines","location":{"file":"Tera\/State\/TeraAddStore.swift","line":11,"character":7},"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length"},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","location":{"file":"Tera\/State\/TeraAddStore.swift","line":280,"character":3},"severity":"error","ruleIdentifier":"function_body_length"},"text":" func submit() async {"},{"violation":{"ruleName":"Function Body Length","location":{"character":3,"line":438,"file":"Tera\/State\/TeraAddStore.swift"},"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 51 lines"},"text":" func retract(_ card: TeraTodayCard) async {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","location":{"line":524,"character":11,"file":"Tera\/State\/TeraAddStore.swift"},"ruleIdentifier":"function_body_length","ruleName":"Function Body Length","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 60 lines"},"text":" private func uploadPendingMedia(_ initial: TeraDraftStatus) async throws"},{"violation":{"ruleDescription":"Files should not span too many lines.","severity":"error","ruleIdentifier":"file_length","location":{"line":810,"file":"Tera\/State\/TeraAddStore.swift","character":1},"reason":"File should contain 400 lines or less: currently contains 810","ruleName":"File Length"},"text":"}"},{"violation":{"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines","severity":"error","location":{"line":78,"character":1,"file":"Tera\/State\/TeraConfigurationStore.swift"},"ruleIdentifier":"type_body_length"},"text":"actor TeraConfigurationStore {"},{"violation":{"reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines","severity":"error","location":{"line":148,"character":5,"file":"Tera\/State\/TeraConfigurationStore.swift"},"ruleIdentifier":"function_body_length"},"text":" func load() throws -> TeraAppConfiguration {"},{"text":"}","violation":{"location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":759,"character":1},"reason":"File should contain 400 lines or less: currently contains 759","ruleDescription":"Files should not span too many lines.","ruleName":"File Length","severity":"error","ruleIdentifier":"file_length"}},{"text":"actor TeraSessionStore {","violation":{"severity":"error","ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","location":{"character":1,"file":"Tera\/State\/TeraSessionStore.swift","line":40},"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 430 lines"}},{"text":" static func production(","violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","location":{"line":70,"character":10,"file":"Tera\/State\/TeraSessionStore.swift"},"severity":"error"}},{"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {","violation":{"ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 14","ruleDescription":"Complexity of function bodies should be limited.","location":{"line":182,"character":11,"file":"Tera\/State\/TeraSessionStore.swift"},"severity":"error"}},{"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {","violation":{"severity":"error","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","location":{"file":"Tera\/State\/TeraSessionStore.swift","line":182,"character":11},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 64 lines","ruleDescription":"Function bodies should not span too many lines"}},{"text":" private func reconcileIdentity(_ identity: TeraAppIdentity) async throws {","violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","location":{"character":11,"file":"Tera\/State\/TeraSessionStore.swift","line":388},"ruleDescription":"Function bodies should not span too many lines","severity":"error","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines"}},{"text":"}","violation":{"reason":"File should contain 400 lines or less: currently contains 492","severity":"error","ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"line":492,"character":1,"file":"Tera\/State\/TeraSessionStore.swift"}}},{"text":"}","violation":{"ruleDescription":"Files should not span too many lines.","severity":"error","reason":"File should contain 400 lines or less: currently contains 451","ruleIdentifier":"file_length","ruleName":"File Length","location":{"line":451,"character":1,"file":"Tera\/State\/TeraSupportingStores.swift"}}},{"text":"struct TeraAddView: View {","violation":{"ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":4},"reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 454 lines","ruleName":"Type Body Length","severity":"error","ruleIdentifier":"type_body_length"}},{"text":"}","violation":{"ruleIdentifier":"file_length","severity":"error","ruleName":"File Length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":560},"reason":"File should contain 400 lines or less: currently contains 560"}},{"text":"struct TeraSettingsView: View {","violation":{"ruleName":"Type Body Length","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":365},"ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","severity":"error"}},{"text":"}","violation":{"ruleName":"File Length","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":629},"ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","reason":"File should contain 400 lines or less: currently contains 629","severity":"error"}},{"text":"}","violation":{"ruleIdentifier":"file_length","severity":"error","ruleName":"File Length","location":{"file":"Tera\/Views\/TeraTodayView.swift","line":493,"character":1},"reason":"File should contain 400 lines or less: currently contains 493","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraAddStoreTests: XCTestCase {","violation":{"ruleDescription":"Type bodies should not span too many lines","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 665 lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":6,"character":7}}},{"text":"private actor AddBackend: TeraRuntimeBackend {","violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":9,"line":847},"ruleIdentifier":"type_body_length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 579 lines"}},{"text":" nonisolated static func schemas() -> [TeraAddSchema] {","violation":{"ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 89 lines","ruleName":"Function Body Length","severity":"error","ruleIdentifier":"function_body_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":22,"line":934}}},{"text":" private func makeStatus(","violation":{"ruleDescription":"Number of function parameters should be low.","ruleIdentifier":"function_parameter_count","ruleName":"Function Parameter Count","reason":"Function should have 5 parameters or less: it currently has 8","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1371,"character":11},"severity":"error"}},{"text":" var counts: (enqueue: Int, retry: Int, cancel: Int, acceptedSettlement: Int) {","violation":{"ruleDescription":"Tuples shouldn't have too many members. Create a custom type instead.","location":{"line":1635,"character":15,"file":"TeraTests\/TeraAddStoreTests.swift"},"ruleIdentifier":"large_tuple","reason":"Tuples should have at most 2 members","ruleName":"Large Tuple","severity":"error"}},{"text":"}","violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1784","severity":"error","ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1784,"character":1}}},{"text":"final class TeraRemoteQualificationTests: XCTestCase {","violation":{"severity":"error","ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":7,"character":7},"reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines"}},{"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {","violation":{"location":{"line":45,"character":3,"file":"TeraTests\/TeraRemoteQualificationTests.swift"},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","ruleIdentifier":"function_body_length","severity":"error","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length"}},{"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {","violation":{"ruleName":"Function Body Length","location":{"character":3,"line":212,"file":"TeraTests\/TeraRemoteQualificationTests.swift"},"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","severity":"error"}},{"text":"final class TeraRuntimeClientTests: XCTestCase {","violation":{"ruleName":"Type Body Length","location":{"character":7,"file":"TeraTests\/TeraRuntimeClientTests.swift","line":4},"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 334 lines","ruleDescription":"Type bodies should not span too many lines"}},{"text":"final class TeraStateMigrationTests: XCTestCase {","violation":{"ruleName":"Type Body Length","location":{"character":7,"file":"TeraTests\/TeraStateMigrationTests.swift","line":6},"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testRelayValidationMatchesRuntimeProfiles() throws {","violation":{"location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":5,"line":7},"ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","ruleIdentifier":"function_body_length","severity":"error","ruleName":"Function Body Length"}},{"text":"}","violation":{"reason":"File should contain 400 lines or less: currently contains 622","ruleName":"File Length","severity":"error","ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":1,"line":622}}},{"text":"private actor SupportingBackend: TeraRuntimeBackend {","violation":{"ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":9,"line":207},"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 253 lines"}},{"text":"}","violation":{"severity":"error","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","line":487,"character":1},"reason":"File should contain 400 lines or less: currently contains 487","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","ruleName":"File Length"}},{"text":"final class TeraRemoteQualificationUITests: XCTestCase {","violation":{"ruleDescription":"Type bodies should not span too many lines","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines","ruleIdentifier":"type_body_length","location":{"line":4,"character":7,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"},"ruleName":"Type Body Length","severity":"error"}},{"text":" func testLocalSocialFiveFlowScenario() throws {","violation":{"ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","ruleName":"Function Body Length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":62,"character":3},"ruleDescription":"Function bodies should not span too many lines","severity":"error"}},{"text":" func testLocalSocialDeterministicPersonas() throws {","violation":{"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","severity":"error","location":{"line":174,"character":3,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines"}},{"text":" func testRemoteBlossomUploadAndRecovery() throws {","violation":{"location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":247,"character":3},"severity":"error","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines"}},{"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {","violation":{"ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","ruleName":"Cyclomatic Complexity","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":573,"character":11},"severity":"error","reason":"Function should have complexity 10 or less; currently complexity is 18"}},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"severity":"error","reason":"Function should have complexity 10 or less; currently complexity is 11","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","ruleName":"Cyclomatic Complexity","location":{"line":767,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11}}},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","severity":"error","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"line":767,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11},"ruleName":"Function Body Length"}},{"text":" static func environment(","violation":{"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","severity":"error","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"line":1736,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":10},"ruleName":"Function Body Length"}},{"violation":{"reason":"File should contain 1000 lines or less: currently contains 2212","severity":"error","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","ruleName":"File Length","location":{"line":2212,"character":1,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"}},"text":"}"}]
+\ No newline at end of file