build(ssh): add relay release asset gate

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-15 12:46:20 -04:00
co-authored by Orca
parent 88a77eee91
commit 5460563ff4
6 changed files with 596 additions and 15 deletions
@@ -220,6 +220,8 @@ jobs:
node --check config/scripts/ssh-relay-runtime-draft-readback.test.mjs
node --check config/scripts/ssh-relay-runtime-draft-upload.mjs
node --check config/scripts/ssh-relay-runtime-draft-upload.test.mjs
node --check config/scripts/ssh-relay-runtime-release-assets.mjs
node --check config/scripts/ssh-relay-runtime-release-assets.test.mjs
node --check config/scripts/ssh-relay-runtime-manifest-validation.mjs
node --check config/scripts/ssh-relay-runtime-manifest-assembly.mjs
node --check config/scripts/ssh-relay-runtime-manifest-assembly.test.mjs
@@ -276,6 +278,7 @@ jobs:
config/scripts/ssh-relay-runtime-aggregate-input.test.mjs \
config/scripts/ssh-relay-runtime-draft-readback.test.mjs \
config/scripts/ssh-relay-runtime-draft-upload.test.mjs \
config/scripts/ssh-relay-runtime-release-assets.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 \
@@ -641,6 +644,10 @@ jobs:
if ($LASTEXITCODE -ne 0) { throw 'runtime draft upload syntax check failed' }
node --check config/scripts/ssh-relay-runtime-draft-upload.test.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime draft upload test syntax check failed' }
node --check config/scripts/ssh-relay-runtime-release-assets.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime release assets syntax check failed' }
node --check config/scripts/ssh-relay-runtime-release-assets.test.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime release assets test syntax check failed' }
node --check config/scripts/ssh-relay-runtime-manifest-validation.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime manifest validation syntax check failed' }
node --check config/scripts/ssh-relay-runtime-manifest-assembly.mjs
@@ -726,6 +733,7 @@ jobs:
config/scripts/ssh-relay-runtime-aggregate-input.test.mjs `
config/scripts/ssh-relay-runtime-draft-readback.test.mjs `
config/scripts/ssh-relay-runtime-draft-upload.test.mjs `
config/scripts/ssh-relay-runtime-release-assets.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 `
@@ -0,0 +1,289 @@
import { parseSshRelayRuntimeUnsignedManifest } from './ssh-relay-runtime-manifest-validation.mjs'
const ASSET_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,239}$/u
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u
const SIGNATURE_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u
const MANAGED_ASSET_PREFIX = 'orca-ssh-relay-runtime-'
const MANIFEST_NAME = 'orca-ssh-relay-runtime-manifest.json'
const SIGNATURE_NAME = 'orca-ssh-relay-runtime-manifest.sig'
const MAX_ASSET_BYTES = 100 * 1024 * 1024
const MAX_TOTAL_BYTES = 1024 * 1024 * 1024
const MAX_MANIFEST_BYTES = 1024 * 1024
const MAX_SIGNATURE_BYTES = 4096
const MAX_MANAGED_ASSETS = 26
const MAX_RELEASE_ASSETS = 1000
const MAX_SIGNATURES = 4
function assertObject(value, label) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`SSH relay runtime release assets ${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 release assets ${label} has unexpected or missing fields`)
}
}
function parseReleaseTag(tag) {
if (typeof tag !== 'string') {
throw new Error('SSH relay runtime release assets tag is invalid')
}
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, channel, version: match[1] }
}
}
throw new Error('SSH relay runtime release assets tag is invalid')
}
function normalizeSignatures(signatures) {
if (!Array.isArray(signatures) || signatures.length === 0 || signatures.length > MAX_SIGNATURES) {
throw new Error('SSH relay runtime release assets verified manifest signatures are invalid')
}
const keyIds = new Set()
for (const [index, signature] of signatures.entries()) {
assertExactFields(signature, ['algorithm', 'keyId', 'signature'], `manifest signature ${index}`)
if (signature.algorithm !== 'ed25519-v1' || !DIGEST_PATTERN.test(signature.keyId)) {
throw new Error('SSH relay runtime release assets verified manifest signature is invalid')
}
if (typeof signature.signature !== 'string' || !SIGNATURE_PATTERN.test(signature.signature)) {
throw new Error('SSH relay runtime release assets verified manifest signature is invalid')
}
const signatureBytes = Buffer.from(signature.signature, 'base64')
if (
signatureBytes.length !== 64 ||
signatureBytes.toString('base64') !== signature.signature ||
keyIds.has(signature.keyId)
) {
throw new Error('SSH relay runtime release assets verified manifest signature is invalid')
}
keyIds.add(signature.keyId)
}
return [...keyIds].sort()
}
function normalizeAssetDescriptor(asset, expectedName, label, maximum = MAX_ASSET_BYTES) {
assertExactFields(asset, ['name', 'sha256', 'size'], label)
if (asset.name !== expectedName || !ASSET_NAME_PATTERN.test(asset.name)) {
throw new Error(`SSH relay runtime release assets ${label} has an invalid name`)
}
if (!DIGEST_PATTERN.test(asset.sha256)) {
throw new Error(`SSH relay runtime release assets ${label} has an invalid SHA-256`)
}
if (!Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > maximum) {
throw new Error(`SSH relay runtime release assets ${label} has an invalid size`)
}
return { ...asset }
}
function normalizeSignatureAsset(asset, manifestAsset, manifestKeyIds) {
assertExactFields(
asset,
['keyIds', 'manifestSha256', 'name', 'sha256', 'size'],
'signature asset'
)
const descriptor = normalizeAssetDescriptor(
{ name: asset.name, sha256: asset.sha256, size: asset.size },
SIGNATURE_NAME,
'signature asset descriptor',
MAX_SIGNATURE_BYTES
)
if (
!Array.isArray(asset.keyIds) ||
asset.keyIds.length === 0 ||
asset.keyIds.length > MAX_SIGNATURES ||
asset.keyIds.some((keyId) => typeof keyId !== 'string' || !DIGEST_PATTERN.test(keyId))
) {
throw new Error('SSH relay runtime release assets signature keys are invalid')
}
if (!DIGEST_PATTERN.test(asset.manifestSha256)) {
throw new Error('SSH relay runtime release assets signature manifest identity is invalid')
}
if (asset.manifestSha256 !== manifestAsset.sha256) {
throw new Error(
'SSH relay runtime release assets signature manifest identity disagrees with the manifest'
)
}
const keyIds = [...new Set(asset.keyIds)].sort()
if (
keyIds.length !== asset.keyIds.length ||
JSON.stringify(keyIds) !== JSON.stringify(manifestKeyIds)
) {
// Why: the detached encoding stays separately gated, but it cannot claim different signers.
throw new Error('SSH relay runtime release assets signature keys disagree with the manifest')
}
return descriptor
}
function normalizeVerifiedManifest(envelope) {
assertExactFields(
envelope,
['manifest', 'manifestAsset', 'signatureAsset'],
'verified manifest envelope'
)
assertObject(envelope.manifest, 'verified manifest')
const { signatures, ...unsignedManifest } = envelope.manifest
const manifest = parseSshRelayRuntimeUnsignedManifest(unsignedManifest)
const manifestKeyIds = normalizeSignatures(signatures)
const manifestAsset = normalizeAssetDescriptor(
envelope.manifestAsset,
MANIFEST_NAME,
'manifest asset',
MAX_MANIFEST_BYTES
)
const signatureAsset = normalizeSignatureAsset(
envelope.signatureAsset,
manifestAsset,
manifestKeyIds
)
return { manifest, manifestAsset, signatureAsset }
}
function coveredManifestAssets(manifest) {
const assets = []
for (const tuple of manifest.tuples) {
assets.push(
normalizeAssetDescriptor(
{
name: tuple.archive.name,
sha256: tuple.archive.sha256,
size: tuple.archive.size
},
tuple.archive.name,
`${tuple.tupleId} archive`
),
normalizeAssetDescriptor(
tuple.metadataAssets.sbom,
tuple.metadataAssets.sbom.name,
`${tuple.tupleId} SBOM`
),
normalizeAssetDescriptor(
tuple.metadataAssets.provenance,
tuple.metadataAssets.provenance.name,
`${tuple.tupleId} provenance`
)
)
}
return assets
}
function exactManagedAssets(verified) {
const assets = [
...coveredManifestAssets(verified.manifest),
verified.manifestAsset,
verified.signatureAsset
]
if (assets.length === 0 || assets.length > MAX_MANAGED_ASSETS) {
throw new Error('SSH relay runtime release assets coverage must be a bounded non-empty set')
}
const names = new Set()
let totalBytes = 0
for (const asset of assets) {
if (!asset.name.startsWith(MANAGED_ASSET_PREFIX) || names.has(asset.name)) {
throw new Error(
`SSH relay runtime release assets has duplicate or invalid coverage: ${asset.name}`
)
}
names.add(asset.name)
totalBytes += asset.size
}
if (!Number.isSafeInteger(totalBytes) || totalBytes > MAX_TOTAL_BYTES) {
throw new Error('SSH relay runtime release assets coverage exceeds the total size limit')
}
return assets
}
function validateBuildIdentity(manifest, expected) {
if (manifest.build.tag !== expected.tag) {
throw new Error('SSH relay runtime release assets manifest tag does not match the release')
}
if (manifest.build.channel !== expected.channel || manifest.build.version !== expected.version) {
throw new Error('SSH relay runtime release assets manifest build identity does not match')
}
}
function validateRelease(release, releaseId, expectedIdentity, expectedAssets) {
assertObject(release, 'authenticated release metadata')
if (!Number.isSafeInteger(releaseId) || releaseId <= 0 || release.id !== releaseId) {
throw new Error('SSH relay runtime release assets release ID does not match')
}
if (release.draft !== true) {
throw new Error('SSH relay runtime release assets requires an exact unpublished draft')
}
if (release.tag_name !== expectedIdentity.tag) {
throw new Error('SSH relay runtime release assets release tag does not match')
}
const expectedPrerelease = expectedIdentity.channel !== 'stable'
if (release.prerelease !== expectedPrerelease) {
throw new Error('SSH relay runtime release assets prerelease state disagrees with its channel')
}
if (!Array.isArray(release.assets) || release.assets.length > MAX_RELEASE_ASSETS) {
throw new Error('SSH relay runtime release assets metadata must contain a bounded asset array')
}
const expectedByName = new Map(expectedAssets.map((asset) => [asset.name, asset]))
const releaseByName = new Map()
for (const asset of release.assets) {
assertObject(asset, 'release asset')
if (typeof asset.name !== 'string' || !ASSET_NAME_PATTERN.test(asset.name)) {
throw new Error('SSH relay runtime release assets has an invalid release asset')
}
if (releaseByName.has(asset.name)) {
throw new Error(
`SSH relay runtime release assets has a duplicate release asset: ${asset.name}`
)
}
releaseByName.set(asset.name, asset)
if (asset.name.startsWith(MANAGED_ASSET_PREFIX) && !expectedByName.has(asset.name)) {
throw new Error(
`SSH relay runtime release assets has an unexpected managed asset: ${asset.name}`
)
}
}
for (const expected of expectedAssets) {
const actual = releaseByName.get(expected.name)
if (!actual) {
throw new Error(`SSH relay runtime release assets is missing managed asset: ${expected.name}`)
}
if (!Number.isSafeInteger(actual.id) || actual.id <= 0) {
throw new Error(`SSH relay runtime release asset ID is invalid: ${expected.name}`)
}
if (actual.state !== 'uploaded') {
throw new Error(`SSH relay runtime release asset is not uploaded: ${expected.name}`)
}
if (actual.size === 0) {
throw new Error(`SSH relay runtime release asset is empty: ${expected.name}`)
}
if (actual.size !== expected.size) {
throw new Error(`SSH relay runtime release asset size disagrees: ${expected.name}`)
}
}
}
export function verifySshRelayRuntimeReleaseAssets({ releaseId, tag, release, verifiedManifest }) {
const expectedIdentity = parseReleaseTag(tag)
const verified = normalizeVerifiedManifest(verifiedManifest)
validateBuildIdentity(verified.manifest, expectedIdentity)
const expectedAssets = exactManagedAssets(verified)
// Why: this pure boundary can consume authenticated metadata without gaining release authority.
validateRelease(release, releaseId, expectedIdentity, expectedAssets)
return {
releaseId: release.id,
tag,
channel: expectedIdentity.channel,
draft: true,
prerelease: release.prerelease,
checked: expectedAssets.map((asset) => asset.name).sort()
}
}
@@ -0,0 +1,193 @@
import { describe, expect, it, vi } from 'vitest'
import { createSshRelayArtifactTestManifest } from '../../src/main/ssh/ssh-relay-artifact-test-manifest'
import { verifySshRelayRuntimeReleaseAssets } from './ssh-relay-runtime-release-assets.mjs'
const MANIFEST_NAME = 'orca-ssh-relay-runtime-manifest.json'
const SIGNATURE_NAME = 'orca-ssh-relay-runtime-manifest.sig'
const digest = (character) => `sha256:${character.repeat(64)}`
function buildIdentity(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, channel, version: match[1] }
}
}
throw new Error(`Unsupported test tag: ${tag}`)
}
function fixture(tag = 'v1.4.140-rc.1') {
const manifest = createSshRelayArtifactTestManifest()
manifest.build = { ...manifest.build, ...buildIdentity(tag) }
const manifestAsset = { name: MANIFEST_NAME, size: 2_048, sha256: digest('e') }
const signatureAsset = {
name: SIGNATURE_NAME,
size: 256,
sha256: digest('f'),
manifestSha256: manifestAsset.sha256,
keyIds: manifest.signatures.map((signature) => signature.keyId)
}
const expectedAssets = [
manifest.tuples[0].archive,
manifest.tuples[0].metadataAssets.sbom,
manifest.tuples[0].metadataAssets.provenance,
manifestAsset,
signatureAsset
]
const release = {
id: 42,
tag_name: tag,
draft: true,
prerelease: manifest.build.channel !== 'stable',
assets: [
...expectedAssets.map((asset, index) => ({
id: index + 1,
name: asset.name,
state: 'uploaded',
size: asset.size
})),
{ id: 99, name: 'orca-windows-setup.exe', state: 'uploaded', size: 3_000 }
]
}
return {
releaseId: release.id,
tag,
release,
verifiedManifest: { manifest, manifestAsset, signatureAsset }
}
}
function managedAsset(input, name) {
return input.release.assets.find((asset) => asset.name === name)
}
describe('SSH relay runtime release required assets', () => {
it.each([
['v1.4.140', 'stable', false],
['v1.4.140-rc.2', 'rc', true],
['v1.4.140-rc.2.perf', 'perf', true]
])(
'accepts exact %s manifest coverage and authenticated draft metadata',
(tag, channel, prerelease) => {
const input = fixture(tag)
expect(verifySshRelayRuntimeReleaseAssets(input)).toEqual({
releaseId: 42,
tag,
channel,
draft: true,
prerelease,
checked: expect.arrayContaining([
input.verifiedManifest.manifest.tuples[0].archive.name,
input.verifiedManifest.manifest.tuples[0].metadataAssets.sbom.name,
input.verifiedManifest.manifest.tuples[0].metadataAssets.provenance.name,
MANIFEST_NAME,
SIGNATURE_NAME
])
})
}
)
it.each([
['missing', (input) => input.release.assets.splice(0, 1), /missing managed asset/i],
[
'extra managed',
(input) =>
input.release.assets.push({
id: 100,
name: 'orca-ssh-relay-runtime-uncovered.zip',
state: 'uploaded',
size: 1
}),
/unexpected managed asset/i
],
[
'duplicate',
(input) => input.release.assets.push(structuredClone(input.release.assets[0])),
/duplicate release asset/i
]
])('rejects a %s managed asset set', (_label, mutate, message) => {
const input = fixture()
mutate(input)
expect(() => verifySshRelayRuntimeReleaseAssets(input)).toThrow(message)
})
it.each([
['non-uploaded', (asset) => (asset.state = 'new'), /not uploaded/i],
['empty', (asset) => (asset.size = 0), /empty/i],
['wrong-size', (asset) => (asset.size += 1), /size disagrees/i]
])('rejects a %s covered asset', (_label, mutate, message) => {
const input = fixture()
mutate(managedAsset(input, MANIFEST_NAME))
expect(() => verifySshRelayRuntimeReleaseAssets(input)).toThrow(message)
})
it('rejects release, manifest, or prerelease identity crossing tags and channels', () => {
const crossRelease = fixture()
crossRelease.release.tag_name = 'v1.4.140-rc.2'
expect(() => verifySshRelayRuntimeReleaseAssets(crossRelease)).toThrow(/release tag/i)
const crossDraft = fixture()
crossDraft.release.id += 1
expect(() => verifySshRelayRuntimeReleaseAssets(crossDraft)).toThrow(/release ID/i)
const crossManifest = fixture()
Object.assign(crossManifest.verifiedManifest.manifest.build, buildIdentity('v1.4.140-rc.2'))
expect(() => verifySshRelayRuntimeReleaseAssets(crossManifest)).toThrow(/manifest tag/i)
const crossChannel = fixture()
crossChannel.verifiedManifest.manifest.build.channel = 'stable'
expect(() => verifySshRelayRuntimeReleaseAssets(crossChannel)).toThrow(/manifest.*identity/i)
const wrongPrerelease = fixture()
wrongPrerelease.release.prerelease = false
expect(() => verifySshRelayRuntimeReleaseAssets(wrongPrerelease)).toThrow(/prerelease/i)
})
it('rejects a detached signature asset that disagrees with verified manifest signatures', () => {
const input = fixture()
input.verifiedManifest.signatureAsset.keyIds = [digest('0')]
expect(() => verifySshRelayRuntimeReleaseAssets(input)).toThrow(/signature keys disagree/i)
const crossManifest = fixture()
crossManifest.verifiedManifest.signatureAsset.manifestSha256 = digest('0')
expect(() => verifySshRelayRuntimeReleaseAssets(crossManifest)).toThrow(
/signature manifest identity disagrees/i
)
})
it('rejects malformed or incomplete verified-manifest asset bindings', () => {
const malformedDigest = fixture()
malformedDigest.verifiedManifest.manifestAsset.sha256 = digest('A')
expect(() => verifySshRelayRuntimeReleaseAssets(malformedDigest)).toThrow(/sha-256/i)
const missingSignature = fixture()
missingSignature.verifiedManifest.manifest.signatures = []
expect(() => verifySshRelayRuntimeReleaseAssets(missingSignature)).toThrow(/signature/i)
const malformedSignature = fixture()
malformedSignature.verifiedManifest.manifest.signatures[0].signature = 42
expect(() => verifySshRelayRuntimeReleaseAssets(malformedSignature)).toThrow(/signature/i)
const unexpectedField = fixture()
unexpectedField.verifiedManifest.manifestAsset.path = '/tmp/manifest'
expect(() => verifySshRelayRuntimeReleaseAssets(unexpectedField)).toThrow(/fields/i)
})
it('is a disconnected metadata gate with no network request', () => {
const fetchMock = vi.fn(() => {
throw new Error('network must remain disconnected')
})
vi.stubGlobal('fetch', fetchMock)
expect(() => verifySshRelayRuntimeReleaseAssets(fixture())).not.toThrow()
expect(fetchMock).not.toHaveBeenCalled()
vi.unstubAllGlobals()
})
})
@@ -134,7 +134,8 @@ describe('SSH relay runtime artifact workflow', () => {
'draft-recovery',
'aggregate-input',
'draft-readback',
'draft-upload'
'draft-upload',
'release-assets'
]) {
const script = `ssh-relay-runtime-${moduleName}`
expect(source.split(`${script}.test.mjs`)).toHaveLength(5)
@@ -15,9 +15,11 @@ in progress, 2026-07-15, Codex implementation owner.** Exact-head run
target-native artifact jobs and the uploader's eight-test suite on every Node 24 runner under
`E-M4-DRAFT-UPLOAD-CI-001`. PR Checks and Golden E2E are green; the artifact workflow is red only for
the retained Windows ARM build-26200-versus-26100 floor mismatch after successful runtime smoke.
Next, add the disconnected relay-specific release required-asset gate for exact manifest coverage,
names, sizes, signature, upload state, and channel identity. Production keys/environment/seed,
publication, desktop/default consumers, tuple enablement, and merge to `main` remain disconnected.
The disconnected relay-specific release required-asset gate is locally green under
`E-M4-RELEASE-ASSETS-LOCAL-RED-001` and `E-M4-RELEASE-ASSETS-LOCAL-001`: exact manifest coverage,
names, sizes, signature binding, upload state, and stable/RC/perf identity. Exact-head all-six native
CI is required. Production keys/environment/seed, publication, desktop/default consumers, tuple
enablement, and merge to `main` remain disconnected.
## Safety status
@@ -261,6 +263,10 @@ publication, desktop/default consumers, tuple enablement, and merge to `main` re
`E-M4-PROTECTED-MANIFEST-WORKFLOW-LOCAL-001`, and exact-head execution passes on all six native
jobs under `E-M4-PROTECTED-MANIFEST-WORKFLOW-CI-001`. Live protected signing remains open; no
accepted production key, environment, or seed is provisioned.
- [ ] Add the relay-specific required-asset gate to the release composition. The disconnected pure
gate is locally green under `E-M4-RELEASE-ASSETS-LOCAL-RED-001` and
`E-M4-RELEASE-ASSETS-LOCAL-001`; exact-head all-six native CI is required before closing the
capability, and release/default workflow composition remains separately gated.
- [ ] 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.
A disconnected bounded upload/recovery implementation is locally green under
@@ -8,8 +8,8 @@ work; keep exact commands, runner identities, hashes, metrics, and residual gaps
Date created: 2026-07-14<br>
Last updated: 2026-07-15<br>
Current phase: Milestone 4 / Work Package 3 disconnected relay required-asset gate — **In progress — 2026-07-15, Codex implementation owner**. The bounded draft upload/recovery capability is closed locally and on all six exact-head native jobs under E-M4-DRAFT-UPLOAD-LOCAL-RED-001, E-M4-DRAFT-UPLOAD-LOCAL-001, and E-M4-DRAFT-UPLOAD-CI-001. The active package must add a disconnected relay-specific release gate that consumes only an already verified signed manifest plus authenticated draft metadata and rejects missing, extra managed, non-uploaded, empty, wrong-size, uncovered, cross-tag/channel, or signature-disagreeing assets. Keep the existing desktop required-asset gate unchanged until composition is explicitly reviewed. Release-cut, desktop builds, publication, and every tuple stay disconnected; production/default behavior is unchanged.<br>
Session checkpoint: **In progress — 2026-07-15, Codex implementation owner** — exact pushed head `b37cacecf8f1c865f09c118f274ab62302e951ad` passes all six target-native artifact jobs and the eight-test uploader suite in run 29431510990 under E-M4-DRAFT-UPLOAD-CI-001. Both Linux oldest-userland supplements and Windows x64 floor pass. Windows arm64 runtime smoke passes in 7,752.6288ms with 49,684,480-byte RSS, then its floor job retains the expected fail-closed hosted-build 26200 versus required 26100 mismatch. PR Checks 29431510764 and Golden E2E 29431510883 are green. The user authorizes commits, pushes, draft-PR updates, CI reruns, and protected rehearsals within PRs, but not merging to `main`. No accepted production key, protected environment/seed, native-signing trust proof, release write, publication path, desktop consumer, tuple enablement, or production/default change is connected. Legacy remains the production default.<br>
Current phase: Milestone 4 / Work Package 3 disconnected relay required-asset gate — **In progress — 2026-07-15, Codex implementation owner**. The bounded draft upload/recovery capability is closed locally and on all six exact-head native jobs under E-M4-DRAFT-UPLOAD-LOCAL-RED-001, E-M4-DRAFT-UPLOAD-LOCAL-001, and E-M4-DRAFT-UPLOAD-CI-001. The active package's purpose-named missing-module RED and locally green implementation are recorded under E-M4-RELEASE-ASSETS-LOCAL-RED-001 and E-M4-RELEASE-ASSETS-LOCAL-001. Exact-head execution on all six native Node 24 jobs is required before closing this package. The gate remains a disconnected pure metadata boundary; detached-signature byte encoding, the existing desktop required-asset gate, and workflow composition are unchanged. Release-cut, desktop builds, publication, and every tuple stay disconnected; production/default behavior is unchanged.<br>
Session checkpoint: **In progress — 2026-07-15, Codex implementation owner** — the locally green release-asset gate pins exact release ID/tag/channel/prerelease, derives the complete managed set from a validated already-verified signed-manifest envelope, binds opaque manifest/signature descriptors by hash and signer IDs, and rejects missing, extra managed, duplicate, non-uploaded, empty, wrong-size, uncovered, or cross-identity metadata under E-M4-RELEASE-ASSETS-LOCAL-001. The final local run passes 13 focused tests, 64 adjacent release tests, all 283 SSH-relay script tests, typecheck, lint, formatting, and diff checks. Only syntax/test execution is added to the six native artifact jobs; there is no command-line entrypoint, release API call, upload/publication caller, desktop consumer, or tuple enablement. The user authorizes commits, pushes, draft-PR updates, CI reruns, and protected rehearsals within PRs, but not merging to `main`. No accepted production key, protected environment/seed, native-signing trust proof, release write, publication path, desktop consumer, tuple enablement, or production/default change is connected. Legacy remains the production default.<br>
Primary design: [SSH relay GitHub Release plan](./2026-07-14-ssh-relay-github-release-plan.html)<br>
Motivating issues: [#8450](https://github.com/stablyai/orca/issues/8450), [#1693](https://github.com/stablyai/orca/issues/1693)
@@ -113,8 +113,11 @@ same change as the work it records.
is no CLI entrypoint or workflow caller, and no real release write occurred.
- Active package: Work Package 3 disconnected relay required-asset gate. Begin with a purpose-named
missing-module RED for exact verified-manifest coverage, signature, name, size, upload state,
managed-asset closure, tag/channel identity, and consumer disconnection. Keep the existing desktop
required-asset gate unchanged in this slice.
managed-asset closure, tag/channel identity, and consumer disconnection. The missing-module RED is
recorded under E-M4-RELEASE-ASSETS-LOCAL-RED-001. The locally green pure gate and test-only native
workflow wiring are recorded under E-M4-RELEASE-ASSETS-LOCAL-001; require exact-head all-six CI.
Keep the existing desktop required-asset gate and detached-signature byte encoding unchanged in
this slice.
- Completed Work Package 2 gate: target-native Windows source-signature reports from exact-head
artifact jobs 87267322867 and 87267322870 were independently downloaded and matched to their
identities and signing-stage reports under E-M3-WINDOWS-SOURCE-SIGNATURE-CI-001. PR Checks
@@ -11355,6 +11358,88 @@ lint` passes all 41 reliability gates, the 355-entry max-lines ratchet, bundled-
- Checklist items satisfied: exact-head all-six CI gate for the disconnected draft upload/recovery
capability. The broader draft upload/read-back/execution and Work Package 3 boxes remain open.
### E-M4-RELEASE-ASSETS-LOCAL-RED-001 — Relay-specific release required-asset gate is absent
- Date: 2026-07-15
- Owner: Codex implementation owner
- Source: uncommitted purpose-named test
`config/scripts/ssh-relay-runtime-release-assets.test.mjs`; draft PR #8741.
- Runner/remote/network: local macOS 26.2 arm64, Node v26.0.0 and pnpm 10.24.0. No network request,
credential, release write, signer, protected environment, SSH remote, desktop consumer,
publication, or enabled tuple.
- Command:
- `/usr/bin/time -l pnpm exec vitest run config/scripts/ssh-relay-runtime-release-assets.test.mjs`
- Result: expected FAIL, one suite / zero imported tests, 200ms Vitest and 0.97s wall. Maximum RSS
132,022,272 bytes; peak memory footprint 95,950,176 bytes.
- Failure oracle: Vitest reports only `Cannot find module './ssh-relay-runtime-release-assets.mjs'`
at the purpose-named import. No production module existed that derived exact archive/SBOM/
provenance/manifest/signature coverage from an already verified signed manifest and compared it
with authenticated draft metadata.
- Does not prove: implementation correctness, exact tag/channel closure, managed-asset closure,
upload state/size enforcement, signature-asset binding, native runner behavior, real GitHub
metadata, read-back byte identity, publication, desktop embedding, SSH behavior, or an enabled
tuple.
- Follow-up: implement only the disconnected metadata gate, retain the detached-signature encoding
decision and existing desktop release gate unchanged, then run the focused GREEN and broader
local regressions before adding native test-job integration.
### E-M4-RELEASE-ASSETS-LOCAL-001 — Exact signed-manifest release coverage passes locally
- Date: 2026-07-15
- Owner: Codex implementation owner
- Source: uncommitted `config/scripts/ssh-relay-runtime-release-assets.mjs`, its purpose-named test,
and POSIX/Windows native test-job wiring atop exact pushed head
`88a77eee91a34f8936882d096e568f499e9160e6`; draft PR #8741.
- Runner/remote/network: local macOS 26.2 arm64, Node v26.0.0 and pnpm 10.24.0. In-memory manifest and
authenticated-release-metadata fixtures only. No network request, credential, release write,
signer, protected environment, SSH remote, desktop consumer, publication, 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-release-assets.test.mjs` — PASS, one file / 13
tests in 203ms Vitest and 0.92s wall; maximum RSS 152,403,968 bytes, peak footprint 96,114,064
bytes, and zero swaps.
- Adjacent release command: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-release-assets.test.mjs
config/scripts/ssh-relay-runtime-draft-upload.test.mjs
config/scripts/ssh-relay-runtime-draft-recovery.test.mjs
config/scripts/ssh-relay-runtime-draft-readback.test.mjs
config/scripts/ssh-relay-runtime-release-stage-gate.test.mjs
config/scripts/ssh-relay-runtime-workflow.test.mjs
config/scripts/verify-release-required-assets.test.mjs` — PASS, 7 files / 64 tests in 988ms
Vitest and 1.72s wall; maximum RSS 135,806,976 bytes, peak footprint 95,802,744 bytes, and zero
swaps.
- Broad release command: `/usr/bin/time -l sh -c "rg --files config/scripts | rg
'/ssh-relay.*[.]test[.]mjs$' | xargs pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1"` — PASS, 53 files / 283 tests in 9.97s Vitest and 10.94s wall; maximum RSS
189,071,360 bytes and zero swaps.
- Static gates: `node --check` passes both new files. `pnpm run typecheck` passes. `pnpm run lint`
passes all 41 reliability gates, the 355-entry max-lines ratchet, bundled-skill verification,
localization catalog/parity, and localization coverage; all 26 warnings remain in untouched
existing files. Focused `oxfmt --check` and `git diff --check` pass. The protected Node/npm
resolver files and existing `verify-release-required-assets.mjs` plus its tests have zero diff.
- Oracle proved: the pure gate accepts only an exact release ID and stable/RC/perf tag whose
authenticated metadata remains a draft with channel-correct prerelease state. It revalidates the
already-verified signed-manifest schema, derives every tuple archive/SBOM/provenance plus exact
manifest/signature descriptors, bounds count/per-file/aggregate sizes, and requires unique
managed names. The opaque detached-signature descriptor must bind the exact manifest SHA-256 and
exact embedded signer key IDs without deciding its byte encoding. Missing, unexpected managed,
duplicate, non-uploaded, empty, wrong-size, malformed, signature-disagreeing, cross-release,
cross-tag, and cross-channel states fail closed; unrelated desktop assets remain untouched.
- Consumer-disconnection oracle: repository search finds the new production module only in its own
focused test and POSIX/Windows artifact-job syntax checks; only the test file is executed. There is
no CLI entrypoint, API fetch, release-cut/default workflow caller, upload, publication, desktop
consumer, tuple enablement, or change to the legacy SSH path.
- Does not prove: Node 24/native-runner behavior, real authenticated GitHub metadata, cryptographic
verification before envelope construction, final detached-signature byte encoding, byte
read-back, downloaded archive execution, a real release draft/write, native signing/trust,
publication, desktop embedding, SSH transfer/install, performance against legacy, or any enabled
tuple.
- Checklist items satisfied: local implementation gate for the disconnected relay-specific
required-asset capability. The broader Milestone 4 release composition/publication box remains
open.
- Follow-up: commit and push the exact package, then require syntax and 13-test execution on all six
exact-head native Node 24 jobs plus PR Checks and Golden E2E. Keep every production/default
consumer disconnected.
## Accepted Gaps
No product gap is accepted merely because it appears in this list. Each entry requires explicit
@@ -11413,13 +11498,12 @@ The project is not complete until every applicable item below is checked with ev
## Next Required Action
Checkpoint and push E-M4-DRAFT-UPLOAD-CI-001. Then begin a purpose-named disconnected RED for the
relay-specific release required-asset gate: consume only an already verified signed manifest and
authenticated draft metadata; require exact manifest/signature coverage, asset names, sizes, upload
state, managed-asset closure, and stable/RC/perf tag identity. Keep the existing desktop
`verify-release-required-assets.mjs` behavior unchanged until composition is separately reviewed.
Do not add a workflow caller or perform a real release write. The protected environment, accepted
production keys, and secret remain unprovisioned, so live signing evidence stays BLOCKED.
Checkpoint and push E-M4-RELEASE-ASSETS-LOCAL-RED-001 and E-M4-RELEASE-ASSETS-LOCAL-001. Require
exact-head syntax and 13-test execution on all six native artifact jobs plus PR Checks and Golden
E2E. Do not add a workflow caller or perform a real release write; keep the existing desktop
`verify-release-required-assets.mjs` behavior and detached-signature byte encoding unchanged until
composition is separately reviewed. The protected environment, accepted production keys, and
secret remain unprovisioned, so live signing evidence stays 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.