mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
build(ssh): use portable relay archives
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -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" \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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`,
|
||||
|
||||
@@ -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')
|
||||
},
|
||||
|
||||
@@ -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')}`
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 })))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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}`
|
||||
)
|
||||
|
||||
@@ -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)}`
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user