From 8b0ab52203e013c5a6d874e909b51208b5d4d9c7 Mon Sep 17 00:00:00 2001 From: SamsonCJ Date: Thu, 23 Jul 2026 07:03:18 -0700 Subject: [PATCH 1/2] fix: allow managed runtime downloads to finish --- Scripts/smoke_babeldoc_runtime.sh | 16 +++++ .../BabelDOCRuntimeDistribution.swift | 45 +++++++++--- .../BabelDOCRuntimeDistributionTests.swift | 70 +++++++++++++++++-- 3 files changed, 116 insertions(+), 15 deletions(-) create mode 100755 Scripts/smoke_babeldoc_runtime.sh diff --git a/Scripts/smoke_babeldoc_runtime.sh b/Scripts/smoke_babeldoc_runtime.sh new file mode 100755 index 0000000..781e523 --- /dev/null +++ b/Scripts/smoke_babeldoc_runtime.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT_DIR" + +APP_VERSION="$( + /usr/libexec/PlistBuddy \ + -c 'Print :CFBundleShortVersionString' \ + Resources/Info.plist +)" + +GLOSS_RUN_BABELDOC_RUNTIME_SMOKE=1 \ + GLOSS_RUNTIME_SMOKE_APP_VERSION="$APP_VERSION" \ + swift test \ + --filter BabelDOCRuntimeDistributionTests/publicRuntimeReleaseInstallsEndToEnd diff --git a/Sources/GlossCore/BabelDOCRuntimeDistribution.swift b/Sources/GlossCore/BabelDOCRuntimeDistribution.swift index a54f275..5c755cb 100644 --- a/Sources/GlossCore/BabelDOCRuntimeDistribution.swift +++ b/Sources/GlossCore/BabelDOCRuntimeDistribution.swift @@ -188,23 +188,33 @@ public struct BabelDOCRuntimeTransport: Sendable { public static let live = ephemeral() public static func ephemeral( - policy: BabelDOCRuntimeNetworkPolicy = BabelDOCRuntimeNetworkPolicy() + policy: BabelDOCRuntimeNetworkPolicy ) -> Self { - let configuration = URLSessionConfiguration.ephemeral - configuration.timeoutIntervalForRequest = policy.requestTimeout - configuration.timeoutIntervalForResource = policy.resourceTimeout - configuration.waitsForConnectivity = policy.waitsForConnectivity - configuration.requestCachePolicy = .reloadIgnoringLocalCacheData - let session = URLSession(configuration: configuration) + ephemeral( + metadataPolicy: policy, + downloadPolicy: policy + ) + } + + public static func ephemeral( + metadataPolicy: BabelDOCRuntimeNetworkPolicy = .metadata, + downloadPolicy: BabelDOCRuntimeNetworkPolicy = .runtimeArchive + ) -> Self { + let metadataSession = URLSession( + configuration: sessionConfiguration(for: metadataPolicy) + ) + let downloadSession = URLSession( + configuration: sessionConfiguration(for: downloadPolicy) + ) return Self( fetchData: { url in - let (data, response) = try await session.data(from: url) + let (data, response) = try await metadataSession.data(from: url) try validateHTTPResponse(response, for: url) return data }, download: { url, destination in - let (temporaryURL, response) = try await session.download(from: url) + let (temporaryURL, response) = try await downloadSession.download(from: url) try validateHTTPResponse(response, for: url) let fileManager = FileManager.default @@ -216,6 +226,17 @@ public struct BabelDOCRuntimeTransport: Sendable { ) } + static func sessionConfiguration( + for policy: BabelDOCRuntimeNetworkPolicy + ) -> URLSessionConfiguration { + let configuration = URLSessionConfiguration.ephemeral + configuration.timeoutIntervalForRequest = policy.requestTimeout + configuration.timeoutIntervalForResource = policy.resourceTimeout + configuration.waitsForConnectivity = policy.waitsForConnectivity + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + return configuration + } + private static func validateHTTPResponse(_ response: URLResponse, for url: URL) throws { guard let response = response as? HTTPURLResponse else { return @@ -230,6 +251,12 @@ public struct BabelDOCRuntimeTransport: Sendable { } public struct BabelDOCRuntimeNetworkPolicy: Equatable, Sendable { + public static let metadata = Self() + public static let runtimeArchive = Self( + requestTimeout: 60, + resourceTimeout: 60 * 60 + ) + public let requestTimeout: TimeInterval public let resourceTimeout: TimeInterval public let waitsForConnectivity: Bool diff --git a/Tests/GlossCoreTests/BabelDOCRuntimeDistributionTests.swift b/Tests/GlossCoreTests/BabelDOCRuntimeDistributionTests.swift index d1d5c88..95c4a1b 100644 --- a/Tests/GlossCoreTests/BabelDOCRuntimeDistributionTests.swift +++ b/Tests/GlossCoreTests/BabelDOCRuntimeDistributionTests.swift @@ -68,13 +68,71 @@ struct BabelDOCRuntimeDistributionTests { ) } - @Test("live transport uses bounded offline timeouts") - func liveNetworkPolicyIsBounded() { - let policy = BabelDOCRuntimeNetworkPolicy() + @Test("live transport keeps metadata fast and allows large archive downloads") + func liveNetworkPoliciesMatchPayloadSize() { + let metadataPolicy = BabelDOCRuntimeNetworkPolicy.metadata + let archivePolicy = BabelDOCRuntimeNetworkPolicy.runtimeArchive + let metadataConfiguration = BabelDOCRuntimeTransport.sessionConfiguration( + for: metadataPolicy + ) + let archiveConfiguration = BabelDOCRuntimeTransport.sessionConfiguration( + for: archivePolicy + ) + + #expect(metadataConfiguration.timeoutIntervalForRequest == 12) + #expect(metadataConfiguration.timeoutIntervalForResource == 60) + #expect(!metadataConfiguration.waitsForConnectivity) + #expect(archiveConfiguration.timeoutIntervalForRequest == 60) + #expect(archiveConfiguration.timeoutIntervalForResource == 60 * 60) + #expect(!archiveConfiguration.waitsForConnectivity) + } + + @Test( + "public runtime release installs and restores end to end", + .enabled( + if: ProcessInfo.processInfo.environment[ + "GLOSS_RUN_BABELDOC_RUNTIME_SMOKE" + ] == "1", + "Set GLOSS_RUN_BABELDOC_RUNTIME_SMOKE=1 to download the public runtime." + ) + ) + func publicRuntimeReleaseInstallsEndToEnd() async throws { + let currentGlossVersion = try #require( + ProcessInfo.processInfo.environment[ + "GLOSS_RUNTIME_SMOKE_APP_VERSION" + ] + ) - #expect(policy.requestTimeout == 12) - #expect(policy.resourceTimeout == 60) - #expect(!policy.waitsForConnectivity) + let root = try temporaryDirectory() + defer { + try? FileManager.default.removeItem(at: root) + } + let manager = try BabelDOCRuntimeManager( + rootDirectory: root, + currentGlossVersion: currentGlossVersion + ) + + let installed = try await manager.update { progress in + print( + "BabelDOC runtime smoke: \(progress.operation.rawValue)" + + (progress.version.map { " \($0)" } ?? "") + ) + } + let installedVersion = try #require(installed.currentVersion) + let executableURL = try #require(installed.currentExecutableURL) + #expect(installed.operation == .ready) + #expect(installed.lastError == nil) + #expect(FileManager.default.isExecutableFile(atPath: executableURL.path)) + + let restoredManager = try BabelDOCRuntimeManager( + rootDirectory: root, + currentGlossVersion: currentGlossVersion + ) + let restored = await restoredManager.snapshot() + #expect(restored.currentVersion == installedVersion) + #expect(restored.currentExecutableURL == executableURL) + #expect(restored.operation == .idle) + #expect(restored.lastError == nil) } @Test("raw runtime install verifies SHA, permissions, and persisted state") From edec0515014b9e2c2f28a0f424ba69d96a6dd82f Mon Sep 17 00:00:00 2001 From: SamsonCJ Date: Thu, 23 Jul 2026 07:03:47 -0700 Subject: [PATCH 2/2] chore: prepare Gloss 0.8.1 --- Resources/Info.plist | 4 ++-- docs/release-notes/v0.8.1.md | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 docs/release-notes/v0.8.1.md diff --git a/Resources/Info.plist b/Resources/Info.plist index e01e96d..8bb61df 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.8.0 + 0.8.1 CFBundleVersion - 8 + 9 CFBundleDocumentTypes diff --git a/docs/release-notes/v0.8.1.md b/docs/release-notes/v0.8.1.md new file mode 100644 index 0000000..198099c --- /dev/null +++ b/docs/release-notes/v0.8.1.md @@ -0,0 +1,27 @@ +# Gloss 0.8.1 + +Gloss 0.8.1 fixes first-time installation of the managed BabelDOC runtime. + +## PDF runtime + +- Keeps signed manifest and signature requests on a short, bounded network + timeout. +- Gives the 200+ MiB runtime archive its own download session with a one-hour + total timeout and a 60-second inactivity timeout, so slow connections can + finish without hiding a stalled transfer. +- Preserves the existing single-policy transport API for callers that need the + same timeout behavior for metadata and downloads. + +## Validation + +- Adds an opt-in command-line smoke test that downloads the public runtime, + verifies its signed manifest, archive size and SHA-256, extracts it, checks + the executable, and restores the persisted installation state. +- The arm64 public runtime completed this path on a real slow connection in + 1,766.952 seconds; the previous release stopped after 60 seconds. + +## Compatibility + +This release uses the same signed BabelDOC `0.6.4+gloss.4` runtime and remains +compatible with macOS 14 or newer. Existing verified runtime installations do +not need to be downloaded again.