diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml
index 86b521deead..cff5861094d 100644
--- a/.github/workflows/ssh-relay-runtime-artifacts.yml
+++ b/.github/workflows/ssh-relay-runtime-artifacts.yml
@@ -268,6 +268,7 @@ jobs:
src/main/ssh/ssh-relay-artifact-cache-entry.test.ts \
src/main/ssh/ssh-relay-artifact-cache-in-use-lease.test.ts \
src/main/ssh/ssh-relay-artifact-cache-eviction.test.ts \
+ src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts \
src/main/ssh/ssh-relay-packaged-manifest.test.ts \
src/main/ssh/ssh-relay-runtime-identity.test.ts \
config/scripts/ssh-relay-runtime-compatibility.test.mjs \
@@ -803,6 +804,7 @@ jobs:
src/main/ssh/ssh-relay-artifact-cache-entry.test.ts `
src/main/ssh/ssh-relay-artifact-cache-in-use-lease.test.ts `
src/main/ssh/ssh-relay-artifact-cache-eviction.test.ts `
+ src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts `
src/main/ssh/ssh-relay-packaged-manifest.test.ts `
src/main/ssh/ssh-relay-runtime-identity.test.ts `
config/scripts/ssh-relay-runtime-compatibility.test.mjs `
diff --git a/config/scripts/ssh-relay-runtime-workflow.test.mjs b/config/scripts/ssh-relay-runtime-workflow.test.mjs
index a23b0ae2a07..5807d274477 100644
--- a/config/scripts/ssh-relay-runtime-workflow.test.mjs
+++ b/config/scripts/ssh-relay-runtime-workflow.test.mjs
@@ -126,6 +126,7 @@ describe('SSH relay runtime artifact workflow', () => {
'ssh-relay-artifact-cache-entry.test.ts',
'ssh-relay-artifact-cache-in-use-lease.test.ts',
'ssh-relay-artifact-cache-eviction.test.ts',
+ 'ssh-relay-manifest-accepted-keys.test.ts',
'ssh-relay-packaged-manifest.test.ts',
'ssh-relay-runtime-identity.test.ts'
]) {
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 a43ea2973c0..ad6c2ad4e1e 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
@@ -9,9 +9,12 @@ keeps commands, hashes, runner identities, timings, and failure details.
A checked box means the work has evidence in the detailed ledger. Design approval alone does not
complete a box.
-Active checkpoint: **Milestone 5 / Work Package 4 desktop resolver/cache — disconnected
-official-build manifest loader proven on all six native clients; accepted-key/resource-input audit
-next, 2026-07-15, Codex implementation owner.** `E-M5-PACKAGED-MANIFEST-LOCAL-001` passes 6/6
+Active checkpoint: **Milestone 5 / Work Package 4 desktop resolver/cache — disconnected desktop
+accepted-public-key parser locally proven and wired; isolated commit/all-six native proof next,
+2026-07-15, Codex implementation owner.** `E-M5-ACCEPTED-KEYS-AUDIT-001` and its RED precede
+`E-M5-ACCEPTED-KEYS-LOCAL-001`: 4/4 focused, 19/19 trust, 304/304 non-full-size SSH relay, 12/12
+workflow/aggregate, typecheck, and lint gates pass. No production public key file or secret exists,
+and no resource or consumer is connected. The prior `E-M5-PACKAGED-MANIFEST-LOCAL-001` passes 6/6
purpose-named tests, 300/300 non-full-size SSH relay tests, 279/279 release-script tests, typecheck,
lint, format, max-lines, and diff gates. `E-M5-PACKAGED-MANIFEST-CI-WIRING-LOCAL-001` pins the suite
into both native job families; exact-head run
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 5b551679a0b..4fd3f1e74fc 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
@@ -8,7 +8,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 5 / Work Package 4 desktop resolver/cache — **In progress — 2026-07-15, Codex implementation owner: all-six native packaged-manifest source gate closed; audit the next disconnected accepted-key/resource-embedding prerequisite**. `E-M5-PACKAGED-MANIFEST-CI-001` proves the fixed-resource loader on Linux, macOS, and Windows, x64 and arm64, under Node 24.18.0. Keep production keys/secrets, signed embedded bytes, Electron/cache-root/downloader/SSH/settings/tuples/publication/default behavior disconnected while selecting the next purpose-named RED.
+Current phase: Milestone 5 / Work Package 4 desktop resolver/cache — **In progress — 2026-07-15, Codex implementation owner: checkpoint and obtain all-six native evidence for the locally proven disconnected accepted-public-key parser**. `E-M5-ACCEPTED-KEYS-LOCAL-001` proves strict protected-aggregate document parity and both native workflow command lists locally. Keep the production key file/seed/environment, signed embedded bytes, builder/resource/consumer/cache/downloader/SSH/settings/tuples/publication/default behavior absent until exact-head native proof passes.
Session checkpoint: **In progress — 2026-07-15, Codex implementation owner** — user authority covers end-to-end PR-contained implementation, commits, pushes, CI, rehearsals, and release writes; merging to `main` remains explicitly prohibited. Commit `b79bcbd04` passes all six native source/full-size cache jobs, PR Checks, and Golden E2E. The artifact workflow is red only for the retained hosted Windows arm64 build-26200 versus required-26100 floor rejection after its primary proof. No Electron, downloader, SSH, setting, tuple, publication, or default-path consumer exists. Legacy remains the production default.
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)
@@ -13933,6 +13933,81 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`.
and immutable resource-input contract. External protected-environment/secret provisioning remains
a separate repository-administrator gate and must not be inferred from source tests.
+### E-M5-ACCEPTED-KEYS-AUDIT-001 — Desktop accepted-public-key document parity
+
+- Date: 2026-07-15
+- Owner: Codex implementation owner
+- Source: exact pushed evidence head `6358fb466b2b2099f4c9add9b8e2f772664ec3ea`.
+- Audit: complete reads of the desktop signature/loader modules and tests, protected manifest
+ workflow, aggregate command/signing handoff and tests, Electron builder config/tests, package
+ release scripts, and Linux/Windows/macOS release workflow build/package boundaries; targeted `rg`
+ over accepted keys, public-key encoding, `extraResources`, manifest signing, desktop build inputs,
+ and release packaging.
+- Findings:
+ - the protected aggregate already defines the authoritative source format: exact
+ `{schemaVersion, keys}` fields, schema version 1, one to four keys, exact
+ `{keyId, publicKeyBase64}` entries, canonical base64 for exactly 32 Ed25519 public-key bytes,
+ derived SHA-256 key IDs, uniqueness, key-ID sorting, and a SHA-256 fingerprint over canonical
+ JSON;
+ - the protected workflow deliberately references an absent
+ `config/ssh-relay-runtime-manifest-accepted-keys.json` and tests ENOENT so no placeholder can be
+ mistaken for a reviewed trust root. Release-cut and macOS release workflows remain disconnected;
+ - the desktop signature verifier accepts decoded key bytes but has no strict parser for the
+ aggregate document. Adding that parser with deterministic test keys is the smallest useful
+ PR-contained parity boundary before production key review or immutable resource embedding;
+ - accepted keys must ultimately be compile-time desktop inputs. Loading a mutable adjacent key
+ file beside the manifest would let the trust root move with untrusted bytes and is not allowed;
+ - builder mapping, aggregate-to-desktop artifact download, tag checkout dependencies, byte-identical
+ packaged extraction, real public key review, and protected seed/environment provisioning remain
+ separate gates.
+- Selected package: pure desktop parser returning cloned/sorted accepted key bytes plus the canonical
+ accepted-key document fingerprint; strict hostile-input tests and release-format parity; no file
+ I/O, Electron, resource mapping, manifest, signer, secret, cache, network, SSH, mode, or consumer.
+
+### E-M5-ACCEPTED-KEYS-LOCAL-RED-001 — Desktop accepted-key parser is absent
+
+- Date: 2026-07-15
+- Owner: Codex implementation owner
+- Source: purpose-named `src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts` atop exact pushed
+ evidence head `6358fb466b2b2099f4c9add9b8e2f772664ec3ea`; production parser deliberately absent.
+- Exact command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
+--maxWorkers=1 src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts`.
+- Environment: macOS 26.2 arm64 build 25C56, Node v26.0.0, pnpm 10.24.0, Vitest 4.1.5.
+- Result: required RED; one failed suite / zero collected tests in 174ms Vitest / 1.16s wall,
+ 131,989,504-byte maximum RSS, 96,179,600-byte peak memory footprint, and zero swaps. Import fails
+ because `./ssh-relay-manifest-accepted-keys` does not exist.
+- Contracts encoded before implementation: canonical sort/fingerprint, cloned key bytes, frozen
+ container/records, one-to-four bound, exact fields/schema, canonical base64/32-byte keys, derived
+ identity, duplicate rejection, and input-order independence.
+- Consumer-disconnection oracle: only the purpose-named test imports the absent module; fixtures use
+ deterministic test seeds. No production key file/seed/environment, resource mapping, Electron,
+ cache, network, SSH, setting, tuple, publication, or default call site is added.
+
+### E-M5-ACCEPTED-KEYS-LOCAL-001 — Desktop accepted-key parser and native wiring
+
+- Date: 2026-07-15
+- Owner: Codex implementation owner
+- Source: working package atop exact pushed evidence head
+ `6358fb466b2b2099f4c9add9b8e2f772664ec3ea`; implementation/test are
+ `src/main/ssh/ssh-relay-manifest-accepted-keys.ts` and its purpose-named test.
+- Focused command/result: `/usr/bin/time -l pnpm exec vitest run --config
+config/vitest.config.ts --maxWorkers=1 src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts` passes
+ 1 file / 4 tests in 301ms Vitest / 1.23s wall, 131,874,816-byte maximum RSS,
+ 96,048,504-byte peak memory footprint, and zero swaps.
+- Proven: exact schema/fields; one-to-four bound; canonical 32-byte base64; derived SHA-256 IDs;
+ duplicate rejection; input-order-independent sorting; cloned bytes; frozen container/records; and
+ the exact canonical JSON fingerprint used by the protected aggregate.
+- Regression commands/results: accepted-keys/signature/packaged-loader trust suites pass 19/19;
+ complete `src/main/ssh/ssh-relay-*.test.ts` passes 23 files / 304 tests with three declared
+ full-size skips; workflow plus aggregate-command suites pass 12/12; full `pnpm typecheck` and
+ `pnpm lint` pass, including 41 reliability gates and no new max-lines bypass.
+- Native wiring: the purpose-named suite is present once in each POSIX/PowerShell command list; the
+ workflow oracle requires exactly two occurrences. Exact-head all-six Node 24 execution remains
+ required before this source gate closes.
+- Consumer-disconnection oracle: repository consumers remain test-only. The protected workflow still
+ requires the production accepted-key file to be absent; no key/seed/environment, resource bytes,
+ builder mapping, Electron/cache/network/SSH/mode/tuple/publication/default caller is added.
+
## Accepted Gaps
No product gap is accepted merely because it appears in this list. Each entry requires explicit
diff --git a/src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts b/src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts
new file mode 100644
index 00000000000..49f69eaf442
--- /dev/null
+++ b/src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts
@@ -0,0 +1,88 @@
+import { createHash } from 'node:crypto'
+
+import nacl from 'tweetnacl'
+import { describe, expect, it } from 'vitest'
+
+import { parseSshRelayManifestAcceptedKeyDocument } from './ssh-relay-manifest-accepted-keys'
+import { sshRelayManifestKeyId } from './ssh-relay-manifest-signature'
+
+const first = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index))
+const second = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => 31 - index))
+
+function record(publicKey: Uint8Array) {
+ return {
+ keyId: sshRelayManifestKeyId(publicKey),
+ publicKeyBase64: Buffer.from(publicKey).toString('base64')
+ }
+}
+
+function document(keys = [record(first.publicKey)]) {
+ return { schemaVersion: 1, keys }
+}
+
+describe('SSH relay manifest accepted keys', () => {
+ it('parses, clones, sorts, freezes, and fingerprints the canonical release document', () => {
+ const input = document([record(second.publicKey), record(first.publicKey)])
+ const parsed = parseSshRelayManifestAcceptedKeyDocument(input)
+ const sortedRecords = [...input.keys].sort((left, right) =>
+ left.keyId < right.keyId ? -1 : left.keyId > right.keyId ? 1 : 0
+ )
+ const canonicalBytes = Buffer.from(
+ JSON.stringify({ schemaVersion: 1, keys: sortedRecords }),
+ 'utf8'
+ )
+
+ expect(parsed.acceptedKeys.map(({ keyId }) => keyId)).toEqual(
+ sortedRecords.map(({ keyId }) => keyId)
+ )
+ expect(parsed.acceptedKeys[0].publicKey).not.toBe(first.publicKey)
+ expect(parsed.sha256).toBe(
+ `sha256:${createHash('sha256').update(canonicalBytes).digest('hex')}`
+ )
+ expect(Object.isFrozen(parsed)).toBe(true)
+ expect(Object.isFrozen(parsed.acceptedKeys)).toBe(true)
+ expect(parsed.acceptedKeys.every(Object.isFrozen)).toBe(true)
+ })
+
+ it('accepts one to four unique keys independent of input order', () => {
+ const records = [record(first.publicKey), record(second.publicKey)]
+ const forward = parseSshRelayManifestAcceptedKeyDocument(document(records))
+ const reverse = parseSshRelayManifestAcceptedKeyDocument(document(records.toReversed()))
+
+ expect(forward.sha256).toBe(reverse.sha256)
+ expect(forward.acceptedKeys.map(({ keyId }) => keyId)).toEqual(
+ reverse.acceptedKeys.map(({ keyId }) => keyId)
+ )
+ })
+
+ it('rejects malformed document shape and key counts', () => {
+ for (const input of [
+ null,
+ [],
+ {},
+ { schemaVersion: 2, keys: [record(first.publicKey)] },
+ { schemaVersion: 1, keys: [], extra: true },
+ { schemaVersion: 1, keys: [] },
+ { schemaVersion: 1, keys: Array.from({ length: 5 }, () => record(first.publicKey)) }
+ ]) {
+ expect(() => parseSshRelayManifestAcceptedKeyDocument(input)).toThrow(/accepted|key|field/i)
+ }
+ })
+
+ it('rejects extra fields, non-canonical base64, wrong sizes, identity drift, and duplicates', () => {
+ const valid = record(first.publicKey)
+ for (const key of [
+ { ...valid, extra: true },
+ { ...valid, publicKeyBase64: `${valid.publicKeyBase64}\n` },
+ { ...valid, publicKeyBase64: Buffer.alloc(31).toString('base64') },
+ { ...valid, keyId: `sha256:${'0'.repeat(64)}` }
+ ]) {
+ expect(() =>
+ parseSshRelayManifestAcceptedKeyDocument({ schemaVersion: 1, keys: [key] })
+ ).toThrow(/accepted|key|base64|field|32/i)
+ }
+ expect(() =>
+ parseSshRelayManifestAcceptedKeyDocument(document([valid, structuredClone(valid)]))
+ ).toThrow(/duplicate/i)
+ })
+})
diff --git a/src/main/ssh/ssh-relay-manifest-accepted-keys.ts b/src/main/ssh/ssh-relay-manifest-accepted-keys.ts
new file mode 100644
index 00000000000..f6e5b82ae0c
--- /dev/null
+++ b/src/main/ssh/ssh-relay-manifest-accepted-keys.ts
@@ -0,0 +1,99 @@
+import { createHash } from 'node:crypto'
+
+import {
+ sshRelayManifestKeyId,
+ type SshRelayManifestAcceptedKey
+} from './ssh-relay-manifest-signature'
+import type { SshRelayDigest } from './ssh-relay-runtime-identity'
+
+const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/u
+const MAXIMUM_KEYS = 4
+
+type AcceptedKeyRecord = {
+ keyId: string
+ publicKeyBase64: string
+}
+
+function assertObject(value: unknown, label: string): asserts value is Record {
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
+ throw new Error(`SSH relay manifest ${label} must be an object`)
+ }
+}
+
+function assertExactFields(value: Record, fields: string[], label: string): void {
+ const actual = Object.keys(value).sort()
+ const expected = [...fields].sort()
+ if (
+ actual.length !== expected.length ||
+ actual.some((field, index) => field !== expected[index])
+ ) {
+ throw new Error(`SSH relay manifest ${label} has unexpected or missing fields`)
+ }
+}
+
+function decodePublicKey(value: unknown): { bytes: Buffer; base64: string } {
+ if (typeof value !== 'string' || !BASE64.test(value)) {
+ throw new Error('SSH relay manifest accepted public key is not canonical base64')
+ }
+ const bytes = Buffer.from(value, 'base64')
+ if (bytes.byteLength !== 32 || bytes.toString('base64') !== value) {
+ throw new Error('SSH relay manifest accepted public key must contain exactly 32 bytes')
+ }
+ return { bytes, base64: value }
+}
+
+function sha256(bytes: Uint8Array): SshRelayDigest {
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
+}
+
+export function parseSshRelayManifestAcceptedKeyDocument(input: unknown): Readonly<{
+ acceptedKeys: readonly Readonly[]
+ sha256: SshRelayDigest
+}> {
+ assertObject(input, 'accepted-key document')
+ assertExactFields(input, ['keys', 'schemaVersion'], 'accepted-key document')
+ if (
+ input.schemaVersion !== 1 ||
+ !Array.isArray(input.keys) ||
+ input.keys.length === 0 ||
+ input.keys.length > MAXIMUM_KEYS
+ ) {
+ throw new Error('SSH relay manifest accepted keys must be a bounded schema-version-1 array')
+ }
+
+ const seen = new Set()
+ const parsed = input.keys
+ .map((value, index) => {
+ assertObject(value, `accepted key ${index}`)
+ assertExactFields(value, ['keyId', 'publicKeyBase64'], `accepted key ${index}`)
+ const decoded = decodePublicKey(value.publicKeyBase64)
+ const publicKey = decoded.bytes
+ const keyId = sshRelayManifestKeyId(publicKey)
+ if (value.keyId !== keyId) {
+ throw new Error('SSH relay manifest accepted key ID disagrees with its public key bytes')
+ }
+ if (seen.has(keyId)) {
+ throw new Error(`Duplicate accepted SSH relay manifest key: ${keyId}`)
+ }
+ seen.add(keyId)
+ return { keyId, publicKeyBase64: decoded.base64, publicKey }
+ })
+ .sort((left, right) => (left.keyId < right.keyId ? -1 : left.keyId > right.keyId ? 1 : 0))
+
+ // Why: the protected aggregate fingerprints this exact sorted projection, so desktop build
+ // evidence can prove it embedded the reviewed public-key set without trusting input order.
+ const canonicalRecords: AcceptedKeyRecord[] = parsed.map(({ keyId, publicKeyBase64 }) => ({
+ keyId,
+ publicKeyBase64
+ }))
+ const canonicalBytes = Buffer.from(
+ JSON.stringify({ schemaVersion: 1, keys: canonicalRecords }),
+ 'utf8'
+ )
+ const acceptedKeys = Object.freeze(
+ parsed.map(({ keyId, publicKey }) => Object.freeze({ keyId, publicKey }))
+ )
+ return Object.freeze({ acceptedKeys, sha256: sha256(canonicalBytes) })
+}
+
+export const SSH_RELAY_MANIFEST_ACCEPTED_KEY_LIMITS = Object.freeze({ maximumKeys: MAXIMUM_KEYS })