diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml index e4fdd4efadc..3312e25d890 100644 --- a/.github/workflows/ssh-relay-runtime-artifacts.yml +++ b/.github/workflows/ssh-relay-runtime-artifacts.yml @@ -246,6 +246,7 @@ jobs: node --check config/scripts/ssh-relay-runtime-post-sign-metadata.test.mjs node --check config/scripts/ssh-relay-runtime-archive-extraction.mjs node --check config/scripts/ssh-relay-runtime-archive-extraction.test.mjs + node --check config/scripts/ssh-relay-runtime-portable-archive.test.mjs node --check config/scripts/ssh-relay-runtime-native-signing-stage-report.mjs node --check config/scripts/ssh-relay-runtime-macos-signing.mjs node --check config/scripts/ssh-relay-runtime-macos-signing.test.mjs @@ -272,6 +273,7 @@ jobs: config/scripts/ssh-relay-node-pty-windows-settlement.test.mjs \ config/scripts/ssh-relay-node-zip-inspection.test.mjs \ config/scripts/ssh-relay-runtime-artifact.test.mjs \ + config/scripts/ssh-relay-runtime-portable-archive.test.mjs \ config/scripts/ssh-relay-runtime-baseline.test.mjs \ config/scripts/ssh-relay-runtime-build.test.mjs \ config/scripts/ssh-relay-runtime-linux-build-evidence.test.mjs \ @@ -388,7 +390,7 @@ jobs: --git-commit "$source_commit" first_output="$output_root/first" identity="$first_output/orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json" - archive=$(find "$first_output" -maxdepth 1 -name '*.tar.xz' -print -quit) + archive=$(find "$first_output" -maxdepth 1 -name '*.tar.br' -print -quit) test -n "$archive" executed_runtime="$output_root/readback-executed" # Why: exercise only local verified bytes; release writes and remote consumers stay disconnected. @@ -449,7 +451,7 @@ jobs: --source-date-epoch "$source_date_epoch" \ --git-commit "$source_commit" identity="$output/orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json" - archive=$(find "$output" -maxdepth 1 -name '*.tar.xz' -print -quit) + archive=$(find "$output" -maxdepth 1 -name '*.tar.br' -print -quit) test -n "$archive" /usr/bin/time -p node config/scripts/verify-ssh-relay-runtime.mjs \ --runtime-directory "$output/runtime" \ @@ -467,7 +469,7 @@ jobs: --first-output-directory "$first_output" \ --second-output-directory "$second_output" identity="$first_output/orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json" - archive=$(find "$first_output" -maxdepth 1 -name '*.tar.xz' -print -quit) + archive=$(find "$first_output" -maxdepth 1 -name '*.tar.br' -print -quit) test -n "$archive" executed_runtime="$output_root/readback-executed" # Why: exercise only local verified bytes; release writes and remote consumers stay disconnected. @@ -496,7 +498,7 @@ jobs: test ! -e "$signing_stage/bin/node" rm -rf -- "$signing_stage" mkdir -p "$GITHUB_WORKSPACE/runtime-evidence/${{ matrix.tuple }}" - cp "$first_output"/*.tar.xz "$first_output"/*.spdx.json "$first_output"/*.provenance.json "$first_output"/*.identity.json \ + cp "$first_output"/*.tar.br "$first_output"/*.spdx.json "$first_output"/*.provenance.json "$first_output"/*.identity.json \ "$signing_report" "$GITHUB_WORKSPACE/runtime-evidence/${{ matrix.tuple }}/" - name: Upload unpublished artifact evidence @@ -739,6 +741,8 @@ jobs: if ($LASTEXITCODE -ne 0) { throw 'runtime archive extraction syntax check failed' } node --check config/scripts/ssh-relay-runtime-archive-extraction.test.mjs if ($LASTEXITCODE -ne 0) { throw 'runtime archive extraction test syntax check failed' } + node --check config/scripts/ssh-relay-runtime-portable-archive.test.mjs + if ($LASTEXITCODE -ne 0) { throw 'runtime portable archive test syntax check failed' } node --check config/scripts/ssh-relay-runtime-native-signing-stage-report.mjs if ($LASTEXITCODE -ne 0) { throw 'runtime signing stage report syntax check failed' } node --check config/scripts/ssh-relay-runtime-macos-signing.mjs @@ -775,6 +779,7 @@ jobs: config/scripts/ssh-relay-node-pty-windows-settlement.test.mjs ` config/scripts/ssh-relay-node-zip-inspection.test.mjs ` config/scripts/ssh-relay-runtime-artifact.test.mjs ` + config/scripts/ssh-relay-runtime-portable-archive.test.mjs ` config/scripts/ssh-relay-runtime-baseline.test.mjs ` config/scripts/ssh-relay-runtime-build.test.mjs ` config/scripts/ssh-relay-runtime-linux-build-evidence.test.mjs ` @@ -1079,7 +1084,7 @@ jobs: set -euo pipefail evidence="$GITHUB_WORKSPACE/runtime-evidence/${{ matrix.tuple }}" mapfile -d '' identities < <(find "$evidence" -maxdepth 1 -type f -name '*.identity.json' -print0) - mapfile -d '' archives < <(find "$evidence" -maxdepth 1 -type f -name '*.tar.xz' -print0) + mapfile -d '' archives < <(find "$evidence" -maxdepth 1 -type f -name '*.tar.br' -print0) if (( ${#identities[@]} != 1 || ${#archives[@]} != 1 )); then printf 'runtime baseline evidence is ambiguous: identities=%s archives=%s\n' \ "${#identities[@]}" "${#archives[@]}" >&2 @@ -1088,8 +1093,10 @@ jobs: identity=${identities[0]} archive=${archives[0]} runtime="$RUNNER_TEMP/baseline-runtime/${{ matrix.tuple }}" - mkdir -p "$runtime" - tar -xJf "$archive" -C "$runtime" + node config/scripts/ssh-relay-runtime-archive-extraction.mjs \ + --identity "$identity" \ + --archive "$archive" \ + --output-directory "$runtime" node config/scripts/verify-ssh-relay-runtime.mjs \ --runtime-directory "$runtime" \ --identity "$identity" \ diff --git a/.github/workflows/ssh-relay-runtime-native-signing.yml b/.github/workflows/ssh-relay-runtime-native-signing.yml index b0d45edd69a..68a94a25633 100644 --- a/.github/workflows/ssh-relay-runtime-native-signing.yml +++ b/.github/workflows/ssh-relay-runtime-native-signing.yml @@ -63,8 +63,6 @@ jobs: shell: bash run: | set -euo pipefail - brew list xz >/dev/null 2>&1 || brew install xz - command -v xz test -x /usr/bin/codesign security help >/dev/null @@ -80,7 +78,7 @@ jobs: set -euo pipefail evidence="$RUNNER_TEMP/ssh-relay-runtime-build/${{ matrix.tuple }}" identity="$evidence/orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json" - archive=$(find "$evidence" -maxdepth 1 -type f -name 'orca-ssh-relay-runtime-v1-*.tar.xz' -print) + archive=$(find "$evidence" -maxdepth 1 -type f -name 'orca-ssh-relay-runtime-v1-*.tar.br' -print) test "$(printf '%s\n' "$archive" | sed '/^$/d' | wc -l | tr -d ' ')" = 1 runtime="$RUNNER_TEMP/ssh-relay-runtime-source/${{ matrix.tuple }}" node config/scripts/ssh-relay-runtime-archive-extraction.mjs \ diff --git a/config/scripts/ssh-relay-runtime-aggregate-input.mjs b/config/scripts/ssh-relay-runtime-aggregate-input.mjs index 6279593dcee..a328e652847 100644 --- a/config/scripts/ssh-relay-runtime-aggregate-input.mjs +++ b/config/scripts/ssh-relay-runtime-aggregate-input.mjs @@ -33,7 +33,7 @@ function assertExactFields(value, fields, label) { function expectedArchiveName(tupleId, contentId) { const digest = contentId.slice('sha256:'.length) - const extension = tupleId.startsWith('win32-') ? 'zip' : 'tar.xz' + const extension = tupleId.startsWith('win32-') ? 'zip' : 'tar.br' return `orca-ssh-relay-runtime-v1-${tupleId}-${digest}.${extension}` } diff --git a/config/scripts/ssh-relay-runtime-aggregate-input.test.mjs b/config/scripts/ssh-relay-runtime-aggregate-input.test.mjs index 727016e2753..dcb1e86b6ff 100644 --- a/config/scripts/ssh-relay-runtime-aggregate-input.test.mjs +++ b/config/scripts/ssh-relay-runtime-aggregate-input.test.mjs @@ -16,7 +16,7 @@ async function fixture() { temporaryDirectories.push(inputDirectory) const contentId = `sha256:${'a'.repeat(64)}` const bytes = Buffer.from('verified runtime bytes') - const name = `orca-ssh-relay-runtime-v1-linux-x64-glibc-${contentId.slice(7)}.tar.xz` + const name = `orca-ssh-relay-runtime-v1-linux-x64-glibc-${contentId.slice(7)}.tar.br` await writeFile(join(inputDirectory, name), bytes) return { inputDirectory, @@ -80,7 +80,7 @@ describe('SSH relay runtime aggregate inputs', () => { it('rejects duplicate tuples, names, and unsupported identities', async () => { const duplicateTuple = await fixture() - duplicateTuple.assets.push({ ...duplicateTuple.assets[0], name: 'different.tar.xz' }) + duplicateTuple.assets.push({ ...duplicateTuple.assets[0], name: 'different.tar.br' }) await expect(verifySshRelayRuntimeAggregateInputs(duplicateTuple)).rejects.toThrow( /duplicate tuple/i ) @@ -100,7 +100,7 @@ describe('SSH relay runtime aggregate inputs', () => { it('derives the archive name from tuple and content identity', async () => { const input = await fixture() - input.assets[0].name = 'latest.tar.xz' + input.assets[0].name = 'latest.tar.br' await expect(verifySshRelayRuntimeAggregateInputs(input)).rejects.toThrow(/archive name/i) }) diff --git a/config/scripts/ssh-relay-runtime-archive-extraction.mjs b/config/scripts/ssh-relay-runtime-archive-extraction.mjs index b0491aec047..e221a7714a1 100644 --- a/config/scripts/ssh-relay-runtime-archive-extraction.mjs +++ b/config/scripts/ssh-relay-runtime-archive-extraction.mjs @@ -1,14 +1,17 @@ -import { spawn } from 'node:child_process' import { createHash } from 'node:crypto' import { createReadStream } from 'node:fs' import { lstat, mkdir, realpath, rm } from 'node:fs/promises' import { basename, dirname, resolve } from 'node:path' import { pipeline } from 'node:stream/promises' import { pathToFileURL } from 'node:url' +import { createBrotliDecompress } from 'node:zlib' import { extract } from 'tar' -import { inspectSshRelayRuntimeArchive } from './ssh-relay-runtime-archive.mjs' +import { + inspectSshRelayRuntimeArchive, + SSH_RELAY_RUNTIME_POSIX_ARCHIVE_LIMITS +} from './ssh-relay-runtime-archive.mjs' import { assertSshRelayRuntimeClosureEntries } from './ssh-relay-runtime-closure.mjs' import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs' import { readSshRelayRuntimeNativeSigningIdentity } from './ssh-relay-runtime-native-signing-plan.mjs' @@ -17,10 +20,9 @@ import { verifyRuntimeTree } from './verify-ssh-relay-runtime.mjs' const MAX_ARCHIVE_BYTES = 100 * 1024 * 1024 const EXTRACTION_TIMEOUT_MS = 5 * 60_000 -const MAX_DIAGNOSTIC_BYTES = 64 * 1024 function expectedArchiveName(identity) { - const extension = identity.os === 'win32' ? 'zip' : 'tar.xz' + const extension = identity.os === 'win32' ? 'zip' : 'tar.br' return `orca-ssh-relay-runtime-v1-${identity.tupleId}-${identity.contentId.slice('sha256:'.length)}.${extension}` } @@ -97,27 +99,7 @@ async function assertOutputAbsent(outputDirectory) { throw new Error('Runtime archive extraction requires an exclusive output directory') } -async function extractTarXz(archivePath, outputDirectory, signal) { - const child = spawn('xz', ['--decompress', '--stdout', '--single-stream', '--', archivePath], { - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - signal - }) - let stderr = '' - child.stderr.setEncoding('utf8') - child.stderr.on('data', (chunk) => { - stderr = `${stderr}${chunk}`.slice(-MAX_DIAGNOSTIC_BYTES) - }) - const completion = new Promise((resolvePromise, reject) => { - child.once('error', reject) - child.once('close', (code, closeSignal) => { - if (code === 0) { - resolvePromise() - } else { - reject(new Error(`xz extraction failed (${code ?? closeSignal ?? 'unknown'}): ${stderr}`)) - } - }) - }) +async function extractTarBrotli(archivePath, outputDirectory, signal) { const unpack = extract({ cwd: outputDirectory, strict: true, @@ -125,15 +107,16 @@ async function extractTarXz(archivePath, outputDirectory, signal) { noChmod: false, unlink: false }) - try { - await Promise.all([pipeline(child.stdout, unpack, { signal }), completion]) - } catch (error) { - if (child.exitCode === null && child.signalCode === null) { - child.kill() - } - await completion.catch(() => {}) - throw error - } + await pipeline( + createReadStream(archivePath, { + highWaterMark: SSH_RELAY_RUNTIME_POSIX_ARCHIVE_LIMITS.chunkBytes + }), + createBrotliDecompress({ + chunkSize: SSH_RELAY_RUNTIME_POSIX_ARCHIVE_LIMITS.chunkBytes + }), + unpack, + { signal } + ) } export async function extractSshRelayRuntimeArchive({ @@ -169,7 +152,7 @@ export async function extractSshRelayRuntimeArchive({ identity: finalIdentity, signal: effectiveSignal }) - : extractTarXz(absoluteArchive, physicalOutput, effectiveSignal)) + : extractTarBrotli(absoluteArchive, physicalOutput, effectiveSignal)) const tree = await verifyRuntimeTree(physicalOutput, finalIdentity, { signal: effectiveSignal }) const after = await describeArchive(absoluteArchive, identity.archive.size, effectiveSignal) if (!sameFileState(before.state, after.state) || before.sha256 !== after.sha256) { diff --git a/config/scripts/ssh-relay-runtime-archive.mjs b/config/scripts/ssh-relay-runtime-archive.mjs index 67a0bd4f9d1..62a2880bd05 100644 --- a/config/scripts/ssh-relay-runtime-archive.mjs +++ b/config/scripts/ssh-relay-runtime-archive.mjs @@ -1,95 +1,66 @@ -import { spawn } from 'node:child_process' import { createHash } from 'node:crypto' import { createReadStream, createWriteStream } from 'node:fs' -import { mkdtemp, rm, stat } from 'node:fs/promises' -import { tmpdir } from 'node:os' +import { rm, stat } from 'node:fs/promises' import { join } from 'node:path' import { pipeline } from 'node:stream/promises' +import { constants as zlibConstants, createBrotliCompress, createBrotliDecompress } from 'node:zlib' import { create, Parser } from 'tar' import { createSshRelayRuntimeZip, inspectSshRelayRuntimeZip } from './ssh-relay-runtime-zip.mjs' const MAX_ARCHIVE_BYTES = 100 * 1024 * 1024 -const MAX_DIAGNOSTIC_BYTES = 64 * 1024 -const XZ_TIMEOUT_MS = 5 * 60 * 1000 +const ARCHIVE_TIMEOUT_MS = 5 * 60 * 1000 +const BROTLI_CHUNK_BYTES = 64 * 1024 +const BROTLI_QUALITY = 9 +const BROTLI_WINDOW_BITS = 20 function archiveName(tuple, contentId) { const match = /^sha256:([0-9a-f]{64})$/.exec(contentId) if (!match) { throw new Error('Runtime content identity is not a SHA-256 digest') } - const suffix = tuple.startsWith('win32-') ? 'zip' : 'tar.xz' + const suffix = tuple.startsWith('win32-') ? 'zip' : 'tar.br' return `orca-ssh-relay-runtime-v1-${tuple}-${match[1]}.${suffix}` } -async function compressTar(tarPath, archivePath, signal) { - const child = spawn( - 'xz', - ['--compress', '--stdout', '--threads=1', '--check=crc64', '-9e', '--', tarPath], - { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, signal } +async function compressRuntimeTree({ + runtimeRoot, + paths, + sourceDateEpoch, + archivePath, + signal, + markOutputCreated +}) { + const archiveOutput = createWriteStream(archivePath, { flags: 'wx', mode: 0o600 }) + archiveOutput.once('open', markOutputCreated) + const tarStream = create( + { + cwd: runtimeRoot, + portable: true, + noPax: true, + noDirRecurse: true, + mtime: new Date(sourceDateEpoch * 1000) + }, + paths ) - let stderr = '' - child.stderr.setEncoding('utf8') - child.stderr.on('data', (chunk) => { - stderr = `${stderr}${chunk}`.slice(-MAX_DIAGNOSTIC_BYTES) - }) - const completion = new Promise((resolve, reject) => { - child.once('error', reject) - child.once('close', (code, closeSignal) => { - if (code === 0) { - resolve() - } else { - reject(new Error(`xz failed (${code ?? closeSignal ?? 'unknown'}): ${stderr.trim()}`)) - } - }) - }) - try { - await Promise.all([ - pipeline(child.stdout, createWriteStream(archivePath, { flags: 'wx', mode: 0o600 }), { - signal - }), - completion - ]) - } catch (error) { - if (child.exitCode === null && child.signalCode === null) { - child.kill() + const compression = createBrotliCompress({ + chunkSize: BROTLI_CHUNK_BYTES, + params: { + [zlibConstants.BROTLI_PARAM_QUALITY]: BROTLI_QUALITY, + [zlibConstants.BROTLI_PARAM_LGWIN]: BROTLI_WINDOW_BITS } - await completion.catch(() => {}) - throw error - } + }) + await pipeline(tarStream, compression, archiveOutput, { signal }) } async function decompressArchive(archivePath, destination, signal) { - const child = spawn('xz', ['--decompress', '--stdout', '--single-stream', '--', archivePath], { - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - signal - }) - let stderr = '' - child.stderr.setEncoding('utf8') - child.stderr.on('data', (chunk) => { - stderr = `${stderr}${chunk}`.slice(-MAX_DIAGNOSTIC_BYTES) - }) - const completion = new Promise((resolve, reject) => { - child.once('error', reject) - child.once('close', (code, closeSignal) => { - if (code === 0) { - resolve() - } else { - reject(new Error(`xz failed (${code ?? closeSignal ?? 'unknown'}): ${stderr.trim()}`)) - } - }) - }) - try { - await Promise.all([pipeline(child.stdout, destination, { signal }), completion]) - } catch (error) { - if (child.exitCode === null && child.signalCode === null) { - child.kill() - } - await completion.catch(() => {}) - throw error - } + await pipeline( + createReadStream(archivePath, { highWaterMark: BROTLI_CHUNK_BYTES }), + createBrotliDecompress({ chunkSize: BROTLI_CHUNK_BYTES }), + destination, + { signal } + ) } export async function createSshRelayRuntimeArchive({ @@ -111,25 +82,25 @@ export async function createSshRelayRuntimeArchive({ if (!Number.isSafeInteger(sourceDateEpoch) || sourceDateEpoch < 0) { throw new Error('Runtime archive SOURCE_DATE_EPOCH must be a non-negative safe integer') } - const temporaryDirectory = await mkdtemp(join(tmpdir(), 'orca-runtime-tar-')) - const tarPath = join(temporaryDirectory, 'runtime.tar') const name = archiveName(identity.tupleId, identity.contentId) const archivePath = join(outputDirectory, name) - const effectiveSignal = signal ?? AbortSignal.timeout(XZ_TIMEOUT_MS) + const timeoutSignal = AbortSignal.timeout(ARCHIVE_TIMEOUT_MS) + const effectiveSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal + effectiveSignal.throwIfAborted() + let outputCreated = false try { const paths = identity.entries.map((entry) => entry.path).sort() - await create( - { - cwd: runtimeRoot, - file: tarPath, - portable: true, - noPax: true, - noDirRecurse: true, - mtime: new Date(sourceDateEpoch * 1000) - }, - paths - ) - await compressTar(tarPath, archivePath, effectiveSignal) + await compressRuntimeTree({ + runtimeRoot, + paths, + sourceDateEpoch, + archivePath, + signal: effectiveSignal, + // Why: an exclusive-open failure must never delete an output owned by another build. + markOutputCreated: () => { + outputCreated = true + } + }) const metadata = await stat(archivePath) if (!metadata.isFile() || metadata.size === 0 || metadata.size > MAX_ARCHIVE_BYTES) { throw new Error('Runtime archive exceeds the release-manifest compressed-size limit') @@ -145,10 +116,10 @@ export async function createSshRelayRuntimeArchive({ sha256: `sha256:${digest.digest('hex')}` } } catch (error) { - await rm(archivePath, { force: true }) + if (outputCreated) { + await rm(archivePath, { force: true }) + } throw error - } finally { - await rm(temporaryDirectory, { recursive: true, force: true }) } } @@ -202,7 +173,10 @@ export async function inspectSshRelayRuntimeArchive(archivePath, identity, { sig parser.abort(error) } }) - await decompressArchive(archivePath, parser, signal ?? AbortSignal.timeout(XZ_TIMEOUT_MS)) + const timeoutSignal = AbortSignal.timeout(ARCHIVE_TIMEOUT_MS) + const effectiveSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal + effectiveSignal.throwIfAborted() + await decompressArchive(archivePath, parser, effectiveSignal) await Promise.all(pendingFiles) const missing = [...expected.keys()].filter((path) => !seen.has(path)) if (missing.length > 0) { @@ -214,3 +188,9 @@ export async function inspectSshRelayRuntimeArchive(archivePath, identity, { sig expandedBytes: identity.expandedSize } } + +export const SSH_RELAY_RUNTIME_POSIX_ARCHIVE_LIMITS = Object.freeze({ + chunkBytes: BROTLI_CHUNK_BYTES, + quality: BROTLI_QUALITY, + windowBits: BROTLI_WINDOW_BITS +}) diff --git a/config/scripts/ssh-relay-runtime-draft-readback-materialization.test.mjs b/config/scripts/ssh-relay-runtime-draft-readback-materialization.test.mjs index dd1a52ca0c3..44f3afef6e4 100644 --- a/config/scripts/ssh-relay-runtime-draft-readback-materialization.test.mjs +++ b/config/scripts/ssh-relay-runtime-draft-readback-materialization.test.mjs @@ -11,7 +11,7 @@ const REPO = 'stablyai/orca' const RELEASE_ID = 42 const TAG = 'v1.4.140-rc.1' const TOKEN = 'secret-token' -const NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.xz' +const NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.br' const BYTES = Buffer.from('immutable release bytes') const SECOND_NAME = 'orca-ssh-relay-runtime-manifest.json' const SECOND_BYTES = Buffer.from('immutable manifest bytes') diff --git a/config/scripts/ssh-relay-runtime-draft-readback.test.mjs b/config/scripts/ssh-relay-runtime-draft-readback.test.mjs index 5903acd5ef2..10c88a2353e 100644 --- a/config/scripts/ssh-relay-runtime-draft-readback.test.mjs +++ b/config/scripts/ssh-relay-runtime-draft-readback.test.mjs @@ -7,7 +7,7 @@ const REPO = 'stablyai/orca' const TOKEN = 'secret-token' const bytes = Buffer.from('immutable release bytes') const expected = { - name: 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.xz', + name: 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.br', sha256: 'sha256:5396515749878bd28c5dae110040b4fbae1c33f59318b9c23f446319d68e236a', size: bytes.length } diff --git a/config/scripts/ssh-relay-runtime-draft-recovery.test.mjs b/config/scripts/ssh-relay-runtime-draft-recovery.test.mjs index 0567062b053..479bfafcc8b 100644 --- a/config/scripts/ssh-relay-runtime-draft-recovery.test.mjs +++ b/config/scripts/ssh-relay-runtime-draft-recovery.test.mjs @@ -11,7 +11,7 @@ function asset(name, digit) { function fixture() { const expectedAssets = [ - asset('orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.xz', '1'), + asset('orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.br', '1'), asset('orca-ssh-relay-runtime-v1-win32-x64-b.zip', '2'), asset('orca-ssh-relay-runtime-manifest.json', '3'), asset('orca-ssh-relay-runtime-manifest.sig', '4') diff --git a/config/scripts/ssh-relay-runtime-draft-release-verification.mjs b/config/scripts/ssh-relay-runtime-draft-release-verification.mjs index aa60f962ee2..da073241c22 100644 --- a/config/scripts/ssh-relay-runtime-draft-release-verification.mjs +++ b/config/scripts/ssh-relay-runtime-draft-release-verification.mjs @@ -11,7 +11,7 @@ const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u const REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u const TAG_PATTERN = /^v\d+\.\d+\.\d+(?:-rc\.\d+(?:\.[0-9A-Za-z]+)?)?$/u const TUPLE_PATTERN = /^(?:linux-(?:x64|arm64)-glibc|darwin-(?:x64|arm64)|win32-(?:x64|arm64))$/u -const RUNTIME_ARCHIVE_PATTERN = /^orca-ssh-relay-runtime-v1-.+\.(?:tar\.xz|zip)$/u +const RUNTIME_ARCHIVE_PATTERN = /^orca-ssh-relay-runtime-v1-.+\.(?:tar\.br|zip)$/u const MANAGED_ASSET_PATTERN = /^orca-ssh-relay-runtime-[A-Za-z0-9._-]+$/u const MAX_ASSET_BYTES = 100 * 1024 * 1024 const MAX_ASSETS = 26 diff --git a/config/scripts/ssh-relay-runtime-draft-release-verification.test.mjs b/config/scripts/ssh-relay-runtime-draft-release-verification.test.mjs index e6b2e71cf82..672e2e4aed9 100644 --- a/config/scripts/ssh-relay-runtime-draft-release-verification.test.mjs +++ b/config/scripts/ssh-relay-runtime-draft-release-verification.test.mjs @@ -12,7 +12,7 @@ const RELEASE_ID = 42 const TAG = 'v1.5.0-rc.1' const SOURCE_COMMIT = 'a'.repeat(40) const TOKEN = 'test-token' -const ARCHIVE_NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.xz' +const ARCHIVE_NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.br' const ARCHIVE_BYTES = Buffer.from('verified runtime archive') const SECOND_ARCHIVE_NAME = 'orca-ssh-relay-runtime-v1-win32-x64-c.zip' const SECOND_ARCHIVE_BYTES = Buffer.from('verified Windows runtime archive') diff --git a/config/scripts/ssh-relay-runtime-draft-upload.test.mjs b/config/scripts/ssh-relay-runtime-draft-upload.test.mjs index 3de52f73284..bc85d711b79 100644 --- a/config/scripts/ssh-relay-runtime-draft-upload.test.mjs +++ b/config/scripts/ssh-relay-runtime-draft-upload.test.mjs @@ -12,7 +12,7 @@ const RELEASE_ID = 42 const TAG = 'v1.5.0-rc.1' const SOURCE_COMMIT = 'a'.repeat(40) const TOKEN = 'test-token' -const NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.xz' +const NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.br' const BYTES = Buffer.from('immutable relay runtime bytes') const SECOND_NAME = 'orca-ssh-relay-runtime-manifest.json' const SECOND_BYTES = Buffer.from('immutable signed manifest bytes') diff --git a/config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs b/config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs index d3903158359..a3d78291636 100644 --- a/config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs +++ b/config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs @@ -333,7 +333,7 @@ describe('SSH relay runtime macOS signature verification', () => { } } const staleArchive = structuredClone(fixture.finalIdentity) - staleArchive.archive = { fileName: 'unsigned.tar.xz' } + staleArchive.archive = { fileName: 'unsigned.tar.br' } await expect( verifySshRelayRuntimeMacosSignatures({ ...common, finalIdentity: staleArchive }) ).rejects.toThrow(/does not match its unsigned source/i) diff --git a/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs b/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs index 16e9fb78f7c..5a752dc5924 100644 --- a/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs +++ b/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs @@ -133,7 +133,7 @@ function tupleFor(tupleId) { tuple.metadataAssets.sbom.name = `orca-ssh-relay-runtime-${tupleId}.spdx.json` tuple.metadataAssets.provenance.name = `orca-ssh-relay-runtime-${tupleId}.provenance.json` tuple.contentId = computeSshRelayRuntimeContentId(tuple) - const extension = os === 'win32' ? 'zip' : 'tar.xz' + const extension = os === 'win32' ? 'zip' : 'tar.br' tuple.archive.name = `orca-ssh-relay-runtime-v1-${tupleId}-${tuple.contentId.slice(7)}.${extension}` return tuple } diff --git a/config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs b/config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs index 63df8669147..ca99fdb86d1 100644 --- a/config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs +++ b/config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs @@ -42,7 +42,7 @@ function arm64TupleFrom(source) { tuple.metadataAssets.sbom.name = 'orca-ssh-relay-runtime-linux-arm64-glibc.spdx.json' tuple.metadataAssets.provenance.name = 'orca-ssh-relay-runtime-linux-arm64-glibc.provenance.json' tuple.contentId = computeSshRelayRuntimeContentId(tuple) - tuple.archive.name = `orca-ssh-relay-runtime-v1-${tuple.tupleId}-${tuple.contentId.slice('sha256:'.length)}.tar.xz` + tuple.archive.name = `orca-ssh-relay-runtime-v1-${tuple.tupleId}-${tuple.contentId.slice('sha256:'.length)}.tar.br` return tuple } diff --git a/config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs b/config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs index c5b2bce8f9c..69c6293d994 100644 --- a/config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs +++ b/config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs @@ -30,7 +30,7 @@ function arm64TupleFrom(source) { tuple.metadataAssets.sbom.name = 'orca-ssh-relay-runtime-linux-arm64-glibc.spdx.json' tuple.metadataAssets.provenance.name = 'orca-ssh-relay-runtime-linux-arm64-glibc.provenance.json' tuple.contentId = computeSshRelayRuntimeContentId(tuple) - tuple.archive.name = `orca-ssh-relay-runtime-v1-${tuple.tupleId}-${tuple.contentId.slice('sha256:'.length)}.tar.xz` + tuple.archive.name = `orca-ssh-relay-runtime-v1-${tuple.tupleId}-${tuple.contentId.slice('sha256:'.length)}.tar.br` return tuple } @@ -40,10 +40,10 @@ describe('SSH relay runtime canonical manifest assembly', () => { expect(assembled.canonicalBytes.at(-1)).not.toBe('\n'.charCodeAt(0)) expect(createHash('sha256').update(assembled.canonicalBytes).digest('hex')).toBe( - 'e78bf4416628a91055035dc7926035cbf633f29d3618be34e041c6dc5e0794fb' + 'dc36bff51330eb3aa4791bf30c25eeedd6e8d4cbc57470d50f377c24e0a5ed06' ) expect(assembled.sha256).toBe( - 'sha256:e78bf4416628a91055035dc7926035cbf633f29d3618be34e041c6dc5e0794fb' + 'sha256:dc36bff51330eb3aa4791bf30c25eeedd6e8d4cbc57470d50f377c24e0a5ed06' ) expect(parseCanonicalSshRelayRuntimeManifestBytes(assembled.canonicalBytes)).toEqual( assembled.manifest diff --git a/config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs b/config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs index 91b93ad487a..31d0946062c 100644 --- a/config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs +++ b/config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs @@ -50,7 +50,7 @@ describe('SSH relay runtime manifest signing handoff', () => { ]) expect(request).toMatchObject({ algorithm: 'ed25519-v1', - payloadSha256: 'sha256:e78bf4416628a91055035dc7926035cbf633f29d3618be34e041c6dc5e0794fb', + payloadSha256: 'sha256:dc36bff51330eb3aa4791bf30c25eeedd6e8d4cbc57470d50f377c24e0a5ed06', payloadSize: canonical.length }) expect(request.canonicalBytes).toEqual(canonical) diff --git a/config/scripts/ssh-relay-runtime-manifest-tuple.mjs b/config/scripts/ssh-relay-runtime-manifest-tuple.mjs index 8755986c57e..ea770e0aa48 100644 --- a/config/scripts/ssh-relay-runtime-manifest-tuple.mjs +++ b/config/scripts/ssh-relay-runtime-manifest-tuple.mjs @@ -57,7 +57,7 @@ async function physicalDirectory(path, label) { function expectedNames(identity) { const digest = identity.contentId.slice('sha256:'.length) - const extension = identity.tupleId.startsWith('win32-') ? 'zip' : 'tar.xz' + const extension = identity.tupleId.startsWith('win32-') ? 'zip' : 'tar.br' const prefix = `orca-ssh-relay-runtime-${identity.tupleId}` return { archive: `orca-ssh-relay-runtime-v1-${identity.tupleId}-${digest}.${extension}`, diff --git a/config/scripts/ssh-relay-runtime-manifest-validation.mjs b/config/scripts/ssh-relay-runtime-manifest-validation.mjs index 2bbf6d18d04..ed8d672a97e 100644 --- a/config/scripts/ssh-relay-runtime-manifest-validation.mjs +++ b/config/scripts/ssh-relay-runtime-manifest-validation.mjs @@ -197,7 +197,7 @@ function expectedTupleId(tuple) { } function expectedArchiveName(tuple) { - const extension = tuple.os === 'win32' ? 'zip' : 'tar.xz' + const extension = tuple.os === 'win32' ? 'zip' : 'tar.br' return `orca-ssh-relay-runtime-v1-${tuple.tupleId}-${tuple.contentId.slice(7)}.${extension}` } diff --git a/config/scripts/ssh-relay-runtime-native-signing-workflow.test.mjs b/config/scripts/ssh-relay-runtime-native-signing-workflow.test.mjs index cab01b06900..524fff1a1a1 100644 --- a/config/scripts/ssh-relay-runtime-native-signing-workflow.test.mjs +++ b/config/scripts/ssh-relay-runtime-native-signing-workflow.test.mjs @@ -69,6 +69,9 @@ describe('SSH relay runtime native signing workflow', () => { expect(actionRefs(source).every((ref) => /^[0-9a-f]{40}$/u.test(ref))).toBe(true) expect(source).not.toContain('continue-on-error') expect(source).not.toMatch(/\b(?:gh\s+release|softprops\/action-gh-release)\b/u) + expect(source).toContain('orca-ssh-relay-runtime-v1-*.tar.br') + expect(source).not.toContain('brew install xz') + expect(source).not.toContain('command -v xz') expect(source.match(/ssh-relay-runtime-archive-extraction\.mjs/g)).toHaveLength(2) expect(source.match(/ssh-relay-runtime-native-signing-stage\.mjs/g)).toHaveLength(2) expect(source.match(/ssh-relay-runtime-native-signing-finalization\.mjs/g)).toHaveLength(2) diff --git a/config/scripts/ssh-relay-runtime-portable-archive.test.mjs b/config/scripts/ssh-relay-runtime-portable-archive.test.mjs new file mode 100644 index 00000000000..3698a25be9a --- /dev/null +++ b/config/scripts/ssh-relay-runtime-portable-archive.test.mjs @@ -0,0 +1,156 @@ +import { createHash } from 'node:crypto' +import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +import { afterEach, describe, expect, it } from 'vitest' + +import * as runtimeArchive from './ssh-relay-runtime-archive.mjs' +import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs' + +const temporaryDirectories = [] + +function digest(bytes) { + return `sha256:${createHash('sha256').update(bytes).digest('hex')}` +} + +function archiveName(identity) { + return `orca-ssh-relay-runtime-v1-${identity.tupleId}-${identity.contentId.slice('sha256:'.length)}.tar.br` +} + +async function fixture() { + const root = await mkdtemp(join(tmpdir(), 'ssh-relay-portable-archive-')) + temporaryDirectories.push(root) + const runtimeRoot = join(root, 'runtime') + const outputDirectory = join(root, 'output') + const nodeBytes = Buffer.from('portable bundled node') + await Promise.all([mkdir(join(runtimeRoot, 'bin'), { recursive: true }), mkdir(outputDirectory)]) + await writeFile(join(runtimeRoot, 'bin', 'node'), nodeBytes) + await chmod(join(runtimeRoot, 'bin', 'node'), 0o755) + const base = { + tupleId: 'darwin-arm64', + os: 'darwin', + architecture: 'arm64', + compatibility: { kind: 'darwin', minimumVersion: '13.5' }, + nodeVersion: '24.18.0', + dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' }, + entries: [ + { path: 'bin', type: 'directory', mode: 0o755 }, + { + path: 'bin/node', + type: 'file', + role: 'node', + size: nodeBytes.length, + mode: 0o755, + sha256: digest(nodeBytes) + } + ], + fileCount: 1, + expandedSize: nodeBytes.length + } + return { + runtimeRoot, + outputDirectory, + identity: { ...base, contentId: computeSshRelayRuntimeContentId(base) } + } +} + +afterEach(async () => { + await Promise.all( + temporaryDirectories + .splice(0) + .map((directory) => rm(directory, { recursive: true, force: true })) + ) +}) + +describe('SSH relay portable POSIX archive', () => { + it.skipIf(process.platform === 'win32')( + 'creates deterministic Brotli archives and verifies their exact entries', + async () => { + const value = await fixture() + const secondOutput = join(value.outputDirectory, 'second') + await mkdir(secondOutput) + + const first = await runtimeArchive.createSshRelayRuntimeArchive({ + ...value, + sourceDateEpoch: 1_788_739_200 + }) + const second = await runtimeArchive.createSshRelayRuntimeArchive({ + runtimeRoot: value.runtimeRoot, + outputDirectory: secondOutput, + identity: value.identity, + sourceDateEpoch: 1_788_739_200 + }) + + expect(first.name).toMatch(/\.tar\.br$/u) + expect(await readFile(first.path)).toEqual(await readFile(second.path)) + await expect( + runtimeArchive.inspectSshRelayRuntimeArchive(first.path, value.identity) + ).resolves.toEqual({ entries: 2, files: 1, expandedBytes: value.identity.expandedSize }) + } + ) + + it('declares the bounded architecture-independent compression contract', () => { + expect(runtimeArchive.SSH_RELAY_RUNTIME_POSIX_ARCHIVE_LIMITS).toEqual({ + chunkBytes: 64 * 1024, + quality: 9, + windowBits: 20 + }) + }) + + it.skipIf(process.platform === 'win32')( + 'rejects truncated Brotli and preserves outputs it did not create', + async () => { + const truncated = await fixture() + const archive = await runtimeArchive.createSshRelayRuntimeArchive({ + ...truncated, + sourceDateEpoch: 1_788_739_200 + }) + const bytes = await readFile(archive.path) + await writeFile(archive.path, bytes.subarray(0, -1)) + await expect( + runtimeArchive.inspectSshRelayRuntimeArchive(archive.path, truncated.identity) + ).rejects.toThrow() + + const existing = await fixture() + const existingPath = join(existing.outputDirectory, archiveName(existing.identity)) + await writeFile(existingPath, 'owned by another build') + await expect( + runtimeArchive.createSshRelayRuntimeArchive({ + ...existing, + sourceDateEpoch: 1_788_739_200 + }) + ).rejects.toMatchObject({ code: 'EEXIST' }) + await expect(readFile(existingPath, 'utf8')).resolves.toBe('owned by another build') + } + ) + + it.skipIf(process.platform === 'win32')( + 'settles pre-cancellation without retaining a partial archive', + async () => { + const value = await fixture() + await expect( + runtimeArchive.createSshRelayRuntimeArchive({ + ...value, + sourceDateEpoch: 1_788_739_200, + signal: AbortSignal.abort() + }) + ).rejects.toThrow(/abort/i) + await expect( + readFile(join(value.outputDirectory, archiveName(value.identity))) + ).rejects.toMatchObject({ code: 'ENOENT' }) + } + ) + + it('does not depend on a client-native or system XZ executable', async () => { + const source = await readFile( + new URL('./ssh-relay-runtime-archive.mjs', import.meta.url), + 'utf8' + ) + + expect(source).not.toContain("from 'node:child_process'") + expect(source).not.toContain("spawn('xz'") + expect(source).toContain('createBrotliCompress') + expect(source).toContain('createBrotliDecompress') + }) +}) diff --git a/config/scripts/ssh-relay-runtime-post-sign-metadata.mjs b/config/scripts/ssh-relay-runtime-post-sign-metadata.mjs index c06f4e11c3f..016e3a90194 100644 --- a/config/scripts/ssh-relay-runtime-post-sign-metadata.mjs +++ b/config/scripts/ssh-relay-runtime-post-sign-metadata.mjs @@ -37,7 +37,7 @@ async function physicalDirectory(path, label) { function expectedNames(identity) { const prefix = `orca-ssh-relay-runtime-${identity.tupleId}` - const extension = identity.os === 'win32' ? 'zip' : 'tar.xz' + const extension = identity.os === 'win32' ? 'zip' : 'tar.br' return { archive: `orca-ssh-relay-runtime-v1-${identity.tupleId}-${identity.contentId.slice('sha256:'.length)}.${extension}`, sbom: `${prefix}.spdx.json`, diff --git a/config/scripts/ssh-relay-runtime-provenance.test.mjs b/config/scripts/ssh-relay-runtime-provenance.test.mjs index 112c1a06d0e..8a12d6422fd 100644 --- a/config/scripts/ssh-relay-runtime-provenance.test.mjs +++ b/config/scripts/ssh-relay-runtime-provenance.test.mjs @@ -44,7 +44,7 @@ function fixture() { tuple, identity, archive: { - name: `orca-ssh-relay-runtime-v1-${tuple}-${'f'.repeat(64)}.tar.xz`, + name: `orca-ssh-relay-runtime-v1-${tuple}-${'f'.repeat(64)}.tar.br`, size: 123, sha256: sha256('a') }, diff --git a/config/scripts/ssh-relay-runtime-readback-archive-execution.test.mjs b/config/scripts/ssh-relay-runtime-readback-archive-execution.test.mjs index db7c6589601..caaf82e140b 100644 --- a/config/scripts/ssh-relay-runtime-readback-archive-execution.test.mjs +++ b/config/scripts/ssh-relay-runtime-readback-archive-execution.test.mjs @@ -20,7 +20,7 @@ import { parseSshRelayRuntimeReadbackArchiveExecutionArguments } from './ssh-relay-runtime-readback-archive-execution.mjs' -const ARCHIVE_NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.xz' +const ARCHIVE_NAME = 'orca-ssh-relay-runtime-v1-linux-x64-glibc-a.tar.br' const ARCHIVE_BYTES = Buffer.from('verified materialized archive bytes') const ARCHIVE_SHA256 = `sha256:${createHash('sha256').update(ARCHIVE_BYTES).digest('hex')}` diff --git a/config/scripts/ssh-relay-runtime-release-stage-gate.test.mjs b/config/scripts/ssh-relay-runtime-release-stage-gate.test.mjs index 7aeddfcbe51..dcccab46ddb 100644 --- a/config/scripts/ssh-relay-runtime-release-stage-gate.test.mjs +++ b/config/scripts/ssh-relay-runtime-release-stage-gate.test.mjs @@ -37,7 +37,7 @@ function stage(id, output, overrides = {}) { } function successfulFixture() { - const linux = asset(LINUX, 'orca-ssh-relay-runtime-linux.tar.xz', SHA.linux, 'a') + const linux = asset(LINUX, 'orca-ssh-relay-runtime-linux.tar.br', SHA.linux, 'a') const windowsUnsigned = asset( WINDOWS, 'orca-ssh-relay-runtime-windows-unsigned.zip', @@ -111,7 +111,7 @@ describe('SSH relay runtime release stage gate', () => { expect(result.candidateTupleIds).toEqual([LINUX, WINDOWS]) expect(result.finalRuntimeAssets.map((entry) => entry.tupleId)).toEqual([LINUX, WINDOWS]) expect(result.releaseAssets.map((entry) => entry.name)).toEqual([ - 'orca-ssh-relay-runtime-linux.tar.xz', + 'orca-ssh-relay-runtime-linux.tar.br', 'orca-ssh-relay-runtime-windows-signed.zip', 'orca-ssh-relay-runtime-manifest.json', 'orca-ssh-relay-runtime-manifest.sig' diff --git a/config/scripts/ssh-relay-runtime-reproducibility.test.mjs b/config/scripts/ssh-relay-runtime-reproducibility.test.mjs index d14e9a0b752..456bce7d9cc 100644 --- a/config/scripts/ssh-relay-runtime-reproducibility.test.mjs +++ b/config/scripts/ssh-relay-runtime-reproducibility.test.mjs @@ -12,7 +12,7 @@ const tuple = 'linux-x64-glibc' const contentId = `sha256:${'a'.repeat(64)}` const archiveBytes = 'archive bytes' const archiveSha256 = `sha256:${createHash('sha256').update(archiveBytes).digest('hex')}` -const archiveName = `orca-ssh-relay-runtime-v1-${tuple}-${contentId.slice('sha256:'.length)}.tar.xz` +const archiveName = `orca-ssh-relay-runtime-v1-${tuple}-${contentId.slice('sha256:'.length)}.tar.br` afterEach(async () => { await Promise.all(temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true }))) diff --git a/config/scripts/ssh-relay-runtime-workflow.test.mjs b/config/scripts/ssh-relay-runtime-workflow.test.mjs index a6455ec0457..1a2a6de185d 100644 --- a/config/scripts/ssh-relay-runtime-workflow.test.mjs +++ b/config/scripts/ssh-relay-runtime-workflow.test.mjs @@ -232,7 +232,10 @@ describe('SSH relay runtime artifact workflow', () => { ) expect(source).not.toContain('work_directory="$output_root/build-work"') expect(source).not.toContain("Join-Path $outputRoot 'build-work'") - expect(source).toContain('cp "$first_output"/*.tar.xz') + expect(source).toContain('cp "$first_output"/*.tar.br') + expect(source).toContain("-name '*.tar.br'") + expect(source).not.toContain('tar -xJf "$archive"') + expect(source.match(/ssh-relay-runtime-portable-archive\.test\.mjs/g)).toHaveLength(4) expect(source).toContain("Get-ChildItem -LiteralPath $firstOutput -Filter '*.zip'") expect(source).toContain('ssh-relay-node-zip-inspection.test.mjs') expect(source).toContain('ssh-relay-runtime-pty-smoke.test.mjs') diff --git a/src/main/ssh/ssh-relay-artifact-schema.test.ts b/src/main/ssh/ssh-relay-artifact-schema.test.ts index ef004d3655c..bb4ecfa36fe 100644 --- a/src/main/ssh/ssh-relay-artifact-schema.test.ts +++ b/src/main/ssh/ssh-relay-artifact-schema.test.ts @@ -336,7 +336,7 @@ describe('SSH relay artifact manifest schema', () => { expect(() => parseSshRelayArtifactManifest(identityMismatch)).toThrow(/content identity/i) const nameMismatch = createSshRelayArtifactTestManifest() - nameMismatch.tuples[0].archive.name = 'latest.tar.xz' + nameMismatch.tuples[0].archive.name = 'latest.tar.br' expect(() => parseSshRelayArtifactManifest(nameMismatch)).toThrow(/archive name/i) }) diff --git a/src/main/ssh/ssh-relay-manifest-signature.test.ts b/src/main/ssh/ssh-relay-manifest-signature.test.ts index 8516579e104..1ff71cc9f7c 100644 --- a/src/main/ssh/ssh-relay-manifest-signature.test.ts +++ b/src/main/ssh/ssh-relay-manifest-signature.test.ts @@ -50,7 +50,7 @@ describe('SSH relay manifest signatures', () => { const canonical = canonicalUnsignedSshRelayManifestBytes(manifest) expect(canonicalUnsignedSshRelayManifestBytes(reordered)).toEqual(canonical) expect(createHash('sha256').update(canonical).digest('hex')).toBe( - 'e78bf4416628a91055035dc7926035cbf633f29d3618be34e041c6dc5e0794fb' + 'dc36bff51330eb3aa4791bf30c25eeedd6e8d4cbc57470d50f377c24e0a5ed06' ) }) @@ -95,7 +95,7 @@ describe('SSH relay manifest signatures', () => { ]) expect(() => { - Object.defineProperty(verified.tuples[0].archive, 'name', { value: 'latest.tar.xz' }) + Object.defineProperty(verified.tuples[0].archive, 'name', { value: 'latest.tar.br' }) }).toThrow(TypeError) }) diff --git a/src/main/ssh/ssh-relay-release-asset.test.ts b/src/main/ssh/ssh-relay-release-asset.test.ts index 1ec020bb24d..d7a5bdd938f 100644 --- a/src/main/ssh/ssh-relay-release-asset.test.ts +++ b/src/main/ssh/ssh-relay-release-asset.test.ts @@ -25,7 +25,7 @@ describe('SSH relay release asset identity', () => { it('derives a content-qualified archive name and exact direct URL', () => { const name = sshRelayRuntimeArchiveName('linux-x64-glibc', contentId) - expect(name).toBe(`orca-ssh-relay-runtime-v1-linux-x64-glibc-${'a'.repeat(64)}.tar.xz`) + expect(name).toBe(`orca-ssh-relay-runtime-v1-linux-x64-glibc-${'a'.repeat(64)}.tar.br`) expect(sshRelayRuntimeDownloadUrl('v1.2.3-rc.4', name)).toBe( `https://github.com/stablyai/orca/releases/download/v1.2.3-rc.4/${name}` ) diff --git a/src/main/ssh/ssh-relay-release-asset.ts b/src/main/ssh/ssh-relay-release-asset.ts index 1305507a11b..16bb82e9cbe 100644 --- a/src/main/ssh/ssh-relay-release-asset.ts +++ b/src/main/ssh/ssh-relay-release-asset.ts @@ -33,13 +33,13 @@ export function sshRelayRuntimeArchiveName( if (!match) { throw new Error(`Invalid SSH relay runtime content identity: ${contentId}`) } - const extension = tupleId.startsWith('win32-') ? 'zip' : 'tar.xz' + const extension = tupleId.startsWith('win32-') ? 'zip' : 'tar.br' return `orca-ssh-relay-runtime-v1-${tupleId}-${match[1]}.${extension}` } export function sshRelayRuntimeDownloadUrl(tag: string, archiveName: string): string { parseSshRelayReleaseTag(tag) - if (!/^orca-ssh-relay-runtime-v1-[A-Za-z0-9.-]+\.(?:tar\.xz|zip)$/.test(archiveName)) { + if (!/^orca-ssh-relay-runtime-v1-[A-Za-z0-9.-]+\.(?:tar\.br|zip)$/.test(archiveName)) { throw new Error(`Invalid SSH relay runtime archive name: ${archiveName}`) } return `https://github.com/stablyai/orca/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(archiveName)}` diff --git a/src/main/ssh/ssh-relay-runtime-identity.test.ts b/src/main/ssh/ssh-relay-runtime-identity.test.ts index d86759021b0..f270edb63f4 100644 --- a/src/main/ssh/ssh-relay-runtime-identity.test.ts +++ b/src/main/ssh/ssh-relay-runtime-identity.test.ts @@ -49,7 +49,7 @@ describe('SSH relay runtime content identity', () => { const manifest = createSshRelayArtifactTestManifest() const tuple = structuredClone(manifest.tuples[0]) tuple.entries.reverse() - tuple.archive.name = 'ignored-by-runtime-identity.tar.xz' + tuple.archive.name = 'ignored-by-runtime-identity.tar.br' tuple.archive.sha256 = `sha256:${'f'.repeat(64)}` tuple.metadataAssets.sbom.sha256 = `sha256:${'e'.repeat(64)}` tuple.nativeVerification.verifiedAt = '2030-01-01T00:00:00.000Z'