commit 6a360059c435bee2c8e8d21589aec43072fbacfb
parent 19772412fa3d29376a77791825f1805d4c59c688
Author: Michael Camilleri <[email protected]>
Date: Thu, 23 Jul 2026 22:00:10 +0900
Remove bugfix migrations for Chronicles
Diffstat:
4 files changed, 0 insertions(+), 115 deletions(-)
diff --git a/Crossmate/Services/AppServices.swift b/Crossmate/Services/AppServices.swift
@@ -1351,17 +1351,6 @@ final class AppServices {
syncMonitor.note("iCloud sync disabled — engine startup skipped")
return
}
-
- // TEMPORARY v1.1 MIGRATION: early Chronicle builds opened their local
- // projections as live games and consequently wrote Player records into
- // the private Chronicle zone. The permanent lifecycle gate lives with
- // PuzzleDisplayView; this one-shot repair can later be removed as one
- // block together with `purgeChroniclePlayers_v1` and its
- // NotificationState flag.
- Task { [syncEngine] in
- await syncEngine.purgeChroniclePlayers_v1()
- }
-
// One-shot migration of pre-existing single-zone friendships to the
// two-mailbox model. Runs only once the engine is up (it enqueues zone
// creates, a share, and a bootstrap ping) and only with sync enabled.
diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift
@@ -1048,76 +1048,6 @@ actor SyncEngine {
}
}
- /// TEMPORARY v1.1 MIGRATION: removes `Player` records accidentally written
- /// into the private Chronicle zone by early Chronicle builds. Remove this
- /// method together with its AppServices startup call and NotificationState
- /// flag after those builds have aged out.
- func purgeChroniclePlayers_v1() async {
- guard NotificationState.chroniclePlayerPurgeNeeded() else { return }
-
- // Cancel unsent writes first; otherwise a queued clock/presence update
- // could recreate a record immediately after the server-side deletion.
- if let privateEngine {
- let pendingSaves = privateEngine.state.pendingRecordZoneChanges.filter { change in
- guard case .saveRecord(let recordID) = change else { return false }
- return Self.isChroniclePlayerRecordID(recordID)
- }
- if !pendingSaves.isEmpty {
- privateEngine.state.remove(pendingRecordZoneChanges: pendingSaves)
- }
- }
-
- do {
- let records = try await chroniclePlayerRecords()
- try await deleteRecords(
- withIDs: records.map(\.recordID),
- in: container.privateCloudDatabase
- )
- NotificationState.markChroniclePlayersPurged()
- await trace(
- "Chronicle Player purge: cancelled pending saves and deleted " +
- "\(records.count) record(s)"
- )
- } catch {
- await trace("Chronicle Player purge failed: \(describe(error))")
- }
- }
-
- /// Enumerates the zone's change history rather than issuing a CKQuery.
- /// Production does not have a queryable `recordName` index for Player, but
- /// a zone-change fetch needs no schema index and is already the mechanism
- /// used by the storage audit.
- private func chroniclePlayerRecords() async throws -> [CKRecord] {
- let database = container.privateCloudDatabase
- var records: [CKRecord] = []
- var token: CKServerChangeToken?
- var moreComing = true
-
- while moreComing {
- let page = try await database.recordZoneChanges(
- inZoneWith: Archive.zoneID,
- since: token,
- desiredKeys: []
- )
- token = page.changeToken
- moreComing = page.moreComing
- for result in page.modificationResultsByID.values {
- let record = try result.get().record
- if record.recordType == "Player" {
- records.append(record)
- }
- }
- }
- return records
- }
-
- /// Pure classifier shared with tests so the migration cannot remove a
- /// Player record from a live game zone.
- nonisolated static func isChroniclePlayerRecordID(_ recordID: CKRecord.ID) -> Bool {
- recordID.zoneID == Archive.zoneID
- && RecordSerializer.parsePlayerRecordName(recordID.recordName) != nil
- }
-
private func purgeLegacyPlayPings(
in zoneIDs: [CKRecordZone.ID],
database: CKDatabase
diff --git a/Shared/NotificationState.swift b/Shared/NotificationState.swift
@@ -106,7 +106,6 @@ enum NotificationState {
private static let staleHailPurgeKey = "migration.staleHailPurge.v1"
private static let debugPreviewFriendPurgeKey = "migration.debugPreviewFriendPurge.v1"
private static let legacyPlayPingPurgeKey = "migration.legacyPlayPingPurge.v1"
- private static let chroniclePlayerPurgeKey = "migration.chroniclePlayerPurge.v1"
private static let friendMailboxMigrationKey = "migration.friendMailboxes.v1"
/// True if the one-shot cleanup of legacy `.opened`/`.closed` lease pings
@@ -173,17 +172,6 @@ enum NotificationState {
defaults?.set(true, forKey: legacyPlayPingPurgeKey)
}
- /// True until this device has successfully removed the `Player` records
- /// written into the Chronicle zone by the first v1.1 builds.
- static func chroniclePlayerPurgeNeeded() -> Bool {
- defaults?.bool(forKey: chroniclePlayerPurgeKey) == false
- }
-
- /// Records that the Chronicle-zone Player cleanup completed successfully.
- static func markChroniclePlayersPurged() {
- defaults?.set(true, forKey: chroniclePlayerPurgeKey)
- }
-
/// True if the one-shot migration of pre-mailbox friendships (a single
/// elected-owner shared zone) to the two-mailbox model has not yet run on
/// this device.
diff --git a/Tests/Unit/ArchiveTests.swift b/Tests/Unit/ArchiveTests.swift
@@ -98,28 +98,6 @@ struct ArchiveTests {
private let aliceKey = JournalDeviceKey(authorID: "alice", deviceID: "deviceA")
private let bobKey = JournalDeviceKey(authorID: "bob", deviceID: "deviceB")
- @Test("Chronicle Player cleanup is confined to Player IDs in the archive zone")
- func chroniclePlayerCleanupScope() {
- let gameID = UUID()
- let playerName = "player-\(gameID.uuidString)-alice"
- let archivePlayer = CKRecord.ID(recordName: playerName, zoneID: Archive.zoneID)
- let livePlayer = CKRecord.ID(
- recordName: playerName,
- zoneID: CKRecordZone.ID(
- zoneName: gameID.uuidString,
- ownerName: CKCurrentUserDefaultName
- )
- )
- let chronicle = CKRecord.ID(
- recordName: "chronicle-\(gameID.uuidString)",
- zoneID: Archive.zoneID
- )
-
- #expect(SyncEngine.isChroniclePlayerRecordID(archivePlayer))
- #expect(!SyncEngine.isChroniclePlayerRecordID(livePlayer))
- #expect(!SyncEngine.isChroniclePlayerRecordID(chronicle))
- }
-
private func sampleSnapshot(originalGameID: UUID) -> Archive.Snapshot {
Archive.Snapshot(
originalGameID: originalGameID,