From 7e1894fc3f5f410aff76064cfb53ff6400eebb43 Mon Sep 17 00:00:00 2001 From: Rod Christiansen Date: Sun, 14 Jun 2026 13:46:07 -0700 Subject: [PATCH] Fetch bootstrap data uncached so manifest hash diffs always self-heal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NetworkManager built requests with the default cache policy and the shared URLCache, so management.json could be served stale — from the device URL cache or a CDN edge that hadn't purged yet. ManifestManager.downloadIfNeeded then compared cached files against that stale expected hash, matched, and logged 'Already have valid file. Skipping re-download' — so an updated payload such as ProvisioningWatcher.sh kept running the old cached copy across BootstrapMate runs. Use a shared no-cache session (urlCache = nil, requestCachePolicy = reloadIgnoringLocalAndRemoteCacheData) for every fetch so the manifest, and thus each per-item hash comparison, always reflects origin truth and a changed hash self-heals on the next run. --- Managers/NetworkManager.swift | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Managers/NetworkManager.swift b/Managers/NetworkManager.swift index f3b2c4b..4320d11 100644 --- a/Managers/NetworkManager.swift +++ b/Managers/NetworkManager.swift @@ -12,18 +12,30 @@ public final class NetworkManager { private init() {} + /// Session that never serves cached responses. Bootstrap data must reflect ORIGIN + /// truth on every run: management.json drives the per-item hash check, so a stale + /// manifest (from the local URL cache or a CDN edge that hasn't purged yet) makes a + /// changed file — e.g. ProvisioningWatcher.sh — compare against a stale expected hash, + /// get judged "already valid", and never re-download. Disabling the URL cache and + /// ignoring local + remote caches makes the hash diff always self-heal. + private static let noCacheSession: URLSession = { + let config = URLSessionConfiguration.default + config.urlCache = nil + config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData + return URLSession(configuration: config) + }() + public func downloadData( from url: URL, followRedirects: Bool, authHeader: String?, completion: @escaping @Sendable (Data?, Error?) -> Void ) { - var request = URLRequest(url: url) + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData) if let header = authHeader { request.addValue(header, forHTTPHeaderField: "Authorization") } - let session = URLSession(configuration: .default) - session.dataTask(with: request) { data, _, error in + Self.noCacheSession.dataTask(with: request) { data, _, error in completion(data, error) }.resume() } @@ -39,13 +51,12 @@ public final class NetworkManager { completion(.failure(DownloadError.invalidURL)) return } - var request = URLRequest(url: url) + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData) if let header = authHeader ?? authorizationHeader { request.addValue(header, forHTTPHeaderField: "Authorization") } - let session = URLSession(configuration: .default) - let task = session.downloadTask(with: request) { tempURL, _, error in + let task = Self.noCacheSession.downloadTask(with: request) { tempURL, _, error in if let error = error { completion(.failure(error)) return