TeraComposerMediaOwnership.swift (2213B)
1 import CryptoKit 2 import Foundation 3 import RadrootsKit 4 5 /// The Apple host proves file durability; Rust remains the sole owner of scoped 6 /// composer references. The two stores deliberately have no shared transaction. 7 enum TeraComposerMediaOwnership { 8 static func confirm(_ media: [TeraComposerMedia], roots: RadrootsAppleFileRoots) throws { 9 guard !media.isEmpty else { return } 10 let durable = try TeraDurableMediaRoots.selectingStaging(in: roots) 11 guard media.count <= 20, roots.stagedBlobsRoot == durable.stagedBlobsRoot else { 12 throw TeraComposerAcknowledgment.unconfirmed 13 } 14 let access = RadrootsAppleFileAccess(roots: roots) 15 for item in media { 16 try Task.checkCancellation() 17 let blob = try reference(item) 18 try TeraDurableMediaRoots.restoreLegacyBlob(blob, roots: roots) 19 // Governed reads reject symlinks and oversized/replaced files. Process one 20 // derivative at a time under the preparer's existing 10 MiB output bound. 21 let bytes = try access.readStagedBlob(blob) 22 let digest = SHA256.hash(data: bytes).map { String(format: "%02x", $0) }.joined() 23 guard digest == item.sha256 else { throw TeraComposerAcknowledgment.unconfirmed } 24 // This exact install flushes both file and directory, including an 25 // existing matching object. It never deletes another draft's reference. 26 try access.installStagedBlob(bytes, reference: blob) 27 } 28 // Hash-named published files are identifiable orphans if the following DB 29 // commit fails or its result is lost. Keep them for C087 reconciliation. 30 } 31 32 private static func reference(_ item: TeraComposerMedia) throws -> RadrootsStagedBlobReference { 33 guard item.sha256.range(of: "^[0-9a-f]{64}$", options: .regularExpression) != nil, 34 item.opaqueReference == "media:\(item.sha256)", item.mediaType == "image/png", 35 (1 ... 10 * 1024 * 1024).contains(item.byteSize), let size = Int(exactly: item.byteSize) 36 else { throw TeraComposerAcknowledgment.unconfirmed } 37 return try RadrootsStagedBlobReference(blobID: item.sha256, sizeBytes: size, 38 mediaType: item.mediaType, filenameHint: "\(item.sha256).png") 39 } 40 }