diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml index f58cadb26c0..52e7101ccc0 100644 --- a/.github/workflows/ssh-relay-runtime-artifacts.yml +++ b/.github/workflows/ssh-relay-runtime-artifacts.yml @@ -168,6 +168,9 @@ jobs: node --check config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs node --check config/scripts/ssh-relay-runtime-manifest-seed-signing.mjs node --check config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs + node --check config/scripts/ssh-relay-runtime-manifest-artifact-collection.mjs + node --check config/scripts/ssh-relay-runtime-manifest-aggregate-command.mjs + node --check config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs node --check config/scripts/ssh-relay-runtime-manifest-aggregate.mjs node --check config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs node --check config/scripts/ssh-relay-runtime-manifest-tuple.mjs @@ -215,6 +218,7 @@ jobs: config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs \ config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs \ config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs \ + config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs \ config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs \ config/scripts/ssh-relay-runtime-manifest-tuple.test.mjs \ config/scripts/ssh-relay-runtime-post-sign-metadata.test.mjs \ @@ -585,6 +589,12 @@ jobs: if ($LASTEXITCODE -ne 0) { throw 'runtime manifest seed signing syntax check failed' } node --check config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs if ($LASTEXITCODE -ne 0) { throw 'runtime manifest seed signing test syntax check failed' } + node --check config/scripts/ssh-relay-runtime-manifest-artifact-collection.mjs + if ($LASTEXITCODE -ne 0) { throw 'runtime manifest artifact collection syntax check failed' } + node --check config/scripts/ssh-relay-runtime-manifest-aggregate-command.mjs + if ($LASTEXITCODE -ne 0) { throw 'runtime manifest aggregate command syntax check failed' } + node --check config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs + if ($LASTEXITCODE -ne 0) { throw 'runtime manifest aggregate command test syntax check failed' } node --check config/scripts/ssh-relay-runtime-manifest-aggregate.mjs if ($LASTEXITCODE -ne 0) { throw 'runtime manifest aggregate syntax check failed' } node --check config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs @@ -650,6 +660,7 @@ jobs: config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs ` config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs ` config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs ` + config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs ` config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs ` config/scripts/ssh-relay-runtime-manifest-tuple.test.mjs ` config/scripts/ssh-relay-runtime-post-sign-metadata.test.mjs ` diff --git a/config/scripts/ssh-relay-runtime-manifest-aggregate-command.mjs b/config/scripts/ssh-relay-runtime-manifest-aggregate-command.mjs new file mode 100644 index 00000000000..4bc3f018908 --- /dev/null +++ b/config/scripts/ssh-relay-runtime-manifest-aggregate-command.mjs @@ -0,0 +1,401 @@ +import { createHash } from 'node:crypto' +import { lstat, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises' +import { basename, dirname, join, resolve } from 'node:path' +import { pathToFileURL } from 'node:url' + +import { collectSshRelayRuntimeManifestArtifacts } from './ssh-relay-runtime-manifest-artifact-collection.mjs' +import { + finalizeSshRelayRuntimeManifestAggregate, + prepareSshRelayRuntimeManifestAggregate +} from './ssh-relay-runtime-manifest-aggregate.mjs' +import { + decodeSshRelayRuntimeManifestSigningRequestArtifact, + encodeSshRelayRuntimeManifestSigningRequestArtifact +} from './ssh-relay-runtime-manifest-seed-signing.mjs' +import { sshRelayRuntimeManifestKeyId } from './ssh-relay-runtime-manifest-signing-handoff.mjs' + +const MAX_JSON_BYTES = 48 * 1024 * 1024 +const SOURCE_SHA = /^[0-9a-f]{40}$/u +const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u +const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/u +const DIGEST = /^sha256:[0-9a-f]{64}$/u +const PREPARED_FIELDS = [ + 'acceptedKeysSha256', + 'createdAt', + 'inputTuples', + 'inputTuplesSha256', + 'relayProtocolVersion', + 'releaseTag', + 'schemaVersion', + 'signingRequest', + 'sourceSha', + 'unsignedManifest' +] + +function assertObject(value, label) { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`SSH relay runtime manifest aggregate command ${label} must be an object`) + } +} + +function assertExactFields(value, fields, label) { + assertObject(value, label) + const actual = Object.keys(value).sort() + const expected = [...fields].sort() + if (JSON.stringify(actual) !== JSON.stringify(expected)) { + throw new Error(`SSH relay runtime manifest aggregate command ${label} has invalid fields`) + } +} + +function sha256(bytes) { + return `sha256:${createHash('sha256').update(bytes).digest('hex')}` +} + +function canonicalJson(value) { + return Buffer.from(`${JSON.stringify(value)}\n`, 'utf8') +} + +function parseReleaseTag(tag) { + for (const [pattern, channel] of [ + [/^v(\d+\.\d+\.\d+)$/u, 'stable'], + [/^v(\d+\.\d+\.\d+-rc\.\d+)$/u, 'rc'], + [/^v(\d+\.\d+\.\d+-rc\.\d+\.perf)$/u, 'perf'] + ]) { + const match = pattern.exec(tag) + if (match) { + return { tag, version: match[1], channel } + } + } + throw new Error('SSH relay runtime manifest aggregate command release tag is invalid') +} + +function commandIdentity(input) { + if (!SOURCE_SHA.test(input.sourceSha ?? '')) { + throw new Error('SSH relay runtime manifest aggregate command source SHA is invalid') + } + const release = parseReleaseTag(input.releaseTag) + if ( + typeof input.createdAt !== 'string' || + !TIMESTAMP.test(input.createdAt) || + new Date(input.createdAt).toISOString() !== input.createdAt + ) { + throw new Error('SSH relay runtime manifest aggregate command timestamp is invalid') + } + if (!Number.isSafeInteger(input.relayProtocolVersion) || input.relayProtocolVersion <= 0) { + throw new Error('SSH relay runtime manifest aggregate command protocol version is invalid') + } + return { + sourceSha: input.sourceSha, + releaseTag: input.releaseTag, + createdAt: input.createdAt, + relayProtocolVersion: input.relayProtocolVersion, + build: { ...release, relayProtocolVersion: input.relayProtocolVersion } + } +} + +async function readBoundedJson(path, label) { + const before = await lstat(path, { bigint: true }) + if ( + !before.isFile() || + before.isSymbolicLink() || + before.size <= 0n || + before.size > BigInt(MAX_JSON_BYTES) + ) { + throw new Error( + `SSH relay runtime manifest aggregate command ${label} must be bounded regular JSON` + ) + } + try { + const bytes = await readFile(path) + const after = await lstat(path, { bigint: true }) + if ( + before.dev !== after.dev || + before.ino !== after.ino || + before.size !== after.size || + before.mtimeNs !== after.mtimeNs || + before.ctimeNs !== after.ctimeNs + ) { + throw new Error('file changed while reading') + } + return JSON.parse(bytes.toString('utf8')) + } catch (error) { + throw new Error( + `SSH relay runtime manifest aggregate command ${label} is invalid JSON: ${error.message}` + ) + } +} + +function decodeCanonicalBase64(value, label, expectedSize) { + if (typeof value !== 'string' || !BASE64.test(value)) { + throw new Error(`SSH relay runtime manifest aggregate command ${label} is not canonical base64`) + } + const bytes = Buffer.from(value, 'base64') + if (bytes.length !== expectedSize || bytes.toString('base64') !== value) { + throw new Error( + `SSH relay runtime manifest aggregate command ${label} must contain ${expectedSize} bytes` + ) + } + return bytes +} + +async function readAcceptedKeys(path) { + const document = await readBoundedJson(resolve(path), 'accepted keys') + assertExactFields(document, ['keys', 'schemaVersion'], 'accepted keys') + if ( + document.schemaVersion !== 1 || + !Array.isArray(document.keys) || + document.keys.length === 0 || + document.keys.length > 4 + ) { + throw new Error('SSH relay runtime manifest aggregate command accepted keys are invalid') + } + const seen = new Set() + const artifactKeys = document.keys + .map((key, index) => { + assertExactFields(key, ['keyId', 'publicKeyBase64'], `accepted key ${index}`) + const publicKey = decodeCanonicalBase64(key.publicKeyBase64, 'accepted public key', 32) + const keyId = sshRelayRuntimeManifestKeyId(publicKey) + if (!DIGEST.test(key.keyId ?? '') || key.keyId !== keyId || seen.has(keyId)) { + throw new Error( + 'SSH relay runtime manifest aggregate command accepted key identity is invalid' + ) + } + seen.add(keyId) + return { keyId, publicKeyBase64: key.publicKeyBase64, publicKey } + }) + .sort((left, right) => (left.keyId < right.keyId ? -1 : left.keyId > right.keyId ? 1 : 0)) + const serialized = artifactKeys.map(({ keyId, publicKeyBase64 }) => ({ keyId, publicKeyBase64 })) + return { + acceptedKeys: artifactKeys.map(({ keyId, publicKey }) => ({ keyId, publicKey })), + sha256: sha256(canonicalJson({ schemaVersion: 1, keys: serialized })) + } +} + +async function exclusiveOutputDirectory(outputDirectory) { + const absolute = resolve(outputDirectory) + const parent = resolve(await realpath(dirname(absolute))) + const parentMetadata = await lstat(parent) + if (!parentMetadata.isDirectory() || parentMetadata.isSymbolicLink()) { + throw new Error('SSH relay runtime manifest aggregate command output parent must be real') + } + const output = resolve(parent, basename(absolute)) + try { + await lstat(output) + } catch (error) { + if (error.code === 'ENOENT') { + return output + } + throw error + } + throw new Error( + 'SSH relay runtime manifest aggregate command requires an exclusive output directory' + ) +} + +function assertReceiptBindings(bindings, prepared) { + const tuples = new Map(prepared.unsignedManifest.tuples.map((tuple) => [tuple.tupleId, tuple])) + for (const binding of bindings) { + if (tuples.get(binding.tupleId)?.contentId !== binding.contentId) { + throw new Error( + `SSH relay runtime manifest aggregate command receipt content drifted: ${binding.tupleId}` + ) + } + } +} + +function preparedArtifact(identity, acceptedKeysSha256, prepared) { + return { + schemaVersion: 1, + sourceSha: identity.sourceSha, + releaseTag: identity.releaseTag, + createdAt: identity.createdAt, + relayProtocolVersion: identity.relayProtocolVersion, + acceptedKeysSha256, + inputTuples: prepared.inputTuples, + inputTuplesSha256: prepared.inputTuplesSha256, + unsignedManifest: prepared.unsignedManifest, + signingRequest: encodeSshRelayRuntimeManifestSigningRequestArtifact(prepared.signingRequest) + } +} + +async function collectAndPrepare(input, output, identity, signal) { + const stagingDirectory = join(output, 'aggregate-input') + await mkdir(stagingDirectory) + const bindings = await collectSshRelayRuntimeManifestArtifacts({ + artifactsDirectory: input.artifactsDirectory, + stagingDirectory, + signal + }) + const prepared = await prepareSshRelayRuntimeManifestAggregate({ + inputDirectory: stagingDirectory, + build: identity.build, + createdAt: identity.createdAt, + tupleInputs: bindings.map(({ aggregateInput }) => aggregateInput), + signal + }) + assertReceiptBindings(bindings, prepared) + return prepared +} + +export async function prepareSshRelayRuntimeManifestAggregateCommand(input) { + const identity = commandIdentity(input) + // Why: public-key policy must be valid before canonical bytes can leave this boundary. + const keys = await readAcceptedKeys(input.acceptedKeysPath) + const output = await exclusiveOutputDirectory(input.outputDirectory) + let created = false + try { + await mkdir(output, { mode: 0o700 }) + created = true + const prepared = await collectAndPrepare(input, output, identity, input.signal) + const artifact = preparedArtifact(identity, keys.sha256, prepared) + await writeFile(join(output, 'prepared-aggregate.json'), canonicalJson(artifact), { + flag: 'wx', + mode: 0o600 + }) + await writeFile(join(output, 'signing-request.json'), canonicalJson(artifact.signingRequest), { + flag: 'wx', + mode: 0o600 + }) + await rm(join(output, 'aggregate-input'), { recursive: true, force: true }) + return prepared + } catch (error) { + if (created) { + await rm(output, { recursive: true, force: true }) + } + throw error + } +} + +function normalizePreparedArtifact(value) { + assertExactFields(value, PREPARED_FIELDS, 'prepared aggregate') + if (value.schemaVersion !== 1 || !DIGEST.test(value.acceptedKeysSha256 ?? '')) { + throw new Error('SSH relay runtime manifest aggregate command prepared aggregate is invalid') + } + decodeSshRelayRuntimeManifestSigningRequestArtifact(value.signingRequest) + return value +} + +function sameJson(left, right) { + return JSON.stringify(left) === JSON.stringify(right) +} + +export async function finalizeSshRelayRuntimeManifestAggregateCommand(input) { + const identity = commandIdentity(input) + const keys = await readAcceptedKeys(input.acceptedKeysPath) + const prior = normalizePreparedArtifact( + await readBoundedJson( + join(resolve(input.preparedDirectory), 'prepared-aggregate.json'), + 'prepared aggregate' + ) + ) + const signingResult = await readBoundedJson(resolve(input.signingResultPath), 'signing result') + assertExactFields(signingResult, ['keyId', 'signature'], 'signing result') + const output = await exclusiveOutputDirectory(input.outputDirectory) + let created = false + try { + await mkdir(output, { mode: 0o700 }) + created = true + const prepared = await collectAndPrepare(input, output, identity, input.signal) + const current = preparedArtifact(identity, keys.sha256, prepared) + if (!sameJson(prior, current)) { + throw new Error('SSH relay runtime manifest aggregate command prepared receipt drifted') + } + const finalized = finalizeSshRelayRuntimeManifestAggregate({ + prepared, + signingResults: [signingResult], + acceptedKeys: keys.acceptedKeys + }) + const assets = join(output, 'assets') + const evidence = join(output, 'evidence') + await Promise.all([mkdir(assets), mkdir(evidence)]) + await writeFile(join(assets, finalized.manifestAsset.name), finalized.bytes, { + flag: 'wx', + mode: 0o600 + }) + await writeFile( + join(evidence, 'manifest-aggregate.json'), + canonicalJson({ + schemaVersion: 1, + sourceSha: identity.sourceSha, + releaseTag: identity.releaseTag, + acceptedKeysSha256: keys.sha256, + inputTuplesSha256: finalized.inputTuplesSha256, + manifestAsset: finalized.manifestAsset + }), + { flag: 'wx', mode: 0o600 } + ) + await rm(join(output, 'aggregate-input'), { recursive: true, force: true }) + return finalized + } catch (error) { + if (created) { + await rm(output, { recursive: true, force: true }) + } + throw error + } +} + +export function parseSshRelayRuntimeManifestAggregateCommandArguments(argv) { + const command = argv[0] + if (command !== 'prepare' && command !== 'finalize') { + throw new Error('SSH relay runtime manifest aggregate command requires prepare or finalize') + } + const fields = new Map([ + ['--artifacts-directory', 'artifactsDirectory'], + ['--accepted-keys', 'acceptedKeysPath'], + ['--output-directory', 'outputDirectory'], + ['--source-sha', 'sourceSha'], + ['--release-tag', 'releaseTag'], + ['--created-at', 'createdAt'], + ['--relay-protocol-version', 'relayProtocolVersion'], + ['--prepared-directory', 'preparedDirectory'], + ['--signing-result', 'signingResultPath'] + ]) + const result = { command } + for (let index = 1; index < argv.length; index += 2) { + const field = fields.get(argv[index]) + const value = argv[index + 1] + if (!field || !value || value.startsWith('--') || result[field]) { + throw new Error( + `Invalid SSH relay runtime manifest aggregate command argument: ${argv[index]}` + ) + } + result[field] = field === 'relayProtocolVersion' ? Number(value) : value + } + const required = [ + 'artifactsDirectory', + 'acceptedKeysPath', + 'outputDirectory', + 'sourceSha', + 'releaseTag', + 'createdAt', + 'relayProtocolVersion' + ] + if (command === 'finalize') { + required.push('preparedDirectory', 'signingResultPath') + } + for (const field of required) { + if (!result[field]) { + throw new Error(`Missing SSH relay runtime manifest aggregate command argument: ${field}`) + } + } + return result +} + +async function main() { + const options = parseSshRelayRuntimeManifestAggregateCommandArguments(process.argv.slice(2)) + const operation = + options.command === 'prepare' + ? prepareSshRelayRuntimeManifestAggregateCommand + : finalizeSshRelayRuntimeManifestAggregateCommand + const result = await operation(options) + process.stdout.write(`${JSON.stringify({ inputTuplesSha256: result.inputTuplesSha256 })}\n`) +} + +if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) { + main().catch((error) => { + process.stderr.write( + `SSH relay runtime manifest aggregate command failed: ${error.stack ?? error}\n` + ) + process.exitCode = 1 + }) +} diff --git a/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs b/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs new file mode 100644 index 00000000000..16e9fb78f7c --- /dev/null +++ b/config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs @@ -0,0 +1,420 @@ +import { createHash } from 'node:crypto' +import { lstat, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +import nacl from 'tweetnacl' +import { afterEach, describe, expect, it } from 'vitest' + +import { createSshRelayArtifactTestManifest } from '../../src/main/ssh/ssh-relay-artifact-test-manifest.ts' +import { verifySshRelayArtifactManifest } from '../../src/main/ssh/ssh-relay-manifest-signature.ts' +import { sshRelayRuntimeCompatibility } from './ssh-relay-runtime-compatibility.mjs' +import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs' +import { + finalizeSshRelayRuntimeManifestAggregateCommand, + parseSshRelayRuntimeManifestAggregateCommandArguments, + prepareSshRelayRuntimeManifestAggregateCommand +} from './ssh-relay-runtime-manifest-aggregate-command.mjs' +import { + encodeSshRelayRuntimeManifestSigningRequestArtifact, + signSshRelayRuntimeManifestRequest +} from './ssh-relay-runtime-manifest-seed-signing.mjs' +import { sshRelayRuntimeManifestKeyId } from './ssh-relay-runtime-manifest-signing-handoff.mjs' + +const temporaryDirectories = [] +const keyPair = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index)) +const TUPLES = [ + 'linux-x64-glibc', + 'linux-arm64-glibc', + 'darwin-x64', + 'darwin-arm64', + 'win32-x64', + 'win32-arm64' +] + +function digest(bytes) { + return `sha256:${createHash('sha256').update(bytes).digest('hex')}` +} + +function fileReference(name, bytes) { + return { name, size: bytes.length, sha256: digest(bytes) } +} + +function watcherPackage(tupleId) { + return tupleId.startsWith('linux-') + ? `@parcel/watcher-linux-${tupleId.split('-')[1]}-glibc` + : `@parcel/watcher-${tupleId}` +} + +function nativeEntry(path, role, marker) { + return { + path, + type: 'file', + role, + size: marker.length, + mode: 0o755, + sha256: digest(marker) + } +} + +function tupleFor(tupleId) { + const tuple = structuredClone(createSshRelayArtifactTestManifest().tuples[0]) + const [os, architecture] = tupleId.split('-') + tuple.tupleId = tupleId + tuple.os = os + tuple.architecture = architecture + tuple.compatibility = structuredClone(sshRelayRuntimeCompatibility[tupleId]) + const packageName = watcherPackage(tupleId) + tuple.entries = tuple.entries.map((entry) => { + const next = structuredClone(entry) + next.path = next.path.replace('@parcel/watcher-linux-x64-glibc', packageName) + if (next.role === 'node') { + next.path = os === 'win32' ? 'bin/node.exe' : 'bin/node' + } + return next + }) + if (os === 'darwin') { + tuple.entries.push( + nativeEntry( + 'node_modules/node-pty/build/Release/spawn-helper', + 'native-runtime', + `${tupleId}:spawn-helper` + ) + ) + } + if (os === 'win32') { + tuple.entries = tuple.entries.filter((entry) => entry.role !== 'node-pty-native') + tuple.entries.push( + { path: 'node_modules/node-pty/build/Release/conpty', type: 'directory', mode: 0o755 }, + nativeEntry( + 'node_modules/node-pty/build/Release/conpty.node', + 'node-pty-native', + `${tupleId}:conpty-node` + ), + nativeEntry( + 'node_modules/node-pty/build/Release/conpty_console_list.node', + 'node-pty-native', + `${tupleId}:console-list` + ), + nativeEntry( + 'node_modules/node-pty/build/Release/conpty/OpenConsole.exe', + 'native-runtime', + `${tupleId}:open-console` + ), + nativeEntry( + 'node_modules/node-pty/build/Release/conpty/conpty.dll', + 'native-runtime', + `${tupleId}:conpty-dll` + ) + ) + } + const nativeRoles = new Set([ + 'node', + 'node-pty-native', + 'parcel-watcher-native', + 'native-runtime' + ]) + tuple.nativeVerification = { + policy: + os === 'linux' + ? 'linux-hash-only-v1' + : os === 'darwin' + ? 'apple-developer-id-v1' + : 'signpath-authenticode-v1', + tool: { name: 'fixture-verifier', version: '1.0.0' }, + verifiedAt: '2026-07-15T00:00:00.000Z', + files: tuple.entries + .filter((entry) => entry.type === 'file' && nativeRoles.has(entry.role)) + .map(({ path, sha256 }) => ({ path, sha256 })) + } + const files = tuple.entries.filter((entry) => entry.type === 'file') + tuple.archive.fileCount = files.length + tuple.archive.expandedSize = files.reduce((total, entry) => total + entry.size, 0) + 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' + tuple.archive.name = `orca-ssh-relay-runtime-v1-${tupleId}-${tuple.contentId.slice(7)}.${extension}` + return tuple +} + +async function writeTupleArtifact(root, tupleId) { + const tuple = tupleFor(tupleId) + const archiveBytes = Buffer.from(`${tupleId}:archive`) + const sbomBytes = Buffer.from(`${JSON.stringify({ spdxVersion: 'SPDX-2.3', tupleId })}\n`) + const provenanceBytes = Buffer.from(`${JSON.stringify({ _type: 'in-toto', tupleId })}\n`) + tuple.archive.size = archiveBytes.length + tuple.archive.sha256 = digest(archiveBytes) + tuple.metadataAssets.sbom = fileReference(tuple.metadataAssets.sbom.name, sbomBytes) + tuple.metadataAssets.provenance = fileReference( + tuple.metadataAssets.provenance.name, + provenanceBytes + ) + const descriptorBytes = Buffer.from( + `${JSON.stringify({ schemaVersion: 1, tuple }, null, 2)}\n`, + 'utf8' + ) + const input = { + tupleId, + descriptor: fileReference( + `orca-ssh-relay-runtime-${tupleId}.manifest-tuple.json`, + descriptorBytes + ), + archive: fileReference(tuple.archive.name, archiveBytes), + sbom: { ...tuple.metadataAssets.sbom }, + provenance: { ...tuple.metadataAssets.provenance } + } + const artifactName = tupleId.startsWith('linux-') + ? `ssh-relay-runtime-${tupleId}` + : `ssh-relay-runtime-signed-${tupleId}` + const artifactRoot = join(root, artifactName) + const assetsRoot = tupleId.startsWith('linux-') ? artifactRoot : join(artifactRoot, 'assets') + const evidenceRoot = tupleId.startsWith('linux-') ? artifactRoot : join(artifactRoot, 'evidence') + await mkdir(assetsRoot, { recursive: true }) + await mkdir(evidenceRoot, { recursive: true }) + await Promise.all([ + writeFile(join(assetsRoot, input.descriptor.name), descriptorBytes), + writeFile(join(assetsRoot, input.archive.name), archiveBytes), + writeFile(join(assetsRoot, input.sbom.name), sbomBytes), + writeFile(join(assetsRoot, input.provenance.name), provenanceBytes) + ]) + const receipt = tupleId.startsWith('linux-') + ? { tupleId, contentId: tuple.contentId, verification: {}, aggregateInput: input } + : { + tupleId, + sourceContentId: tuple.contentId, + finalContentId: tuple.contentId, + returnedFiles: [], + nativeVerification: tuple.nativeVerification, + smoke: {}, + metadata: {}, + aggregateInput: input + } + const suffix = tupleId.startsWith('linux-') ? 'linux-finalization' : 'finalization' + await writeFile(join(evidenceRoot, `${tupleId}.${suffix}.json`), `${JSON.stringify(receipt)}\n`) + return { artifactRoot, assetsRoot, evidenceRoot, input, tuple } +} + +async function fixture() { + const root = await mkdtemp(join(tmpdir(), 'orca-manifest-command-')) + temporaryDirectories.push(root) + const artifactsDirectory = join(root, 'artifacts') + await mkdir(artifactsDirectory) + const tuples = [] + for (const tupleId of TUPLES) { + tuples.push(await writeTupleArtifact(artifactsDirectory, tupleId)) + } + const acceptedKeysPath = join(root, 'accepted-keys.json') + const acceptedKeys = { + schemaVersion: 1, + keys: [ + { + keyId: sshRelayRuntimeManifestKeyId(keyPair.publicKey), + publicKeyBase64: Buffer.from(keyPair.publicKey).toString('base64') + } + ] + } + await writeFile(acceptedKeysPath, `${JSON.stringify(acceptedKeys)}\n`) + return { + root, + artifactsDirectory, + acceptedKeysPath, + acceptedKeys, + tuples, + prepareOutput: join(root, 'prepared'), + finalOutput: join(root, 'final'), + sourceSha: 'a'.repeat(40), + releaseTag: 'v1.4.140-rc.1', + createdAt: '2026-07-15T01:02:03.000Z', + relayProtocolVersion: 1 + } +} + +function commandInput(value, outputDirectory = value.prepareOutput) { + return { + artifactsDirectory: value.artifactsDirectory, + acceptedKeysPath: value.acceptedKeysPath, + outputDirectory, + sourceSha: value.sourceSha, + releaseTag: value.releaseTag, + createdAt: value.createdAt, + relayProtocolVersion: value.relayProtocolVersion + } +} + +afterEach(async () => { + await Promise.all( + temporaryDirectories + .splice(0) + .map((directory) => rm(directory, { recursive: true, force: true })) + ) +}) + +describe('SSH relay runtime manifest aggregate filesystem command', () => { + it('independently recollects six receipt-bound artifacts around signature-only handoff', async () => { + const value = await fixture() + const prepared = await prepareSshRelayRuntimeManifestAggregateCommand(commandInput(value)) + const requestArtifact = JSON.parse( + await readFile(join(value.prepareOutput, 'signing-request.json'), 'utf8') + ) + expect(requestArtifact).toEqual( + encodeSshRelayRuntimeManifestSigningRequestArtifact(prepared.signingRequest) + ) + const signingResult = signSshRelayRuntimeManifestRequest({ + requestArtifact, + seedBase64: Buffer.from(keyPair.secretKey.subarray(0, 32)).toString('base64') + }) + const signingResultPath = join(value.root, 'signing-result.json') + await writeFile(signingResultPath, `${JSON.stringify(signingResult)}\n`) + const finalized = await finalizeSshRelayRuntimeManifestAggregateCommand({ + ...commandInput(value, value.finalOutput), + preparedDirectory: value.prepareOutput, + signingResultPath + }) + + expect(finalized.manifest.tuples.map((tuple) => tuple.tupleId)).toEqual(TUPLES.toSorted()) + expect( + verifySshRelayArtifactManifest(finalized.manifest, [ + { keyId: value.acceptedKeys.keys[0].keyId, publicKey: keyPair.publicKey } + ]) + ).toEqual(finalized.manifest) + const finalEntries = (await lstat(value.finalOutput)).isDirectory() + expect(finalEntries).toBe(true) + await expect(lstat(join(value.finalOutput, 'aggregate-input'))).rejects.toMatchObject({ + code: 'ENOENT' + }) + }) + + it('rejects missing, extra, mutated, duplicate, and receipt-drifted artifact inputs', async () => { + const missing = await fixture() + await rm(missing.tuples[0].artifactRoot, { recursive: true }) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(missing)) + ).rejects.toThrow(/missing|unexpected/i) + + const extra = await fixture() + await mkdir(join(extra.artifactsDirectory, 'ssh-relay-runtime-extra')) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(extra)) + ).rejects.toThrow(/missing|unexpected/i) + + const mutated = await fixture() + await writeFile( + join(mutated.tuples[1].assetsRoot, mutated.tuples[1].input.archive.name), + 'drift' + ) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(mutated)) + ).rejects.toThrow(/size|sha-?256|changed/i) + + const receiptDrift = await fixture() + const receiptPath = join(receiptDrift.tuples[2].evidenceRoot, `${TUPLES[2]}.finalization.json`) + const document = JSON.parse(await readFile(receiptPath, 'utf8')) + document.tupleId = TUPLES[3] + await writeFile(receiptPath, `${JSON.stringify(document)}\n`) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(receiptDrift)) + ).rejects.toThrow(/receipt|tuple/i) + + const duplicate = await fixture() + const duplicateReceiptPath = join( + duplicate.tuples[1].evidenceRoot, + `${TUPLES[1]}.linux-finalization.json` + ) + const duplicateReceipt = JSON.parse(await readFile(duplicateReceiptPath, 'utf8')) + duplicateReceipt.aggregateInput.archive.name = duplicate.tuples[0].input.archive.name + await writeFile(duplicateReceiptPath, `${JSON.stringify(duplicateReceipt)}\n`) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(duplicate)) + ).rejects.toThrow(/duplicate/i) + }) + + it.skipIf(process.platform === 'win32')('rejects linked finalization receipts', async () => { + const linked = await fixture() + const receipt = join(linked.tuples[0].evidenceRoot, `${TUPLES[0]}.linux-finalization.json`) + const realReceipt = `${receipt}.real` + await writeFile(realReceipt, await readFile(receipt)) + await rm(receipt) + await symlink(realReceipt, receipt) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(linked)) + ).rejects.toThrow(/regular|symbolic|link/i) + }) + + it('validates accepted keys before exposing a request and removes partial final output', async () => { + const invalidKeys = await fixture() + invalidKeys.acceptedKeys.keys[0].publicKeyBase64 = Buffer.alloc(31).toString('base64') + await writeFile(invalidKeys.acceptedKeysPath, JSON.stringify(invalidKeys.acceptedKeys)) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand(commandInput(invalidKeys)) + ).rejects.toThrow(/public key|32 bytes|accepted key/i) + await expect(lstat(invalidKeys.prepareOutput)).rejects.toMatchObject({ code: 'ENOENT' }) + + const invalidSignature = await fixture() + await prepareSshRelayRuntimeManifestAggregateCommand(commandInput(invalidSignature)) + const signingResultPath = join(invalidSignature.root, 'signing-result.json') + await writeFile( + signingResultPath, + JSON.stringify({ + keyId: invalidSignature.acceptedKeys.keys[0].keyId, + signature: Buffer.alloc(64).toString('base64') + }) + ) + await expect( + finalizeSshRelayRuntimeManifestAggregateCommand({ + ...commandInput(invalidSignature, invalidSignature.finalOutput), + preparedDirectory: invalidSignature.prepareOutput, + signingResultPath + }) + ).rejects.toThrow(/signature/i) + await expect(lstat(invalidSignature.finalOutput)).rejects.toMatchObject({ code: 'ENOENT' }) + }) + + it('rejects prepared handoff drift, cancellation, and incomplete CLI arguments', async () => { + const drifted = await fixture() + const prepared = await prepareSshRelayRuntimeManifestAggregateCommand(commandInput(drifted)) + const requestArtifact = encodeSshRelayRuntimeManifestSigningRequestArtifact( + prepared.signingRequest + ) + const signingResultPath = join(drifted.root, 'signing-result.json') + await writeFile( + signingResultPath, + JSON.stringify({ + ...signSshRelayRuntimeManifestRequest({ + requestArtifact, + seedBase64: Buffer.from(keyPair.secretKey.subarray(0, 32)).toString('base64') + }) + }) + ) + const preparedPath = join(drifted.prepareOutput, 'prepared-aggregate.json') + const preparedDocument = JSON.parse(await readFile(preparedPath, 'utf8')) + preparedDocument.sourceSha = 'b'.repeat(40) + await writeFile(preparedPath, `${JSON.stringify(preparedDocument)}\n`) + await expect( + finalizeSshRelayRuntimeManifestAggregateCommand({ + ...commandInput(drifted, drifted.finalOutput), + preparedDirectory: drifted.prepareOutput, + signingResultPath + }) + ).rejects.toThrow(/prepared|drift/i) + await expect(lstat(drifted.finalOutput)).rejects.toMatchObject({ code: 'ENOENT' }) + + const cancelled = await fixture() + const controller = new AbortController() + controller.abort(new Error('cancel aggregate collection')) + await expect( + prepareSshRelayRuntimeManifestAggregateCommand({ + ...commandInput(cancelled), + signal: controller.signal + }) + ).rejects.toThrow(/cancel aggregate collection/i) + await expect(lstat(cancelled.prepareOutput)).rejects.toMatchObject({ code: 'ENOENT' }) + + expect(() => parseSshRelayRuntimeManifestAggregateCommandArguments(['prepare'])).toThrow( + /missing/i + ) + expect(() => + parseSshRelayRuntimeManifestAggregateCommandArguments(['finalize', '--unknown', 'value']) + ).toThrow(/invalid/i) + }) +}) diff --git a/config/scripts/ssh-relay-runtime-manifest-artifact-collection.mjs b/config/scripts/ssh-relay-runtime-manifest-artifact-collection.mjs new file mode 100644 index 00000000000..23e120f1da3 --- /dev/null +++ b/config/scripts/ssh-relay-runtime-manifest-artifact-collection.mjs @@ -0,0 +1,236 @@ +import { constants } from 'node:fs' +import { copyFile, lstat, readFile, readdir } from 'node:fs/promises' +import { basename, join, resolve } from 'node:path' + +const MAX_RECEIPT_BYTES = 32 * 1024 * 1024 +const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,239}$/u +const DIGEST = /^sha256:[0-9a-f]{64}$/u +const INPUT_FIELDS = ['archive', 'descriptor', 'provenance', 'sbom', 'tupleId'] +const FILE_FIELDS = ['name', 'sha256', 'size'] + +export const SSH_RELAY_RUNTIME_MANIFEST_ARTIFACTS = Object.freeze([ + { + tupleId: 'linux-x64-glibc', + artifactName: 'ssh-relay-runtime-linux-x64-glibc', + layout: 'flat' + }, + { + tupleId: 'linux-arm64-glibc', + artifactName: 'ssh-relay-runtime-linux-arm64-glibc', + layout: 'flat' + }, + { + tupleId: 'darwin-x64', + artifactName: 'ssh-relay-runtime-signed-darwin-x64', + layout: 'signed' + }, + { + tupleId: 'darwin-arm64', + artifactName: 'ssh-relay-runtime-signed-darwin-arm64', + layout: 'signed' + }, + { + tupleId: 'win32-x64', + artifactName: 'ssh-relay-runtime-signed-win32-x64', + layout: 'signed' + }, + { + tupleId: 'win32-arm64', + artifactName: 'ssh-relay-runtime-signed-win32-arm64', + layout: 'signed' + } +]) + +function assertObject(value, label) { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`SSH relay runtime manifest artifact ${label} must be an object`) + } +} + +function assertExactFields(value, fields, label) { + assertObject(value, label) + const actual = Object.keys(value).sort() + const expected = [...fields].sort() + if (JSON.stringify(actual) !== JSON.stringify(expected)) { + throw new Error(`SSH relay runtime manifest artifact ${label} has invalid fields`) + } +} + +function stableState(before, after) { + return ( + before.dev === after.dev && + before.ino === after.ino && + before.size === after.size && + before.mtimeNs === after.mtimeNs && + before.ctimeNs === after.ctimeNs + ) +} + +async function realDirectory(path, label) { + const metadata = await lstat(path) + if (!metadata.isDirectory() || metadata.isSymbolicLink()) { + throw new Error(`SSH relay runtime manifest artifact ${label} must be a real directory`) + } + return resolve(path) +} + +async function readStableJson(path, label, signal) { + signal?.throwIfAborted() + const before = await lstat(path, { bigint: true }) + if ( + !before.isFile() || + before.isSymbolicLink() || + before.size <= 0n || + before.size > BigInt(MAX_RECEIPT_BYTES) + ) { + throw new Error(`SSH relay runtime manifest artifact ${label} must be bounded regular JSON`) + } + const bytes = await readFile(path, { signal }) + const after = await lstat(path, { bigint: true }) + if (!stableState(before, after)) { + throw new Error(`SSH relay runtime manifest artifact ${label} changed while reading`) + } + try { + return JSON.parse(bytes.toString('utf8')) + } catch (error) { + throw new Error( + `SSH relay runtime manifest artifact ${label} is invalid JSON: ${error.message}` + ) + } +} + +function normalizeFile(value, label) { + assertExactFields(value, FILE_FIELDS, label) + if ( + typeof value.name !== 'string' || + basename(value.name) !== value.name || + !SAFE_NAME.test(value.name) + ) { + throw new Error(`SSH relay runtime manifest artifact ${label} has an unsafe name`) + } + if (!DIGEST.test(value.sha256)) { + throw new Error(`SSH relay runtime manifest artifact ${label} has an invalid SHA-256`) + } + if (!Number.isSafeInteger(value.size) || value.size <= 0) { + throw new Error(`SSH relay runtime manifest artifact ${label} has an invalid size`) + } + return { name: value.name, sha256: value.sha256, size: value.size } +} + +function normalizeAggregateInput(value, expectedTupleId) { + assertExactFields(value, INPUT_FIELDS, `${expectedTupleId} aggregate input`) + if (value.tupleId !== expectedTupleId) { + throw new Error(`SSH relay runtime manifest artifact receipt tuple drifted: ${expectedTupleId}`) + } + return { + tupleId: expectedTupleId, + descriptor: normalizeFile(value.descriptor, `${expectedTupleId} descriptor`), + archive: normalizeFile(value.archive, `${expectedTupleId} archive`), + sbom: normalizeFile(value.sbom, `${expectedTupleId} SBOM`), + provenance: normalizeFile(value.provenance, `${expectedTupleId} provenance`) + } +} + +function receiptBinding(receipt, artifact) { + assertObject(receipt, `${artifact.tupleId} finalization receipt`) + if (receipt.tupleId !== artifact.tupleId) { + throw new Error( + `SSH relay runtime manifest artifact receipt tuple drifted: ${artifact.tupleId}` + ) + } + const contentId = artifact.layout === 'flat' ? receipt.contentId : receipt.finalContentId + if (!DIGEST.test(contentId ?? '')) { + throw new Error( + `SSH relay runtime manifest artifact receipt content drifted: ${artifact.tupleId}` + ) + } + return { + tupleId: artifact.tupleId, + contentId, + aggregateInput: normalizeAggregateInput(receipt.aggregateInput, artifact.tupleId) + } +} + +async function copyStableFile(source, destination, file, signal) { + signal?.throwIfAborted() + const before = await lstat(source, { bigint: true }) + if (!before.isFile() || before.isSymbolicLink()) { + throw new Error( + `SSH relay runtime manifest artifact input is not the declared regular file: ${file.name}` + ) + } + if (before.size !== BigInt(file.size)) { + throw new Error(`SSH relay runtime manifest artifact input size mismatch: ${file.name}`) + } + await copyFile(source, destination, constants.COPYFILE_EXCL) + const after = await lstat(source, { bigint: true }) + if (!stableState(before, after)) { + throw new Error(`SSH relay runtime manifest artifact input changed while copying: ${file.name}`) + } +} + +async function assertExactArtifactDirectories(root) { + const entries = await readdir(root, { withFileTypes: true }) + const actual = entries.map((entry) => entry.name).sort() + const expected = SSH_RELAY_RUNTIME_MANIFEST_ARTIFACTS.map( + ({ artifactName }) => artifactName + ).sort() + if (JSON.stringify(actual) !== JSON.stringify(expected)) { + throw new Error( + 'SSH relay runtime manifest artifact root has missing or unexpected directories' + ) + } + for (const entry of entries) { + if (!entry.isDirectory() || entry.isSymbolicLink()) { + throw new Error(`SSH relay runtime manifest artifact is not a real directory: ${entry.name}`) + } + } +} + +export async function collectSshRelayRuntimeManifestArtifacts({ + artifactsDirectory, + stagingDirectory, + signal +}) { + const root = await realDirectory(resolve(artifactsDirectory), 'root') + const staging = await realDirectory(resolve(stagingDirectory), 'staging directory') + await assertExactArtifactDirectories(root) + const names = new Set() + const bindings = [] + for (const artifact of SSH_RELAY_RUNTIME_MANIFEST_ARTIFACTS) { + signal?.throwIfAborted() + const artifactRoot = await realDirectory( + join(root, artifact.artifactName), + artifact.artifactName + ) + const assetsRoot = + artifact.layout === 'flat' + ? artifactRoot + : await realDirectory(join(artifactRoot, 'assets'), `${artifact.tupleId} assets`) + const evidenceRoot = + artifact.layout === 'flat' + ? artifactRoot + : await realDirectory(join(artifactRoot, 'evidence'), `${artifact.tupleId} evidence`) + const suffix = artifact.layout === 'flat' ? 'linux-finalization' : 'finalization' + const receipt = await readStableJson( + join(evidenceRoot, `${artifact.tupleId}.${suffix}.json`), + `${artifact.tupleId} receipt`, + signal + ) + const binding = receiptBinding(receipt, artifact) + for (const file of [ + binding.aggregateInput.descriptor, + binding.aggregateInput.archive, + binding.aggregateInput.sbom, + binding.aggregateInput.provenance + ]) { + if (names.has(file.name)) { + throw new Error(`SSH relay runtime manifest artifact has a duplicate file: ${file.name}`) + } + names.add(file.name) + await copyStableFile(join(assetsRoot, file.name), join(staging, file.name), file, signal) + } + bindings.push(binding) + } + return bindings +} diff --git a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md index 8d895aaeafc..6fb74adf9c0 100644 --- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md +++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md @@ -242,8 +242,11 @@ complete a box. signing remains blocked and no release, desktop, publication, or tuple consumer is connected. The missing-workflow/seed-signer RED is `E-M4-PROTECTED-MANIFEST-WORKFLOW-LOCAL-RED-001`; the seed signer is locally green under - `E-M4-PROTECTED-MANIFEST-SEED-LOCAL-001`, while the filesystem aggregate command and workflow - remain open. + `E-M4-PROTECTED-MANIFEST-SEED-LOCAL-001` and all-six exact-head CI is closed by + `E-M4-PROTECTED-MANIFEST-SEED-CI-001`. The filesystem prepare/finalize command is locally + green under `E-M4-MANIFEST-AGGREGATE-COMMAND-LOCAL-RED-001` and + `E-M4-MANIFEST-AGGREGATE-COMMAND-LOCAL-001`; exact-head CI and the callable workflow remain + open. - [ ] Embed the exact signed manifest and accepted keys in each desktop build. - [ ] Upload to a draft release, read back, re-hash, and execute the downloaded archives. - [ ] Test timeouts, retries, approval denial, signing failure, partial output, and draft recovery. diff --git a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md index 50e1eb39676..2986ee50794 100644 --- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md +++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md @@ -9,7 +9,7 @@ work; keep exact commands, runner identities, hashes, metrics, and residual gaps Date created: 2026-07-14
Last updated: 2026-07-15
Current phase: Milestone 4 / Work Package 3 disconnected aggregate and protected manifest-signing workflow contract — **In progress — 2026-07-15, Codex implementation owner**. Linux aggregate-ready finalization is closed locally and on exact-head native x64/arm64 runners under E-M4-LINUX-FINALIZATION-LOCAL-RED-001, E-M4-LINUX-FINALIZATION-LOCAL-001, and E-M4-LINUX-FINALIZATION-CI-001. The active package must consume only the six exact aggregate-ready outputs, prepare canonical bytes credential-free, expose only those bytes to the tag-restricted `relay-runtime-manifest-signing` environment, accept only key ID plus Ed25519 signature, reconstruct and verify before final output, and fail closed on missing/extra/mutated inputs, approval/secret/signing failure, timeout, or partial output. It remains callable and disconnected. Release-cut, desktop builds, publication, and every tuple stay disconnected; production/default behavior is unchanged.
-Session checkpoint: **In progress — 2026-07-15, Codex implementation owner** — exact-head run 29419759256 at `91f59d19e5251fb7b6c9f6d397cd9cd82b43cbdf` passes all six native builds, both Linux userland supplements, and Windows x64 baseline. Windows arm64 fails only the retained build-floor gate after successful runtime verification (hosted 26200 versus required 26100). Downloaded artifacts 8344803641 and 8344824674 contain the expected Linux x64/arm64 descriptors and finalization receipts, whose tuple/content/archive/metadata bindings and smoke/resource metrics were inspected under E-M4-LINUX-FINALIZATION-CI-001. Golden E2E and replacement PR Checks pass. The protected seed signer is now locally green under E-M4-PROTECTED-MANIFEST-WORKFLOW-LOCAL-RED-001 and E-M4-PROTECTED-MANIFEST-SEED-LOCAL-001: canonical request serialization, exact base64 32-byte seed validation, signature-only output, drift/symlink rejection, and exclusive failure cleanup pass, and both native contract families execute the tests. The callable workflow remains intentionally absent/red while the filesystem prepare/finalize command is implemented next. No signing credential, aggregate workflow, release consumer, publication path, desktop consumer, tuple enablement, or production/default change is connected. Legacy remains the production default, and merge to `main` remains prohibited.
+Session checkpoint: **In progress — 2026-07-15, Codex implementation owner** — protected seed-signer exact-head CI is closed by E-M4-PROTECTED-MANIFEST-SEED-CI-001 at pushed head `4180e04ac4e732f082dc576af69364325a678d2b`: all six native build jobs, PR Checks, and Golden E2E pass, with the artifact workflow red only for the retained Windows arm64 build-floor mismatch. The exclusive six-artifact filesystem command is locally green under E-M4-MANIFEST-AGGREGATE-COMMAND-LOCAL-RED-001 and E-M4-MANIFEST-AGGREGATE-COMMAND-LOCAL-001. Exact-head CI for that command is the next checkpoint, after which the still-red callable workflow may be implemented. No signing credential, aggregate workflow, release consumer, publication path, desktop consumer, tuple enablement, or production/default change is connected. Legacy remains the production default, and merge to `main` remains prohibited.
Primary design: [SSH relay GitHub Release plan](./2026-07-14-ssh-relay-github-release-plan.html)
Motivating issues: [#8450](https://github.com/stablyai/orca/issues/8450), [#1693](https://github.com/stablyai/orca/issues/1693) @@ -10776,6 +10776,96 @@ typecheck` passes in 1.88 seconds wall. Full `pnpm run lint` passes in 9.26 seco receipt/path/mutation/accepted-key/signature/partial-output failures, before adding the callable workflow. +### E-M4-MANIFEST-AGGREGATE-COMMAND-LOCAL-RED-001 — Six-artifact filesystem command is absent + +- Date: 2026-07-15 +- Owner: Codex implementation owner +- Source: uncommitted purpose-named test atop pushed head + `4180e04ac4e732f082dc576af69364325a678d2b`; draft PR #8741. +- Runner/remote/network: local macOS 26.2 arm64, Node v26.0.0 and pnpm 10.24.0. Generated local + fixtures only; no Actions runner, GitHub Environment, seed, release, desktop consumer, SSH host, + publication, or enabled tuple. +- Command: `pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 +config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs`. +- Result: expected FAIL, 1 suite / 0 tests collected in 225 ms. The only failure is module + resolution for absent `config/scripts/ssh-relay-runtime-manifest-aggregate-command.mjs`. +- Oracle proved: there is no file-based boundary that collects only the exact two flattened Linux + and four signed native artifacts, binds finalization receipts and accepted public keys, exposes + canonical bytes without credentials, independently recollects after signature return, verifies + the signature, and removes partial output on failure. The test defines missing/extra directory, + symlink, mutation, receipt drift, invalid-key, invalid-signature, and cleanup failures. +- Does not prove: implementation, real downloaded artifact inputs, Actions execution, protected + signing, publication, desktop embedding, SSH behavior, or an enabled tuple. +- Follow-up: implement the command and make this suite green before adding the callable workflow. + +### E-M4-PROTECTED-MANIFEST-SEED-CI-001 — Seed signer passes all six native contract families + +- Date: 2026-07-15 +- Owner: Codex implementation owner +- Source: exact pushed head `4180e04ac4e732f082dc576af69364325a678d2b`; draft PR #8741. +- Workflow: [SSH Relay Runtime Artifacts run 29422587380](https://github.com/stablyai/orca/actions/runs/29422587380), + 14:13:18Z–14:27:59Z. The workflow conclusion is the expected failure solely because Windows + arm64 hosted build 26200 does not equal the separately required build 26100 after successful + runtime execution. +- Native jobs: Linux arm64 87376626186, Linux x64 87376626194, macOS x64 87376626222, Windows arm64 + 87376626264, macOS arm64 87376626297, and Windows x64 87376626317 all pass. Linux/macOS report 48 + files / 251 tests; Windows reports 49 files / 246 passed and nine skipped of 255. The delta from + the prior package is the protected seed-signing purpose suite under Node 24.18.0 on every family. +- Supplemental gates: Linux x64 87378044229, Linux arm64 87378044358, and Windows x64 87379238862 + pass. Windows arm64 87379238661 fails only the retained 26200-versus-26100 floor assertion. +- Adjacent regressions: [PR Checks 29422587487](https://github.com/stablyai/orca/actions/runs/29422587487) + job 87376597444 passes from 14:13:22Z–14:26:30Z. [Golden E2E 29422587419](https://github.com/stablyai/orca/actions/runs/29422587419) + passes macOS job 87376596910 and Linux job 87376596912. +- Oracle proved: canonical request serialization and seed-signing failure contracts execute under + repository Node 24 on all target-native runner families without regressing builds, packaged + smoke, full PR checks, or Golden E2E. +- Does not prove: a real protected environment/seed, approval, filesystem aggregate command CI, + callable aggregate workflow, native signing/trust, oldest Windows arm64/macOS/Linux kernel + floors, publication, desktop embedding, SSH behavior, or an enabled tuple. +- Follow-up: checkpoint the locally green filesystem aggregate command and require it on the same + six native families before implementing the callable workflow. + +### E-M4-MANIFEST-AGGREGATE-COMMAND-LOCAL-001 — Six-artifact prepare/finalize boundary passes locally + +- Date: 2026-07-15 +- Owner: Codex implementation owner +- Source: local implementation atop pushed head + `4180e04ac4e732f082dc576af69364325a678d2b`; draft PR #8741. +- Runner/remote/network: local macOS 26.2 arm64, Node v26.0.0 and pnpm 10.24.0. Six generated + aggregate-ready fixture directories and an in-memory Ed25519 test seed only; no Actions runner, + GitHub Environment, production key, release, desktop consumer, SSH host, publication, or enabled + tuple. +- Focused command: `pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 +config/scripts/ssh-relay-runtime-manifest-aggregate-command.test.mjs` — PASS, 1 file / 4 tests in + 409 ms Vitest. A final cross-platform split rerun passes 1 file / 5 tests in 504 ms, preserving + the symlink case on POSIX while leaving all other hostile cases active on Windows. The suite covers + exact six-directory enumeration, flattened Linux versus signed + `assets/` + `evidence/` layouts, stable exclusive copies, finalization receipts, content and + descriptor bindings, duplicate names/tuples, missing/extra directories, symlinks, mutation, + invalid accepted keys, prepared drift, cancellation, invalid signatures, and failure cleanup. +- Broad release command: `rg --files config/scripts | rg +'/ssh-relay.*[.]test[.]mjs$' | rg -v +'ssh-relay-runtime-manifest-signing-workflow[.]test[.]mjs$' | xargs /usr/bin/time -l pnpm exec + vitest run --config config/vitest.config.ts --maxWorkers=1` — PASS, 50 files / 260 tests in 8.68 + seconds Vitest / 9.56 seconds wall, 189,104,128-byte maximum RSS, 96,130,448-byte peak footprint, + zero swaps. The preceding unfiltered 51-file run passes all implemented suites and fails only the + recorded absent-workflow RED; its pre-split count was 50 files / 259 tests. +- Desktop parity and static gates: manifest schema/signature/release-asset parity passes 3 files / 48 + tests; `pnpm run typecheck`, `node --check`, focused oxlint/formatting, and full `pnpm run lint` + pass. Full lint reports all 41 reliability gates, the 355-entry max-lines ratchet, bundled-skill, + localization catalog/parity, and localization coverage green; 26 warnings remain only in + untouched existing files. +- Oracle proved: accepted public keys are validated and hash-bound before a request is exposed. The + preparation phase copies and verifies only the four declared files from each exact finalization + receipt into exclusive staging. Finalization independently recollects all six inputs, compares + the complete prepared receipt/request, verifies the returned key/signature, emits the manifest + and evidence exclusively, and removes staging/partial output on every tested failure. +- Does not prove: actual downloaded native artifact directories, Node 24/native CI, a real protected + signer/environment/approval, the callable workflow, native signing/trust, publication, desktop + embedding, SSH behavior, or an enabled tuple. +- Follow-up: checkpoint and push this command, require it on all six exact-head native jobs, then + implement the still-red callable workflow without connecting release or product consumers. + ## Accepted Gaps No product gap is accepted merely because it appears in this list. Each entry requires explicit @@ -10834,16 +10924,14 @@ The project is not complete until every applicable item below is checked with ev ## Next Required Action -Checkpoint the locally proven protected seed signer and require its contract on all six native test -families. Then implement the exclusive filesystem prepare/finalize command that collects only the -six exact aggregate-ready artifacts, binds receipts and accepted public keys, serializes canonical -bytes without credentials, reconstructs after signature return, and removes partial output on every -failure. Only after that command is green may the still-red callable workflow isolate the base64 -32-byte seed in the tag-restricted `relay-runtime-manifest-signing` environment. The environment, -accepted production keys, and secret remain unprovisioned, so live signing evidence must stay -BLOCKED. Keep Windows arm64 build 26100, macOS 13.5, Linux kernel 4.18, release-cut, desktop builds, -publication, and every tuple separately gated. Do not merge to `main`; retain every -production/default gate. +Checkpoint the locally proven filesystem prepare/finalize command and require its purpose suite on +all six exact-head native test families. After that CI evidence is green, implement the still-red +callable workflow so only canonical request bytes and the base64 32-byte seed enter the +tag-restricted `relay-runtime-manifest-signing` environment, while credential-free preparation and +final reconstruction independently download the six exact artifacts. The environment, accepted +production keys, and secret remain unprovisioned, so live signing evidence must stay BLOCKED. Keep +Windows arm64 build 26100, macOS 13.5, Linux kernel 4.18, release-cut, desktop builds, publication, +and every tuple separately gated. Do not merge to `main`; retain every production/default gate. 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