field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit 40d955bcdd55de28cbc13c78666bddcd3fd8f754
parent e251dff19e5d3ae2481968d2400ee8ff4f95a61a
Author: triesap <tyson@radroots.org>
Date:   Wed,  9 Sep 2026 05:38:18 +0000

runtime: separate durable ids from session generations

- Preserve validated context, draft, operation and artifact identities at app boundaries.
- Distinguish host session generations from projection hints and reject generation reuse.
- Verify unchanged wire values and durable operation recovery across session restarts.
- Regenerate native provenance and project sources with narrower Swift lint debt.

Diffstat:
MPackage.swift | 1+
MTera.xcodeproj/project.pbxproj | 8++++++++
MTera/Runtime/TeraGeneratedRuntimeBackend.swift | 2+-
MTera/Runtime/TeraRuntimeClient.swift | 14+++++++-------
MTera/Runtime/TeraRuntimeModels.swift | 20+++++---------------
ATera/Runtime/TeraSessionGeneration.swift | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTera/State/TeraSessionStore.swift | 20++++++++++----------
MTeraFFI/provenance.json | 54+++++++++++++++++++++++++++---------------------------
MTeraFFI/source.lock | 4++--
MTeraFFI/source/aarch64-apple-darwin.json | 56++++++++++++++++++++++++++++++++++----------------------
MTeraFFI/source/aarch64-apple-ios-sim.json | 56++++++++++++++++++++++++++++++++++----------------------
MTeraFFI/source/aarch64-apple-ios.json | 56++++++++++++++++++++++++++++++++++----------------------
ATeraTests/TeraRuntimeClientFixtures.swift | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTeraTests/TeraRuntimeClientTests.swift | 232+++----------------------------------------------------------------------------
ATeraTests/TeraSessionGenerationTests.swift | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/product_surface.rs | 5++++-
Mcore/crates/tera_core/src/runtime/product_surface/context.rs | 6++++--
Mcore/crates/tera_core/src/runtime/product_surface/cursor.rs | 24+++++++-----------------
Acore/crates/tera_core/src/runtime/product_surface/local_network_id.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/product_surface/outbox.rs | 76++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mcore/crates/tera_core/src/runtime/product_surface/today.rs | 13+++++++------
Mcore/crates/tera_ffi/src/dto.rs | 2+-
Acore/crates/tera_ffi/tests/identity_contract.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mproject.yml | 2++
Mrelease/provenance.json | 6+++---
Mtest-fixtures/legacy-identifiers.v1.json | 4++++
Mtest-fixtures/swiftlint-maintainability-baseline.v1.json | 4++--
27 files changed, 715 insertions(+), 420 deletions(-)

diff --git a/Package.swift b/Package.swift @@ -59,6 +59,7 @@ let package = Package( "Runtime/TeraLifecycleCoordinator.swift", "Runtime/TeraRuntimeClient.swift", "Runtime/TeraRuntimeModels.swift", + "Runtime/TeraSessionGeneration.swift", "Runtime/TeraUserMessageClassifier.swift", "Runtime/TeraUserMessages.swift", "State/TeraAddStore.swift", diff --git a/Tera.xcodeproj/project.pbxproj b/Tera.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 671EF50DB4EFBC25AABF26DC /* TeraLifecycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C890BDE885536BC2AB215393 /* TeraLifecycleTests.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 */; }; 81AF9DDA0E64DC4EEFCEF25D /* TeraRemoteQualificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E351F52E77C74A4E99095E45 /* TeraRemoteQualificationTests.swift */; }; 852FF7514FF7F682A246FB47 /* TeraStateMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E969BCFF7CED33A1D4A040 /* TeraStateMigrationTests.swift */; }; 912C8743116D929BB6A02778 /* TeraRuntimeClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */; }; @@ -24,6 +25,7 @@ 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 */; }; + C219B5F4E24188E1116BF45B /* TeraRuntimeClientFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */; }; C3177E827FE62639579C3EF5 /* TeraRemoteQualificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBDAE974FB8D7408108E69 /* TeraRemoteQualificationUITests.swift */; }; CDBE38E6B521611436F28858 /* TeraMediaStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B6F0B16BFD8F046E9C163F /* TeraMediaStoreTests.swift */; }; DF97F408D7D32F72C5AF5939 /* TeraRootShellUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5238B5F236C392C82CEF9838 /* TeraRootShellUITests.swift */; }; @@ -62,6 +64,8 @@ 6967B3EAC4CF1A410B26DD1A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 6B8677670E203FF68EC88983 /* TeraRootShellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRootShellTests.swift; sourceTree = "<group>"; }; 794D475C70BA8ED994D1CA66 /* TeraTodayStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraTodayStoreTests.swift; sourceTree = "<group>"; }; + 7C0836F5D5F71F9A54795C57 /* TeraSessionGenerationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraSessionGenerationTests.swift; sourceTree = "<group>"; }; + 88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeClientFixtures.swift; sourceTree = "<group>"; }; 98E969BCFF7CED33A1D4A040 /* TeraStateMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraStateMigrationTests.swift; sourceTree = "<group>"; }; 999BDCEF144A19F35D048D15 /* TeraTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = TeraTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9F22CFABCF27A28408F68D25 /* TeraAddStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraAddStoreTests.swift; sourceTree = "<group>"; }; @@ -143,7 +147,9 @@ 61B6F0B16BFD8F046E9C163F /* TeraMediaStoreTests.swift */, E351F52E77C74A4E99095E45 /* TeraRemoteQualificationTests.swift */, 6B8677670E203FF68EC88983 /* TeraRootShellTests.swift */, + 88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */, FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */, + 7C0836F5D5F71F9A54795C57 /* TeraSessionGenerationTests.swift */, 98E969BCFF7CED33A1D4A040 /* TeraStateMigrationTests.swift */, C0636AEDEB50DDDD328374A9 /* TeraSupportingStoreTests.swift */, 794D475C70BA8ED994D1CA66 /* TeraTodayStoreTests.swift */, @@ -362,7 +368,9 @@ CDBE38E6B521611436F28858 /* TeraMediaStoreTests.swift in Sources */, 81AF9DDA0E64DC4EEFCEF25D /* TeraRemoteQualificationTests.swift in Sources */, 9AD968F0B37C64B5AF8D9A68 /* TeraRootShellTests.swift in Sources */, + C219B5F4E24188E1116BF45B /* TeraRuntimeClientFixtures.swift in Sources */, 912C8743116D929BB6A02778 /* TeraRuntimeClientTests.swift in Sources */, + 79D06C6BF2B55C0C4104FB91 /* TeraSessionGenerationTests.swift in Sources */, 852FF7514FF7F682A246FB47 /* TeraStateMigrationTests.swift in Sources */, 2479571FA99A7962794999C4 /* TeraSupportingStoreTests.swift in Sources */, 64D49ADFD3FDD54CBA509DC5 /* TeraTodayStoreTests.swift in Sources */, diff --git a/Tera/Runtime/TeraGeneratedRuntimeBackend.swift b/Tera/Runtime/TeraGeneratedRuntimeBackend.swift @@ -92,7 +92,7 @@ private final class TeraGeneratedRuntimeObserver: TeraRuntimeObserver, @unchecke continuation.yield( TeraRuntimeChange( schemaVersion: change.schemaVersion, - generation: change.generation, + generation: TeraProjectionRevision(rawValue: change.generation), kind: change.kind.appValue, entityID: change.entityId ) diff --git a/Tera/Runtime/TeraRuntimeClient.swift b/Tera/Runtime/TeraRuntimeClient.swift @@ -457,14 +457,14 @@ actor TeraRuntimeClient { } private struct Subscription { - let generation: UInt64 + let generation: TeraSessionGeneration let continuation: AsyncStream<TeraRuntimeChange>.Continuation var token: (any TeraRuntimeSubscriptionToken)? } private let factory: TeraRuntimeBackendFactory private let deadlines: TeraRuntimeDeadlinePolicy - private var generation: UInt64 = 0 + private var generation = TeraSessionGeneration.initial private var operationSequence: UInt64 = 0 private var lifecycleState: TeraRuntimeLifecycle = .stopped private var configuration: TeraRuntimeLaunchConfiguration? @@ -530,7 +530,7 @@ actor TeraRuntimeClient { _ = try await finishShutdown(operation) } - generation &+= 1 + generation = try generation.next() let operationGeneration = generation lifecycleState = .starting(generation: operationGeneration) @@ -989,7 +989,7 @@ actor TeraRuntimeClient { } if let startupOperation { - generation &+= 1 + generation = generation.invalidated() startupOperation.task.cancel() self.startupOperation = nil configuration = nil @@ -1059,7 +1059,7 @@ actor TeraRuntimeClient { } private func beginShutdown() -> ShutdownOperation { - generation &+= 1 + generation = generation.invalidated() let operationGeneration = generation let pendingStartup = startupOperation let activeBackend = backend ?? quarantinedBackend @@ -1241,7 +1241,7 @@ actor TeraRuntimeClient { private func receive( _ change: TeraRuntimeChange, subscriptionID: UUID, - generation subscriptionGeneration: UInt64 + generation subscriptionGeneration: TeraSessionGeneration ) { guard generation == subscriptionGeneration, case .running = lifecycleState, @@ -1253,7 +1253,7 @@ actor TeraRuntimeClient { subscription.continuation.yield(change) } - private func cancelSubscription(id: UUID, generation subscriptionGeneration: UInt64) { + private func cancelSubscription(id: UUID, generation subscriptionGeneration: TeraSessionGeneration) { guard let subscription = subscriptions[id], subscription.generation == subscriptionGeneration else { diff --git a/Tera/Runtime/TeraRuntimeModels.swift b/Tera/Runtime/TeraRuntimeModels.swift @@ -338,7 +338,7 @@ enum TeraRuntimeChangeKind: Sendable, Equatable { struct TeraRuntimeChange: Sendable, Equatable { let schemaVersion: UInt16 - let generation: UInt64 + let generation: TeraProjectionRevision let kind: TeraRuntimeChangeKind let entityID: String? } @@ -358,16 +358,6 @@ enum TeraRuntimeOperationKind: String, Sendable, Equatable, Hashable { case shutdown } -struct TeraRuntimeOperationIdentity: Sendable, Equatable, Hashable { - let generation: UInt64 - let sequence: UInt64 - let kind: TeraRuntimeOperationKind - - var rawValue: String { - "ios-runtime-\(generation)-\(sequence)-\(kind.rawValue)" - } -} - struct TeraRuntimeDeadlinePolicy: Sendable, Equatable { let startupNanoseconds: UInt64 let operationNanoseconds: UInt64 @@ -1237,8 +1227,8 @@ struct TeraNativeUploadCompletion: Sendable, Equatable { enum TeraRuntimeLifecycle: Sendable, Equatable { case stopped - case starting(generation: UInt64) - case running(generation: UInt64) - case stopping(generation: UInt64) - case failed(generation: UInt64, failure: TeraRuntimeFailure) + case starting(generation: TeraSessionGeneration) + case running(generation: TeraSessionGeneration) + case stopping(generation: TeraSessionGeneration) + case failed(generation: TeraSessionGeneration, failure: TeraRuntimeFailure) } diff --git a/Tera/Runtime/TeraSessionGeneration.swift b/Tera/Runtime/TeraSessionGeneration.swift @@ -0,0 +1,57 @@ +/// A transient callback guard. This value is never a persisted operation key. +struct TeraSessionGeneration: Sendable, Equatable, Hashable { + private let value: UInt64? + + static let initial = Self(rawValue: 0) + + init(rawValue: UInt64) { + value = rawValue + } + + private init(exhausted _: Void) { + value = nil + } + + var isActive: Bool { + value != nil + } + + var diagnosticValue: String { + value.map(String.init) ?? "exhausted" + } + + func requireActive() throws -> Self { + guard isActive else { + throw TeraStateTransitionError.generationOverflow + } + return self + } + + func next() throws -> Self { + guard let value else { + throw TeraStateTransitionError.generationOverflow + } + return try Self(rawValue: TeraCheckedStateTransition.nextGeneration(after: value)) + } + + /// Cleanup must invalidate old callbacks even when no further session can start. + func invalidated() -> Self { + (try? next()) ?? Self(exhausted: ()) + } +} + +/// Diagnostic identity of an in-memory call, never the authored operation ID. +struct TeraRuntimeOperationIdentity: Sendable, Equatable, Hashable { + let generation: TeraSessionGeneration + let sequence: UInt64 + let kind: TeraRuntimeOperationKind + + var rawValue: String { + "ios-runtime-\(generation.diagnosticValue)-\(sequence)-\(kind.rawValue)" + } +} + +/// Revision carried by a Rust invalidation hint, distinct from host sessions. +struct TeraProjectionRevision: Sendable, Equatable, Hashable { + let rawValue: UInt64 +} diff --git a/Tera/State/TeraSessionStore.swift b/Tera/State/TeraSessionStore.swift @@ -45,7 +45,7 @@ actor TeraSessionStore { private let protectedData: TeraProtectedDataMonitor private let automatesQualificationIdentity: Bool private let qualificationEvidenceStore: TeraRemoteQualificationEvidenceStore? - private var generation: UInt64 = 0 + private var generation = TeraSessionGeneration.initial private var phase: TeraSessionPhase = .starting init( @@ -142,7 +142,7 @@ actor TeraSessionStore { } func applySettingsReconfiguration() async -> TeraSessionPhase { - generation &+= 1 + generation = generation.invalidated() let requestedGeneration = generation phase = .starting do { @@ -154,7 +154,7 @@ actor TeraSessionStore { phase = try await startRuntime( configuration: configuration, identity: identity, - generation: requestedGeneration, + generation: requestedGeneration.requireActive(), forceReconfiguration: true, adoptBootstrapSettings: false ) @@ -172,7 +172,7 @@ actor TeraSessionStore { } func suspend() async { - generation &+= 1 + generation = generation.invalidated() await runtimeClient.suspend() if case .starting = phase { phase = .stopped @@ -180,7 +180,7 @@ actor TeraSessionStore { } private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase { - generation &+= 1 + generation = generation.invalidated() let requestedGeneration = generation phase = .starting do { @@ -221,7 +221,7 @@ actor TeraSessionStore { phase = try await startRuntime( configuration: configuration, identity: identity, - generation: requestedGeneration, + generation: requestedGeneration.requireActive(), forceReconfiguration: configuration.activationState == .reconfigurationRequired, adoptBootstrapSettings: configuration.activationState @@ -268,7 +268,7 @@ actor TeraSessionStore { } func lockIdentity() async -> TeraSessionPhase { - generation &+= 1 + generation = generation.invalidated() if case .running = phase, let settings = try? await runtimeClient.mobileSettings() { @@ -308,7 +308,7 @@ actor TeraSessionStore { } func stop() async -> TeraSessionPhase { - generation &+= 1 + generation = generation.invalidated() do { _ = try await runtimeClient.stop() await identityStore.lock() @@ -331,7 +331,7 @@ actor TeraSessionStore { private func startRuntime( configuration: TeraAppConfiguration, identity: TeraAppIdentity, - generation requestedGeneration: UInt64, + generation requestedGeneration: TeraSessionGeneration, forceReconfiguration: Bool, adoptBootstrapSettings: Bool ) async throws -> TeraSessionPhase { @@ -451,7 +451,7 @@ actor TeraSessionStore { } private func failIdentityOperation(_ error: Error) -> TeraSessionPhase { - generation &+= 1 + generation = generation.invalidated() phase = .failed( .local( operation: "identity.operation", diff --git a/TeraFFI/provenance.json b/TeraFFI/provenance.json @@ -22,9 +22,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 71177128, + "bytes": 71188664, "path": "TeraFFI.xcframework/ios-arm64-simulator/libtera_ffi.a", - "sha256": "4e150bccf169257bf39539bcc97660b9c39fa25cb0187c7cfd57db4460dd624d" + "sha256": "ee12302300492db430289d66f9f2ed8a7a3ed36d2176e64123e7fd27b987be15" }, { "bytes": 68654, @@ -37,9 +37,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 71229312, + "bytes": 71241672, "path": "TeraFFI.xcframework/ios-arm64/libtera_ffi.a", - "sha256": "005a6f155fb78da2e1d560fd1bc5f62a1ff17dfa101419b1b7fe33ca86c9a458" + "sha256": "7e40fb24eeb783ea13bda1a8f764250a22dfe5baa1034fd7f5dd29f815a07d84" }, { "bytes": 40735, @@ -77,34 +77,34 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 19797472, + "bytes": 19798752, "path": "native/aarch64-apple-darwin/libtera_ffi.dylib", - "sha256": "fc24bae706f1577093e7671b57cd290be588701e4c98c0520fe9dfc72ad9d150" + "sha256": "74db7e443bb923f94c2fa4ad0b3983d7f4e0e3ac5b55a0bb4f776eb86d5a7a5b" }, { - "bytes": 71177128, + "bytes": 71188664, "path": "native/aarch64-apple-ios-sim/libtera_ffi.a", - "sha256": "4e150bccf169257bf39539bcc97660b9c39fa25cb0187c7cfd57db4460dd624d" + "sha256": "ee12302300492db430289d66f9f2ed8a7a3ed36d2176e64123e7fd27b987be15" }, { - "bytes": 71229312, + "bytes": 71241672, "path": "native/aarch64-apple-ios/libtera_ffi.a", - "sha256": "005a6f155fb78da2e1d560fd1bc5f62a1ff17dfa101419b1b7fe33ca86c9a458" + "sha256": "7e40fb24eeb783ea13bda1a8f764250a22dfe5baa1034fd7f5dd29f815a07d84" }, { - "bytes": 53746, + "bytes": 54300, "path": "source/aarch64-apple-darwin.json", - "sha256": "9d1b04aef4fd2376df5cbcd201364ba939a86c81b8ad477df180c12405b598ab" + "sha256": "a50ccfae127636b2c0abc6606ae689f301def7ea48f0c01ab2659b03fcb20223" }, { - "bytes": 53590, + "bytes": 54144, "path": "source/aarch64-apple-ios-sim.json", - "sha256": "6db7eb95aeb6a31b6a27892818398f6738dca6a85dbdc58e1af5377259299ac5" + "sha256": "33c3f36e171e83286d73436c08d271581865845428f4b7411fbb99ccfea8014a" }, { - "bytes": 53586, + "bytes": 54140, "path": "source/aarch64-apple-ios.json", - "sha256": "ed84372beba812566226a673b6fee5d210765c08756adda1b3e08c87fb74b636" + "sha256": "9341e757796dd1504a84bea8b5b122cbdd59d8639273386112b1f007ca05dae0" } ], "language": "swift", @@ -112,7 +112,7 @@ "schema": "radroots.artifact-manifest.v2", "source": { "repository": "https://github.com/radrootslabs/tera", - "tree": "ed13f3efac75566f8f21f1fcc0f9cea938d21d53" + "tree": "bd58272c2149b87ee0070d16578d144e1e12aaab" }, "source_records": { "aarch64-apple-darwin": "source/aarch64-apple-darwin.json", @@ -139,9 +139,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 71177128, + "bytes": 71188664, "path": "Tera/Frameworks/TeraFFI.xcframework/ios-arm64-simulator/libtera_ffi.a", - "sha256": "4e150bccf169257bf39539bcc97660b9c39fa25cb0187c7cfd57db4460dd624d" + "sha256": "ee12302300492db430289d66f9f2ed8a7a3ed36d2176e64123e7fd27b987be15" }, { "bytes": 68654, @@ -154,9 +154,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 71229312, + "bytes": 71241672, "path": "Tera/Frameworks/TeraFFI.xcframework/ios-arm64/libtera_ffi.a", - "sha256": "005a6f155fb78da2e1d560fd1bc5f62a1ff17dfa101419b1b7fe33ca86c9a458" + "sha256": "7e40fb24eeb783ea13bda1a8f764250a22dfe5baa1034fd7f5dd29f815a07d84" }, { "bytes": 481606, @@ -174,19 +174,19 @@ "sha256": "6542c8294920b3c41e987619de58690e2af16896079910b657129d3573b34e68" }, { - "bytes": 53746, + "bytes": 54300, "path": "TeraFFI/source/aarch64-apple-darwin.json", - "sha256": "9d1b04aef4fd2376df5cbcd201364ba939a86c81b8ad477df180c12405b598ab" + "sha256": "a50ccfae127636b2c0abc6606ae689f301def7ea48f0c01ab2659b03fcb20223" }, { - "bytes": 53590, + "bytes": 54144, "path": "TeraFFI/source/aarch64-apple-ios-sim.json", - "sha256": "6db7eb95aeb6a31b6a27892818398f6738dca6a85dbdc58e1af5377259299ac5" + "sha256": "33c3f36e171e83286d73436c08d271581865845428f4b7411fbb99ccfea8014a" }, { - "bytes": 53586, + "bytes": 54140, "path": "TeraFFI/source/aarch64-apple-ios.json", - "sha256": "ed84372beba812566226a673b6fee5d210765c08756adda1b3e08c87fb74b636" + "sha256": "9341e757796dd1504a84bea8b5b122cbdd59d8639273386112b1f007ca05dae0" } ], "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 = "ed13f3efac75566f8f21f1fcc0f9cea938d21d53" -manifest_sha256 = "5ee596b004f9c633f044ed91530f9cdd60427e3828b38f237d327ca5161d42d0" +source_tree = "bd58272c2149b87ee0070d16578d144e1e12aaab" +manifest_sha256 = "299a3bc6733ff7d84bc398e68f0b97013e80b2fa551ce2f29029d5876403b6cf" source_date_epoch = 1787871027 [foundation] diff --git a/TeraFFI/source/aarch64-apple-darwin.json b/TeraFFI/source/aarch64-apple-darwin.json @@ -607,10 +607,10 @@ "sha256": "b5d852f6c0afc97284fd322c8f1b1214533ea8e3df05fe550aaa9eaac64a0d12" }, "core/crates/tera_core/src/runtime/product_surface.rs": { - "bytes": 5929, - "git_blob": "d3103eb8f287287126ce6759a2a07780eeef7059", + "bytes": 6019, + "git_blob": "90780f31f2d3153fd3d526e63b3d32ce4a133306", "mode": "100644", - "sha256": "59d0d86d70abb41dd73594b4131eb5d63c0a14b33d855b3e85738f80e4452e92" + "sha256": "3a922f3dfb5733f624074197cbfee5350f43f547fe19b0a4a4b7b00d544e57e2" }, "core/crates/tera_core/src/runtime/product_surface/authoring.rs": { "bytes": 11282, @@ -619,16 +619,16 @@ "sha256": "7fc683ca39eb27d52dcdd26bcdd9ade96e36a0b1f83c2135b3a1ce48515f988e" }, "core/crates/tera_core/src/runtime/product_surface/context.rs": { - "bytes": 14019, - "git_blob": "79521d4a0fcfca0b4ead3523fe65f1d7b8b90931", + "bytes": 14063, + "git_blob": "5e3133a521209c8424b63b0cbe149858161aeb4f", "mode": "100644", - "sha256": "4b74089519dfa290173cb7604826392f2a80140190c954a24d28cbb77c4f0756" + "sha256": "e2bba7483e5c22413d621fdc7e18e79648b2afaab67078ffaba18f9737e2f120" }, "core/crates/tera_core/src/runtime/product_surface/cursor.rs": { - "bytes": 16389, - "git_blob": "263439eed947ff248e56b4d06293c0bee2f6c278", + "bytes": 16205, + "git_blob": "0a389e6415da2b6b14cc5b338af9ec09361809cc", "mode": "100644", - "sha256": "90b9a3d333910d06dfc93b25ccbb6dee821422f6306d811f9a8dc5375e4f5831" + "sha256": "b8fe77aafce5b60e02a5e020c774a1396973deb48fbc108745fc4babc20318c6" }, "core/crates/tera_core/src/runtime/product_surface/identity.rs": { "bytes": 6310, @@ -636,6 +636,12 @@ "mode": "100644", "sha256": "2f15da2b1f648b417785e2a12eff8e984ab7e8d6c6b87fb9cb5552e195d8a3ec" }, + "core/crates/tera_core/src/runtime/product_surface/local_network_id.rs": { + "bytes": 2073, + "git_blob": "a4cc9eea655532f541fcb7753f44d57c047925eb", + "mode": "100644", + "sha256": "05c360696b21b238fb416cf3b8b00207f7bf0d4188eb5ed2d2c609d5ab24cd2b" + }, "core/crates/tera_core/src/runtime/product_surface/media.rs": { "bytes": 75981, "git_blob": "89d2d07cb9af04c9cce3a7371e5519a3720444ec", @@ -649,10 +655,10 @@ "sha256": "86271be5ec0ae8ef6906752272f5e091415b0581ed717809f50a973919275a1e" }, "core/crates/tera_core/src/runtime/product_surface/outbox.rs": { - "bytes": 162394, - "git_blob": "8df4b906247e6c7dc0ac62aefbe8245395a86e11", + "bytes": 163082, + "git_blob": "48d3502d1cc1e885ba42a071cf83fa927f99fdfc", "mode": "100644", - "sha256": "84d865c12d51ae5d14361da22b2e9905334ca7452b1bca397d180d4878eea896" + "sha256": "297975a76245389c1b799cd9eb4c9d773d5ddd75b9c8faa07b06d6a866efeec9" }, "core/crates/tera_core/src/runtime/product_surface/projection.rs": { "bytes": 23469, @@ -673,10 +679,10 @@ "sha256": "18a82705b14e35ac716f4e9ae4ac39545c7ec9a8a4ded898e8c8febfa054938b" }, "core/crates/tera_core/src/runtime/product_surface/today.rs": { - "bytes": 134099, - "git_blob": "d5f3f2ae2bc87625be1b8a37dae6511a7cd5cff3", + "bytes": 134211, + "git_blob": "03554393477cf579708bcacfe60286c058ce68a3", "mode": "100644", - "sha256": "a666070309bf7d16a26628246a9af18440051fcbff106aa9dae0017a4a8856a8" + "sha256": "bb54b4559249028ad9631f0af6a7db41a5f6062211f7f3691969da37fbc8e20e" }, "core/crates/tera_core/src/runtime/sdk.rs": { "bytes": 20071, @@ -721,10 +727,10 @@ "sha256": "146992d4a39f0c2688b5d1f49509b1bf02765f885b00a842fc698690f3f45197" }, "core/crates/tera_ffi/src/dto.rs": { - "bytes": 110132, - "git_blob": "f937e4d522622234af0e95c1467e76bd2fed85cc", + "bytes": 110139, + "git_blob": "fececbed232acd192a261db05bb66dada6837d1d", "mode": "100644", - "sha256": "660551ee9b5aa18882290335cec8a849c0e749fe8753f2a9a918e1328b9aa169" + "sha256": "0f19070bd3beb9dbbc871ceca44c30e590ea2d38e875339fe9dbcde749b3aa6e" }, "core/crates/tera_ffi/src/error.rs": { "bytes": 18086, @@ -780,6 +786,12 @@ "mode": "100644", "sha256": "92f6d3622741f6619a409cc064f88559c5f1391eb4c8253d13420bce653ad397" }, + "core/crates/tera_ffi/tests/identity_contract.rs": { + "bytes": 2317, + "git_blob": "69c30c9237fb74ecb2b1410fc094bfa9e10b09bf", + "mode": "100644", + "sha256": "4cb6aa1ff8b006d8a8710e807c9965d47fdd3e72eca67b28a5d909696574f161" + }, "core/crates/tera_ffi/tests/local_mvp_real_io.rs": { "bytes": 34183, "git_blob": "0b385862df5ff1ae258891da221653f625eb5a25", @@ -1153,13 +1165,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 109807, - "git_blob": "df2cff39e67e170db7c57b3c4d5e57149dbc9c69", + "bytes": 109918, + "git_blob": "6418ea4bf4ba8ecce2fbee409cc044f0a05980b4", "mode": "100644", - "sha256": "84a114a8803778895a504655d0836ffd8d71dfc8953b06911ca78f93f711be6c" + "sha256": "f309e862b20c7ecf9ea43d6edc8e6b9a7ed2b6883aaf70863ba51ef5cbe6a0d0" } }, "policy": "staged_inputs", - "tree": "ed13f3efac75566f8f21f1fcc0f9cea938d21d53" + "tree": "bd58272c2149b87ee0070d16578d144e1e12aaab" } } diff --git a/TeraFFI/source/aarch64-apple-ios-sim.json b/TeraFFI/source/aarch64-apple-ios-sim.json @@ -603,10 +603,10 @@ "sha256": "b5d852f6c0afc97284fd322c8f1b1214533ea8e3df05fe550aaa9eaac64a0d12" }, "core/crates/tera_core/src/runtime/product_surface.rs": { - "bytes": 5929, - "git_blob": "d3103eb8f287287126ce6759a2a07780eeef7059", + "bytes": 6019, + "git_blob": "90780f31f2d3153fd3d526e63b3d32ce4a133306", "mode": "100644", - "sha256": "59d0d86d70abb41dd73594b4131eb5d63c0a14b33d855b3e85738f80e4452e92" + "sha256": "3a922f3dfb5733f624074197cbfee5350f43f547fe19b0a4a4b7b00d544e57e2" }, "core/crates/tera_core/src/runtime/product_surface/authoring.rs": { "bytes": 11282, @@ -615,16 +615,16 @@ "sha256": "7fc683ca39eb27d52dcdd26bcdd9ade96e36a0b1f83c2135b3a1ce48515f988e" }, "core/crates/tera_core/src/runtime/product_surface/context.rs": { - "bytes": 14019, - "git_blob": "79521d4a0fcfca0b4ead3523fe65f1d7b8b90931", + "bytes": 14063, + "git_blob": "5e3133a521209c8424b63b0cbe149858161aeb4f", "mode": "100644", - "sha256": "4b74089519dfa290173cb7604826392f2a80140190c954a24d28cbb77c4f0756" + "sha256": "e2bba7483e5c22413d621fdc7e18e79648b2afaab67078ffaba18f9737e2f120" }, "core/crates/tera_core/src/runtime/product_surface/cursor.rs": { - "bytes": 16389, - "git_blob": "263439eed947ff248e56b4d06293c0bee2f6c278", + "bytes": 16205, + "git_blob": "0a389e6415da2b6b14cc5b338af9ec09361809cc", "mode": "100644", - "sha256": "90b9a3d333910d06dfc93b25ccbb6dee821422f6306d811f9a8dc5375e4f5831" + "sha256": "b8fe77aafce5b60e02a5e020c774a1396973deb48fbc108745fc4babc20318c6" }, "core/crates/tera_core/src/runtime/product_surface/identity.rs": { "bytes": 6310, @@ -632,6 +632,12 @@ "mode": "100644", "sha256": "2f15da2b1f648b417785e2a12eff8e984ab7e8d6c6b87fb9cb5552e195d8a3ec" }, + "core/crates/tera_core/src/runtime/product_surface/local_network_id.rs": { + "bytes": 2073, + "git_blob": "a4cc9eea655532f541fcb7753f44d57c047925eb", + "mode": "100644", + "sha256": "05c360696b21b238fb416cf3b8b00207f7bf0d4188eb5ed2d2c609d5ab24cd2b" + }, "core/crates/tera_core/src/runtime/product_surface/media.rs": { "bytes": 75981, "git_blob": "89d2d07cb9af04c9cce3a7371e5519a3720444ec", @@ -645,10 +651,10 @@ "sha256": "86271be5ec0ae8ef6906752272f5e091415b0581ed717809f50a973919275a1e" }, "core/crates/tera_core/src/runtime/product_surface/outbox.rs": { - "bytes": 162394, - "git_blob": "8df4b906247e6c7dc0ac62aefbe8245395a86e11", + "bytes": 163082, + "git_blob": "48d3502d1cc1e885ba42a071cf83fa927f99fdfc", "mode": "100644", - "sha256": "84d865c12d51ae5d14361da22b2e9905334ca7452b1bca397d180d4878eea896" + "sha256": "297975a76245389c1b799cd9eb4c9d773d5ddd75b9c8faa07b06d6a866efeec9" }, "core/crates/tera_core/src/runtime/product_surface/projection.rs": { "bytes": 23469, @@ -669,10 +675,10 @@ "sha256": "18a82705b14e35ac716f4e9ae4ac39545c7ec9a8a4ded898e8c8febfa054938b" }, "core/crates/tera_core/src/runtime/product_surface/today.rs": { - "bytes": 134099, - "git_blob": "d5f3f2ae2bc87625be1b8a37dae6511a7cd5cff3", + "bytes": 134211, + "git_blob": "03554393477cf579708bcacfe60286c058ce68a3", "mode": "100644", - "sha256": "a666070309bf7d16a26628246a9af18440051fcbff106aa9dae0017a4a8856a8" + "sha256": "bb54b4559249028ad9631f0af6a7db41a5f6062211f7f3691969da37fbc8e20e" }, "core/crates/tera_core/src/runtime/sdk.rs": { "bytes": 20071, @@ -717,10 +723,10 @@ "sha256": "146992d4a39f0c2688b5d1f49509b1bf02765f885b00a842fc698690f3f45197" }, "core/crates/tera_ffi/src/dto.rs": { - "bytes": 110132, - "git_blob": "f937e4d522622234af0e95c1467e76bd2fed85cc", + "bytes": 110139, + "git_blob": "fececbed232acd192a261db05bb66dada6837d1d", "mode": "100644", - "sha256": "660551ee9b5aa18882290335cec8a849c0e749fe8753f2a9a918e1328b9aa169" + "sha256": "0f19070bd3beb9dbbc871ceca44c30e590ea2d38e875339fe9dbcde749b3aa6e" }, "core/crates/tera_ffi/src/error.rs": { "bytes": 18086, @@ -776,6 +782,12 @@ "mode": "100644", "sha256": "92f6d3622741f6619a409cc064f88559c5f1391eb4c8253d13420bce653ad397" }, + "core/crates/tera_ffi/tests/identity_contract.rs": { + "bytes": 2317, + "git_blob": "69c30c9237fb74ecb2b1410fc094bfa9e10b09bf", + "mode": "100644", + "sha256": "4cb6aa1ff8b006d8a8710e807c9965d47fdd3e72eca67b28a5d909696574f161" + }, "core/crates/tera_ffi/tests/local_mvp_real_io.rs": { "bytes": 34183, "git_blob": "0b385862df5ff1ae258891da221653f625eb5a25", @@ -1149,13 +1161,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 109807, - "git_blob": "df2cff39e67e170db7c57b3c4d5e57149dbc9c69", + "bytes": 109918, + "git_blob": "6418ea4bf4ba8ecce2fbee409cc044f0a05980b4", "mode": "100644", - "sha256": "84a114a8803778895a504655d0836ffd8d71dfc8953b06911ca78f93f711be6c" + "sha256": "f309e862b20c7ecf9ea43d6edc8e6b9a7ed2b6883aaf70863ba51ef5cbe6a0d0" } }, "policy": "staged_inputs", - "tree": "ed13f3efac75566f8f21f1fcc0f9cea938d21d53" + "tree": "bd58272c2149b87ee0070d16578d144e1e12aaab" } } diff --git a/TeraFFI/source/aarch64-apple-ios.json b/TeraFFI/source/aarch64-apple-ios.json @@ -603,10 +603,10 @@ "sha256": "b5d852f6c0afc97284fd322c8f1b1214533ea8e3df05fe550aaa9eaac64a0d12" }, "core/crates/tera_core/src/runtime/product_surface.rs": { - "bytes": 5929, - "git_blob": "d3103eb8f287287126ce6759a2a07780eeef7059", + "bytes": 6019, + "git_blob": "90780f31f2d3153fd3d526e63b3d32ce4a133306", "mode": "100644", - "sha256": "59d0d86d70abb41dd73594b4131eb5d63c0a14b33d855b3e85738f80e4452e92" + "sha256": "3a922f3dfb5733f624074197cbfee5350f43f547fe19b0a4a4b7b00d544e57e2" }, "core/crates/tera_core/src/runtime/product_surface/authoring.rs": { "bytes": 11282, @@ -615,16 +615,16 @@ "sha256": "7fc683ca39eb27d52dcdd26bcdd9ade96e36a0b1f83c2135b3a1ce48515f988e" }, "core/crates/tera_core/src/runtime/product_surface/context.rs": { - "bytes": 14019, - "git_blob": "79521d4a0fcfca0b4ead3523fe65f1d7b8b90931", + "bytes": 14063, + "git_blob": "5e3133a521209c8424b63b0cbe149858161aeb4f", "mode": "100644", - "sha256": "4b74089519dfa290173cb7604826392f2a80140190c954a24d28cbb77c4f0756" + "sha256": "e2bba7483e5c22413d621fdc7e18e79648b2afaab67078ffaba18f9737e2f120" }, "core/crates/tera_core/src/runtime/product_surface/cursor.rs": { - "bytes": 16389, - "git_blob": "263439eed947ff248e56b4d06293c0bee2f6c278", + "bytes": 16205, + "git_blob": "0a389e6415da2b6b14cc5b338af9ec09361809cc", "mode": "100644", - "sha256": "90b9a3d333910d06dfc93b25ccbb6dee821422f6306d811f9a8dc5375e4f5831" + "sha256": "b8fe77aafce5b60e02a5e020c774a1396973deb48fbc108745fc4babc20318c6" }, "core/crates/tera_core/src/runtime/product_surface/identity.rs": { "bytes": 6310, @@ -632,6 +632,12 @@ "mode": "100644", "sha256": "2f15da2b1f648b417785e2a12eff8e984ab7e8d6c6b87fb9cb5552e195d8a3ec" }, + "core/crates/tera_core/src/runtime/product_surface/local_network_id.rs": { + "bytes": 2073, + "git_blob": "a4cc9eea655532f541fcb7753f44d57c047925eb", + "mode": "100644", + "sha256": "05c360696b21b238fb416cf3b8b00207f7bf0d4188eb5ed2d2c609d5ab24cd2b" + }, "core/crates/tera_core/src/runtime/product_surface/media.rs": { "bytes": 75981, "git_blob": "89d2d07cb9af04c9cce3a7371e5519a3720444ec", @@ -645,10 +651,10 @@ "sha256": "86271be5ec0ae8ef6906752272f5e091415b0581ed717809f50a973919275a1e" }, "core/crates/tera_core/src/runtime/product_surface/outbox.rs": { - "bytes": 162394, - "git_blob": "8df4b906247e6c7dc0ac62aefbe8245395a86e11", + "bytes": 163082, + "git_blob": "48d3502d1cc1e885ba42a071cf83fa927f99fdfc", "mode": "100644", - "sha256": "84d865c12d51ae5d14361da22b2e9905334ca7452b1bca397d180d4878eea896" + "sha256": "297975a76245389c1b799cd9eb4c9d773d5ddd75b9c8faa07b06d6a866efeec9" }, "core/crates/tera_core/src/runtime/product_surface/projection.rs": { "bytes": 23469, @@ -669,10 +675,10 @@ "sha256": "18a82705b14e35ac716f4e9ae4ac39545c7ec9a8a4ded898e8c8febfa054938b" }, "core/crates/tera_core/src/runtime/product_surface/today.rs": { - "bytes": 134099, - "git_blob": "d5f3f2ae2bc87625be1b8a37dae6511a7cd5cff3", + "bytes": 134211, + "git_blob": "03554393477cf579708bcacfe60286c058ce68a3", "mode": "100644", - "sha256": "a666070309bf7d16a26628246a9af18440051fcbff106aa9dae0017a4a8856a8" + "sha256": "bb54b4559249028ad9631f0af6a7db41a5f6062211f7f3691969da37fbc8e20e" }, "core/crates/tera_core/src/runtime/sdk.rs": { "bytes": 20071, @@ -717,10 +723,10 @@ "sha256": "146992d4a39f0c2688b5d1f49509b1bf02765f885b00a842fc698690f3f45197" }, "core/crates/tera_ffi/src/dto.rs": { - "bytes": 110132, - "git_blob": "f937e4d522622234af0e95c1467e76bd2fed85cc", + "bytes": 110139, + "git_blob": "fececbed232acd192a261db05bb66dada6837d1d", "mode": "100644", - "sha256": "660551ee9b5aa18882290335cec8a849c0e749fe8753f2a9a918e1328b9aa169" + "sha256": "0f19070bd3beb9dbbc871ceca44c30e590ea2d38e875339fe9dbcde749b3aa6e" }, "core/crates/tera_ffi/src/error.rs": { "bytes": 18086, @@ -776,6 +782,12 @@ "mode": "100644", "sha256": "92f6d3622741f6619a409cc064f88559c5f1391eb4c8253d13420bce653ad397" }, + "core/crates/tera_ffi/tests/identity_contract.rs": { + "bytes": 2317, + "git_blob": "69c30c9237fb74ecb2b1410fc094bfa9e10b09bf", + "mode": "100644", + "sha256": "4cb6aa1ff8b006d8a8710e807c9965d47fdd3e72eca67b28a5d909696574f161" + }, "core/crates/tera_ffi/tests/local_mvp_real_io.rs": { "bytes": 34183, "git_blob": "0b385862df5ff1ae258891da221653f625eb5a25", @@ -1149,13 +1161,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 109807, - "git_blob": "df2cff39e67e170db7c57b3c4d5e57149dbc9c69", + "bytes": 109918, + "git_blob": "6418ea4bf4ba8ecce2fbee409cc044f0a05980b4", "mode": "100644", - "sha256": "84a114a8803778895a504655d0836ffd8d71dfc8953b06911ca78f93f711be6c" + "sha256": "f309e862b20c7ecf9ea43d6edc8e6b9a7ed2b6883aaf70863ba51ef5cbe6a0d0" } }, "policy": "staged_inputs", - "tree": "ed13f3efac75566f8f21f1fcc0f9cea938d21d53" + "tree": "bd58272c2149b87ee0070d16578d144e1e12aaab" } } diff --git a/TeraTests/TeraRuntimeClientFixtures.swift b/TeraTests/TeraRuntimeClientFixtures.swift @@ -0,0 +1,229 @@ +import Foundation +@testable import TeraApp + +struct TestRuntimeSigner: TeraRuntimeSigner { + func availability() async -> TeraRuntimeSignerAvailability { + .ready + } + + func sign(_: TeraRuntimeSigningRequest) async -> TeraRuntimeSigningOutcome { + .failed + } +} + +actor RuntimeHarness { + private var configurations: [TeraRuntimeLaunchConfiguration] = [] + + func launchConfigurations() -> [TeraRuntimeLaunchConfiguration] { + configurations + } + + private let startDelayNanoseconds: UInt64 + private let snapshotDelayNanoseconds: UInt64 + private let shutdownDelayNanoseconds: UInt64 + private let shutdownFailure: TeraRuntimeFailure? + private var subscriptionFailures: Int + private var starts = 0 + private var shutdowns = 0 + private var cancels = 0 + private var subscriptionAttempts = 0 + private var receivers: [UUID: @Sendable (TeraRuntimeChange) async -> Void] = [:] + + init( + startDelayNanoseconds: UInt64 = 0, + snapshotDelayNanoseconds: UInt64 = 0, + shutdownDelayNanoseconds: UInt64 = 10_000_000, + subscriptionFailures: Int = 0, + shutdownFailure: TeraRuntimeFailure? = nil + ) { + self.startDelayNanoseconds = startDelayNanoseconds + self.snapshotDelayNanoseconds = snapshotDelayNanoseconds + self.shutdownDelayNanoseconds = shutdownDelayNanoseconds + self.subscriptionFailures = subscriptionFailures + self.shutdownFailure = shutdownFailure + } + + func start( + configuration: TeraRuntimeLaunchConfiguration + ) async throws -> TeraRuntimeBackendStart { + configurations.append(configuration) + starts += 1 + if startDelayNanoseconds > 0 { + await Task.detached { [startDelayNanoseconds] in + try? await Task.sleep(nanoseconds: startDelayNanoseconds) + }.value + } + let backend = TestRuntimeBackend( + harness: self, + publicKeyHex: configuration.publicKeyHex, + snapshotDelayNanoseconds: snapshotDelayNanoseconds, + shutdownDelayNanoseconds: shutdownDelayNanoseconds, + shutdownFailure: shutdownFailure + ) + return await TeraRuntimeBackendStart( + backend: backend, + snapshot: backend.snapshotValue() + ) + } + + func addReceiver( + _ receive: @escaping @Sendable (TeraRuntimeChange) async -> Void + ) throws -> UUID { + subscriptionAttempts += 1 + if subscriptionFailures > 0 { + subscriptionFailures -= 1 + throw TeraRuntimeFailure.local( + operation: "test.subscribe", + code: "test.subscribe_retryable", + safeMessage: "The test subscription is temporarily unavailable." + ) + } + let id = UUID() + receivers[id] = receive + return id + } + + func removeReceiver(id: UUID) { + if receivers.removeValue(forKey: id) != nil { + cancels += 1 + } + } + + func emit(_ change: TeraRuntimeChange) async { + for receive in receivers.values { + await receive(change) + } + } + + func recordShutdown() { + shutdowns += 1 + } + + func startCount() -> Int { + starts + } + + func shutdownCount() -> Int { + shutdowns + } + + func cancelCount() -> Int { + cancels + } + + func subscriptionAttemptCount() -> Int { + subscriptionAttempts + } +} + +private actor TestRuntimeBackend: TeraRuntimeBackend { + private let harness: RuntimeHarness + private let snapshotValueStored: TeraRuntimeSnapshot + private let snapshotDelayNanoseconds: UInt64 + private let shutdownDelayNanoseconds: UInt64 + private let shutdownFailure: TeraRuntimeFailure? + private var closed = false + + init( + harness: RuntimeHarness, + publicKeyHex: String, + snapshotDelayNanoseconds: UInt64, + shutdownDelayNanoseconds: UInt64, + shutdownFailure: TeraRuntimeFailure? + ) { + self.harness = harness + self.snapshotDelayNanoseconds = snapshotDelayNanoseconds + self.shutdownDelayNanoseconds = shutdownDelayNanoseconds + self.shutdownFailure = shutdownFailure + snapshotValueStored = TeraRuntimeSnapshot( + identity: TeraRuntimeIdentity( + publicKeyHex: publicKeyHex, + hostSignerConfigured: true + ), + relay: nil, + blossomConfiguration: nil, + blossomEvidence: nil, + crateName: "tera_ffi", + crateVersion: "0.1.0-alpha", + isClosed: false + ) + } + + func snapshotValue() -> TeraRuntimeSnapshot { + snapshotValueStored + } + + func snapshot() async throws -> TeraRuntimeSnapshot { + if snapshotDelayNanoseconds > 0 { + await Task.detached { [snapshotDelayNanoseconds] in + try? await Task.sleep(nanoseconds: snapshotDelayNanoseconds) + }.value + } + if closed { + throw TeraRuntimeFailure.local( + operation: "test.snapshot", + code: "test.closed", + safeMessage: "The test runtime is closed." + ) + } + return snapshotValueStored + } + + func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage { + throw TeraRuntimeFailure.local( + operation: "test.today.page", + code: "test.unsupported", + safeMessage: "Today is not configured for this lifecycle test." + ) + } + + func refreshToday( + context _: TeraLocalNetwork, + nowUnixSeconds _: UInt64, + update _: TeraTodayProjectionUpdate + ) throws -> TeraTodayRefreshReceipt { + throw TeraRuntimeFailure.local( + operation: "test.today.refresh", + code: "test.unsupported", + safeMessage: "Today is not configured for this lifecycle test." + ) + } + + func subscribe( + bufferCapacity _: Int, + receive: @escaping @Sendable (TeraRuntimeChange) async -> Void + ) async throws -> any TeraRuntimeSubscriptionToken { + let id = try await harness.addReceiver(receive) + return TestSubscriptionToken(harness: harness, id: id) + } + + func shutdown() async throws -> TeraRuntimeShutdownReceipt { + await harness.recordShutdown() + await Task.detached { [shutdownDelayNanoseconds] in + try? await Task.sleep(nanoseconds: shutdownDelayNanoseconds) + }.value + if let shutdownFailure { + throw shutdownFailure + } + let alreadyClosed = closed + closed = true + return TeraRuntimeShutdownReceipt(state: "closed", alreadyClosed: alreadyClosed) + } +} + +private actor TestSubscriptionToken: TeraRuntimeSubscriptionToken { + private let harness: RuntimeHarness + private let id: UUID + private var cancelled = false + + init(harness: RuntimeHarness, id: UUID) { + self.harness = harness + self.id = id + } + + func cancel() async { + guard !cancelled else { return } + cancelled = true + await harness.removeReceiver(id: id) + } +} diff --git a/TeraTests/TeraRuntimeClientTests.swift b/TeraTests/TeraRuntimeClientTests.swift @@ -20,7 +20,7 @@ final class TeraRuntimeClientTests: XCTestCase { let startCount = await harness.startCount() let lifecycle = await client.lifecycle() XCTAssertEqual(startCount, 1) - XCTAssertEqual(lifecycle, .running(generation: 1)) + XCTAssertEqual(lifecycle, .running(generation: TeraSessionGeneration(rawValue: 1))) _ = try await client.stop() } @@ -38,14 +38,14 @@ final class TeraRuntimeClientTests: XCTestCase { var firstIterator = first.makeAsyncIterator() var secondIterator = second.makeAsyncIterator() let firstValues = await [firstIterator.next(), firstIterator.next()].compactMap { - $0?.generation + $0?.generation.rawValue } let secondValues = await [ secondIterator.next(), secondIterator.next(), secondIterator.next(), secondIterator.next(), - ].compactMap { $0?.generation } + ].compactMap { $0?.generation.rawValue } XCTAssertEqual(firstValues, [9, 10]) XCTAssertEqual(secondValues, [7, 8, 9, 10]) @@ -252,7 +252,7 @@ final class TeraRuntimeClientTests: XCTestCase { XCTAssertEqual(failure.code, "ios.runtime.cancelled") } let lifecycle = await client.lifecycle() - XCTAssertEqual(lifecycle, .running(generation: 1)) + XCTAssertEqual(lifecycle, .running(generation: TeraSessionGeneration(rawValue: 1))) try await Task.sleep(nanoseconds: 80_000_000) let recoveredSnapshot = try await client.snapshot() XCTAssertEqual(recoveredSnapshot.identity.publicKeyHex, configuration.publicKeyHex) @@ -340,7 +340,7 @@ final class TeraRuntimeClientTests: XCTestCase { XCTAssertTrue(predicate()) } - private func makeConfiguration(generation: String) -> TeraRuntimeLaunchConfiguration { + func makeConfiguration(generation: String) -> TeraRuntimeLaunchConfiguration { TeraRuntimeLaunchConfiguration( applicationSupportDirectory: "/tmp/radroots-runtime-tests-\(generation)", publicKeyHex: String(repeating: generation, count: 32), @@ -370,229 +370,9 @@ final class TeraRuntimeClientTests: XCTestCase { private func change(generation: UInt64) -> TeraRuntimeChange { TeraRuntimeChange( schemaVersion: 1, - generation: generation, + generation: TeraProjectionRevision(rawValue: generation), kind: .today, entityID: "card-\(generation)" ) } } - -private struct TestRuntimeSigner: TeraRuntimeSigner { - func availability() async -> TeraRuntimeSignerAvailability { - .ready - } - - func sign(_: TeraRuntimeSigningRequest) async -> TeraRuntimeSigningOutcome { - .failed - } -} - -private actor RuntimeHarness { - private let startDelayNanoseconds: UInt64 - private let snapshotDelayNanoseconds: UInt64 - private let shutdownDelayNanoseconds: UInt64 - private let shutdownFailure: TeraRuntimeFailure? - private var subscriptionFailures: Int - private var starts = 0 - private var shutdowns = 0 - private var cancels = 0 - private var subscriptionAttempts = 0 - private var receivers: [UUID: @Sendable (TeraRuntimeChange) async -> Void] = [:] - - init( - startDelayNanoseconds: UInt64 = 0, - snapshotDelayNanoseconds: UInt64 = 0, - shutdownDelayNanoseconds: UInt64 = 10_000_000, - subscriptionFailures: Int = 0, - shutdownFailure: TeraRuntimeFailure? = nil - ) { - self.startDelayNanoseconds = startDelayNanoseconds - self.snapshotDelayNanoseconds = snapshotDelayNanoseconds - self.shutdownDelayNanoseconds = shutdownDelayNanoseconds - self.subscriptionFailures = subscriptionFailures - self.shutdownFailure = shutdownFailure - } - - func start( - configuration: TeraRuntimeLaunchConfiguration - ) async throws -> TeraRuntimeBackendStart { - starts += 1 - if startDelayNanoseconds > 0 { - await Task.detached { [startDelayNanoseconds] in - try? await Task.sleep(nanoseconds: startDelayNanoseconds) - }.value - } - let backend = TestRuntimeBackend( - harness: self, - publicKeyHex: configuration.publicKeyHex, - snapshotDelayNanoseconds: snapshotDelayNanoseconds, - shutdownDelayNanoseconds: shutdownDelayNanoseconds, - shutdownFailure: shutdownFailure - ) - return await TeraRuntimeBackendStart( - backend: backend, - snapshot: backend.snapshotValue() - ) - } - - func addReceiver( - _ receive: @escaping @Sendable (TeraRuntimeChange) async -> Void - ) throws -> UUID { - subscriptionAttempts += 1 - if subscriptionFailures > 0 { - subscriptionFailures -= 1 - throw TeraRuntimeFailure.local( - operation: "test.subscribe", - code: "test.subscribe_retryable", - safeMessage: "The test subscription is temporarily unavailable." - ) - } - let id = UUID() - receivers[id] = receive - return id - } - - func removeReceiver(id: UUID) { - if receivers.removeValue(forKey: id) != nil { - cancels += 1 - } - } - - func emit(_ change: TeraRuntimeChange) async { - for receive in receivers.values { - await receive(change) - } - } - - func recordShutdown() { - shutdowns += 1 - } - - func startCount() -> Int { - starts - } - - func shutdownCount() -> Int { - shutdowns - } - - func cancelCount() -> Int { - cancels - } - - func subscriptionAttemptCount() -> Int { - subscriptionAttempts - } -} - -private actor TestRuntimeBackend: TeraRuntimeBackend { - private let harness: RuntimeHarness - private let snapshotValueStored: TeraRuntimeSnapshot - private let snapshotDelayNanoseconds: UInt64 - private let shutdownDelayNanoseconds: UInt64 - private let shutdownFailure: TeraRuntimeFailure? - private var closed = false - - init( - harness: RuntimeHarness, - publicKeyHex: String, - snapshotDelayNanoseconds: UInt64, - shutdownDelayNanoseconds: UInt64, - shutdownFailure: TeraRuntimeFailure? - ) { - self.harness = harness - self.snapshotDelayNanoseconds = snapshotDelayNanoseconds - self.shutdownDelayNanoseconds = shutdownDelayNanoseconds - self.shutdownFailure = shutdownFailure - snapshotValueStored = TeraRuntimeSnapshot( - identity: TeraRuntimeIdentity( - publicKeyHex: publicKeyHex, - hostSignerConfigured: true - ), - relay: nil, - blossomConfiguration: nil, - blossomEvidence: nil, - crateName: "tera_ffi", - crateVersion: "0.1.0-alpha", - isClosed: false - ) - } - - func snapshotValue() -> TeraRuntimeSnapshot { - snapshotValueStored - } - - func snapshot() async throws -> TeraRuntimeSnapshot { - if snapshotDelayNanoseconds > 0 { - await Task.detached { [snapshotDelayNanoseconds] in - try? await Task.sleep(nanoseconds: snapshotDelayNanoseconds) - }.value - } - if closed { - throw TeraRuntimeFailure.local( - operation: "test.snapshot", - code: "test.closed", - safeMessage: "The test runtime is closed." - ) - } - return snapshotValueStored - } - - func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage { - throw TeraRuntimeFailure.local( - operation: "test.today.page", - code: "test.unsupported", - safeMessage: "Today is not configured for this lifecycle test." - ) - } - - func refreshToday( - context _: TeraLocalNetwork, - nowUnixSeconds _: UInt64, - update _: TeraTodayProjectionUpdate - ) throws -> TeraTodayRefreshReceipt { - throw TeraRuntimeFailure.local( - operation: "test.today.refresh", - code: "test.unsupported", - safeMessage: "Today is not configured for this lifecycle test." - ) - } - - func subscribe( - bufferCapacity _: Int, - receive: @escaping @Sendable (TeraRuntimeChange) async -> Void - ) async throws -> any TeraRuntimeSubscriptionToken { - let id = try await harness.addReceiver(receive) - return TestSubscriptionToken(harness: harness, id: id) - } - - func shutdown() async throws -> TeraRuntimeShutdownReceipt { - await harness.recordShutdown() - await Task.detached { [shutdownDelayNanoseconds] in - try? await Task.sleep(nanoseconds: shutdownDelayNanoseconds) - }.value - if let shutdownFailure { - throw shutdownFailure - } - let alreadyClosed = closed - closed = true - return TeraRuntimeShutdownReceipt(state: "closed", alreadyClosed: alreadyClosed) - } -} - -private actor TestSubscriptionToken: TeraRuntimeSubscriptionToken { - private let harness: RuntimeHarness - private let id: UUID - private var cancelled = false - - init(harness: RuntimeHarness, id: UUID) { - self.harness = harness - self.id = id - } - - func cancel() async { - guard !cancelled else { return } - cancelled = true - await harness.removeReceiver(id: id) - } -} diff --git a/TeraTests/TeraSessionGenerationTests.swift b/TeraTests/TeraSessionGenerationTests.swift @@ -0,0 +1,49 @@ +@testable import TeraApp +import XCTest + +final class TeraSessionGenerationTests: XCTestCase { + func testSessionAdvancesWithoutChangingExistingDiagnosticValues() throws { + let first = try TeraSessionGeneration.initial.next() + let second = try first.next() + XCTAssertNotEqual(first, second) + XCTAssertEqual(first.diagnosticValue, "1") + XCTAssertEqual(second.diagnosticValue, "2") + let identity = TeraRuntimeOperationIdentity(generation: first, sequence: 7, kind: .startup) + XCTAssertEqual(identity.rawValue, "ios-runtime-1-7-startup") + XCTAssertEqual(TeraProjectionRevision(rawValue: UInt64.max).rawValue, UInt64.max) + } + + func testExhaustionInvalidatesCallbacksAndNeverReusesAnEarlierSession() throws { + let beforeLast = TeraSessionGeneration(rawValue: UInt64.max - 1) + let last = try beforeLast.next() + XCTAssertEqual(last.diagnosticValue, String(UInt64.max)) + XCTAssertThrowsError(try last.next()) { error in + XCTAssertEqual(error as? TeraStateTransitionError, .generationOverflow) + } + let exhausted = last.invalidated() + XCTAssertNotEqual(exhausted, last) + XCTAssertNotEqual(exhausted, .initial) + XCTAssertFalse(exhausted.isActive) + XCTAssertEqual(exhausted.invalidated(), exhausted) + XCTAssertThrowsError(try exhausted.next()) + XCTAssertThrowsError(try exhausted.requireActive()) + } +} + +extension TeraRuntimeClientTests { + func testRestartChangesSessionButPreservesDurableLaunchScope() async throws { + let harness = RuntimeHarness() + let client = TeraRuntimeClient(factory: harness.start) + let configuration = makeConfiguration(generation: "06") + let first = try await client.start(configuration: configuration) + let firstLifecycle = await client.lifecycle() + _ = try await client.stop() + let second = try await client.start(configuration: configuration) + let secondLifecycle = await client.lifecycle() + XCTAssertNotEqual(firstLifecycle, secondLifecycle) + XCTAssertEqual(first.identity, second.identity) + let launches = await harness.launchConfigurations() + XCTAssertEqual(launches, [configuration, configuration]) + _ = try await client.stop() + } +} diff --git a/core/crates/tera_core/src/runtime/product_surface.rs b/core/crates/tera_core/src/runtime/product_surface.rs @@ -8,6 +8,7 @@ mod authoring; mod context; mod cursor; mod identity; +mod local_network_id; mod media; mod model; #[cfg(feature = "mobile-social")] @@ -28,6 +29,7 @@ pub use context::{ }; pub use cursor::{CursorError, CursorScope, TodayCursor, TodayCursorPosition}; pub use identity::{CARD_ID_SCHEMA_VERSION, CardId, CardIdError, CardSourceIdentity}; +pub use local_network_id::LocalNetworkId; #[cfg(feature = "mobile-social")] pub use media::Phase1LocalMediaArtifact; pub use media::{ @@ -135,7 +137,8 @@ mod tests { 1, ) .expect("network") - .id, + .id + .as_str(), "nearby" ); assert!( diff --git a/core/crates/tera_core/src/runtime/product_surface/context.rs b/core/crates/tera_core/src/runtime/product_surface/context.rs @@ -4,6 +4,8 @@ use radroots_transport_nostr::{RelayUrl, RelayUrlPolicy}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use super::LocalNetworkId; + const CONTEXT_TEXT_MAX_BYTES: usize = 256; const RELAY_URL_MAX_BYTES: usize = 2_048; @@ -11,7 +13,7 @@ const RELAY_URL_MAX_BYTES: usize = 2_048; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct LocalNetwork { - pub id: String, + pub id: LocalNetworkId, pub label: String, pub relay_urls: Vec<String>, pub locality: Option<String>, @@ -75,7 +77,7 @@ impl LocalNetwork { generation: u64, relay_policy: LocalNetworkRelayPolicy, ) -> Result<Self, LocalNetworkError> { - validate_text(&id, "id")?; + let id = LocalNetworkId::new(id)?; validate_text(&label, "label")?; if let Some(locality) = locality.as_deref() { validate_text(locality, "locality")?; diff --git a/core/crates/tera_core/src/runtime/product_surface/cursor.rs b/core/crates/tera_core/src/runtime/product_surface/cursor.rs @@ -1,19 +1,18 @@ use sha2::{Digest, Sha256}; use thiserror::Error; -use super::{CardId, ContextRank, TODAY_RANK_SCHEMA_VERSION, TodayRank}; +use super::{CardId, ContextRank, LocalNetworkId, TODAY_RANK_SCHEMA_VERSION, TodayRank}; use crate::runtime::product_surface::ranking::TODAY_RANK_ALGORITHM_VERSION; const CURSOR_PREFIX: &str = "rrtc1:"; const CURSOR_DOMAIN: &[u8] = b"radroots.today-cursor.v1\0"; const CURSOR_SCHEMA_VERSION: u16 = 1; -const MAX_CONTEXT_ID_BYTES: usize = 256; const FIXED_PAYLOAD_BYTES: usize = 2 + 2 + 2 + 2 + 8 + 8 + 32 + 8 + 1 + 1 + 8 + 32; const DIGEST_BYTES: usize = 32; #[derive(Clone, Debug, Eq, PartialEq)] pub struct CursorScope { - pub context_id: String, + pub context_id: LocalNetworkId, pub context_generation: u64, pub as_of: u64, pub store_generation: [u8; 32], @@ -28,7 +27,8 @@ impl CursorScope { store_generation: [u8; 32], projection_generation: u64, ) -> Result<Self, CursorError> { - validate_context_id(&context_id)?; + let context_id = + LocalNetworkId::new(context_id).map_err(|_| CursorError::InvalidContext)?; Ok(Self { context_id, context_generation, @@ -165,7 +165,8 @@ fn decode_payload(payload: &[u8]) -> Result<(CursorScope, TodayCursorPosition), let context_len = usize::from(decoder.u16()?); let context_id = core::str::from_utf8(decoder.bytes(context_len)?).map_err(|_| CursorError::Malformed)?; - validate_context_id(context_id)?; + let context_id = + LocalNetworkId::new(context_id.to_owned()).map_err(|_| CursorError::InvalidContext)?; let context_generation = decoder.u64()?; let as_of = decoder.u64()?; let store_generation = decoder.array_32()?; @@ -183,7 +184,7 @@ fn decode_payload(payload: &[u8]) -> Result<(CursorScope, TodayCursorPosition), } Ok(( CursorScope { - context_id: context_id.to_owned(), + context_id, context_generation, as_of, store_generation, @@ -202,17 +203,6 @@ fn decode_payload(payload: &[u8]) -> Result<(CursorScope, TodayCursorPosition), )) } -fn validate_context_id(value: &str) -> Result<(), CursorError> { - if value.is_empty() - || value.len() > MAX_CONTEXT_ID_BYTES - || value.trim() != value - || value.chars().any(char::is_control) - { - return Err(CursorError::InvalidContext); - } - Ok(()) -} - fn cursor_digest(payload: &[u8]) -> [u8; 32] { let mut digest = Sha256::new(); digest.update(CURSOR_DOMAIN); diff --git a/core/crates/tera_core/src/runtime/product_surface/local_network_id.rs b/core/crates/tera_core/src/runtime/product_surface/local_network_id.rs @@ -0,0 +1,73 @@ +use serde::{Deserialize, Serialize}; + +use super::LocalNetworkError; + +/// Stable local context identity, independent of any presentation session. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(try_from = "String", into = "String")] +pub struct LocalNetworkId(String); + +impl LocalNetworkId { + pub fn new(value: String) -> Result<Self, LocalNetworkError> { + if value.is_empty() + || value.len() > 256 + || value.trim() != value + || value.chars().any(char::is_control) + { + return Err(LocalNetworkError::InvalidText { field: "id" }); + } + Ok(Self(value)) + } + + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl TryFrom<String> for LocalNetworkId { + type Error = LocalNetworkError; + + fn try_from(value: String) -> Result<Self, Self::Error> { + Self::new(value) + } +} + +impl From<LocalNetworkId> for String { + fn from(value: LocalNetworkId) -> Self { + value.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stable_context_wire_value_round_trips_without_a_session_generation() { + for value in ["nearby".to_owned(), "é".repeat(128)] { + let id = LocalNetworkId::new(value.clone()).unwrap(); + let wire = serde_json::to_string(&id).unwrap(); + assert_eq!(wire, serde_json::to_string(&value).unwrap()); + assert_eq!(serde_json::from_str::<LocalNetworkId>(&wire).unwrap(), id); + assert_eq!(String::from(id), value); + } + } + + #[test] + fn constructors_and_deserialization_enforce_the_same_byte_bounds() { + for value in [ + "".into(), + " nearby".into(), + "x\u{7f}".into(), + "é".repeat(129), + ] { + assert!(LocalNetworkId::new(value.clone()).is_err()); + let wire = serde_json::to_string(&value).unwrap(); + assert!(serde_json::from_str::<LocalNetworkId>(&wire).is_err()); + } + } +} diff --git a/core/crates/tera_core/src/runtime/product_surface/outbox.rs b/core/crates/tera_core/src/runtime/product_surface/outbox.rs @@ -441,14 +441,15 @@ impl Phase1QueuePolicy { /// Existing durable draft selected for an optimistic replacement. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Phase1ExistingDraft { - draft_id: [u8; 16], - expected_revision: u64, + draft_id: AuthoredDraftId, + expected_revision: AuthoredDraftRevision, } impl Phase1ExistingDraft { pub fn new(draft_id: [u8; 16], expected_revision: u64) -> Result<Self, Phase1DraftError> { - AuthoredDraftId::new(draft_id).map_err(|_| Phase1DraftError::InvalidDraft)?; - AuthoredDraftRevision::new(expected_revision) + let draft_id = + AuthoredDraftId::new(draft_id).map_err(|_| Phase1DraftError::InvalidDraft)?; + let expected_revision = AuthoredDraftRevision::new(expected_revision) .map_err(|_| Phase1DraftError::RevisionConflict)?; Ok(Self { draft_id, @@ -491,14 +492,15 @@ impl Phase1AddIntent { /// cancellation are derived from the active typed Rust transport profile. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Phase1QueueIntent { - draft_id: [u8; 16], - expected_revision: u64, + draft_id: AuthoredDraftId, + expected_revision: AuthoredDraftRevision, } impl Phase1QueueIntent { pub fn new(draft_id: [u8; 16], expected_revision: u64) -> Result<Self, Phase1DraftError> { - AuthoredDraftId::new(draft_id).map_err(|_| Phase1DraftError::InvalidDraft)?; - AuthoredDraftRevision::new(expected_revision) + let draft_id = + AuthoredDraftId::new(draft_id).map_err(|_| Phase1DraftError::InvalidDraft)?; + let expected_revision = AuthoredDraftRevision::new(expected_revision) .map_err(|_| Phase1DraftError::RevisionConflict)?; Ok(Self { draft_id, @@ -510,8 +512,8 @@ impl Phase1QueueIntent { /// Bounded exact-byte input for one Rust-planned Blossom upload attempt. #[derive(Clone)] pub struct Phase1UploadIntent { - draft_id: [u8; 16], - expected_revision: u64, + draft_id: AuthoredDraftId, + expected_revision: AuthoredDraftRevision, bytes: Arc<[u8]>, media_type: MediaType, dimensions: radroots_sdk::transport::BlossomImageDimensions, @@ -526,8 +528,9 @@ impl Phase1UploadIntent { width: u32, height: u32, ) -> Result<Self, Phase1DraftError> { - AuthoredDraftId::new(draft_id).map_err(|_| Phase1DraftError::InvalidDraft)?; - AuthoredDraftRevision::new(expected_revision) + let draft_id = + AuthoredDraftId::new(draft_id).map_err(|_| Phase1DraftError::InvalidDraft)?; + let expected_revision = AuthoredDraftRevision::new(expected_revision) .map_err(|_| Phase1DraftError::RevisionConflict)?; if bytes.is_empty() { return Err(Phase1DraftError::InvalidMedia); @@ -550,8 +553,8 @@ pub struct Phase1UploadPlan { pub authorization_content: String, pub authorization_created_at_unix_s: u64, pub authorization_lifetime_seconds: u64, - pub operation_id: [u8; 16], - pub artifact_id: [u8; 16], + pub operation_id: SigningOperationId, + pub artifact_id: AuthoredArtifactId, pub signing_deadline_unix_ms: u64, pub cancellation: Phase1CancellationPolicy, pub updated_at_unix_ms: u64, @@ -863,8 +866,10 @@ impl Phase1UploadPlan { authorization_created_at_unix_s: now_unix_s .saturating_sub(BLOSSOM_AUTHORIZATION_BACKDATE_SECONDS), authorization_lifetime_seconds: BLOSSOM_AUTHORIZATION_LIFETIME_SECONDS, - operation_id, - artifact_id, + operation_id: SigningOperationId::new(operation_id) + .map_err(|_| Phase1DraftError::InvalidDraft)?, + artifact_id: AuthoredArtifactId::new(artifact_id) + .map_err(|_| Phase1DraftError::InvalidDraft)?, signing_deadline_unix_ms: now_unix_ms .checked_add(BLOSSOM_SIGNING_TIMEOUT_MS) .ok_or(Phase1DraftError::DeadlineOverflow)?, @@ -1389,7 +1394,10 @@ impl TeraRuntime { let now_unix_ms = phase1_operation_now_unix_ms()?; let authored_at_unix_s = now_unix_ms / 1_000; let (draft_id, expected_revision) = match intent.existing { - Some(existing) => (existing.draft_id, Some(existing.expected_revision)), + Some(existing) => ( + *existing.draft_id.as_bytes(), + Some(existing.expected_revision.get()), + ), None => (phase1_random_id()?, None), }; self.phase1_save_draft_with_form( @@ -1413,8 +1421,8 @@ impl TeraRuntime { let now_unix_ms = phase1_operation_now_unix_ms()?; let policy = self.active_queue_policy(now_unix_ms)?; self.phase1_queue_draft( - intent.draft_id, - intent.expected_revision, + *intent.draft_id.as_bytes(), + intent.expected_revision.get(), policy, now_unix_ms, ) @@ -1476,14 +1484,14 @@ impl TeraRuntime { ) .map_err(|_| Phase1DraftError::InvalidMedia)?; self.phase1_upload_draft_media( - intent.draft_id, - intent.expected_revision, + *intent.draft_id.as_bytes(), + intent.expected_revision.get(), request, content, plan.authorization_created_at_unix_s, plan.authorization_lifetime_seconds, - plan.operation_id, - plan.artifact_id, + *plan.operation_id.as_bytes(), + *plan.artifact_id.as_bytes(), plan.signing_deadline_unix_ms, plan.cancellation, radroots_sdk::transport::BlossomCancellation::default(), @@ -1531,8 +1539,8 @@ impl TeraRuntime { .map_err(|_| Phase1DraftError::Operation)?; let authorization = self .phase1_authorize_blossom_upload( - plan.operation_id, - plan.artifact_id, + *plan.operation_id.as_bytes(), + *plan.artifact_id.as_bytes(), claim, plan.signing_deadline_unix_ms, plan.cancellation, @@ -1540,8 +1548,8 @@ impl TeraRuntime { .await?; let uploading = self .phase1_update_draft_media( - intent.draft_id, - intent.expected_revision, + *intent.draft_id.as_bytes(), + intent.expected_revision.get(), remote_url.as_str(), Phase1MediaStage::Uploading, None, @@ -1551,7 +1559,7 @@ impl TeraRuntime { Ok(( uploading, Phase1NativeUploadJob { - operation_id: plan.operation_id, + operation_id: *plan.operation_id.as_bytes(), remote_url, authorization_header: authorization.into_string(), expected_sha256: transaction.request().sha256().to_string(), @@ -1601,8 +1609,8 @@ impl TeraRuntime { { Ok(receipt) => { self.phase1_complete_draft_media( - intent.draft_id, - intent.expected_revision, + *intent.draft_id.as_bytes(), + intent.expected_revision.get(), url.as_str(), receipt, now_unix_ms, @@ -1611,8 +1619,8 @@ impl TeraRuntime { } Err(error) => { self.phase1_fail_draft_media( - intent.draft_id, - intent.expected_revision, + *intent.draft_id.as_bytes(), + intent.expected_revision.get(), url.as_str(), &error, now_unix_ms, @@ -3401,8 +3409,8 @@ mod tests { assert_eq!(plan.authorization_content, BLOSSOM_AUTHORIZATION_CONTENT); assert_eq!(plan.authorization_created_at_unix_s, 1_799_999_995); assert_eq!(plan.authorization_lifetime_seconds, 300); - assert_eq!(plan.operation_id, [7; 16]); - assert_eq!(plan.artifact_id, [8; 16]); + assert_eq!(*plan.operation_id.as_bytes(), [7; 16]); + assert_eq!(*plan.artifact_id.as_bytes(), [8; 16]); assert_eq!(plan.signing_deadline_unix_ms, 1_800_000_060_000); assert_eq!( plan.cancellation, diff --git a/core/crates/tera_core/src/runtime/product_surface/today.rs b/core/crates/tera_core/src/runtime/product_surface/today.rs @@ -458,7 +458,7 @@ impl TeraRuntime { return Err(CursorError::Stale.into()); } let scope = CursorScope::new( - context.id.clone(), + context.id.clone().into(), context.generation, as_of, state.store_generation, @@ -1361,7 +1361,7 @@ fn project_state( } Ok(TodayProjectionState { schema_version: TODAY_PROJECTION_DOCUMENT_SCHEMA_VERSION, - context_id: context.id.clone(), + context_id: context.id.clone().into(), context_generation: context.generation, store_generation: *store_generation, source_events, @@ -1567,7 +1567,7 @@ fn frozen_snapshot( ) -> Result<FrozenTodaySnapshot, TodayError> { Ok(FrozenTodaySnapshot { schema_version: TODAY_SNAPSHOT_SCHEMA_VERSION, - context_id: context.id.clone(), + context_id: context.id.clone().into(), context_generation: context.generation, as_of, store_generation: state.store_generation, @@ -1619,7 +1619,7 @@ fn validate_snapshot( scope: &CursorScope, ) -> Result<(), TodayError> { if snapshot.schema_version != TODAY_SNAPSHOT_SCHEMA_VERSION - || snapshot.context_id != scope.context_id + || snapshot.context_id != scope.context_id.as_str() || snapshot.context_generation != scope.context_generation || snapshot.as_of != scope.as_of || snapshot.store_generation != scope.store_generation @@ -3457,7 +3457,7 @@ mod tests { .expect("load state") .expect("state"); let scope = CursorScope::new( - context.id.clone(), + context.id.clone().into(), context.generation, 2_000_000_200, state.store_generation, @@ -3472,7 +3472,8 @@ mod tests { .to_owned() }; let mut wrong_context = scope.clone(); - wrong_context.context_id = "elsewhere".into(); + wrong_context.context_id = + crate::runtime::product_surface::LocalNetworkId::new("elsewhere".into()).unwrap(); assert!(matches!( runtime .phase1_today_page( diff --git a/core/crates/tera_ffi/src/dto.rs b/core/crates/tera_ffi/src/dto.rs @@ -497,7 +497,7 @@ impl From<LocalNetwork> for FfiLocalNetworkRecord { fn from(value: LocalNetwork) -> Self { Self { schema_version: MOBILE_FFI_SCHEMA_VERSION, - id: value.id, + id: value.id.into(), label: value.label, relay_urls: value.relay_urls, locality: value.locality, diff --git a/core/crates/tera_ffi/tests/identity_contract.rs b/core/crates/tera_ffi/tests/identity_contract.rs @@ -0,0 +1,62 @@ +use radroots_storage::authored_draft::{AuthoredDraftId, AuthoredDraftRevision}; +use tera_core::runtime::product_surface::{LocalNetwork, Phase1ExistingDraft, Phase1QueueIntent}; +use tera_ffi::{FfiLocalNetworkRecord, MOBILE_FFI_SCHEMA_VERSION}; + +#[test] +fn stable_context_id_and_generation_keep_the_existing_wire_shape() { + let context = LocalNetwork::new( + "nearby".into(), + "Nearby".into(), + vec!["wss://relay.example".into()], + None, + vec![], + u64::MAX, + ) + .unwrap(); + let wire = serde_json::to_value(&context).unwrap(); + assert_eq!(wire["id"], "nearby"); + assert_eq!(wire["generation"], u64::MAX); + assert_eq!( + serde_json::from_value::<LocalNetwork>(wire).unwrap(), + context + ); + let dto = FfiLocalNetworkRecord::from(context); + assert_eq!(dto.schema_version, MOBILE_FFI_SCHEMA_VERSION); + assert_eq!(dto.id, "nearby"); + assert_eq!(dto.generation, u64::MAX); +} + +#[test] +fn shared_draft_identity_and_revision_remain_validated_at_app_admission() { + let id = AuthoredDraftId::new([7; 16]).unwrap(); + let encoded = serde_json::to_value(id).unwrap(); + assert_eq!(encoded, serde_json::to_value([7; 16]).unwrap()); + assert_eq!( + serde_json::from_value::<AuthoredDraftId>(encoded).unwrap(), + id + ); + for revision in [1, i64::MAX as u64, (i64::MAX as u64) + 1, u64::MAX] { + let typed = AuthoredDraftRevision::new(revision).unwrap(); + let wire = serde_json::to_string(&typed).unwrap(); + assert_eq!(wire, revision.to_string()); + assert_eq!( + serde_json::from_str::<AuthoredDraftRevision>(&wire).unwrap(), + typed + ); + assert!(Phase1ExistingDraft::new(*id.as_bytes(), revision).is_ok()); + assert!(Phase1QueueIntent::new(*id.as_bytes(), revision).is_ok()); + } + assert!( + AuthoredDraftRevision::new(u64::MAX) + .unwrap() + .next() + .is_err() + ); + for (id, revision) in [([0; 16], 1), ([7; 16], 0)] { + assert!(Phase1ExistingDraft::new(id, revision).is_err()); + assert!(Phase1QueueIntent::new(id, revision).is_err()); + } + for wire in ["0", "-1", "18446744073709551616", "1.5"] { + assert!(serde_json::from_str::<AuthoredDraftRevision>(wire).is_err()); + } +} diff --git a/project.yml b/project.yml @@ -46,6 +46,8 @@ targets: deploymentTarget: "18.0" sources: - path: TeraTests/TeraRuntimeClientTests.swift + - path: TeraTests/TeraRuntimeClientFixtures.swift + - path: TeraTests/TeraSessionGenerationTests.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": "6542c8294920b3c41e987619de58690e2af16896079910b657129d3573b34e68", - "ffi_provenance_sha256": "5ee596b004f9c633f044ed91530f9cdd60427e3828b38f237d327ca5161d42d0", + "ffi_provenance_sha256": "299a3bc6733ff7d84bc398e68f0b97013e80b2fa551ce2f29029d5876403b6cf", "info_plist_sha256": "15ef08b1cdd1096cfb9eeaf5be5bf8f814807a7ca9350bbbb47860fa72ec13ef", "privacy_manifest_sha256": "a331d51864743ebe4e00dd22360b4a538b6b3ac26a6b3eb54094e60a36959a12", "sbom_sha256": "6b5ce897d5273290febc8b831663e12dea759cb0084f182ba7949e1c593fc410", - "xcode_project_sha256": "8539067560a291fde4d1acc083fe3810dd37eae99505a2532abb440a7ad0cf0d" + "xcode_project_sha256": "0656e349d03cd5afb19186359f3e9e5edfe1cb96f717ee87527960cff2f89000" }, "disposition": "unsigned", "platforms": [ @@ -22,7 +22,7 @@ "lib_revision": "ad17b7d3455a7147cfa303d976fc5c70c3a4c0cb", "source_date_epoch": 1787871027, "swift_package_lock_sha256": "94ae067a374726cdaf6b4ca0a5e44663c57fcdc5334060c5ffef5e79cfbf04c0", - "tera_ffi_source_tree": "ed13f3efac75566f8f21f1fcc0f9cea938d21d53", + "tera_ffi_source_tree": "bd58272c2149b87ee0070d16578d144e1e12aaab", "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 @@ -4313,6 +4313,10 @@ { "path": "core/crates/tera_ffi/src/dto.rs", "count": 2 + }, + { + "path": "core/crates/tera_ffi/tests/identity_contract.rs", + "count": 1 } ] }, diff --git a/test-fixtures/swiftlint-maintainability-baseline.v1.json b/test-fixtures/swiftlint-maintainability-baseline.v1.json @@ -1 +1 @@ -[{"text":"final class TeraAppModel: ObservableObject {","violation":{"location":{"character":7,"line":55,"file":"Tera\/App\/TeraAppModel.swift"},"severity":"warning","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","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"}},{"text":" init(","violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","location":{"file":"Tera\/App\/TeraAppModel.swift","line":71,"character":3},"ruleDescription":"Function bodies should not span too many lines","severity":"warning","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines"}},{"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {","violation":{"severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 427 lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":73,"character":1},"ruleDescription":"Type bodies should not span too many lines"}},{"text":" func uploadInBackground(","violation":{"location":{"character":3,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":192},"ruleIdentifier":"function_body_length","ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 55 lines"}},{"text":"#endif","violation":{"ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 580","severity":"warning","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":580}}},{"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {","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 545 lines","severity":"error","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","location":{"character":15,"line":134,"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift"}}},{"text":"}","violation":{"reason":"File should contain 1000 lines or less: currently contains 1696","ruleDescription":"Files should not span too many lines.","severity":"error","ruleName":"File Length","ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":1,"line":1696}}},{"text":"}","violation":{"ruleIdentifier":"file_length","location":{"character":1,"file":"Tera\/Runtime\/TeraLifecycleCoordinator.swift","line":434},"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 434","severity":"warning"}},{"text":"actor TeraRuntimeClient {","violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 847 lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":441},"ruleIdentifier":"type_body_length"}},{"text":" private func start(","violation":{"ruleDescription":"Function bodies should not span too many lines","location":{"line":503,"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":11},"ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 60 lines","ruleIdentifier":"function_body_length","severity":"warning"}},{"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {","violation":{"severity":"warning","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":881,"character":3},"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","ruleIdentifier":"function_body_length","ruleName":"Function Body Length"}},{"text":" private func beginShutdown() -> ShutdownOperation {","violation":{"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines","severity":"warning","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","line":1061,"character":11}}},{"text":"}","violation":{"severity":"error","reason":"File should contain 1000 lines or less: currently contains 1371","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":1371},"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length"}},{"text":"}","violation":{"ruleIdentifier":"file_length","ruleName":"File Length","severity":"error","location":{"character":1,"line":1244,"file":"Tera\/Runtime\/TeraRuntimeModels.swift"},"ruleDescription":"Files should not span too many lines.","reason":"File should contain 1000 lines or less: currently contains 1244"}},{"text":"final class TeraAddStore: ObservableObject {","violation":{"severity":"error","location":{"character":7,"file":"Tera\/State\/TeraAddStore.swift","line":11},"ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 741 lines","ruleName":"Type Body Length"}},{"text":" func submit() async {","violation":{"ruleName":"Function Body Length","severity":"warning","location":{"character":3,"line":280,"file":"Tera\/State\/TeraAddStore.swift"},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func retract(_ card: TeraTodayCard) async {","violation":{"location":{"line":438,"character":3,"file":"Tera\/State\/TeraAddStore.swift"},"severity":"warning","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 51 lines","ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" private func uploadPendingMedia(_ initial: TeraDraftStatus) async throws","violation":{"location":{"line":524,"file":"Tera\/State\/TeraAddStore.swift","character":11},"ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 60 lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","severity":"warning"}},{"text":"}","violation":{"reason":"File should contain 400 lines or less: currently contains 810","location":{"character":1,"line":810,"file":"Tera\/State\/TeraAddStore.swift"},"ruleIdentifier":"file_length","severity":"warning","ruleDescription":"Files should not span too many lines.","ruleName":"File Length"}},{"text":"actor TeraConfigurationStore {","violation":{"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","ruleName":"Type Body Length","severity":"error","ruleIdentifier":"type_body_length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":78,"character":1}}},{"text":" func load() throws -> TeraAppConfiguration {","violation":{"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","ruleName":"Function Body Length","severity":"error","ruleIdentifier":"function_body_length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":148,"character":5}}},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":759,"character":1},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 759"},"text":"}"},{"violation":{"ruleName":"Type Body Length","location":{"line":40,"file":"Tera\/State\/TeraSessionStore.swift","character":1},"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 431 lines","severity":"error","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines"},"text":"actor TeraSessionStore {"},{"violation":{"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","ruleIdentifier":"function_body_length","severity":"warning","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":10,"line":70},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"},"text":" static func production("},{"violation":{"ruleName":"Cyclomatic Complexity","reason":"Function should have complexity 10 or less; currently complexity is 14","severity":"warning","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","location":{"character":11,"line":182,"file":"Tera\/State\/TeraSessionStore.swift"}},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"location":{"character":11,"line":182,"file":"Tera\/State\/TeraSessionStore.swift"},"ruleName":"Function Body Length","severity":"warning","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","ruleIdentifier":"function_body_length"},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","ruleName":"Function Body Length","severity":"warning","ruleIdentifier":"function_body_length","location":{"character":11,"line":389,"file":"Tera\/State\/TeraSessionStore.swift"},"ruleDescription":"Function bodies should not span too many lines"},"text":" private func reconcileIdentity(_ identity: TeraAppIdentity) async throws {"},{"violation":{"severity":"warning","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 493","ruleName":"File Length","ruleDescription":"Files should not span too many lines.","location":{"line":493,"file":"Tera\/State\/TeraSessionStore.swift","character":1}},"text":"}"},{"violation":{"ruleIdentifier":"file_length","location":{"line":451,"file":"Tera\/State\/TeraSupportingStores.swift","character":1},"reason":"File should contain 400 lines or less: currently contains 451","ruleName":"File Length","ruleDescription":"Files should not span too many lines.","severity":"warning"},"text":"}"},{"violation":{"severity":"warning","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 257 lines","location":{"line":13,"file":"Tera\/State\/TeraTodayStore.swift","character":7}},"text":"final class TeraTodayStore: ObservableObject {"},{"violation":{"location":{"character":1,"file":"Tera\/Views\/TeraAddView.swift","line":4},"reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 454 lines","severity":"error","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length"},"text":"struct TeraAddView: View {"},{"violation":{"ruleDescription":"Files should not span too many lines.","location":{"character":1,"line":560,"file":"Tera\/Views\/TeraAddView.swift"},"ruleIdentifier":"file_length","ruleName":"File Length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 560"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","location":{"character":1,"line":365,"file":"Tera\/Views\/TeraSupportingViews.swift"},"ruleIdentifier":"type_body_length","ruleName":"Type Body Length","severity":"warning","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines"},"text":"struct TeraSettingsView: View {"},{"text":"}","violation":{"ruleName":"File Length","severity":"warning","ruleIdentifier":"file_length","location":{"character":1,"file":"Tera\/Views\/TeraSupportingViews.swift","line":629},"ruleDescription":"Files should not span too many lines.","reason":"File should contain 400 lines or less: currently contains 629"}},{"text":"}","violation":{"ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","severity":"warning","reason":"File should contain 400 lines or less: currently contains 493","location":{"character":1,"line":493,"file":"Tera\/Views\/TeraTodayView.swift"},"ruleName":"File Length"}},{"text":"final class TeraAddStoreTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","location":{"character":7,"line":6,"file":"TeraTests\/TeraAddStoreTests.swift"},"reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 665 lines"}},{"text":"private actor AddBackend: TeraRuntimeBackend {","violation":{"ruleIdentifier":"type_body_length","severity":"error","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":847,"character":9},"ruleDescription":"Type bodies should not span too many lines","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 579 lines","ruleName":"Type Body Length"}},{"text":" nonisolated static func schemas() -> [TeraAddSchema] {","violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","severity":"warning","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 89 lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":934,"character":22}}},{"text":" private func makeStatus(","violation":{"ruleIdentifier":"function_parameter_count","ruleName":"Function Parameter Count","reason":"Function should have 5 parameters or less: it currently has 8","location":{"character":11,"file":"TeraTests\/TeraAddStoreTests.swift","line":1371},"severity":"warning","ruleDescription":"Number of function parameters should be low."}},{"text":" var counts: (enqueue: Int, retry: Int, cancel: Int, acceptedSettlement: Int) {","violation":{"ruleIdentifier":"large_tuple","reason":"Tuples should have at most 2 members","severity":"error","location":{"line":1635,"file":"TeraTests\/TeraAddStoreTests.swift","character":15},"ruleDescription":"Tuples shouldn't have too many members. Create a custom type instead.","ruleName":"Large Tuple"}},{"text":"}","violation":{"ruleName":"File Length","ruleDescription":"Files should not span too many lines.","severity":"error","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1784,"character":1},"reason":"File should contain 1000 lines or less: currently contains 1784","ruleIdentifier":"file_length"}},{"text":"final class TeraRemoteQualificationTests: XCTestCase {","violation":{"reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","severity":"warning","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":7,"character":7}}},{"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {","violation":{"ruleName":"Function Body Length","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","location":{"line":45,"character":3,"file":"TeraTests\/TeraRemoteQualificationTests.swift"},"ruleIdentifier":"function_body_length","severity":"warning"}},{"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {","violation":{"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","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":212},"ruleIdentifier":"function_body_length","ruleName":"Function Body Length","severity":"warning"}},{"text":"final class TeraRuntimeClientTests: XCTestCase {","violation":{"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","location":{"file":"TeraTests\/TeraRuntimeClientTests.swift","character":7,"line":4},"ruleIdentifier":"type_body_length","ruleName":"Type Body Length","severity":"warning"}},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","location":{"line":598,"character":1,"file":"TeraTests\/TeraRuntimeClientTests.swift"},"ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 598","severity":"warning"},"text":"}"},{"violation":{"location":{"line":6,"character":7,"file":"TeraTests\/TeraStateMigrationTests.swift"},"severity":"error","ruleDescription":"Type bodies should not span too many lines","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length"},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","severity":"warning","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","line":7,"character":5},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","ruleIdentifier":"function_body_length"},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"violation":{"location":{"character":1,"line":718,"file":"TeraTests\/TeraStateMigrationTests.swift"},"ruleName":"File Length","ruleDescription":"Files should not span too many lines.","reason":"File should contain 400 lines or less: currently contains 718","severity":"warning","ruleIdentifier":"file_length"},"text":"}"},{"violation":{"location":{"line":207,"file":"TeraTests\/TeraSupportingStoreTests.swift","character":9},"reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 253 lines","severity":"warning","ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length"},"text":"private actor SupportingBackend: TeraRuntimeBackend {"},{"violation":{"severity":"warning","reason":"File should contain 400 lines or less: currently contains 487","ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","line":487,"character":1}},"text":"}"},{"violation":{"location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":4,"character":7},"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraRemoteQualificationUITests: XCTestCase {"},{"violation":{"ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","severity":"warning","ruleDescription":"Function bodies should not span too many lines","location":{"line":62,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3},"ruleIdentifier":"function_body_length"},"text":" func testLocalSocialFiveFlowScenario() throws {"},{"violation":{"severity":"warning","ruleIdentifier":"function_body_length","location":{"character":3,"line":174,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"},"ruleName":"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 66 lines"},"text":" func testLocalSocialDeterministicPersonas() throws {"},{"violation":{"ruleIdentifier":"function_body_length","severity":"warning","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":247},"ruleName":"Function Body Length","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":{"location":{"character":11,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":573},"ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 18"},"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {"},{"violation":{"location":{"character":11,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":767},"ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 11"},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"ruleIdentifier":"function_body_length","ruleName":"Function Body Length","severity":"warning","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","location":{"line":767,"character":11,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"}}},{"text":" static func environment(","violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","location":{"character":10,"line":1736,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","severity":"warning","ruleIdentifier":"function_body_length"}},{"text":"}","violation":{"severity":"error","location":{"line":2212,"character":1,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"},"reason":"File should contain 1000 lines or less: currently contains 2212","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","ruleName":"File Length"}}] -\ No newline at end of file +[{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/App\/TeraAppModel.swift","character":7,"line":55},"severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 255 lines"},"text":"final class TeraAppModel: ObservableObject {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/App\/TeraAppModel.swift","character":3,"line":71},"severity":"warning","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines"},"text":" init("},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":73},"severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 427 lines"},"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":3,"line":192},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 55 lines"},"text":" func uploadInBackground("},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":580},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 580"},"text":"#endif"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":15,"line":134},"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 545 lines"},"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":1,"line":1696},"severity":"error","reason":"File should contain 1000 lines or less: currently contains 1696"},"text":"}"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Runtime\/TeraLifecycleCoordinator.swift","character":1,"line":434},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 434"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":441},"severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 847 lines"},"text":"actor TeraRuntimeClient {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":11,"line":503},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 60 lines"},"text":" private func start("},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":3,"line":881},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 88 lines"},"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":11,"line":1061},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines"},"text":" private func beginShutdown() -> ShutdownOperation {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":1371},"severity":"error","reason":"File should contain 1000 lines or less: currently contains 1371"},"text":"}"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Runtime\/TeraRuntimeModels.swift","character":1,"line":1234},"severity":"error","reason":"File should contain 1000 lines or less: currently contains 1234"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":7,"line":11},"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 741 lines"},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":3,"line":280},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines"},"text":" func submit() async {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":3,"line":438},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 51 lines"},"text":" func retract(_ card: TeraTodayCard) async {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":11,"line":524},"severity":"warning","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":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/State\/TeraAddStore.swift","character":1,"line":810},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 810"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":1,"line":78},"severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines"},"text":"actor TeraConfigurationStore {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":5,"line":148},"severity":"error","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines"},"text":" func load() throws -> TeraAppConfiguration {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":1,"line":759},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 759"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":1,"line":40},"severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 431 lines"},"text":"actor TeraSessionStore {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":10,"line":70},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines"},"text":" static func production("},{"violation":{"ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":11,"line":182},"severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 14"},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":11,"line":182},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 64 lines"},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":11,"line":389},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines"},"text":" private func reconcileIdentity(_ identity: TeraAppIdentity) async throws {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":1,"line":493},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 493"},"text":"}"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/State\/TeraSupportingStores.swift","character":1,"line":451},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 451"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/State\/TeraTodayStore.swift","character":7,"line":13},"severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 257 lines"},"text":"final class TeraTodayStore: ObservableObject {"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":4},"severity":"error","reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 454 lines"},"text":"struct TeraAddView: View {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":560},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 560"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":365},"severity":"warning","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines"},"text":"struct TeraSettingsView: View {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":629},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 629"},"text":"}"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"Tera\/Views\/TeraTodayView.swift","character":1,"line":493},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 493"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":7,"line":6},"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 665 lines"},"text":"final class TeraAddStoreTests: XCTestCase {"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":9,"line":847},"severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 579 lines"},"text":"private actor AddBackend: TeraRuntimeBackend {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":22,"line":934},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 89 lines"},"text":" nonisolated static func schemas() -> [TeraAddSchema] {"},{"violation":{"ruleName":"Function Parameter Count","ruleIdentifier":"function_parameter_count","ruleDescription":"Number of function parameters should be low.","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":11,"line":1371},"severity":"warning","reason":"Function should have 5 parameters or less: it currently has 8"},"text":" private func makeStatus("},{"violation":{"ruleName":"Large Tuple","ruleIdentifier":"large_tuple","ruleDescription":"Tuples shouldn't have too many members. Create a custom type instead.","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":15,"line":1635},"severity":"error","reason":"Tuples should have at most 2 members"},"text":" var counts: (enqueue: Int, retry: Int, cancel: Int, acceptedSettlement: Int) {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":1,"line":1784},"severity":"error","reason":"File should contain 1000 lines or less: currently contains 1784"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":7,"line":7},"severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines"},"text":"final class TeraRemoteQualificationTests: XCTestCase {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":45},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines"},"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":212},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines"},"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraTests\/TeraRuntimeClientTests.swift","character":7,"line":4},"severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 334 lines"},"text":"final class TeraRuntimeClientTests: XCTestCase {"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":7,"line":6},"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines"},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":5,"line":7},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines"},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":1,"line":718},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 718"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":9,"line":207},"severity":"warning","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 253 lines"},"text":"private actor SupportingBackend: TeraRuntimeBackend {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":1,"line":487},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 487"},"text":"}"},{"violation":{"ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":7,"line":4},"severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines"},"text":"final class TeraRemoteQualificationUITests: XCTestCase {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":62},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines"},"text":" func testLocalSocialFiveFlowScenario() throws {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":174},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines"},"text":" func testLocalSocialDeterministicPersonas() throws {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":247},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines"},"text":" func testRemoteBlossomUploadAndRecovery() throws {"},{"violation":{"ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":573},"severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 18"},"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {"},{"violation":{"ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":767},"severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 11"},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":767},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines"},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":10,"line":1736},"severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines"},"text":" static func environment("},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":1,"line":2212},"severity":"error","reason":"File should contain 1000 lines or less: currently contains 2212"},"text":"}"}] +\ No newline at end of file