Fix Swift 6 actor-isolation warnings in model inits and services

- Message, Conversation, EmailLog: add nonisolated to inits — plain value
  types have no actor isolation, but the macOS 27 SDK was inferring it
- EncryptionService: replace lazy var encryptionKey (which mutates self and
  gets inferred as @MainActor) with an eagerly-initialized let in init()
- FileLogger: add nonisolated to shared, write, and minimumLevel so they
  are callable from nonisolated AppLogger methods without warnings
- LogLevel.<: add nonisolated to the Comparable conformance method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 14:59:07 +02:00
parent 22f745762f
commit 92e393ab03
5 changed files with 15 additions and 18 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ struct Conversation: Identifiable, Codable {
var updatedAt: Date var updatedAt: Date
var primaryModel: String? // Primary model used in this conversation var primaryModel: String? // Primary model used in this conversation
init( nonisolated init(
id: UUID = UUID(), id: UUID = UUID(),
name: String, name: String,
messages: [Message] = [], messages: [Message] = [],
+1 -1
View File
@@ -44,7 +44,7 @@ struct EmailLog: Identifiable, Codable, Equatable {
let responseTime: TimeInterval? // Time to generate response in seconds let responseTime: TimeInterval? // Time to generate response in seconds
let modelId: String? // Model that handled the email let modelId: String? // Model that handled the email
init( nonisolated init(
id: UUID = UUID(), id: UUID = UUID(),
timestamp: Date = Date(), timestamp: Date = Date(),
sender: String, sender: String,
+1 -1
View File
@@ -66,7 +66,7 @@ struct Message: Identifiable, Codable, Equatable {
// Reasoning/thinking content (not persisted in-memory only) // Reasoning/thinking content (not persisted in-memory only)
var thinkingContent: String? = nil var thinkingContent: String? = nil
init( nonisolated init(
id: UUID = UUID(), id: UUID = UUID(),
role: MessageRole, role: MessageRole,
content: String, content: String,
+7 -10
View File
@@ -31,12 +31,11 @@ import IOKit
class EncryptionService { class EncryptionService {
nonisolated static let shared = EncryptionService() nonisolated static let shared = EncryptionService()
private let salt = "oAI-secure-storage-v1" // App-specific salt private let encryptionKey: SymmetricKey
private lazy var encryptionKey: SymmetricKey = {
deriveEncryptionKey()
}()
private init() {} private init() {
self.encryptionKey = Self.deriveEncryptionKey()
}
// MARK: - Public Interface // MARK: - Public Interface
@@ -73,19 +72,17 @@ class EncryptionService {
// MARK: - Key Derivation // MARK: - Key Derivation
/// Derive encryption key from machine-specific data /// Derive encryption key from machine-specific data
private func deriveEncryptionKey() -> SymmetricKey { private static func deriveEncryptionKey() -> SymmetricKey {
// Combine machine UUID + bundle ID + salt for key material
let machineUUID = getMachineUUID() let machineUUID = getMachineUUID()
let bundleID = Bundle.main.bundleIdentifier ?? "com.oai.oAI" let bundleID = Bundle.main.bundleIdentifier ?? "com.oai.oAI"
let salt = "oAI-secure-storage-v1"
let keyMaterial = "\(machineUUID)-\(bundleID)-\(salt)" let keyMaterial = "\(machineUUID)-\(bundleID)-\(salt)"
// Hash to create consistent 256-bit key
let hash = SHA256.hash(data: Data(keyMaterial.utf8)) let hash = SHA256.hash(data: Data(keyMaterial.utf8))
return SymmetricKey(data: hash) return SymmetricKey(data: hash)
} }
/// Get machine-specific UUID (IOPlatformUUID) /// Get machine-specific UUID (IOPlatformUUID)
private func getMachineUUID() -> String { private static func getMachineUUID() -> String {
// Get IOPlatformUUID from IOKit // Get IOPlatformUUID from IOKit
let platformExpert = IOServiceGetMatchingService( let platformExpert = IOServiceGetMatchingService(
kIOMainPortDefault, kIOMainPortDefault,
+5 -5
View File
@@ -52,7 +52,7 @@ enum LogLevel: Int, Comparable, CaseIterable, Sendable {
} }
} }
static func < (lhs: LogLevel, rhs: LogLevel) -> Bool { nonisolated static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
lhs.rawValue < rhs.rawValue lhs.rawValue < rhs.rawValue
} }
} }
@@ -60,7 +60,7 @@ enum LogLevel: Int, Comparable, CaseIterable, Sendable {
// MARK: - File Logger // MARK: - File Logger
final class FileLogger: @unchecked Sendable { final class FileLogger: @unchecked Sendable {
static let shared = FileLogger() nonisolated static let shared = FileLogger()
private let fileHandle: FileHandle? private let fileHandle: FileHandle?
private let queue = DispatchQueue(label: "com.oai.filelogger") private let queue = DispatchQueue(label: "com.oai.filelogger")
@@ -70,8 +70,8 @@ final class FileLogger: @unchecked Sendable {
return f return f
}() }()
/// Current minimum log level (read from UserDefaults for thread safety) /// Current minimum log level (backed by UserDefaults thread-safe).
var minimumLevel: LogLevel { nonisolated var minimumLevel: LogLevel {
get { get {
let raw = UserDefaults.standard.integer(forKey: "logLevel") let raw = UserDefaults.standard.integer(forKey: "logLevel")
return LogLevel(rawValue: raw) ?? .info return LogLevel(rawValue: raw) ?? .info
@@ -95,7 +95,7 @@ final class FileLogger: @unchecked Sendable {
fileHandle?.seekToEndOfFile() fileHandle?.seekToEndOfFile()
} }
func write(_ level: LogLevel, category: String, message: String) { nonisolated func write(_ level: LogLevel, category: String, message: String) {
guard level >= minimumLevel else { return } guard level >= minimumLevel else { return }
queue.async { [weak self] in queue.async { [weak self] in
guard let self, let fh = self.fileHandle else { return } guard let self, let fh = self.fileHandle else { return }