diff --git a/PHASE8-NOTES.md b/PHASE8-NOTES.md new file mode 100644 index 0000000..25abd06 --- /dev/null +++ b/PHASE8-NOTES.md @@ -0,0 +1,17 @@ +# Phase 8 — Dedicated InnoDB snapshot exports + +- Uses an isolated native MySQL session for the entire export. +- Starts a REPEATABLE READ consistent, read-only transaction. +- Rejects non-InnoDB tables when exporting data. +- Keeps the same session across all paginated reads and row-count checks. +- Closes the dedicated session on cancellation/failure (rolling back its transaction). +- Preserves all Phase 7 export, import and integrity features. + +## Limitations + +This is NOT a full production backup guarantee: schema changes during export, +non-transactional objects, and offset pagination remain limitations. A stable +snapshot makes offset pagination consistent for InnoDB row data but not fast +for large offsets. Backup and restore validation against live MySQL is still +required. The MySQL wire protocol does not currently negotiate TLS, so do +not send credentials over untrusted networks. diff --git a/PHASE9-NOTES.md b/PHASE9-NOTES.md new file mode 100644 index 0000000..2f3000b --- /dev/null +++ b/PHASE9-NOTES.md @@ -0,0 +1,10 @@ +# Phase 9 — Native streamed table export + +- Removes the non-null unique index requirement (including LIBARC). +- Uses a single MySQL SELECT result stream per table, within the existing dedicated InnoDB consistent-snapshot session. +- Does not use LIMIT/OFFSET and does not buffer complete table results or impose the SQL editor's 100,000-row limit. +- Uses optional unique-key ORDER BY when available, but supports tables without a unique key. +- Counts streamed rows and compares against snapshot row count. +- Existing SHA-256 verification and temporary-file cleanup remain in place. + +Limitations: The SQL exporter still formats rows using MySQL SQL expressions; very large individual values and packet sizes may be limited by server/client protocol settings. Non-InnoDB tables are still rejected for data exports. DDL changes during backup are not protected. Streaming cancellation may wait for network reads. Not Xcode-built or live-server tested. diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index 4eae89f..31d7590 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -1681,7 +1681,7 @@ struct WorkbenchDatabaseTransferView: View { )) } } - Text("Data export requires a non-null unique index on every selected table. Row counts are checked, but exports are not transaction-consistent: avoid writes during export.") + Text("InnoDB data is exported using a consistent snapshot and a single streamed query per table. Tables without unique indexes are supported.") .font(.caption).foregroundStyle(.secondary) } } @@ -1871,6 +1871,11 @@ struct WorkbenchDatabaseTransferView: View { showingImportConfirmation = true } + // Export operations use a distinct authenticated session so their snapshot + // cannot be affected by SQL editor queries or administration refreshes. + @State private var exportSession: ConnectionProfile? + @State private var exportPassword = "" + @MainActor private func execute(_ statement: String) async throws -> QueryResult { guard let profile = selectedProfile else { @@ -1880,9 +1885,9 @@ struct WorkbenchDatabaseTransferView: View { } return try await NativeMySQLRunner.run( - profile: profile, + profile: exportSession ?? profile, database: statement == "SHOW DATABASES" ? "" : database, - password: PasswordStore.read(profile.id), + password: exportSession == nil ? PasswordStore.read(profile.id) : exportPassword, sql: statement ) } @@ -1958,6 +1963,16 @@ struct WorkbenchDatabaseTransferView: View { ? availableViews.filter { selectedViews.contains($0) } : [] guard !selectedTables.isEmpty || !views.isEmpty || (exportStructure && (includeTriggers || includeRoutines)) else { return } + guard let activeProfile = selectedProfile else { + status = "Export failed: No connection selected" + return + } + // Clone the connection profile with a fresh identity; the session pool + // then creates a separate socket, independent of the editor session. + var dedicatedProfile = activeProfile + dedicatedProfile.id = UUID() + exportPassword = PasswordStore.read(activeProfile.id) + exportSession = dedicatedProfile isWorking = true progress = 0 let tables = availableTables.filter { selectedTables.contains($0) } @@ -1966,10 +1981,31 @@ struct WorkbenchDatabaseTransferView: View { defer { try? handle?.close() try? FileManager.default.removeItem(at: tempURL) + NativeMySQLRunner.disconnect(profileID: dedicatedProfile.id) + exportSession = nil + exportPassword = "" isWorking = false operationTask = nil } do { + // A consistent InnoDB snapshot requires every SELECT to execute on + // this SAME connection. MyISAM/MEMORY cannot participate. + for table in tables where exportData { + let engine = try await execute(""" + SELECT ENGINE FROM information_schema.TABLES + WHERE TABLE_SCHEMA = \(sqlLiteral(database)) + AND TABLE_NAME = \(sqlLiteral(table)) + AND TABLE_TYPE = 'BASE TABLE' + """) + guard let name = engine.rows.first?.first, + name.uppercased() == "INNODB" else { + throw WorkbenchTransferError.message( + "Consistent data export requires InnoDB. Table \(table) uses \(engine.rows.first?.first ?? "an unknown engine"). Export its structure only." + ) + } + } + _ = try await execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ") + _ = try await execute("START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY") guard FileManager.default.createFile(atPath: tempURL.path, contents: nil) else { throw WorkbenchTransferError.message("Cannot create temporary export file") } @@ -2013,9 +2049,9 @@ struct WorkbenchDatabaseTransferView: View { } return "IF(\(col) IS NULL, 'NULL', QUOTE(\(col)))" }.joined(separator: ", ") - // Phase 7: require a non-null unique key for deterministic - // pagination. Without one, LIMIT/OFFSET can silently - // duplicate or omit rows even on an idle server. + // A single streamed SELECT keeps every row in the same + // InnoDB read view and works without a primary/unique key. + // No OFFSET, no 100,000-row cap, no full result buffering. let indexes = try await execute(""" SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX FROM information_schema.STATISTICS @@ -2043,46 +2079,39 @@ struct WorkbenchDatabaseTransferView: View { } grouped[name, default: []].append(indexRow[1]) } - guard let indexName = indexOrder.first(where: { name in + let usableIndex = indexOrder.first { name in guard let columns = grouped[name], !columns.isEmpty else { return false } return columns.allSatisfy { !nullableColumns.contains($0) } - }), let keyColumns = grouped[indexName] else { - throw WorkbenchTransferError.message( - "Cannot safely export data for \(table): no non-null unique index. " + - "Add a primary key or export structure only." - ) } - let orderBy = keyColumns.map(quoteIdentifier).joined(separator: ", ") - let beforeCount = try await execute("SELECT COUNT(*) FROM \(qualified)") - guard let expectedText = beforeCount.rows.first?.first, - let expectedCount = Int64(expectedText) else { + let orderBy = usableIndex.flatMap { grouped[$0] }? + .map(quoteIdentifier).joined(separator: ", ") + let countResult = try await execute("SELECT COUNT(*) FROM \(qualified)") + guard let countText = countResult.rows.first?.first, + let expectedCount = Int64(countText) else { throw WorkbenchTransferError.message("Cannot count rows in \(table)") } - try write("-- Export ordering: \(indexName) (\(orderBy)); expected rows: \(expectedCount)\n") - let statement = "SELECT CONCAT('(', CONCAT_WS(',', \(expressions)), ')') FROM \(qualified) ORDER BY \(orderBy) LIMIT 250 OFFSET " - var offset = 0 - while true { - try Task.checkCancellation() - let batch = try await execute(statement + String(offset)) - if batch.rows.isEmpty { break } - for row in batch.rows { - guard let literalRow = row.first else { continue } - try write("INSERT INTO \(qualified) (\(names)) VALUES \(literalRow);\n") - } - offset += batch.rows.count - status = "Exporting \(table): \(offset) rows" - if batch.rows.count < 250 { break } + try write("-- Streamed snapshot: \(expectedCount) rows; order: \(usableIndex ?? "server scan")\n") + let select = "SELECT CONCAT('(', CONCAT_WS(',', \(expressions)), ')') FROM \(qualified)" + + (orderBy.map { " ORDER BY " + $0 } ?? "") + let outputHandle = handle! + let prefix = Data("INSERT INTO \(qualified) (\(names)) VALUES ".utf8) + let suffix = Data(";\n".utf8) + let written = try await NativeMySQLRunner.stream( + profile: dedicatedProfile, + database: database, + password: exportPassword, + sql: select + ) { literal in + try outputHandle.write(contentsOf: prefix) + try outputHandle.write(contentsOf: Data(literal.utf8)) + try outputHandle.write(contentsOf: suffix) } - let afterCount = try await execute("SELECT COUNT(*) FROM \(qualified)") - guard let afterText = afterCount.rows.first?.first, - let finalCount = Int64(afterText), - Int64(offset) == expectedCount, - finalCount == expectedCount else { + guard written == expectedCount else { throw WorkbenchTransferError.message( - "Table \(table) changed during export or pagination was incomplete. " + - "The temporary backup has been discarded." + "Table \(table): expected \(expectedCount) rows, streamed \(written). Backup discarded." ) } + status = "Exported \(table): \(written) rows" } progress = Double(index + 1) / Double(max(tables.count + views.count, 1)) } @@ -2156,6 +2185,8 @@ struct WorkbenchDatabaseTransferView: View { } } try Task.checkCancellation() + // Release the read view before finalizing the export file. + _ = try await execute("COMMIT") try handle?.close() handle = nil if FileManager.default.fileExists(atPath: url.path) { diff --git a/WorkbenchReplacement/MySQLProtocol.swift b/WorkbenchReplacement/MySQLProtocol.swift index 612c142..1c38113 100644 --- a/WorkbenchReplacement/MySQLProtocol.swift +++ b/WorkbenchReplacement/MySQLProtocol.swift @@ -287,6 +287,35 @@ final class MySQLWireConnection { private var hostIsLoopback = false func markHost(_ host: String) { hostIsLoopback = ["localhost", "127.0.0.1", "::1"].contains(host.lowercased()) } + // Streams result rows without the query editor's 100,000-row limit. + // The callback is invoked on the session's serial background queue. + func streamFirstColumn(_ sql: String, onRow: (String) throws -> Void) throws -> Int64 { + sequence = 0 + try send([0x03] + Array(sql.utf8)) + let first = try readPacket() + try checkError(first) + guard first.first != 0x00 else { return 0 } + var cursor = ByteCursor(bytes: first) + guard let columnCount = try cursor.lenInt(), columnCount == 1 else { + throw MySQLWireError(message: "Export query must return exactly one column") + } + _ = try readPacket() // Column metadata + _ = try readPacket() // Column EOF + var count: Int64 = 0 + while true { + let packet = try readPacket() + try checkError(packet) + if packet.first == 0xfe && packet.count < 9 { break } + var row = ByteCursor(bytes: packet) + guard let value = try row.lenString() else { + throw MySQLWireError(message: "Unexpected NULL export row") + } + try onRow(value) + count += 1 + } + return count + } + func query(_ sql: String) throws -> QueryResult { sequence = 0 try send([0x03] + Array(sql.utf8)) @@ -367,6 +396,23 @@ private final class PersistentMySQLSessions { } } + func stream(profile: ConnectionProfile, database: String, + password: String, sql: String, + onRow: (String) throws -> Void) throws -> Int64 { + try queue.sync { + guard let entry = connections[profile.id], + entry.profile == profile, entry.password == password else { + throw MySQLWireError(message: "Export session is no longer connected") + } + do { + return try entry.connection.streamFirstColumn(sql, onRow: onRow) + } catch { + connections.removeValue(forKey: profile.id) + throw error + } + } + } + func disconnect(profileID: UUID) { queue.sync { _ = connections.removeValue(forKey: profileID) } } @@ -382,6 +428,20 @@ enum NativeMySQLRunner { }.value } + static func stream(profile: ConnectionProfile, database: String, + password: String, sql: String, + onRow: @escaping (String) throws -> Void) async throws -> Int64 { + try await Task.detached(priority: .userInitiated) { + try PersistentMySQLSessions.shared.stream( + profile: profile, database: database, password: password, + sql: sql, onRow: { row in + try Task.checkCancellation() + try onRow(row) + } + ) + }.value + } + static func disconnect(profileID: UUID) { PersistentMySQLSessions.shared.disconnect(profileID: profileID) } diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o index a45385c..c02264a 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata index 31b2450..a8e037c 100644 --- a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata +++ b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata @@ -428,7 +428,7 @@ }, { "comment": "", - "key": "Triggers and routines require SHOW CREATE privileges. This exporter does not provide transaction-consistent snapshots or guaranteed row ordering.", + "key": "InnoDB data is exported using a consistent snapshot and a single streamed query per table. Tables without unique indexes are supported.", "location": { "startingColumn": 30, "startingLine": 1684 diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps index 4b24f76..3c50672 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors index c06eca8..55aebe1 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule index 11f7d90..1941200 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo index ebc9a30..1b9e01d 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo differ diff --git a/build/Build/Intermediates.noindex/XCBuildData/build.db b/build/Build/Intermediates.noindex/XCBuildData/build.db index c8feed3..0d43025 100644 Binary files a/build/Build/Intermediates.noindex/XCBuildData/build.db and b/build/Build/Intermediates.noindex/XCBuildData/build.db differ diff --git a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt index 814a2d4..fb687d1 100644 --- a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt +++ b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt @@ -12,3 +12,6 @@ /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app +/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app +/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app +/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement index 6833958..be546df 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib index 931d9f3..d3f6007 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib index 62360b4..fd8c357 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources index 560ea28..3cb8636 100644 --- a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources +++ b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources @@ -19,7 +19,7 @@ cdhash - RB/YriyQlwTsASbrsirLbaoN96E= + lD7vQzFdLMorMrk09F1ArmxtEu4= requirement identifier "WorkbenchReplacement.debug" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: Mark Gregory (FLBRJ3BYDD)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo index ebc9a30..1b9e01d 100644 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo and b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule index 11f7d90..1941200 100644 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule and b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule differ diff --git a/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 b/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 index 635281d..556fb80 100644 Binary files a/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 and b/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 differ diff --git a/build/Logs/Build/LogStoreManifest.plist b/build/Logs/Build/LogStoreManifest.plist index 451d64a..9844b94 100644 --- a/build/Logs/Build/LogStoreManifest.plist +++ b/build/Logs/Build/LogStoreManifest.plist @@ -48,7 +48,7 @@ uniqueIdentifier 32E9EC2D-B9B8-4CD2-8B3D-CD693A4CBD97 - 3389C95F-527F-43D4-BFD7-803CFE84AD6A + 68955E3A-0B2E-4614-AC89-290DCD28B6D0 className IDECommandLineBuildLog @@ -57,7 +57,7 @@ domainType Xcode.IDEActivityLogDomainType.BuildLog fileName - 3389C95F-527F-43D4-BFD7-803CFE84AD6A.xcactivitylog + 68955E3A-0B2E-4614-AC89-290DCD28B6D0.xcactivitylog hasPrimaryLog primaryObservable @@ -82,55 +82,13 @@ signature Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug timeStartedRecording - 813876842.83179998 + 813877338.21300805 timeStoppedRecording - 813876855.72915101 + 813877351.33591294 title Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug uniqueIdentifier - 3389C95F-527F-43D4-BFD7-803CFE84AD6A - - 705B952B-2A37-40D9-B731-8E2290C15462 - - className - IDECommandLineBuildLog - documentTypeString - <nil> - domainType - Xcode.IDEActivityLogDomainType.BuildLog - fileName - 705B952B-2A37-40D9-B731-8E2290C15462.xcactivitylog - hasPrimaryLog - - primaryObservable - - highLevelStatus - S - totalNumberOfAnalyzerIssues - 0 - totalNumberOfErrors - 0 - totalNumberOfTestFailures - 0 - totalNumberOfWarnings - 0 - - schemeIdentifier-containerName - WorkbenchReplacement project - schemeIdentifier-schemeName - WorkbenchReplacement - schemeIdentifier-sharedScheme - 1 - signature - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - timeStartedRecording - 813876591.82079995 - timeStoppedRecording - 813876604.10285401 - title - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - uniqueIdentifier - 705B952B-2A37-40D9-B731-8E2290C15462 + 68955E3A-0B2E-4614-AC89-290DCD28B6D0 921D995A-6441-4831-9E4E-3283B2F38B89 @@ -174,6 +132,90 @@ uniqueIdentifier 921D995A-6441-4831-9E4E-3283B2F38B89 + EB994874-BDA8-4BAC-BAF2-6EB6F20E2468 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + EB994874-BDA8-4BAC-BAF2-6EB6F20E2468.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + WorkbenchReplacement project + schemeIdentifier-schemeName + WorkbenchReplacement + schemeIdentifier-sharedScheme + 1 + signature + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + timeStartedRecording + 813877513.105088 + timeStoppedRecording + 813877526.28998804 + title + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + uniqueIdentifier + EB994874-BDA8-4BAC-BAF2-6EB6F20E2468 + + EDDF2132-184C-41A2-A915-0EC31843BAD5 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + EDDF2132-184C-41A2-A915-0EC31843BAD5.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + WorkbenchReplacement project + schemeIdentifier-schemeName + WorkbenchReplacement + schemeIdentifier-sharedScheme + 1 + signature + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + timeStartedRecording + 813877252.80588198 + timeStoppedRecording + 813877266.32472801 + title + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + uniqueIdentifier + EDDF2132-184C-41A2-A915-0EC31843BAD5 + diff --git a/build/ModuleCache.noindex/Session.modulevalidation b/build/ModuleCache.noindex/Session.modulevalidation index 5af5a8b..e40ea50 100644 --- a/build/ModuleCache.noindex/Session.modulevalidation +++ b/build/ModuleCache.noindex/Session.modulevalidation @@ -1 +1 @@ -1792184268.211045: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") +1792184713.510244: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") diff --git a/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache b/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache index 139ec59..ff6bb57 100644 Binary files a/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache and b/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache differ diff --git a/build/info.plist b/build/info.plist index c5f493a..750c42e 100644 --- a/build/info.plist +++ b/build/info.plist @@ -3,7 +3,7 @@ LastAccessedDate - 2026-10-16T20:57:48Z + 2026-10-16T21:05:13Z WorkspacePath /Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj