mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
274 lines
9.1 KiB
JavaScript
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
|
|
})
|
|
}
|