From 4180e04ac4e732f082dc576af69364325a678d2b Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Wed, 15 Jul 2026 10:13:01 -0400 Subject: [PATCH] ci: add protected relay manifest seed signer Co-authored-by: Orca --- .../workflows/ssh-relay-runtime-artifacts.yml | 8 + ...sh-relay-runtime-manifest-seed-signing.mjs | 236 ++++++++++++++++++ ...lay-runtime-manifest-seed-signing.test.mjs | 152 +++++++++++ ...elease-implementation-checklist-summary.md | 4 + ...github-release-implementation-checklist.md | 91 ++++++- 5 files changed, 482 insertions(+), 9 deletions(-) create mode 100644 config/scripts/ssh-relay-runtime-manifest-seed-signing.mjs create mode 100644 config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml index 05f9979d6c8..f58cadb26c0 100644 --- a/.github/workflows/ssh-relay-runtime-artifacts.yml +++ b/.github/workflows/ssh-relay-runtime-artifacts.yml @@ -166,6 +166,8 @@ jobs: node --check config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs node --check config/scripts/ssh-relay-runtime-manifest-signing-handoff.mjs 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-aggregate.mjs node --check config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs node --check config/scripts/ssh-relay-runtime-manifest-tuple.mjs @@ -212,6 +214,7 @@ jobs: config/scripts/ssh-relay-runtime-draft-readback.test.mjs \ 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.test.mjs \ config/scripts/ssh-relay-runtime-manifest-tuple.test.mjs \ config/scripts/ssh-relay-runtime-post-sign-metadata.test.mjs \ @@ -578,6 +581,10 @@ jobs: if ($LASTEXITCODE -ne 0) { throw 'runtime manifest signing handoff syntax check failed' } node --check config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs if ($LASTEXITCODE -ne 0) { throw 'runtime manifest signing handoff test syntax check failed' } + node --check config/scripts/ssh-relay-runtime-manifest-seed-signing.mjs + 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-aggregate.mjs if ($LASTEXITCODE -ne 0) { throw 'runtime manifest aggregate syntax check failed' } node --check config/scripts/ssh-relay-runtime-manifest-aggregate.test.mjs @@ -642,6 +649,7 @@ jobs: config/scripts/ssh-relay-runtime-draft-readback.test.mjs ` 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.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-seed-signing.mjs b/config/scripts/ssh-relay-runtime-manifest-seed-signing.mjs new file mode 100644 index 00000000000..90ee70660e6 --- /dev/null +++ b/config/scripts/ssh-relay-runtime-manifest-seed-signing.mjs @@ -0,0 +1,236 @@ +import { lstat, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises' +import { basename, dirname, resolve } from 'node:path' +import { pathToFileURL } from 'node:url' + +import nacl from 'tweetnacl' + +import { + createSshRelayRuntimeManifestSigningRequest, + sshRelayRuntimeManifestKeyId +} from './ssh-relay-runtime-manifest-signing-handoff.mjs' + +const REQUEST_FIELDS = [ + 'algorithm', + 'payloadBase64', + 'payloadSha256', + 'payloadSize', + 'schemaVersion' +] +const MAX_REQUEST_ARTIFACT_BYTES = 48 * 1024 * 1024 +const BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u + +function assertObject(value, label) { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`SSH relay runtime manifest seed signing ${label} must be an object`) + } +} + +function assertExactFields(value, fields, label) { + assertObject(value, label) + const actual = Object.keys(value).sort() + const expected = [...fields].sort() + if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) { + throw new Error( + `SSH relay runtime manifest seed signing ${label} has unexpected or missing fields` + ) + } +} + +function decodeCanonicalBase64(value, label) { + if (typeof value !== 'string' || !BASE64_PATTERN.test(value)) { + throw new Error(`SSH relay runtime manifest seed signing ${label} is not canonical base64`) + } + const bytes = Buffer.from(value, 'base64') + if (bytes.length === 0 || bytes.toString('base64') !== value) { + throw new Error(`SSH relay runtime manifest seed signing ${label} is not canonical base64`) + } + return bytes +} + +function validateInMemoryRequest(request) { + assertExactFields( + request, + ['algorithm', 'canonicalBytes', 'payloadSha256', 'payloadSize'], + 'request' + ) + if (!Buffer.isBuffer(request.canonicalBytes) && !(request.canonicalBytes instanceof Uint8Array)) { + throw new Error('SSH relay runtime manifest seed signing request payload must be bytes') + } + const expected = createSshRelayRuntimeManifestSigningRequest(request.canonicalBytes) + if ( + request.algorithm !== expected.algorithm || + request.payloadSha256 !== expected.payloadSha256 || + request.payloadSize !== expected.payloadSize + ) { + throw new Error('SSH relay runtime manifest seed signing request binding is inconsistent') + } + return expected +} + +export function encodeSshRelayRuntimeManifestSigningRequestArtifact(request) { + const parsed = validateInMemoryRequest(request) + return { + schemaVersion: 1, + algorithm: parsed.algorithm, + payloadBase64: parsed.canonicalBytes.toString('base64'), + payloadSha256: parsed.payloadSha256, + payloadSize: parsed.payloadSize + } +} + +export function decodeSshRelayRuntimeManifestSigningRequestArtifact(artifact) { + assertExactFields(artifact, REQUEST_FIELDS, 'request artifact') + if (artifact.schemaVersion !== 1 || artifact.algorithm !== 'ed25519-v1') { + throw new Error( + 'SSH relay runtime manifest seed signing request artifact schema is unsupported' + ) + } + const canonicalBytes = decodeCanonicalBase64(artifact.payloadBase64, 'request payload') + const request = createSshRelayRuntimeManifestSigningRequest(canonicalBytes) + if ( + artifact.payloadSize !== request.payloadSize || + artifact.payloadSha256 !== request.payloadSha256 + ) { + throw new Error( + 'SSH relay runtime manifest seed signing request artifact binding is inconsistent' + ) + } + return request +} + +function decodeSeed(seedBase64) { + const seed = decodeCanonicalBase64(seedBase64, 'seed') + if (seed.length !== nacl.sign.seedLength) { + throw new Error('SSH relay runtime manifest signing seed must contain exactly 32 bytes') + } + return seed +} + +export function signSshRelayRuntimeManifestRequest({ requestArtifact, seedBase64 }) { + const request = decodeSshRelayRuntimeManifestSigningRequestArtifact(requestArtifact) + const pair = nacl.sign.keyPair.fromSeed(decodeSeed(seedBase64)) + return { + keyId: sshRelayRuntimeManifestKeyId(pair.publicKey), + signature: Buffer.from(nacl.sign.detached(request.canonicalBytes, pair.secretKey)).toString( + 'base64' + ) + } +} + +export async function readSshRelayRuntimeManifestSigningRequestArtifact(path) { + const metadata = await lstat(path) + if ( + !metadata.isFile() || + metadata.isSymbolicLink() || + metadata.size <= 0 || + metadata.size > MAX_REQUEST_ARTIFACT_BYTES + ) { + throw new Error('SSH relay runtime manifest signing request must be a bounded regular file') + } + let artifact + try { + artifact = JSON.parse(await readFile(path, 'utf8')) + } catch (error) { + throw new Error(`SSH relay runtime manifest signing request is invalid JSON: ${error.message}`) + } + decodeSshRelayRuntimeManifestSigningRequestArtifact(artifact) + return artifact +} + +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 signing output parent must be a real directory') + } + 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 signing requires an exclusive absent output directory' + ) +} + +export async function writeSshRelayRuntimeManifestSigningResult({ + requestArtifact, + seedBase64, + outputDirectory +}) { + const result = signSshRelayRuntimeManifestRequest({ requestArtifact, seedBase64 }) + const output = await exclusiveOutputDirectory(outputDirectory) + let created = false + try { + await mkdir(output, { mode: 0o700 }) + created = true + // Why: the protected job may return only the authenticated key identity and detached signature. + await writeFile(joinResultPath(output), `${JSON.stringify(result)}\n`, { + flag: 'wx', + mode: 0o600 + }) + return result + } catch (error) { + if (created) { + await rm(output, { recursive: true, force: true }) + } + throw error + } +} + +function joinResultPath(output) { + return resolve(output, 'signing-result.json') +} + +export function parseSshRelayRuntimeManifestSeedSigningArguments(argv) { + const options = {} + const fields = new Map([ + ['--request', 'requestPath'], + ['--output-directory', 'outputDirectory'] + ]) + for (let index = 0; index < argv.length; index += 2) { + const field = fields.get(argv[index]) + const value = argv[index + 1] + if (!field || !value || value.startsWith('--') || options[field]) { + throw new Error(`Invalid SSH relay runtime manifest seed signing argument: ${argv[index]}`) + } + options[field] = resolve(value) + } + for (const field of fields.values()) { + if (!options[field]) { + throw new Error(`Missing SSH relay runtime manifest seed signing argument: ${field}`) + } + } + return options +} + +async function main() { + const options = parseSshRelayRuntimeManifestSeedSigningArguments(process.argv.slice(2)) + const requestArtifact = await readSshRelayRuntimeManifestSigningRequestArtifact( + options.requestPath + ) + const result = await writeSshRelayRuntimeManifestSigningResult({ + requestArtifact, + seedBase64: process.env.SSH_RELAY_RUNTIME_MANIFEST_SEED, + outputDirectory: options.outputDirectory + }) + process.stdout.write(`${JSON.stringify({ keyId: result.keyId })}\n`) +} + +if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) { + main().catch((error) => { + process.stderr.write( + `SSH relay runtime manifest seed signing failed: ${error.stack ?? error}\n` + ) + process.exitCode = 1 + }) +} + +export const SSH_RELAY_RUNTIME_MANIFEST_SEED_SIGNING_LIMITS = Object.freeze({ + maximumRequestArtifactBytes: MAX_REQUEST_ARTIFACT_BYTES +}) diff --git a/config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs b/config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs new file mode 100644 index 00000000000..b683554de18 --- /dev/null +++ b/config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs @@ -0,0 +1,152 @@ +import { mkdtemp, readFile, rm, 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 { assembleCanonicalSshRelayRuntimeManifest } from './ssh-relay-runtime-manifest-assembly.mjs' +import { + createSshRelayRuntimeManifestSigningRequest, + finalizeSshRelayRuntimeManifestSigningHandoff, + sshRelayRuntimeManifestKeyId +} from './ssh-relay-runtime-manifest-signing-handoff.mjs' +import { + decodeSshRelayRuntimeManifestSigningRequestArtifact, + encodeSshRelayRuntimeManifestSigningRequestArtifact, + signSshRelayRuntimeManifestRequest, + writeSshRelayRuntimeManifestSigningResult +} from './ssh-relay-runtime-manifest-seed-signing.mjs' + +const temporaryDirectories = [] +const seed = Uint8Array.from({ length: 32 }, (_, index) => index) +const seedBase64 = Buffer.from(seed).toString('base64') +const keyPair = nacl.sign.keyPair.fromSeed(seed) + +function request() { + const { signatures: _signatures, ...unsigned } = createSshRelayArtifactTestManifest() + const canonical = assembleCanonicalSshRelayRuntimeManifest(unsigned).canonicalBytes + return createSshRelayRuntimeManifestSigningRequest(canonical) +} + +afterEach(async () => { + await Promise.all( + temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true })) + ) +}) + +describe('SSH relay runtime protected manifest seed signing', () => { + it('round-trips a bounded canonical request and returns only key ID plus signature', () => { + const source = request() + const artifact = encodeSshRelayRuntimeManifestSigningRequestArtifact(source) + const decoded = decodeSshRelayRuntimeManifestSigningRequestArtifact(artifact) + const result = signSshRelayRuntimeManifestRequest({ requestArtifact: artifact, seedBase64 }) + + expect(decoded).toEqual(source) + expect(Object.keys(artifact).sort()).toEqual([ + 'algorithm', + 'payloadBase64', + 'payloadSha256', + 'payloadSize', + 'schemaVersion' + ]) + expect(result).toEqual({ + keyId: sshRelayRuntimeManifestKeyId(keyPair.publicKey), + signature: Buffer.from(nacl.sign.detached(source.canonicalBytes, keyPair.secretKey)).toString( + 'base64' + ) + }) + expect( + finalizeSshRelayRuntimeManifestSigningHandoff({ + request: decoded, + signingResults: [result], + acceptedKeys: [{ keyId: result.keyId, publicKey: keyPair.publicKey }] + }).manifest.signatures + ).toEqual([{ algorithm: 'ed25519-v1', ...result }]) + }) + + it.each([ + ['', /seed/i], + ['not-base64', /seed|base64/i], + [`${seedBase64}\n`, /seed|base64/i], + [Buffer.alloc(31).toString('base64'), /32 bytes|seed/i], + [Buffer.alloc(33).toString('base64'), /32 bytes|seed/i] + ])('rejects a malformed or non-canonical seed without a result', (candidate, message) => { + expect(() => + signSshRelayRuntimeManifestRequest({ + requestArtifact: encodeSshRelayRuntimeManifestSigningRequestArtifact(request()), + seedBase64: candidate + }) + ).toThrow(message) + }) + + it('rejects request field, size, hash, canonical-byte, and base64 drift', () => { + const valid = encodeSshRelayRuntimeManifestSigningRequestArtifact(request()) + const cases = [ + { ...valid, extra: true }, + { ...valid, algorithm: 'rsa-v1' }, + { ...valid, payloadSize: valid.payloadSize + 1 }, + { ...valid, payloadSha256: `sha256:${'f'.repeat(64)}` }, + { ...valid, payloadBase64: `${valid.payloadBase64}\n` }, + { ...valid, payloadBase64: Buffer.from('{}').toString('base64'), payloadSize: 2 } + ] + for (const artifact of cases) { + expect(() => + signSshRelayRuntimeManifestRequest({ requestArtifact: artifact, seedBase64 }) + ).toThrow() + } + }) + + it('writes only an exclusive bounded result and removes partial output on failure', async () => { + const root = await mkdtemp(join(tmpdir(), 'orca-runtime-manifest-seed-signing-')) + temporaryDirectories.push(root) + const outputDirectory = join(root, 'result') + const result = await writeSshRelayRuntimeManifestSigningResult({ + requestArtifact: encodeSshRelayRuntimeManifestSigningRequestArtifact(request()), + seedBase64, + outputDirectory + }) + expect( + JSON.parse(await readFile(join(outputDirectory, 'signing-result.json'), 'utf8')) + ).toEqual(result) + await expect( + writeSshRelayRuntimeManifestSigningResult({ + requestArtifact: encodeSshRelayRuntimeManifestSigningRequestArtifact(request()), + seedBase64, + outputDirectory + }) + ).rejects.toThrow(/exclusive|absent/i) + + const failed = join(root, 'failed') + await expect( + writeSshRelayRuntimeManifestSigningResult({ + requestArtifact: { invalid: true }, + seedBase64, + outputDirectory: failed + }) + ).rejects.toThrow() + await expect(readFile(join(failed, 'signing-result.json'))).rejects.toMatchObject({ + code: 'ENOENT' + }) + }) + + it('rejects a symlinked request path before reading CLI input', async () => { + const root = await mkdtemp(join(tmpdir(), 'orca-runtime-manifest-seed-signing-')) + temporaryDirectories.push(root) + const target = join(root, 'request.json') + const linked = join(root, 'linked.json') + await writeFile( + target, + JSON.stringify(encodeSshRelayRuntimeManifestSigningRequestArtifact(request())) + ) + await expect( + import('node:fs/promises').then(({ symlink }) => symlink(target, linked)) + ).resolves.toBeUndefined() + const { readSshRelayRuntimeManifestSigningRequestArtifact } = + await import('./ssh-relay-runtime-manifest-seed-signing.mjs') + await expect(readSshRelayRuntimeManifestSigningRequestArtifact(linked)).rejects.toThrow( + /regular file|symlink/i + ) + }) +}) 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 68edae4cb89..8d895aaeafc 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 @@ -240,6 +240,10 @@ complete a box. hash-bound descriptors/receipts. The active slice is the callable, disconnected aggregate and protected Ed25519-signing workflow contract. The environment/seed are not provisioned, so live 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. - [ ] 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 351efdd6526..50e1eb39676 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 passes. PR Checks attempt 1 exposed one unrelated terminal-routing assertion after 29,944 tests passed; the focused test passes locally and exact-head attempt 2 passes all 29,945 tests, unpacked build, and packaged CLI smoke under E-M4-LINUX-FINALIZATION-PR-CHECKS-RED-001 and E-M4-LINUX-FINALIZATION-PR-CHECKS-001. 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** — 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.
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) @@ -10704,6 +10704,78 @@ through command-finished foreground confirmation` receives `csi-u` instead of `a - Follow-up: checkpoint the Linux finalization evidence and start the disconnected aggregate and protected manifest-signing workflow RED. +### E-M4-PROTECTED-MANIFEST-WORKFLOW-LOCAL-RED-001 — Callable protected signing workflow is absent + +- Date: 2026-07-15 +- Owner: Codex implementation owner +- Source: uncommitted purpose-named test atop pushed head + `e5dfffaaa2df55b221c5ee30699ff8fe814b91aa`; draft PR #8741. +- Runner/remote/network: local macOS 26.2 arm64, Node v26.0.0 and pnpm 10.24.0. Static repository + workflow-source read only; no Actions runner, environment approval, secret, signer, artifact + download, release, desktop consumer, SSH host, or enabled tuple. +- Command: + `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-manifest-signing-workflow.test.mjs`. +- Result: expected FAIL, 1 file / 1 test in 182 ms Vitest / 0.89 seconds wall, + 131,743,744-byte maximum RSS, 95,966,656-byte peak footprint, zero swaps. The only failure is + `ENOENT` for `.github/workflows/ssh-relay-runtime-manifest-signing.yml`. +- Seed-signer prerequisite RED: + `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs` + — expected FAIL before collection because + `config/scripts/ssh-relay-runtime-manifest-seed-signing.mjs` is absent; 196 ms Vitest / 0.90 + seconds wall, 132,022,272-byte maximum RSS, 96,228,776-byte peak footprint, zero swaps. The test + defines canonical request serialization, exact 32-byte base64 seed handling, signature-only + output, request/seed drift rejection, exclusive output cleanup, and symlink rejection. +- Oracle proved: no callable workflow currently enforces the required three-stage boundary: exact + six-artifact credential-free preparation; one protected `relay-runtime-manifest-signing` job with + the sole seed reference and signature-only output; then credential-free reconstruction and final + verification. The RED also requires SHA-pinned actions, strict timeouts, no wildcard artifact + merge, read-only permissions, and complete release/desktop/rehearsal disconnection. +- Does not prove: implementation, signer seed validation, file-based prepare/finalize behavior, + Actions execution, environment protection, real signatures, publication, desktop embedding, SSH + behavior, or an enabled tuple. +- Follow-up: implement the seed signer and exclusive filesystem prepare/finalize commands with + hostile-input tests, then add the callable workflow and make this contract green without + connecting any consumer. + +### E-M4-PROTECTED-MANIFEST-SEED-LOCAL-001 — Protected seed signer passes locally + +- Date: 2026-07-15 +- Owner: Codex implementation owner +- Source: local implementation atop pushed head `e5dfffaaa2df55b221c5ee30699ff8fe814b91aa`; + draft PR #8741. +- Runner/remote/network: local macOS 26.2 arm64, Node v26.0.0 and pnpm 10.24.0. Test-only in-memory + Ed25519 seeds and local temporary files; no GitHub Environment, Actions secret, release, desktop + consumer, publication, SSH host, or enabled tuple. +- Focused command: + `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-manifest-seed-signing.test.mjs config/scripts/ssh-relay-runtime-manifest-signing-handoff.test.mjs config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs` + — PASS, 3 files / 20 tests in 638 ms Vitest / 1.30 seconds wall, 188,612,608-byte maximum RSS, + 96,163,336-byte peak footprint, zero swaps. +- Broad release command excluding the deliberately still-red workflow-source test: + `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 $(rg --files config/scripts | rg '/ssh-relay.*\\.test\\.mjs$' | rg -v 'ssh-relay-runtime-manifest-signing-workflow\\.test\\.mjs$')` + — PASS, 49 files / 255 tests in 8.54 seconds Vitest / 9.46 seconds wall, + 188,858,368-byte maximum RSS, 96,523,568-byte peak footprint, zero swaps. The unfiltered 50-file + run passes the same 49 files / 255 tests and fails only the recorded missing-workflow RED. +- Desktop parity: + `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 src/main/ssh/ssh-relay-artifact-schema.test.ts src/main/ssh/ssh-relay-manifest-signature.test.ts src/main/ssh/ssh-relay-release-asset.test.ts` + — PASS, 3 files / 48 tests in 493 ms Vitest / 1.12 seconds wall, 131,858,432-byte maximum RSS, + 96,032,120-byte peak footprint, zero swaps. +- Static results: `node --check`, focused `oxlint`, and focused `oxfmt --check` pass. `pnpm run +typecheck` passes in 1.88 seconds wall. Full `pnpm run lint` passes in 9.26 seconds wall with all 41 + reliability gates, the 355-entry max-lines ratchet, bundled-skill, localization catalog/parity, + and localization coverage gates; all 26 warnings are in untouched existing files. +- Oracle proved: request artifacts contain only the exact canonical payload plus algorithm/hash/size + bindings; malformed fields, noncanonical base64, payload drift, and noncanonical manifest bytes + fail. Only a canonical base64 32-byte seed is accepted. Output contains exactly key ID plus the + detached 64-byte signature, is created in an exclusive directory, and is removed on failure. + Symlinked request files are rejected. Both POSIX and Windows native artifact contract families now + syntax-check and run the purpose suite without exposing a secret. +- Does not prove: a real GitHub Environment or seed, reviewer approval, the filesystem aggregate + prepare/finalize boundary, the callable workflow, Actions execution, publication, desktop + embedding, SSH behavior, or an enabled tuple. +- Follow-up: implement the exclusive six-artifact filesystem prepare/finalize command, including + receipt/path/mutation/accepted-key/signature/partial-output failures, before adding the callable + workflow. + ## Accepted Gaps No product gap is accepted merely because it appears in this list. Each entry requires explicit @@ -10762,14 +10834,15 @@ The project is not complete until every applicable item below is checked with ev ## Next Required Action -Checkpoint E-M4-LINUX-FINALIZATION-CI-001 and the green PR Checks replacement. Then begin a -purpose-named RED for the callable, disconnected aggregate/immutable-manifest workflow: consume all -six exact aggregate-ready artifacts, prepare canonical bytes without credentials, isolate the -base64 32-byte seed in the tag-restricted `relay-runtime-manifest-signing` environment, return only -key ID plus signature, reconstruct and verify before emitting final output, and remove partial -output on every failure. The environment 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 +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. Cross-family Layer B targets, the protected manifest-signing environment, oldest-baseline/native-