feat: define SSH relay runtime source tree

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-16 07:51:53 -04:00
co-authored by Orca
parent e76c5f9e85
commit 2f38700b98
7 changed files with 517 additions and 22 deletions
@@ -282,6 +282,7 @@ jobs:
src/main/ssh/ssh-relay-artifact-cache-population-integration.test.ts \
src/main/ssh/ssh-relay-artifact-acquisition.test.ts \
src/main/ssh/ssh-relay-artifact-acquisition-integration.test.ts \
src/main/ssh/ssh-relay-runtime-source-tree.test.ts \
src/main/ssh/ssh-relay-compiled-manifest-trust.test.ts \
src/main/ssh/ssh-relay-official-manifest.test.ts \
src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts \
@@ -834,6 +835,7 @@ jobs:
src/main/ssh/ssh-relay-artifact-cache-population-integration.test.ts `
src/main/ssh/ssh-relay-artifact-acquisition.test.ts `
src/main/ssh/ssh-relay-artifact-acquisition-integration.test.ts `
src/main/ssh/ssh-relay-runtime-source-tree.test.ts `
src/main/ssh/ssh-relay-compiled-manifest-trust.test.ts `
src/main/ssh/ssh-relay-official-manifest.test.ts `
src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts `
@@ -140,6 +140,7 @@ describe('SSH relay runtime artifact workflow', () => {
'ssh-relay-artifact-cache-population-integration.test.ts',
'ssh-relay-artifact-acquisition.test.ts',
'ssh-relay-artifact-acquisition-integration.test.ts',
'ssh-relay-runtime-source-tree.test.ts',
'ssh-relay-compiled-manifest-trust.test.ts',
'ssh-relay-official-manifest.test.ts',
'ssh-relay-manifest-accepted-keys.test.ts',
@@ -9,9 +9,26 @@ 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 6 / Work Package 5 bounded runtime transfer — host-evidence
composition CI is closed; audit only a disconnected transport-neutral verified source-tree contract
next, 2026-07-15, Codex implementation owner.**
Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — disconnected
transport-neutral source-tree contract is locally green; exact-head native proof next, 2026-07-15,
Codex implementation owner.**
`E-M6-SOURCE-TREE-CONTRACT-AUDIT-001` fixes a pure ready-acquisition → immutable descriptor
boundary: exact verified manifest/cache identity and signed limits, deterministic ASCII ordering,
client-native paths, borrowed live-lease assertion, and later shared use by pre-scan plus SFTP,
POSIX tar/no-tar, and Windows PowerShell/.NET transfer. It performs no I/O and owns no cleanup;
filesystem enumeration, streaming, SSH, remote writes/install, product/mode/fallback/default wiring,
publication, and SignPath remain absent.
`E-M6-SOURCE-TREE-CONTRACT-LOCAL-RED-001` records the expected missing-module failure for ten purpose
cases and the native-workflow oracle's six-pass/one-fail result: the suite occurs only in the oracle,
not either runner command. `E-M6-SOURCE-TREE-CONTRACT-LOCAL-CONTENTION-001` rejects an overlapping
local broad run whose existing suites hit 30-second timeouts under four concurrent heavy commands;
timeouts were not relaxed. `E-M6-SOURCE-TREE-CONTRACT-LOCAL-001` is green after isolated reruns:
10/10 purpose, 85/85 focused, 522/522 broader relay with four declared skips, 280/280 release-script,
typecheck, full lint, format, max-lines, diff, protected-resolver, and no-product-import gates pass.
The pure 123-line module rechecks ready acquisition identity/aggregates/root, freezes deterministic
signed descriptors, and borrows without releasing the live cache lease. Exact-head native proof is
next; filesystem pre-scan/streaming, SSH, remote writes/install, product/mode/fallback/default wiring,
publication, and SignPath remain absent.
`E-M5-ARTIFACT-CACHE-RESOLUTION-CI-001` closes warm-cache commit
`22031fa68` on all six native clients, both Linux supplements, Windows x64 baseline, PR Checks,
Golden E2E, and computer-use; Windows arm64 remains the expected build-26200 rejection against 26100. `E-M5-ARTIFACT-CACHE-RESOLUTION-LOCAL-001` passes 9/9 purpose tests,
@@ -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 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: audit only the smallest disconnected transport-neutral verified source-tree contract after host-composition CI closure**. Keep every capability disconnected from every production/default SSH path. Real Apple/SignPath rehearsals remain an explicit late gate and are not on this package's critical path.<br>
Session checkpoint: **HOST COMPOSITION CI CLOSED / TRANSFER CONTRACT AUDIT NEXT — 2026-07-15, Codex implementation owner** — `E-M5-HOST-EVIDENCE-COMPOSITION-CI-001` closes exact-head all-six native client proof, both Linux supplements, Windows x64 baseline, PR Checks, Golden E2E, and computer-use; Windows arm64 retains only the declared build-26200/26100 runner mismatch after full runtime proof. Audit only a pure verified source-tree contract next. Do not open SSH channels, transfer/install bytes, add a product caller, settings/fallback wiring, tuple enablement, publication, or default behavior. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.<br>
Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: exact-head native proof for the locally green disconnected transport-neutral verified source-tree contract**. Keep every capability disconnected from every production/default SSH path. Real Apple/SignPath rehearsals remain an explicit late gate and are not on this package's critical path.<br>
Session checkpoint: **SOURCE-TREE CONTRACT LOCAL GREEN / EXACT-HEAD CI NEXT — 2026-07-15, Codex implementation owner** — `E-M6-SOURCE-TREE-CONTRACT-LOCAL-001` passes purpose, workflow, focused, broad relay, release-script, typecheck, lint, format, max-lines, diff, protected-resolver, and no-product-caller gates after separately rerunning two contention-invalidated broad commands. Commit and push the isolated package, then require exact-head all-six native proof before checking the Milestone 6 item or advancing to pre-scan. Do not enumerate the filesystem, open SSH channels, transfer/install bytes, add a product caller, settings/fallback wiring, tuple enablement, publication, or default behavior. Merging to `main` remains prohibited, SignPath is deferred, and 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)
@@ -150,9 +150,10 @@ same change as the work it records.
- Completed package: disconnected full host-evidence composition is green locally and on every
primary native client under E-M5-HOST-EVIDENCE-COMPOSITION-LOCAL-001 and
E-M5-HOST-EVIDENCE-COMPOSITION-CI-001. It has no product caller.
- Active package: **In progress — 2026-07-15, Codex implementation owner** — audit only Milestone
6's smallest transport-neutral verified source-tree contract. Do not open SSH channels, transfer
or install bytes, or add product/settings/fallback/tuple/publication/default behavior.
- Active package: **In progress — 2026-07-15, Codex implementation owner** — exact-head native proof
for Milestone 6's locally green pure transport-neutral verified source-tree contract. Do not
enumerate the filesystem, open SSH channels, transfer or install bytes, or add product/settings/
fallback/tuple/publication/default behavior.
- 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
@@ -1153,7 +1154,11 @@ Current behavior must not be assumed adequate for a much larger runtime:
### Shared transfer contract
- [ ] Define one transport-neutral source-tree contract based on the verified manifest.
- [ ] Define one transport-neutral source-tree contract based on the verified manifest. **In
progress — 2026-07-15, Codex implementation owner; audit complete under
E-M6-SOURCE-TREE-CONTRACT-AUDIT-001; RED and local green proof recorded under
E-M6-SOURCE-TREE-CONTRACT-LOCAL-RED-001 and E-M6-SOURCE-TREE-CONTRACT-LOCAL-001; exact-head
native proof required before completion.**
- [ ] Pre-scan and reject local source mutation, symlinks, special files, extra files, and path
collisions before creating remote files.
- [ ] Stream files with bounded buffers; prohibit whole-tree memory materialization.
@@ -16080,6 +16085,126 @@ config/vitest.config.ts --maxWorkers=1` plus the composer, Windows compatibility
`ORCA_RELAY_PATH`, tuple enablement, publication, defaults, and SignPath remain open. Legacy
remains the sole production/default path and every tuple stays disabled.
### E-M6-SOURCE-TREE-CONTRACT-AUDIT-001 — disconnected authenticated source-tree projection
- Date/owner: 2026-07-15, Codex implementation owner.
- Exact input boundary: accept only the `ready` result from the existing artifact acquisition. That
result already binds a selected tuple from a signature-verified immutable manifest, a strict
fully verified cache entry, and a live content-scoped in-use lease. Do not accept a raw manifest,
tuple, cache path, archive, or caller-created file list.
- Immutable projection: recheck tuple/content identity, cache aggregate counts, and the canonical
`runtime` root before exposure. Project the authenticated entries into deeply frozen directory
and file descriptors with client-native absolute paths. Use one explicit ASCII path order,
independent of manifest insertion order and filesystem enumeration order. Preserve signed file
role, exact size, SHA-256, and only the declared 0644/0755 modes.
- Limits: inherit the already authenticated schema ceilings rather than adding a second policy:
5,000 total entries, 250 MiB per file, 350 MiB expanded bytes, and exact signed file-count/
expanded-byte aggregates. A mismatch fails closed before a descriptor is returned.
- Ownership/cancellation: the projection is synchronous and performs no filesystem or network I/O,
so it has no cancellation point and creates no resource to clean up. It exposes only a borrowed
lease-ownership assertion; it never releases the acquisition's lease. The later orchestration
owner must retain and release that lease only after pre-scan, the selected transfer family, and
cleanup have all settled. The next asynchronous pre-scan/transfer package must accept one exact
`AbortSignal`, check it throughout, and assert the borrowed lease before and after source reads.
- Later consumers: the same contract is the sole expected-tree input for pre-scan and for bounded
SFTP, POSIX tar/no-tar system-SSH, and Windows PowerShell/.NET system-SSH transfer. Transport code
may choose framing/concurrency but may not reorder or weaken the authenticated descriptors.
- Required RED/green proof: Linux and Windows signed tuples; deterministic ordering and native path
construction; exact modes/roles/sizes/hashes/aggregates; deep freezing; ready-only input; identity,
aggregate, and runtime-root rejection; borrowed lease assertion with zero implicit release; no
filesystem/SSH/product import or caller; and exact inclusion in both native workflow families.
- Deliberately absent: filesystem enumeration or mutation detection, file opening/streaming,
channels/SFTP/child processes, remote paths/writes/install, progress, cleanup, settings/modes,
fallback, Electron/startup, `ORCA_RELAY_PATH`, tuple enablement, publication, defaults, and
SignPath. Legacy remains the sole production/default path and every tuple stays disabled.
### E-M6-SOURCE-TREE-CONTRACT-LOCAL-RED-001 — missing source-tree contract and native proof
- Date/owner/environment: 2026-07-15, Codex implementation owner; local macOS arm64, Node 26.0.0,
pnpm 10.24.0, atop local evidence commit `e76c5f9e8` and the audit/test-only working diff. Exact
Node 24 remains a later native CI gate.
- Purpose command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 src/main/ssh/ssh-relay-runtime-source-tree.test.ts` — EXPECTED FAIL, exit 1: the
absent `ssh-relay-runtime-source-tree` module prevents collection of the ten audited cases; zero
tests run in 600 ms Vitest / 3.20 seconds wall, 132,104,192-byte maximum RSS and 96,212,248-byte
peak memory footprint.
- Workflow-oracle command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 config/scripts/ssh-relay-runtime-workflow.test.mjs` — EXPECTED FAIL, exit 1: six
workflow cases pass and the native-family inclusion oracle fails because the new purpose suite has
one source occurrence rather than the required three; 544 ms Vitest / 3.18 seconds wall,
131,956,736-byte maximum RSS and 96,245,280-byte peak memory footprint.
- Scope at RED: `git diff --check` passed before the package; protected resolver files had no diff.
No source-tree module, production import/caller, filesystem enumeration, SSH operation, transfer,
remote write, settings/fallback, tuple, publication, or default behavior exists.
### E-M6-SOURCE-TREE-CONTRACT-LOCAL-CONTENTION-001 — rejected concurrent broad-gate run
- Date/owner/environment: 2026-07-15, Codex implementation owner; local macOS arm64. The first broad
relay, release-script, and full-lint commands were launched together while an unrelated local
Vitest process was also active. Process inspection showed all four CPU/I/O-heavy commands
overlapping for more than six minutes; these results are not accepted as implementation evidence.
- Relay command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 --silent=true src/main/ssh/ssh-relay-*.test.ts` — INVALID/FAIL, 39 files passed,
three skipped, and one existing cache-entry suite failed seven cases only at their 30-second test
timeout; 515 tests passed and four were skipped in 449.11 seconds Vitest / 453.40 seconds wall.
- Release-script command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 --silent=true config/scripts/ssh-relay-runtime-*.test.mjs` — INVALID/FAIL, 48 files
passed and two existing suites failed nine cases at 30-second timeouts/teardown races; 271 tests
passed with four unhandled cleanup errors in 445.50 seconds Vitest / 449.76 seconds wall.
- Full lint nevertheless passed every command in 448.00 seconds wall. Because the two test failures
were resource-contention artifacts outside this package, both broad test commands were rerun alone
after process inspection confirmed no competing Vitest/lint process; only those isolated reruns
count below. No timeout was increased and no test or production behavior was weakened.
### E-M6-SOURCE-TREE-CONTRACT-LOCAL-001 — pure authenticated source-tree contract is locally green
- Date/owner/environment: 2026-07-15, Codex implementation owner; local macOS arm64, Node 26.0.0,
pnpm 10.24.0, atop local evidence commit `e76c5f9e8` plus this isolated package. Exact Node 24 and
every client OS/architecture remain mandatory in native CI.
- Implementation: the 123-line `ssh-relay-runtime-source-tree.ts` accepts only an acquisition-ready
type exported by the existing acquisition module. It rechecks signed tuple/content identity,
verified cache aggregates, and the exact cache `runtime` root; then projects Linux/Darwin/Windows
manifest entries into separately ASCII-sorted, deeply frozen client-native directory/file
descriptors. It preserves signed roles, modes, sizes, and hashes, exposes only a borrowed lease
assertion, and never releases the owner lease. It imports only `node:path`, acquisition types, and
runtime identity types; it performs no I/O and has no product caller.
- Post-format purpose command: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 src/main/ssh/ssh-relay-runtime-source-tree.test.ts` — PASS,
one file / ten tests in 1.64 seconds Vitest / 6.64 seconds wall, 132,792,320-byte maximum RSS and
96,998,776-byte peak memory footprint. It covers signed Linux/Windows tuples, reversed insertion
order, client-native paths, exact descriptors/aggregates, deep freezing, lease borrowing, and
ready/identity/aggregate/runtime-root rejection.
- Workflow-oracle command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 config/scripts/ssh-relay-runtime-workflow.test.mjs` — PASS, one file / seven tests in
1.24 seconds Vitest / 6.23 seconds wall, 133,103,616-byte maximum RSS. Both native workflow command
families now execute the purpose suite exactly once.
- Focused command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1` plus the source-tree, acquisition unit/integration, cache-entry, schema, signature,
and workflow-oracle suites — PASS, seven files / 85 tests in 39.79 seconds Vitest. The observed
188.64-second wrapper wall time came from the same local command scheduling period, but no focused
test failed or timed out.
- Isolated broader relay rerun: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 --silent=true src/main/ssh/ssh-relay-*.test.ts` — PASS, 40
files passed / three declared full-size skips, 522 tests passed / four declared platform skips in
54.54 seconds Vitest / 57.40 seconds wall, 222,511,104-byte maximum RSS. Only the established stale
lock diagnostic and local Node 26 `module.register()` deprecation warning appear.
- Isolated release-script rerun: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 --silent=true config/scripts/ssh-relay-runtime-*.test.mjs` —
PASS, 50 files / 280 tests in 35.05 seconds Vitest / 39.91 seconds wall, 190,300,160-byte maximum
RSS.
- Static gates: `/usr/bin/time -l pnpm typecheck` passes in 9.32 seconds wall with
1,238,499,328-byte maximum RSS. `/usr/bin/time -l pnpm lint` passes every full lint,
switch-exhaustiveness, 41 reliability, 355-entry max-lines, bundled-skill, 9,837 localization-
reference, locale-parity, and zero-candidate localization gate; the 26 warnings remain only in
untouched files. Targeted `oxlint`, targeted `oxfmt --check`, `git diff --check`, protected-
resolver empty diff, no other `src/main` importer, and the 123/226-line implementation/test size
checks pass. No max-lines suppression or vague module is added.
- Boundary/residual gaps: this is a synchronous descriptor contract, not filesystem pre-scan,
mutation detection, streaming, live SSH, SFTP/system-SSH, remote staging/install, or a full-size
transfer. Exact-head all-six Node 24 client proof is required before accepting the item. Every
product/startup/settings/fallback/tuple/publication/default path remains absent; legacy remains the
sole production/default path, every tuple stays disabled, and SignPath stays deferred.
## Accepted Gaps
No product gap is accepted merely because it appears in this list. Each entry requires explicit
@@ -16138,12 +16263,12 @@ The project is not complete until every applicable item below is checked with ev
## Next Required Action
Audit only Milestone 6's smallest disconnected transport-neutral verified source-tree contract.
Determine the exact authenticated cache-entry/manifest inputs, immutable file ordering, mode and
size/count limits, ownership/cancellation semantics, and which later transfer families consume it
before adding tests or implementation. Do not open an SSH channel, transfer or install bytes, modify
platform detection/selectors, add an Electron/startup consumer, mode wiring, fallback, tuple
enablement, release publication, or default behavior.
Commit and push the locally green Milestone 6 disconnected transport-neutral verified source-tree
contract, then require exact-head all-six native client proof plus adjacent PR checks before accepting
the item or advancing to the filesystem pre-scan package.
Do not enumerate the filesystem, open an SSH channel, transfer or install bytes, modify platform
detection/selectors, add an Electron/startup consumer, mode wiring, fallback, tuple enablement,
release publication, or default behavior.
Keep Node upstream `.tar.xz` inputs, Windows ZIP, `ORCA_RELAY_PATH`, existing desktop required-assets
behavior, detached-signature byte encoding, Windows arm64 build 26100, macOS 13.5, Linux kernel 4.18,
release-cut, desktop builds, publication, and every tuple separately gated.
@@ -22,16 +22,18 @@ const DEFAULT_OPERATIONS: SshRelayArtifactAcquisitionOperations = Object.freeze(
populate: populateSshRelayArtifactCache
})
export type SshRelayArtifactReadyAcquisition = Readonly<{
kind: 'ready'
source: 'cache' | 'download'
artifact: SshRelaySelectedArtifact
entry: Readonly<SshRelayArtifactCacheEntry>
lease: SshRelayArtifactCacheInUseLease
}>
export type SshRelayArtifactAcquisition =
| Readonly<{ kind: 'unavailable'; reason: 'official-manifest-unavailable' }>
| Readonly<{ kind: 'legacy'; reason: SshRelayArtifactLegacyReason }>
| Readonly<{
kind: 'ready'
source: 'cache' | 'download'
artifact: SshRelaySelectedArtifact
entry: Readonly<SshRelayArtifactCacheEntry>
lease: SshRelayArtifactCacheInUseLease
}>
| SshRelayArtifactReadyAcquisition
function sameArtifactIdentity(
expected: SshRelaySelectedArtifact,
@@ -0,0 +1,225 @@
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import nacl from 'tweetnacl'
import { describe, expect, it, vi } from 'vitest'
import type { SshRelayArtifactReadyAcquisition } from './ssh-relay-artifact-acquisition'
import type { SshRelayArtifactCacheEntry } from './ssh-relay-artifact-cache-entry-verification'
import type { SshRelayArtifactCacheInUseLease } from './ssh-relay-artifact-cache-in-use-lease'
import {
createSshRelayArtifactTestManifest,
createSshRelayWindowsArtifactTestManifest
} from './ssh-relay-artifact-test-manifest'
import { selectSshRelayArtifact } from './ssh-relay-artifact-selector'
import {
signSshRelayArtifactManifest,
sshRelayManifestKeyId,
verifySshRelayArtifactManifest
} from './ssh-relay-manifest-signature'
import { sshRelayRuntimeArchiveName } from './ssh-relay-release-asset'
import { computeSshRelayRuntimeContentId, type SshRelayDigest } from './ssh-relay-runtime-identity'
import { createSshRelayRuntimeSourceTree } from './ssh-relay-runtime-source-tree'
const keyPair = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index))
const cacheRoot = join(tmpdir(), 'orca-relay-runtime-source-tree')
type Fixture = {
acquisition: SshRelayArtifactReadyAcquisition
assertOwned: ReturnType<typeof vi.fn>
release: ReturnType<typeof vi.fn>
}
function readyFixture(
os: 'linux' | 'win32',
{ reverseEntries = false }: { reverseEntries?: boolean } = {}
): Fixture {
const manifest =
os === 'win32'
? createSshRelayWindowsArtifactTestManifest()
: createSshRelayArtifactTestManifest()
const tuple = manifest.tuples[0]
if (reverseEntries) {
tuple.entries.reverse()
tuple.contentId = computeSshRelayRuntimeContentId(tuple)
tuple.archive.name = sshRelayRuntimeArchiveName(tuple.tupleId, tuple.contentId)
}
manifest.signatures = [signSshRelayArtifactManifest(manifest, keyPair.secretKey)]
const verified = verifySshRelayArtifactManifest(manifest, [
{ keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey }
])
const host =
os === 'win32'
? ({
os,
architecture: 'x64',
processTranslated: false,
build: 22631,
openSshVersion: '9.5p1',
powerShellVersion: '5.1',
dotNetFrameworkRelease: 528040
} as const)
: ({
os,
architecture: 'x64',
processTranslated: false,
kernelVersion: '6.8',
libc: { family: 'glibc', version: '2.39' },
libstdcxxVersion: '6.0.33',
glibcxxVersion: '3.4.33'
} as const)
const artifact = selectSshRelayArtifact(verified, host)
if (artifact.kind !== 'selected') {
throw new Error(`Expected selected source-tree fixture, got ${artifact.reason}`)
}
const entryPath = join(cacheRoot, 'entries', artifact.contentId.slice('sha256:'.length))
const entry: SshRelayArtifactCacheEntry = {
contentId: artifact.contentId,
tupleId: artifact.tupleId,
entryPath,
archivePath: join(entryPath, artifact.archive.name),
runtimeRoot: join(entryPath, 'runtime'),
proofPath: join(entryPath, 'proof.json'),
files: artifact.archive.fileCount,
expandedBytes: artifact.archive.expandedSize
}
const assertOwned = vi.fn(async () => {})
const release = vi.fn(async () => {})
const lease: SshRelayArtifactCacheInUseLease = {
leasePath: join(cacheRoot, 'in-use', 'a'.repeat(32)),
token: 'a'.repeat(32),
assertOwned,
release
}
return {
acquisition: { kind: 'ready', source: 'cache', artifact, entry, lease },
assertOwned,
release
}
}
function asciiSorted(paths: string[]): string[] {
return [...paths].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0))
}
const inconsistentCacheEntries: [string, Partial<SshRelayArtifactCacheEntry>][] = [
['tuple identity', { tupleId: 'win32-x64' }],
['content identity', { contentId: `sha256:${'f'.repeat(64)}` as SshRelayDigest }],
['file count', { files: 1 }],
['expanded byte count', { expandedBytes: 1 }]
]
describe('SSH relay runtime source-tree contract', () => {
it.each(['linux', 'win32'] as const)(
'projects an authenticated %s acquisition in immutable ASCII order',
(os) => {
const { acquisition } = readyFixture(os, { reverseEntries: true })
const tree = createSshRelayRuntimeSourceTree(acquisition)
const expectedDirectories = asciiSorted(
acquisition.artifact.tuple.entries
.filter((entry) => entry.type === 'directory')
.map((entry) => entry.path)
)
const expectedFiles = asciiSorted(
acquisition.artifact.tuple.entries
.filter((entry) => entry.type === 'file')
.map((entry) => entry.path)
)
expect(tree).toMatchObject({
tupleId: acquisition.artifact.tupleId,
contentId: acquisition.artifact.contentId,
releaseTag: acquisition.artifact.releaseTag,
os: acquisition.artifact.tuple.os,
architecture: acquisition.artifact.tuple.architecture,
runtimeRoot: acquisition.entry.runtimeRoot,
fileCount: acquisition.entry.files,
expandedBytes: acquisition.entry.expandedBytes
})
expect(tree.directories.map((entry) => entry.path)).toEqual(expectedDirectories)
expect(tree.files.map((entry) => entry.path)).toEqual(expectedFiles)
for (const descriptor of [...tree.directories, ...tree.files]) {
expect(descriptor.localPath).toBe(
join(acquisition.entry.runtimeRoot, ...descriptor.path.split('/'))
)
}
expect(tree.files).toEqual(
expectedFiles.map((path) => {
const entry = acquisition.artifact.tuple.entries.find(
(candidate) => candidate.path === path
)
if (!entry || entry.type !== 'file') {
throw new Error(`Missing expected signed file: ${path}`)
}
return {
path,
localPath: join(acquisition.entry.runtimeRoot, ...path.split('/')),
type: 'file',
role: entry.role,
size: entry.size,
mode: entry.mode,
sha256: entry.sha256
}
})
)
}
)
it('deeply freezes descriptors without mutating signed manifest insertion order', () => {
const { acquisition } = readyFixture('linux')
const originalOrder = acquisition.artifact.tuple.entries.map((entry) => entry.path)
const tree = createSshRelayRuntimeSourceTree(acquisition)
expect(Object.isFrozen(tree)).toBe(true)
expect(Object.isFrozen(tree.directories)).toBe(true)
expect(Object.isFrozen(tree.files)).toBe(true)
expect(tree.directories.every(Object.isFrozen)).toBe(true)
expect(tree.files.every(Object.isFrozen)).toBe(true)
expect(acquisition.artifact.tuple.entries.map((entry) => entry.path)).toEqual(originalOrder)
})
it('borrows lease ownership without asserting or releasing it implicitly', async () => {
const { acquisition, assertOwned, release } = readyFixture('linux')
const tree = createSshRelayRuntimeSourceTree(acquisition)
expect(assertOwned).not.toHaveBeenCalled()
expect(release).not.toHaveBeenCalled()
await tree.assertLeaseOwned()
expect(assertOwned).toHaveBeenCalledTimes(1)
expect(release).not.toHaveBeenCalled()
})
it('rejects a non-ready acquisition', () => {
expect(() =>
createSshRelayRuntimeSourceTree({
kind: 'legacy',
reason: 'kernel-too-old'
} as unknown as SshRelayArtifactReadyAcquisition)
).toThrow(/ready/i)
})
it.each(inconsistentCacheEntries)('rejects inconsistent cache %s', (_name, entryPatch) => {
const { acquisition } = readyFixture('linux')
expect(() =>
createSshRelayRuntimeSourceTree({
...acquisition,
entry: { ...acquisition.entry, ...entryPatch }
})
).toThrow(/identity|file count|expanded byte/i)
})
it('rejects a noncanonical cache runtime root', () => {
const { acquisition } = readyFixture('linux')
expect(() =>
createSshRelayRuntimeSourceTree({
...acquisition,
entry: { ...acquisition.entry, runtimeRoot: join(acquisition.entry.entryPath, 'other') }
})
).toThrow(/runtime root/i)
})
})
@@ -0,0 +1,123 @@
import { join } from 'node:path'
import type { SshRelayArtifactReadyAcquisition } from './ssh-relay-artifact-acquisition'
import type {
SshRelayDigest,
SshRelayRuntimeFileRole,
SshRelayRuntimeTupleId
} from './ssh-relay-runtime-identity'
export type SshRelayRuntimeSourceDirectory = Readonly<{
path: string
localPath: string
type: 'directory'
mode: 0o755
}>
export type SshRelayRuntimeSourceFile = Readonly<{
path: string
localPath: string
type: 'file'
role: SshRelayRuntimeFileRole
size: number
mode: 0o644 | 0o755
sha256: SshRelayDigest
}>
export type SshRelayRuntimeSourceTree = Readonly<{
tupleId: SshRelayRuntimeTupleId
contentId: SshRelayDigest
releaseTag: string
os: 'linux' | 'darwin' | 'win32'
architecture: 'x64' | 'arm64'
runtimeRoot: string
directories: readonly SshRelayRuntimeSourceDirectory[]
files: readonly SshRelayRuntimeSourceFile[]
fileCount: number
expandedBytes: number
assertLeaseOwned: () => Promise<void>
}>
function compareAscii(left: { path: string }, right: { path: string }): number {
return left.path < right.path ? -1 : left.path > right.path ? 1 : 0
}
export function createSshRelayRuntimeSourceTree(
acquisition: SshRelayArtifactReadyAcquisition
): SshRelayRuntimeSourceTree {
if (!acquisition || acquisition.kind !== 'ready') {
throw new Error('SSH relay runtime source tree requires a ready artifact acquisition')
}
const { artifact, entry, lease } = acquisition
if (
artifact.tupleId !== artifact.tuple.tupleId ||
artifact.contentId !== artifact.tuple.contentId ||
entry.tupleId !== artifact.tupleId ||
entry.contentId !== artifact.contentId
) {
throw new Error('SSH relay runtime source tree has inconsistent artifact identity')
}
if (entry.runtimeRoot !== join(entry.entryPath, 'runtime')) {
throw new Error('SSH relay runtime source tree has a noncanonical cache runtime root')
}
const directories: SshRelayRuntimeSourceDirectory[] = []
const files: SshRelayRuntimeSourceFile[] = []
let expandedBytes = 0
for (const manifestEntry of artifact.tuple.entries) {
const localPath = join(entry.runtimeRoot, ...manifestEntry.path.split('/'))
if (manifestEntry.type === 'directory') {
directories.push(
Object.freeze({
path: manifestEntry.path,
localPath,
type: manifestEntry.type,
mode: manifestEntry.mode
})
)
continue
}
expandedBytes += manifestEntry.size
files.push(
Object.freeze({
path: manifestEntry.path,
localPath,
type: manifestEntry.type,
role: manifestEntry.role,
size: manifestEntry.size,
mode: manifestEntry.mode,
sha256: manifestEntry.sha256
})
)
}
if (files.length !== artifact.archive.fileCount || entry.files !== artifact.archive.fileCount) {
throw new Error('SSH relay runtime source tree file count disagrees with the signed artifact')
}
if (
expandedBytes !== artifact.archive.expandedSize ||
entry.expandedBytes !== artifact.archive.expandedSize
) {
throw new Error(
'SSH relay runtime source tree expanded byte count disagrees with the signed artifact'
)
}
directories.sort(compareAscii)
files.sort(compareAscii)
// Why: transports borrow the live cache lease; only their orchestration owner may release it
// after transfer and cleanup have both settled.
const assertLeaseOwned = (): Promise<void> => lease.assertOwned()
return Object.freeze({
tupleId: artifact.tupleId,
contentId: artifact.contentId,
releaseTag: artifact.releaseTag,
os: artifact.tuple.os,
architecture: artifact.tuple.architecture,
runtimeRoot: entry.runtimeRoot,
directories: Object.freeze(directories),
files: Object.freeze(files),
fileCount: files.length,
expandedBytes,
assertLeaseOwned
})
}