// // HubPoCRunner.swift // Zyquo Local // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import Foundation /// Phase 3 gate: `ZyquoLocal --hub-poc` proves search → download with live /// progress → pause mid-flight → resume → validate → delete, end to end, /// against the live Hugging Face Hub, in an isolated temporary Models root. enum HubPoCRunner { private static let testRepo = "mlx-community/Qwen3-0.6B-4bit" @MainActor static func run() async { let hub = HubService(token: nil) // 1 — live search log("1) Live Hub search: “qwen” in mlx-community, by downloads…") do { let results = try await hub.search(query: "qwen", scope: .mlxCommunity, limit: 5) for r in results { log(" • \(r.id) ↓\(r.downloads ?? 0) arch=\(r.architecture ?? "?")\(r.isSupportedArchitecture ? "" : " ⚠️ unsupported")") } guard !results.isEmpty else { return fail("search returned nothing") } } catch { return fail("search: \(error.localizedDescription)") } // Isolated store for the test let root = FileManager.default.temporaryDirectory .appendingPathComponent("ZyquoLocalHubPoC-\(UUID().uuidString)") let store = ModelStore(root: root) let manager = DownloadManager(hub: hub, store: store) defer { try? FileManager.default.removeItem(at: root) } // 2 — file listing + size log("2) File listing for \(testRepo)…") do { let (files, total) = try await hub.requiredFiles(of: testRepo) log(" \(files.count) required files, total \(gb(total))") } catch { return fail("file listing: \(error.localizedDescription)") } // 3 — download with live progress, pause at ≥25 % log("3) Downloading with live progress; pausing mid-flight…") await manager.download(repoID: testRepo) var paused = false while !paused { try? await Task.sleep(for: .milliseconds(200)) guard let t = manager.task(for: testRepo) else { return fail("task vanished") } if t.state == .failed { return fail("download failed: \(t.errorDescription ?? "?")") } if t.fractionCompleted >= 0.25 { manager.pause(repoID: testRepo) paused = true let speed = manager.speeds[testRepo].map { "\(gb(Int64($0)))/s" } ?? "n/a" log(" paused at \(pct(t.fractionCompleted)) (speed was \(speed))") } } try? await Task.sleep(for: .milliseconds(400)) // 4 — verify partials survive, then resume guard let pausedTask = manager.task(for: testRepo), pausedTask.state == .paused else { return fail("expected paused state") } let partialBytes = pausedTask.receivedBytes guard partialBytes > 0 else { return fail("no partial bytes on disk") } log("4) Resuming from \(gb(partialBytes)) (HTTP Range)…") manager.resume(repoID: testRepo) var completed = false var lastLogged = -1 while !completed { try? await Task.sleep(for: .milliseconds(300)) guard let t = manager.task(for: testRepo) else { return fail("task vanished") } switch t.state { case .completed: completed = true case .failed: return fail("resume failed: \(t.errorDescription ?? "?")") default: let percent = Int(t.fractionCompleted * 100) if percent / 20 != lastLogged / 20 { lastLogged = percent log(" … \(percent)% (\(gb(t.receivedBytes))/\(gb(t.totalBytes)))") } } } log(" download completed + size-verified ✅") // 5 — store validation log("5) Validating model directory + store scan…") store.rescan() guard let model = store.model(for: testRepo) else { return fail("ModelStore did not recognize the downloaded model") } log(" \(model.repoID): \(gb(model.sizeBytes)), arch=\(model.architecture ?? "?"), quant=\(model.quantization ?? "?"), ctx=\(model.contextWindow ?? 0), verdict=\(MemoryAdvisor.verdict(weightsBytes: model.sizeBytes).label)") // 6 — delete log("6) Deleting…") let reclaimed = store.delete(repoID: testRepo) guard store.model(for: testRepo) == nil, !FileManager.default.fileExists(atPath: root.appendingPathComponent(testRepo).path) else { return fail("delete left files behind") } log(" reclaimed \(gb(reclaimed)) ✅") log("\nHUB POC: ALL STEPS GREEN") exit(0) } private static func log(_ text: String) { FileHandle.standardError.write(Data((text + "\n").utf8)) } private static func fail(_ text: String) { log("FAIL: \(text)") exit(1) } private static func gb(_ bytes: Int64) -> String { ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file) } private static func pct(_ fraction: Double) -> String { "\(Int(fraction * 100))%" } }