145 lines
6.4 KiB
Swift
145 lines
6.4 KiB
Swift
import Foundation
|
|
import NucleicProtocol
|
|
|
|
/// One snapshot event in a Carbon stream chain (docs/CARBON_SHARDING.md §6.3): what was
|
|
/// captured, which generation/seq fences it, which manifest precedes it, and the ordered
|
|
/// shards whose decrypted concatenation is the payload.
|
|
///
|
|
/// Encoding is **canonical CBOR v1** — RFC 8949 §4.2 core deterministic (definite lengths,
|
|
/// map keys sorted by encoded bytes), which `CBOREncoder` produces by construction. Field
|
|
/// names are the frozen wire strings (`CarbonManifestTests` pins the bytes); `nil` optionals
|
|
/// are omitted, so two encoders can never disagree on a null representation. The manifest ID
|
|
/// is the SHA-256 of the *encrypted* manifest (`sealed(with:)`), mirroring shard IDs.
|
|
public struct CarbonManifest: Sendable, Codable, Equatable {
|
|
/// Project identity (spec: UUID / root commit / normalized remote — the transfer triple)
|
|
/// plus what a restorer needs to re-create the project when absent (§9.1 step 1).
|
|
public struct ProjectRef: Sendable, Codable, Equatable {
|
|
public var uuid: UUID
|
|
public var rootCommitSHA: String?
|
|
public var normalizedRemote: String?
|
|
public var cloneURL: String?
|
|
public var localOnly: Bool
|
|
|
|
public init(
|
|
uuid: UUID, rootCommitSHA: String? = nil, normalizedRemote: String? = nil,
|
|
cloneURL: String? = nil, localOnly: Bool = false
|
|
) {
|
|
self.uuid = uuid
|
|
self.rootCommitSHA = rootCommitSHA
|
|
self.normalizedRemote = normalizedRemote
|
|
self.cloneURL = cloneURL
|
|
self.localOnly = localOnly
|
|
}
|
|
}
|
|
|
|
/// An ordered reference to one payload shard. Plaintext concatenation order is the array
|
|
/// order; `byteCount` is the **ciphertext** size (what disk/wire accounting sums).
|
|
public struct ShardRef: Sendable, Codable, Equatable {
|
|
public var id: String
|
|
public var byteCount: UInt64
|
|
|
|
public init(id: String, byteCount: UInt64) {
|
|
self.id = id
|
|
self.byteCount = byteCount
|
|
}
|
|
}
|
|
|
|
public var v: Int
|
|
public var project: ProjectRef
|
|
public var stream: CarbonStreamKind
|
|
/// nil for per-project chains (the base `history` chain; the lock ledger).
|
|
public var sessionID: String?
|
|
/// Session streams: the session branch. Lock ledger: the domain ref (spec §3.4).
|
|
public var branch: String?
|
|
/// Session streams: the fork anchor, preserved verbatim.
|
|
public var baseSHA: String?
|
|
public var ownerDeviceID: String
|
|
/// The resuscitation-fence stamp (spec §10); the lock ledger rides its
|
|
/// `authorityGeneration` here (§3.4).
|
|
public var ownerGeneration: UInt64
|
|
/// Monotonic per (stream, sessionID, ownerGeneration), starting at 1.
|
|
public var snapshotSeq: UInt64
|
|
/// The previous manifest in this chain, or nil at a chain start/restart.
|
|
public var parentManifest: String?
|
|
/// worktreeSnapshot: tip at capture. history: the new tip the pack reaches.
|
|
public var branchTipSHA: String?
|
|
public var wtCommitSHA: String?
|
|
public var worktreeTreeSHA: String?
|
|
public var idxCommitSHA: String?
|
|
public var indexTreeSHA: String?
|
|
/// The index had unmerged entries at capture (spec §6.1 step 2); restore leaves
|
|
/// everything unstaged and the UI says so honestly.
|
|
public var indexUnavailable: Bool
|
|
public var payloadKind: CarbonPayloadKind
|
|
/// jsonlSegment: byte offset of this segment in the native file (0 restarts the file).
|
|
public var segmentOffset: UInt64?
|
|
public var backendSessionID: String?
|
|
/// Ordered; decrypted concatenation = the payload.
|
|
public var shards: [ShardRef]
|
|
/// Producer wall clock, informational only — **never** used for ordering (spec §10).
|
|
public var createdAtMs: UInt64
|
|
|
|
public init(
|
|
v: Int = 1, project: ProjectRef, stream: CarbonStreamKind, sessionID: String? = nil,
|
|
branch: String? = nil, baseSHA: String? = nil, ownerDeviceID: String,
|
|
ownerGeneration: UInt64, snapshotSeq: UInt64, parentManifest: String? = nil,
|
|
branchTipSHA: String? = nil, wtCommitSHA: String? = nil, worktreeTreeSHA: String? = nil,
|
|
idxCommitSHA: String? = nil, indexTreeSHA: String? = nil, indexUnavailable: Bool = false,
|
|
payloadKind: CarbonPayloadKind, segmentOffset: UInt64? = nil,
|
|
backendSessionID: String? = nil, shards: [ShardRef], createdAtMs: UInt64
|
|
) {
|
|
self.v = v
|
|
self.project = project
|
|
self.stream = stream
|
|
self.sessionID = sessionID
|
|
self.branch = branch
|
|
self.baseSHA = baseSHA
|
|
self.ownerDeviceID = ownerDeviceID
|
|
self.ownerGeneration = ownerGeneration
|
|
self.snapshotSeq = snapshotSeq
|
|
self.parentManifest = parentManifest
|
|
self.branchTipSHA = branchTipSHA
|
|
self.wtCommitSHA = wtCommitSHA
|
|
self.worktreeTreeSHA = worktreeTreeSHA
|
|
self.idxCommitSHA = idxCommitSHA
|
|
self.indexTreeSHA = indexTreeSHA
|
|
self.indexUnavailable = indexUnavailable
|
|
self.payloadKind = payloadKind
|
|
self.segmentOffset = segmentOffset
|
|
self.backendSessionID = backendSessionID
|
|
self.shards = shards
|
|
self.createdAtMs = createdAtMs
|
|
}
|
|
|
|
// MARK: - Canonical codec
|
|
|
|
/// The frozen plaintext encoding (deterministic CBOR). Byte-stable across platforms and
|
|
/// releases — `CarbonManifestTests.canonicalBytesAreFrozen` pins it.
|
|
public func canonicalCBOR() throws -> Data {
|
|
try CBOREncoder().encode(self)
|
|
}
|
|
|
|
public static func decode(canonical: Data) throws -> CarbonManifest {
|
|
try CBORDecoder().decode(CarbonManifest.self, from: canonical)
|
|
}
|
|
|
|
/// Encrypt with the project's crypto; the sealed blob's `id` is the **manifest ID**.
|
|
public func sealed(with crypto: CarbonCrypto) throws -> CarbonCrypto.Sealed {
|
|
try crypto.seal(try canonicalCBOR())
|
|
}
|
|
|
|
/// Decrypt + decode a sealed manifest blob.
|
|
public static func open(_ blob: Data, with crypto: CarbonCrypto) throws -> CarbonManifest {
|
|
try decode(canonical: try crypto.open(blob))
|
|
}
|
|
|
|
/// The head this manifest advances its chain to (once its shards are held — the honest-
|
|
/// head rule, spec §8.2 step 3).
|
|
public func head(manifestID: String) -> CarbonHead {
|
|
CarbonHead(
|
|
key: CarbonStreamKey(
|
|
projectUUID: project.uuid, stream: stream, sessionID: sessionID),
|
|
manifestID: manifestID, ownerGeneration: ownerGeneration, snapshotSeq: snapshotSeq)
|
|
}
|
|
}
|