Files
InvestmentTrackerApp/PortfolioJournal/Utilities/StableID.swift
T
alexandrev-tibco eee0910680 Rama 1.6.0: dedup CloudKit-safe (WIP: StableID + UUIDs estables en defaults)
Funde 1.5.1 en 1.6.0. Parte 1 dedup (prevención): StableID (UUIDv5 namespace+nombre),
Account.defaultAccountStableID, Category.stableID(for:) + asignación en
createDefaultCategories. Pendiente: asignar en repos + limpieza de duplicados existentes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
2026-07-25 12:04:04 +02:00

33 lines
1.6 KiB
Swift

import Foundation
import CryptoKit
/// Deterministic UUIDs (RFC 4122 v5) for records that every device/install must
/// create identically the default Account and the default Categories.
///
/// Why: `awakeFromInsert()` assigns a random UUID, so each device seeded its own
/// "Default" account + default categories with *different* UUIDs before the first
/// CloudKit import. CloudKit keys on the UUID (record name), so those identical-in-
/// meaning records piled up as distinct records (8 accounts, 23 categories in the
/// wild). Giving the defaults a STABLE UUID derived from a fixed namespace + name
/// means every device produces the SAME record name CloudKit natively merges
/// them into one. No dedup, no deletes, no cascade the root-cause fix.
enum StableID {
/// Fixed app namespace (generated once, constant forever never change it).
static let namespace = UUID(uuidString: "F3A1C2D4-5E6F-4A7B-8C9D-0E1F2A3B4C5D")!
/// RFC 4122 v5 UUID: SHA-1(namespace bytes name) with version/variant bits set.
static func v5(_ name: String) -> UUID {
var hasher = Insecure.SHA1()
withUnsafeBytes(of: namespace.uuid) { hasher.update(bufferPointer: $0) }
hasher.update(data: Data(name.utf8))
let digest = Array(hasher.finalize()) // 20 bytes; take first 16
var b = Array(digest.prefix(16))
b[6] = (b[6] & 0x0F) | 0x50 // version 5
b[8] = (b[8] & 0x3F) | 0x80 // RFC 4122 variant
return UUID(uuid: (b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]))
}
}