Files
orca/config/scripts/ssh-relay-runtime-reproducibility.mjs
T
2026-07-14 12:05:53 -04:00

274 lines
9.1 KiB
JavaScript

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(
(left, right) => {
// Runtime bytes are the producer root cause; metadata and archive names often drift after them.
const leftPriority = left === 'runtime' || left.startsWith('runtime/') ? 0 : 1
const rightPriority = right === 'runtime' || right.startsWith('runtime/') ? 0 : 1
return leftPriority - rightPriority || left.localeCompare(right)
}
)
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
})
}