mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
test(ssh): gate runtime clean-build identity
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -110,6 +110,7 @@ jobs:
|
||||
config/scripts/ssh-relay-node-zip-inspection.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-artifact.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-windows-tree.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-zip.test.mjs \
|
||||
@@ -129,34 +130,44 @@ jobs:
|
||||
done
|
||||
printf 'NODE_INPUTS=%s\n' "$inputs" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build, inspect, and smoke exact runtime
|
||||
- name: Build twice, inspect, smoke, and compare exact runtime
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
output="$RUNNER_TEMP/ssh-relay-runtime/${{ matrix.tuple }}"
|
||||
mkdir -p "$(dirname "$output")"
|
||||
output_root="$RUNNER_TEMP/ssh-relay-runtime/${{ matrix.tuple }}"
|
||||
mkdir -p "$output_root"
|
||||
first_output="$output_root/first"
|
||||
second_output="$output_root/second"
|
||||
source_commit=$(git rev-parse HEAD)
|
||||
source_date_epoch=$(git show -s --format=%ct "$source_commit")
|
||||
/usr/bin/time -p node config/scripts/build-ssh-relay-runtime.mjs \
|
||||
for output in "$first_output" "$second_output"; do
|
||||
printf 'clean_build_output=%s\n' "$output"
|
||||
/usr/bin/time -p node config/scripts/build-ssh-relay-runtime.mjs \
|
||||
--tuple '${{ matrix.tuple }}' \
|
||||
--inputs-directory "$NODE_INPUTS" \
|
||||
--output-directory "$output" \
|
||||
--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)
|
||||
test -n "$archive"
|
||||
/usr/bin/time -p node config/scripts/verify-ssh-relay-runtime.mjs \
|
||||
--runtime-directory "$output/runtime" \
|
||||
--identity "$identity" \
|
||||
--archive "$archive"
|
||||
if find "$output/runtime" -type f \( -name npm -o -name npx -o -name corepack -o -name '*.map' \) -print -quit | grep -q .; then
|
||||
echo 'Runtime contains a prohibited package manager or source map.' >&2
|
||||
exit 1
|
||||
fi
|
||||
parcel_packages=$(find "$output/runtime/node_modules/@parcel" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')
|
||||
test "$parcel_packages" = 2
|
||||
done
|
||||
node config/scripts/ssh-relay-runtime-reproducibility.mjs \
|
||||
--tuple '${{ matrix.tuple }}' \
|
||||
--inputs-directory "$NODE_INPUTS" \
|
||||
--output-directory "$output" \
|
||||
--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)
|
||||
/usr/bin/time -p node config/scripts/verify-ssh-relay-runtime.mjs \
|
||||
--runtime-directory "$output/runtime" \
|
||||
--identity "$identity" \
|
||||
--archive "$archive"
|
||||
if find "$output/runtime" -type f \( -name npm -o -name npx -o -name corepack -o -name '*.map' \) -print -quit | grep -q .; then
|
||||
echo 'Runtime contains a prohibited package manager or source map.' >&2
|
||||
exit 1
|
||||
fi
|
||||
parcel_packages=$(find "$output/runtime/node_modules/@parcel" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')
|
||||
test "$parcel_packages" = 2
|
||||
--first-output-directory "$first_output" \
|
||||
--second-output-directory "$second_output"
|
||||
mkdir -p "$GITHUB_WORKSPACE/runtime-evidence/${{ matrix.tuple }}"
|
||||
cp "$output"/*.tar.xz "$output"/*.spdx.json "$output"/*.provenance.json "$output"/*.identity.json \
|
||||
cp "$first_output"/*.tar.xz "$first_output"/*.spdx.json "$first_output"/*.provenance.json "$first_output"/*.identity.json \
|
||||
"$GITHUB_WORKSPACE/runtime-evidence/${{ matrix.tuple }}/"
|
||||
|
||||
- name: Upload unpublished artifact evidence
|
||||
@@ -254,6 +265,7 @@ jobs:
|
||||
config/scripts/ssh-relay-node-zip-inspection.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-artifact.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-windows-tree.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-zip.test.mjs `
|
||||
@@ -284,41 +296,52 @@ jobs:
|
||||
}
|
||||
"NODE_INPUTS=$inputs" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Build, inspect, and smoke exact runtime
|
||||
- name: Build twice, inspect, smoke, and compare exact runtime
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$output = Join-Path $env:RUNNER_TEMP 'ssh-relay-runtime/${{ matrix.tuple }}'
|
||||
New-Item -ItemType Directory -Path (Split-Path -Parent $output) -Force | Out-Null
|
||||
$outputRoot = Join-Path $env:RUNNER_TEMP 'ssh-relay-runtime/${{ matrix.tuple }}'
|
||||
New-Item -ItemType Directory -Path $outputRoot -Force | Out-Null
|
||||
$firstOutput = Join-Path $outputRoot 'first'
|
||||
$secondOutput = Join-Path $outputRoot 'second'
|
||||
$sourceCommit = git rev-parse HEAD
|
||||
$sourceDateEpoch = git show -s --format=%ct $sourceCommit
|
||||
node config/scripts/build-ssh-relay-runtime.mjs `
|
||||
--tuple '${{ matrix.tuple }}' `
|
||||
--inputs-directory $env:NODE_INPUTS `
|
||||
--output-directory $output `
|
||||
--source-date-epoch $sourceDateEpoch `
|
||||
--git-commit $sourceCommit
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime build failed' }
|
||||
$identity = Join-Path $output 'orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json'
|
||||
$archive = Get-ChildItem -LiteralPath $output -Filter '*.zip' -File | Select-Object -First 1
|
||||
if (-not $archive) { throw 'runtime ZIP is missing' }
|
||||
node config/scripts/verify-ssh-relay-runtime.mjs `
|
||||
--runtime-directory (Join-Path $output 'runtime') `
|
||||
--identity $identity `
|
||||
--archive $archive.FullName
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime verification failed' }
|
||||
$prohibited = Get-ChildItem -LiteralPath (Join-Path $output 'runtime') -Recurse -File |
|
||||
Where-Object { $_.Name -in @('npm', 'npm.cmd', 'npx', 'npx.cmd', 'corepack', 'corepack.cmd') -or $_.Name.EndsWith('.map') } |
|
||||
Select-Object -First 1
|
||||
if ($prohibited) { throw "Runtime contains a prohibited file: $($prohibited.FullName)" }
|
||||
$parcelRoot = Join-Path $output 'runtime/node_modules/@parcel'
|
||||
if (@(Get-ChildItem -LiteralPath $parcelRoot -Directory).Count -ne 2) {
|
||||
throw 'Runtime must contain exactly the watcher wrapper and one native package'
|
||||
foreach ($output in @($firstOutput, $secondOutput)) {
|
||||
"clean_build_output=$output"
|
||||
node config/scripts/build-ssh-relay-runtime.mjs `
|
||||
--tuple '${{ matrix.tuple }}' `
|
||||
--inputs-directory $env:NODE_INPUTS `
|
||||
--output-directory $output `
|
||||
--source-date-epoch $sourceDateEpoch `
|
||||
--git-commit $sourceCommit
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime build failed' }
|
||||
$identity = Join-Path $output 'orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json'
|
||||
$archive = Get-ChildItem -LiteralPath $output -Filter '*.zip' -File | Select-Object -First 1
|
||||
if (-not $archive) { throw 'runtime ZIP is missing' }
|
||||
node config/scripts/verify-ssh-relay-runtime.mjs `
|
||||
--runtime-directory (Join-Path $output 'runtime') `
|
||||
--identity $identity `
|
||||
--archive $archive.FullName
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime verification failed' }
|
||||
$prohibited = Get-ChildItem -LiteralPath (Join-Path $output 'runtime') -Recurse -File |
|
||||
Where-Object { $_.Name -in @('npm', 'npm.cmd', 'npx', 'npx.cmd', 'corepack', 'corepack.cmd') -or $_.Name.EndsWith('.map') } |
|
||||
Select-Object -First 1
|
||||
if ($prohibited) { throw "Runtime contains a prohibited file: $($prohibited.FullName)" }
|
||||
$parcelRoot = Join-Path $output 'runtime/node_modules/@parcel'
|
||||
if (@(Get-ChildItem -LiteralPath $parcelRoot -Directory).Count -ne 2) {
|
||||
throw 'Runtime must contain exactly the watcher wrapper and one native package'
|
||||
}
|
||||
}
|
||||
node config/scripts/ssh-relay-runtime-reproducibility.mjs `
|
||||
--tuple '${{ matrix.tuple }}' `
|
||||
--first-output-directory $firstOutput `
|
||||
--second-output-directory $secondOutput
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime reproducibility verification failed' }
|
||||
$evidence = Join-Path $env:GITHUB_WORKSPACE 'runtime-evidence/${{ matrix.tuple }}'
|
||||
New-Item -ItemType Directory -Path $evidence -Force | Out-Null
|
||||
Copy-Item -LiteralPath $archive.FullName -Destination $evidence
|
||||
Get-ChildItem -LiteralPath $output -File |
|
||||
$firstArchive = Get-ChildItem -LiteralPath $firstOutput -Filter '*.zip' -File | Select-Object -First 1
|
||||
Copy-Item -LiteralPath $firstArchive.FullName -Destination $evidence
|
||||
Get-ChildItem -LiteralPath $firstOutput -File |
|
||||
Where-Object { $_.Name -match '\.(?:spdx|provenance|identity)\.json$' } |
|
||||
Copy-Item -Destination $evidence
|
||||
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createHash } from 'node:crypto'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { lstat, opendir, readFile, realpath } from 'node:fs/promises'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
// The bounded output adds four release assets around a runtime tree capped at 5,000 entries.
|
||||
const MAX_ENTRIES = 6_000
|
||||
const MAX_FILE_BYTES = 250 * 1024 * 1024
|
||||
const MAX_TOTAL_BYTES = 500 * 1024 * 1024
|
||||
const MAX_PATH_BYTES = 512
|
||||
const MAX_PATH_DEPTH = 40
|
||||
const MAX_IDENTITY_BYTES = 1024 * 1024
|
||||
const COMPARISON_TIMEOUT_MS = 5 * 60 * 1000
|
||||
const SUPPORTED_TUPLES = new Set([
|
||||
'linux-x64-glibc',
|
||||
'linux-arm64-glibc',
|
||||
'darwin-x64',
|
||||
'darwin-arm64',
|
||||
'win32-x64',
|
||||
'win32-arm64'
|
||||
])
|
||||
|
||||
async function sha256File(path, signal) {
|
||||
const digest = createHash('sha256')
|
||||
for await (const chunk of createReadStream(path, { signal })) {
|
||||
signal.throwIfAborted()
|
||||
digest.update(chunk)
|
||||
}
|
||||
return `sha256:${digest.digest('hex')}`
|
||||
}
|
||||
|
||||
function validateRelativePath(path) {
|
||||
if (
|
||||
Buffer.byteLength(path) > MAX_PATH_BYTES ||
|
||||
path.split('/').length > MAX_PATH_DEPTH ||
|
||||
path.includes('\\')
|
||||
) {
|
||||
throw new Error(`reproducibility output path exceeds limits: ${path}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function snapshotOutput(directory, label, signal) {
|
||||
const entries = new Map()
|
||||
let files = 0
|
||||
let bytes = 0
|
||||
|
||||
async function visit(absoluteDirectory, relativeDirectory = '') {
|
||||
const children = []
|
||||
const handle = await opendir(absoluteDirectory)
|
||||
for await (const child of handle) {
|
||||
children.push(child.name)
|
||||
}
|
||||
children.sort()
|
||||
for (const name of children) {
|
||||
signal.throwIfAborted()
|
||||
const path = relativeDirectory ? `${relativeDirectory}/${name}` : name
|
||||
validateRelativePath(path)
|
||||
const absolutePath = join(absoluteDirectory, name)
|
||||
const metadata = await lstat(absolutePath)
|
||||
if (metadata.isSymbolicLink() || (!metadata.isDirectory() && !metadata.isFile())) {
|
||||
throw new Error(`${label} contains a prohibited special entry: ${path}`)
|
||||
}
|
||||
if (entries.size >= MAX_ENTRIES) {
|
||||
throw new Error(`${label} exceeds the reproducibility entry-count limit`)
|
||||
}
|
||||
if (metadata.isDirectory()) {
|
||||
entries.set(path, { path, type: 'directory', mode: metadata.mode & 0o777 })
|
||||
await visit(absolutePath, path)
|
||||
continue
|
||||
}
|
||||
if (metadata.size > MAX_FILE_BYTES) {
|
||||
throw new Error(`${label} file exceeds the reproducibility size limit: ${path}`)
|
||||
}
|
||||
files += 1
|
||||
bytes += metadata.size
|
||||
if (bytes > MAX_TOTAL_BYTES) {
|
||||
throw new Error(`${label} exceeds the reproducibility total-size limit`)
|
||||
}
|
||||
entries.set(path, {
|
||||
path,
|
||||
type: 'file',
|
||||
mode: metadata.mode & 0o777,
|
||||
size: metadata.size,
|
||||
sha256: await sha256File(absolutePath, signal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const root = await lstat(directory)
|
||||
if (!root.isDirectory() || root.isSymbolicLink()) {
|
||||
throw new Error(`${label} must be a real directory`)
|
||||
}
|
||||
await visit(directory)
|
||||
return { bytes, entries, files }
|
||||
}
|
||||
|
||||
async function validateCompleteOutput(directory, label, tuple, snapshot) {
|
||||
const identityName = `orca-ssh-relay-runtime-${tuple}.identity.json`
|
||||
const required = [
|
||||
'runtime',
|
||||
'runtime/relay.js',
|
||||
'runtime/relay-watcher.js',
|
||||
'runtime/runtime-metadata.json',
|
||||
identityName,
|
||||
`orca-ssh-relay-runtime-${tuple}.spdx.json`,
|
||||
`orca-ssh-relay-runtime-${tuple}.provenance.json`
|
||||
]
|
||||
for (const path of required) {
|
||||
if (!snapshot.entries.has(path)) {
|
||||
throw new Error(`${label} is missing required asset: ${path}`)
|
||||
}
|
||||
}
|
||||
const identityEntry = snapshot.entries.get(identityName)
|
||||
if (identityEntry.type !== 'file' || identityEntry.size > MAX_IDENTITY_BYTES) {
|
||||
throw new Error(`${label} identity exceeds its size or type contract`)
|
||||
}
|
||||
let identity
|
||||
try {
|
||||
identity = JSON.parse(await readFile(join(directory, identityName), 'utf8'))
|
||||
} catch {
|
||||
throw new Error(`${label} identity is not valid JSON`)
|
||||
}
|
||||
if (
|
||||
identity.tupleId !== tuple ||
|
||||
!/^sha256:[0-9a-f]{64}$/.test(identity.contentId) ||
|
||||
typeof identity.archive?.name !== 'string' ||
|
||||
!/^sha256:[0-9a-f]{64}$/.test(identity.archive.sha256)
|
||||
) {
|
||||
throw new Error(`${label} identity does not match the requested tuple or digest contract`)
|
||||
}
|
||||
if (!snapshot.entries.has(identity.archive.name)) {
|
||||
throw new Error(`${label} is missing required asset: ${identity.archive.name}`)
|
||||
}
|
||||
const archiveEntry = snapshot.entries.get(identity.archive.name)
|
||||
if (archiveEntry.type !== 'file' || archiveEntry.sha256 !== identity.archive.sha256) {
|
||||
throw new Error(`${label} archive digest does not match its identity`)
|
||||
}
|
||||
const expectedTopLevel = new Set([...required.slice(4), identity.archive.name, 'runtime'])
|
||||
const unexpected = [...snapshot.entries.keys()].find(
|
||||
(path) => !path.includes('/') && !expectedTopLevel.has(path)
|
||||
)
|
||||
if (unexpected) {
|
||||
throw new Error(`${label} contains an unexpected top-level asset: ${unexpected}`)
|
||||
}
|
||||
return identity
|
||||
}
|
||||
|
||||
function compareSnapshots(first, second) {
|
||||
const paths = [...new Set([...first.entries.keys(), ...second.entries.keys()])].sort()
|
||||
for (const path of paths) {
|
||||
const left = first.entries.get(path)
|
||||
const right = second.entries.get(path)
|
||||
if (!left) {
|
||||
throw new Error(`first output is missing reproducibility entry: ${path}`)
|
||||
}
|
||||
if (!right) {
|
||||
throw new Error(`second output is missing reproducibility entry: ${path}`)
|
||||
}
|
||||
for (const field of ['type', 'mode', 'size', 'sha256']) {
|
||||
if (left[field] !== right[field]) {
|
||||
throw new Error(`reproducibility mismatch for ${path}: ${field}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifySshRelayRuntimeReproducibility({
|
||||
firstOutputDirectory,
|
||||
secondOutputDirectory,
|
||||
tuple,
|
||||
signal = AbortSignal.timeout(COMPARISON_TIMEOUT_MS)
|
||||
}) {
|
||||
const started = process.hrtime.bigint()
|
||||
if (!SUPPORTED_TUPLES.has(tuple)) {
|
||||
throw new Error(`unsupported runtime reproducibility tuple: ${tuple}`)
|
||||
}
|
||||
const firstDirectory = resolve(firstOutputDirectory)
|
||||
const secondDirectory = resolve(secondOutputDirectory)
|
||||
if (firstDirectory === secondDirectory) {
|
||||
throw new Error('clean-build outputs must be distinct directories')
|
||||
}
|
||||
signal.throwIfAborted()
|
||||
const [firstRealPath, secondRealPath] = await Promise.all([
|
||||
realpath(firstDirectory),
|
||||
realpath(secondDirectory)
|
||||
])
|
||||
if (firstRealPath === secondRealPath) {
|
||||
throw new Error('clean-build outputs must resolve to distinct directories')
|
||||
}
|
||||
|
||||
// Walk sequentially so the clean-build gate cannot double file and read pressure on CI runners.
|
||||
const first = await snapshotOutput(firstDirectory, 'first output', signal)
|
||||
const second = await snapshotOutput(secondDirectory, 'second output', signal)
|
||||
const firstIdentity = await validateCompleteOutput(firstDirectory, 'first output', tuple, first)
|
||||
const secondIdentity = await validateCompleteOutput(
|
||||
secondDirectory,
|
||||
'second output',
|
||||
tuple,
|
||||
second
|
||||
)
|
||||
compareSnapshots(first, second)
|
||||
if (
|
||||
firstIdentity.contentId !== secondIdentity.contentId ||
|
||||
firstIdentity.archive.sha256 !== secondIdentity.archive.sha256
|
||||
) {
|
||||
throw new Error('reproducibility identity or archive digest mismatch')
|
||||
}
|
||||
|
||||
return {
|
||||
tuple,
|
||||
contentId: firstIdentity.contentId,
|
||||
archiveSha256: firstIdentity.archive.sha256,
|
||||
entries: first.entries.size,
|
||||
files: first.files,
|
||||
bytes: first.bytes,
|
||||
durationMs: Number(process.hrtime.bigint() - started) / 1e6
|
||||
}
|
||||
}
|
||||
|
||||
function valueAfter(argv, index, flag) {
|
||||
const value = argv[index + 1]
|
||||
if (!value || value.startsWith('--')) {
|
||||
throw new Error(`${flag} requires a value`)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function parseReproducibilityArguments(argv) {
|
||||
const result = {}
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const flag = argv[index]
|
||||
const value = valueAfter(argv, index, flag)
|
||||
if (flag === '--tuple') {
|
||||
result.tuple = value
|
||||
} else if (flag === '--first-output-directory') {
|
||||
result.firstOutputDirectory = resolve(value)
|
||||
} else if (flag === '--second-output-directory') {
|
||||
result.secondOutputDirectory = resolve(value)
|
||||
} else {
|
||||
throw new Error(`Unknown argument: ${flag}`)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
for (const field of ['tuple', 'firstOutputDirectory', 'secondOutputDirectory']) {
|
||||
if (!result[field]) {
|
||||
throw new Error(`Missing required reproducibility argument: ${field}`)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const result = await verifySshRelayRuntimeReproducibility(
|
||||
parseReproducibilityArguments(process.argv.slice(2))
|
||||
)
|
||||
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
||||
}
|
||||
|
||||
if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(
|
||||
`SSH relay runtime reproducibility verification failed: ${error.message}\n`
|
||||
)
|
||||
process.exitCode = 1
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { chmod, cp, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { verifySshRelayRuntimeReproducibility } from './ssh-relay-runtime-reproducibility.mjs'
|
||||
|
||||
const temporaryDirectories = []
|
||||
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`
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true })))
|
||||
})
|
||||
|
||||
async function writeOutput(directory) {
|
||||
await mkdir(join(directory, 'runtime'), { recursive: true })
|
||||
await Promise.all([
|
||||
writeFile(join(directory, 'runtime', 'relay.js'), 'relay bytes'),
|
||||
writeFile(join(directory, 'runtime', 'relay-watcher.js'), 'watcher bytes'),
|
||||
writeFile(join(directory, 'runtime', 'runtime-metadata.json'), '{"schemaVersion":1}\n'),
|
||||
writeFile(join(directory, archiveName), archiveBytes),
|
||||
writeFile(
|
||||
join(directory, `orca-ssh-relay-runtime-${tuple}.identity.json`),
|
||||
`${JSON.stringify({ tupleId: tuple, contentId, archive: { name: archiveName, sha256: archiveSha256 } })}\n`
|
||||
),
|
||||
writeFile(
|
||||
join(directory, `orca-ssh-relay-runtime-${tuple}.spdx.json`),
|
||||
'{"spdxVersion":"SPDX-2.3"}\n'
|
||||
),
|
||||
writeFile(
|
||||
join(directory, `orca-ssh-relay-runtime-${tuple}.provenance.json`),
|
||||
'{"predicateType":"https://slsa.dev/provenance/v1"}\n'
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
async function createOutputs() {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'orca-runtime-reproducibility-'))
|
||||
temporaryDirectories.push(directory)
|
||||
const firstOutputDirectory = join(directory, 'first')
|
||||
const secondOutputDirectory = join(directory, 'second')
|
||||
await writeOutput(firstOutputDirectory)
|
||||
await cp(firstOutputDirectory, secondOutputDirectory, { recursive: true })
|
||||
return { directory, firstOutputDirectory, secondOutputDirectory }
|
||||
}
|
||||
|
||||
describe('SSH relay runtime clean-build reproducibility', () => {
|
||||
it('accepts byte-and-mode-identical complete outputs', async () => {
|
||||
const fixture = await createOutputs()
|
||||
|
||||
await expect(
|
||||
verifySshRelayRuntimeReproducibility({
|
||||
...fixture,
|
||||
tuple
|
||||
})
|
||||
).resolves.toEqual(expect.objectContaining({ tuple, contentId, archiveSha256, files: 7 }))
|
||||
})
|
||||
|
||||
it('rejects runtime-tree byte drift', async () => {
|
||||
const fixture = await createOutputs()
|
||||
await writeFile(join(fixture.secondOutputDirectory, 'runtime', 'relay.js'), 'other bytes')
|
||||
|
||||
await expect(verifySshRelayRuntimeReproducibility({ ...fixture, tuple })).rejects.toThrow(
|
||||
'reproducibility mismatch for runtime/relay.js: sha256'
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects SBOM byte drift', async () => {
|
||||
const fixture = await createOutputs()
|
||||
await writeFile(
|
||||
join(fixture.secondOutputDirectory, `orca-ssh-relay-runtime-${tuple}.spdx.json`),
|
||||
'{"spdxVersion":"SPDX-2.2"}\n'
|
||||
)
|
||||
|
||||
await expect(verifySshRelayRuntimeReproducibility({ ...fixture, tuple })).rejects.toThrow(
|
||||
`reproducibility mismatch for orca-ssh-relay-runtime-${tuple}.spdx.json: sha256`
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects an archive that disagrees with its identity', async () => {
|
||||
const fixture = await createOutputs()
|
||||
await writeFile(join(fixture.secondOutputDirectory, archiveName), 'other archive bytes')
|
||||
|
||||
await expect(verifySshRelayRuntimeReproducibility({ ...fixture, tuple })).rejects.toThrow(
|
||||
'second output archive digest does not match its identity'
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects a missing published metadata asset', async () => {
|
||||
const fixture = await createOutputs()
|
||||
await rm(join(fixture.secondOutputDirectory, `orca-ssh-relay-runtime-${tuple}.spdx.json`))
|
||||
|
||||
await expect(verifySshRelayRuntimeReproducibility({ ...fixture, tuple })).rejects.toThrow(
|
||||
`second output is missing required asset: orca-ssh-relay-runtime-${tuple}.spdx.json`
|
||||
)
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')('rejects mode drift', async () => {
|
||||
const fixture = await createOutputs()
|
||||
await chmod(join(fixture.secondOutputDirectory, 'runtime', 'relay.js'), 0o755)
|
||||
|
||||
await expect(verifySshRelayRuntimeReproducibility({ ...fixture, tuple })).rejects.toThrow(
|
||||
'reproducibility mismatch for runtime/relay.js: mode'
|
||||
)
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'rejects links before reading their targets',
|
||||
async () => {
|
||||
const fixture = await createOutputs()
|
||||
await symlink(
|
||||
join(fixture.directory, 'outside'),
|
||||
join(fixture.firstOutputDirectory, 'runtime', 'escape')
|
||||
)
|
||||
|
||||
await expect(verifySshRelayRuntimeReproducibility({ ...fixture, tuple })).rejects.toThrow(
|
||||
'first output contains a prohibited special entry: runtime/escape'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('rejects the same directory and a pre-aborted comparison', async () => {
|
||||
const fixture = await createOutputs()
|
||||
await expect(
|
||||
verifySshRelayRuntimeReproducibility({
|
||||
firstOutputDirectory: fixture.firstOutputDirectory,
|
||||
secondOutputDirectory: fixture.firstOutputDirectory,
|
||||
tuple
|
||||
})
|
||||
).rejects.toThrow('clean-build outputs must be distinct directories')
|
||||
await expect(
|
||||
verifySshRelayRuntimeReproducibility({
|
||||
...fixture,
|
||||
tuple,
|
||||
signal: AbortSignal.abort()
|
||||
})
|
||||
).rejects.toThrow('aborted')
|
||||
})
|
||||
})
|
||||
@@ -35,12 +35,13 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('uploads only archive and metadata evidence after executable verification', async () => {
|
||||
it('uploads only the first output after two clean builds verify and compare', async () => {
|
||||
const source = await readFile(workflowUrl, 'utf8')
|
||||
const workflow = parse(source)
|
||||
const steps = workflow.jobs['build-posix-runtime'].steps
|
||||
const windowsSteps = workflow.jobs['build-windows-runtime'].steps
|
||||
const buildIndex = steps.findIndex(
|
||||
(step) => step.name === 'Build, inspect, and smoke exact runtime'
|
||||
(step) => step.name === 'Build twice, inspect, smoke, and compare exact runtime'
|
||||
)
|
||||
const uploadIndex = steps.findIndex(
|
||||
(step) => step.name === 'Upload unpublished artifact evidence'
|
||||
@@ -52,12 +53,16 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(source).toContain('ssh-relay-runtime-workflow.test.mjs')
|
||||
expect(source).toContain('pnpm install --frozen-lockfile --ignore-scripts')
|
||||
expect(source).toContain('--connect-timeout 20 --max-time 300 --retry 2')
|
||||
expect(source).toContain('mkdir -p "$(dirname "$output")"')
|
||||
expect(source).toContain('mkdir -p "$output_root"')
|
||||
expect(source).toContain('source_commit=$(git rev-parse HEAD)')
|
||||
expect(source).toContain('--git-commit "$source_commit"')
|
||||
expect(source).not.toContain('--git-commit "$GITHUB_SHA"')
|
||||
expect(source).toContain('cp "$output"/*.tar.xz')
|
||||
expect(source).toContain("Get-ChildItem -LiteralPath $output -Filter '*.zip'")
|
||||
expect(source).toContain('for output in "$first_output" "$second_output"')
|
||||
expect(source).toContain('foreach ($output in @($firstOutput, $secondOutput))')
|
||||
expect(source).toContain('ssh-relay-runtime-reproducibility.mjs')
|
||||
expect(source).toContain('ssh-relay-runtime-reproducibility.test.mjs')
|
||||
expect(source).toContain('cp "$first_output"/*.tar.xz')
|
||||
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')
|
||||
expect(source).toContain('ssh-relay-runtime-resource-diagnostics.test.mjs')
|
||||
@@ -65,6 +70,17 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(source).toContain('node-v24.18.0-headers.tar.gz')
|
||||
expect(source).toContain('node_library: win-x64/node.lib')
|
||||
expect(source).toContain("@('gpg.exe', 'gpgv.exe')")
|
||||
for (const jobSteps of [steps, windowsSteps]) {
|
||||
const run = jobSteps.find(
|
||||
(step) => step.name === 'Build twice, inspect, smoke, and compare exact runtime'
|
||||
).run
|
||||
expect(run.indexOf('ssh-relay-runtime-reproducibility.mjs')).toBeGreaterThan(
|
||||
run.indexOf('verify-ssh-relay-runtime.mjs')
|
||||
)
|
||||
expect(run.indexOf('runtime-evidence/${{ matrix.tuple }}')).toBeGreaterThan(
|
||||
run.indexOf('ssh-relay-runtime-reproducibility.mjs')
|
||||
)
|
||||
}
|
||||
expect(steps[uploadIndex].with.path).toBe('runtime-evidence/${{ matrix.tuple }}/')
|
||||
expect(source).not.toMatch(/releases\/|gh release|contents:\s*write/i)
|
||||
})
|
||||
|
||||
+26
-17
@@ -2,7 +2,7 @@
|
||||
|
||||
Date created: 2026-07-14<br>
|
||||
Last updated: 2026-07-14<br>
|
||||
Current phase: Milestone 3 / Work Package 2 target-native runtime assembly — add and prove a same-head, same-runner two-clean-build identity oracle now that all six glibc/macOS/Windows target-native artifact and executable cells are green; oldest-baseline, native-trust, cross-family remote, and measured-baseline gates remain open; no bundled-runtime path is enabled<br>
|
||||
Current phase: Milestone 3 / Work Package 2 target-native runtime assembly — the bounded same-head, same-runner two-clean-build identity oracle is locally green and awaits exact-head execution on all six glibc/macOS/Windows native runners; oldest-baseline, native-trust, cross-family remote, and measured-baseline gates remain open; no bundled-runtime path is enabled<br>
|
||||
Primary design: [SSH relay GitHub Release plan](./2026-07-14-ssh-relay-github-release-plan.html)<br>
|
||||
Motivating issues: [#8450](https://github.com/stablyai/orca/issues/8450), [#1693](https://github.com/stablyai/orca/issues/1693)
|
||||
|
||||
@@ -87,8 +87,10 @@ same change as the work it records.
|
||||
diff gates. The first exact-head native run exposed a macOS `/var` versus `/private/var` watcher
|
||||
oracle mismatch under E-M3-CI-RED-001. The corrected exact-head run passed target-native build,
|
||||
archive/tree verification, bundled Node, patched PTY, and watcher smoke for Linux x64/arm64 and
|
||||
macOS x64/arm64 under E-M3-CI-001. Same-runner clean-rebuild identity, oldest-baseline,
|
||||
native-trust, SSH, Windows, and musl cells remain open.
|
||||
macOS x64/arm64 under E-M3-CI-001. A bounded comparator now rejects incomplete outputs and any
|
||||
runtime-tree, archive, identity, SPDX, provenance, type, mode, size, or digest drift after two
|
||||
independently verified clean builds; its focused contract is locally green, but exact-head native
|
||||
execution remains pending. Oldest-baseline, native-trust, SSH, and musl cells remain open.
|
||||
- Windows input correction: E-M3-WINDOWS-INPUT-GAP-001 proved the official Windows ZIP lacks headers
|
||||
and `node.lib`. Both artifacts now require the exact signed headers archive and tuple import
|
||||
library as explicit inputs. The schema, signed-checksum verifier, bounded ZIP/header extraction,
|
||||
@@ -125,11 +127,12 @@ same change as the work it records.
|
||||
- Rollout control: existing per-SSH-target configuration; legacy is the default and the bundled
|
||||
runtime is an explicit per-target Beta opt-in under E-M1-ROLLOUT-DECISION-001.
|
||||
- Legacy fallback removal: not authorized.
|
||||
- Next required action: add a same-head/same-runner two-clean-build identity comparison that fails
|
||||
closed on any tree, archive, SBOM, provenance, or identity drift, then run it for every target-
|
||||
native candidate. Keep oldest-baseline, cross-family remote infrastructure, signing/trust, and
|
||||
measured legacy baseline gates open; do not introduce publication, resolver, transfer, rollout,
|
||||
tuple enablement, or default behavior.
|
||||
- Next required action: finish the exact implementation-commit local gates, push the bounded
|
||||
two-clean-build comparator/workflow package to draft PR #8741, and require every target-native job
|
||||
to compare complete runtime-tree, archive, identity, SPDX, and provenance outputs before upload.
|
||||
Record every native drift instead of weakening the oracle. Keep oldest-baseline, cross-family
|
||||
remote infrastructure, signing/trust, and measured legacy baseline gates open; do not introduce
|
||||
publication, resolver, transfer, rollout, tuple enablement, or default behavior.
|
||||
|
||||
## Non-Negotiable Invariants
|
||||
|
||||
@@ -597,8 +600,11 @@ transfer, install, fallback, rollout setting, or tuple enablement belongs in thi
|
||||
|
||||
**Active sub-gate — 2026-07-14, Codex implementation owner:** exact-head run 29345126283 proves all
|
||||
six target-native artifact/executable jobs, including normal Windows x64/arm64 settlement and
|
||||
unpublished upload. Add a bounded same-head/same-runner two-clean-build comparison without changing
|
||||
artifact consumers, publication, the repository-wide node-pty patch, or the legacy/default path.
|
||||
unpublished upload. The locally green bounded comparator now requires two independently verified
|
||||
clean outputs and exact type/mode/size/SHA-256 equality for the runtime tree, archive, identity,
|
||||
SPDX, and provenance before uploading only the first output. Exact-head native execution remains
|
||||
open; artifact consumers, publication, the repository-wide node-pty patch, and the legacy/default
|
||||
path remain unchanged.
|
||||
|
||||
Each runtime must contain only the executable closure required by the relay.
|
||||
|
||||
@@ -1258,6 +1264,7 @@ focused commands as their scripts/tests are introduced.
|
||||
- [x] `pnpm exec vitest run --config config/vitest.config.ts config/scripts/ssh-relay-runtime-pty-smoke.test.mjs` (E-M3-WINDOWS-SMOKE-SETTLEMENT-LOCAL-RED-001, E-M3-WINDOWS-SMOKE-SETTLEMENT-LOCAL-001; native evidence remains pending)
|
||||
- [x] `pnpm exec vitest run --config config/vitest.config.ts config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs` (E-M3-WINDOWS-RESOURCE-DIAGNOSTIC-LOCAL-001; native classification pending)
|
||||
- [x] `pnpm exec vitest run --config config/vitest.config.ts config/scripts/ssh-relay-node-pty-windows-settlement.test.mjs` (E-M3-WINDOWS-CONPTY-WORKER-LOCAL-RED-001, E-M3-WINDOWS-CONPTY-WORKER-LOCAL-001; native settlement pending)
|
||||
- [ ] `pnpm exec vitest run --config config/vitest.config.ts config/scripts/ssh-relay-runtime-reproducibility.test.mjs config/scripts/ssh-relay-runtime-workflow.test.mjs` (locally green on the uncommitted implementation tree; rerun on the exact implementation commit before checking)
|
||||
- [x] `node config/scripts/verify-ssh-relay-node-release-inputs.mjs --inputs-directory <verified-windows-input-directory> --archive win32-x64` (E-M3-WINDOWS-INPUT-001; signed real Node ZIP, headers, and import library; no Windows execution)
|
||||
- [x] `node config/scripts/build-ssh-relay-runtime.mjs --tuple linux-arm64-glibc --inputs-directory <verified-input-directory> --output-directory <exclusive-output> --source-date-epoch <epoch> --git-commit <full-sha>` (E-M3-RUNTIME-LOCAL-001; local native Linux arm64 only)
|
||||
- [x] `node config/scripts/verify-ssh-relay-runtime.mjs --runtime-directory <runtime-tree> --identity <identity.json> --archive <runtime.tar.xz>` (E-M3-RUNTIME-LOCAL-001; local native Linux arm64 only)
|
||||
@@ -3599,10 +3606,12 @@ The project is not complete until every applicable item below is checked with ev
|
||||
|
||||
## Next Required Action
|
||||
|
||||
Commit and push the bounded post-smoke resource diagnostic, then capture both native Windows
|
||||
resource snapshots through the existing parent timeout without making another cleanup change. Use
|
||||
that evidence to isolate the owning subsystem before requiring exact-head normal exit and artifact
|
||||
upload. The separate clean-build identity gate, cross-family Layer B target pool, protected
|
||||
manifest-signing environment, and paired legacy performance baseline remain release/default-path
|
||||
blockers; no publication, desktop resolver, SSH transfer/install, per-target Beta, fallback, tuple
|
||||
enablement, or default behavior may be connected by this package.
|
||||
Complete the exact implementation-commit local gates, push the bounded two-clean-build
|
||||
reproducibility package to draft PR #8741, and run all six target-native jobs. Each job must build,
|
||||
verify, and smoke two exclusive outputs, compare every runtime-tree/archive/identity/SPDX/provenance
|
||||
entry, and upload only after equality. Record exact differing paths and fields for every native red
|
||||
result and correct the producer without weakening the oracle. Cross-family Layer B targets, the
|
||||
protected manifest-signing environment, oldest-baseline/native-trust cells, and the paired legacy
|
||||
performance baseline remain release/default-path blockers; no publication, desktop resolver, SSH
|
||||
transfer/install, per-target Beta, fallback, tuple enablement, or default behavior may be connected
|
||||
by this package.
|
||||
|
||||
Reference in New Issue
Block a user