From a56dded6ff0b10046e58e8ff2a3a99b8d6578291 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Thu, 16 Jul 2026 14:33:23 -0400 Subject: [PATCH] test(ssh): prove full-size POSIX system transfer Co-authored-by: Orca --- .../workflows/ssh-relay-runtime-artifacts.yml | 90 +++++ .../ssh-relay-runtime-workflow.test.mjs | 36 ++ ...elease-implementation-checklist-summary.md | 13 + ...github-release-implementation-checklist.md | 123 +++++- ...posix-system-ssh-openssh-full-size.test.ts | 377 ++++++++++++++++++ 5 files changed, 632 insertions(+), 7 deletions(-) create mode 100644 src/main/ssh/ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml index eda2274aacd..fe859cd0b6d 100644 --- a/.github/workflows/ssh-relay-runtime-artifacts.yml +++ b/.github/workflows/ssh-relay-runtime-artifacts.yml @@ -632,6 +632,96 @@ jobs: pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 --reporter=verbose \ src/main/ssh/ssh-relay-runtime-sftp-openssh-full-size.test.ts + - name: Start restricted loopback OpenSSH POSIX system-SSH fixture + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + fixture="$RUNNER_TEMP/orca-live-sftp" + test -x /usr/sbin/sshd + test -s "$fixture/client-key" + test -s "$fixture/host-key" + system_fixture="$fixture/system-ssh" + primitives="$system_fixture/primitives" + mkdir -p "$primitives" + ln -s "$(command -v mkdir)" "$primitives/mkdir" + ln -s "$(command -v chmod)" "$primitives/chmod" + ln -s "$(command -v cat)" "$primitives/cat" + ln -s "$(command -v rm)" "$primitives/rm" + wrapper="$system_fixture/restricted-command" + printf '%s\n' \ + '#!/bin/sh' \ + 'PATH="${0%/*}/primitives"' \ + 'export PATH' \ + 'exec /bin/sh -c "$SSH_ORIGINAL_COMMAND"' > "$wrapper" + chmod 755 "$wrapper" + port=22223 + known_hosts="$HOME/.ssh/known_hosts" + printf '[127.0.0.1]:%s %s\n' "$port" "$(cat "$fixture/host-key.pub")" >> "$known_hosts" + chmod 600 "$known_hosts" + config="$system_fixture/sshd_config" + printf '%s\n' \ + "Port $port" \ + 'ListenAddress 127.0.0.1' \ + "HostKey $fixture/host-key" \ + "PidFile $system_fixture/sshd.pid" \ + "AuthorizedKeysFile $HOME/.ssh/authorized_keys" \ + "AllowUsers $USER" \ + 'PasswordAuthentication no' \ + 'KbdInteractiveAuthentication no' \ + 'ChallengeResponseAuthentication no' \ + 'UsePAM no' \ + 'StrictModes yes' \ + 'LogLevel VERBOSE' \ + "ForceCommand $wrapper" > "$config" + sudo /usr/sbin/sshd -f "$config" -E "$system_fixture/sshd.log" + for attempt in {1..20}; do + if ssh -F /dev/null -i "$fixture/client-key" -p "$port" \ + -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile="$known_hosts" "$USER@127.0.0.1" true; then + break + fi + if test "$attempt" = 20; then + sudo tail -200 "$system_fixture/sshd.log" + exit 1 + fi + sleep 0.25 + done + server_version=$(/usr/sbin/sshd -V 2>&1 | head -1) + { + printf 'ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_HOST=127.0.0.1\n' + printf 'ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_PORT=%s\n' "$port" + printf 'ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_USER=%s\n' "$USER" + printf 'ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_IDENTITY=%s\n' "$fixture/client-key" + printf 'ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_REMOTE_ROOT=%s\n' "$fixture/remote" + printf 'ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_SERVER_VERSION=%s\n' "$server_version" + } >> "$GITHUB_ENV" + + - name: Measure exact full-size runtime over POSIX system SSH + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + first_output="$RUNNER_TEMP/ssh-relay-runtime/${{ matrix.tuple }}/first" + ORCA_SSH_FORCE_SYSTEM_TRANSPORT=1 \ + ORCA_SSH_RELAY_FULL_SIZE_RUNTIME_ROOT="$first_output/runtime" \ + ORCA_SSH_RELAY_FULL_SIZE_IDENTITY="$first_output/orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json" \ + pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 --reporter=verbose \ + src/main/ssh/ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts + + - name: Stop restricted loopback OpenSSH POSIX system-SSH fixture + if: always() && runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + system_fixture="$RUNNER_TEMP/orca-live-sftp/system-ssh" + if test -s "$system_fixture/sshd.pid"; then + sudo kill "$(cat "$system_fixture/sshd.pid")" || true + fi + if test -f "$system_fixture/sshd.log"; then + sudo tail -200 "$system_fixture/sshd.log" + fi + - name: Stop loopback OpenSSH SFTP fixture if: always() && runner.os == 'Linux' shell: bash diff --git a/config/scripts/ssh-relay-runtime-workflow.test.mjs b/config/scripts/ssh-relay-runtime-workflow.test.mjs index 32d559a9ff0..4f7d8a2ec2c 100644 --- a/config/scripts/ssh-relay-runtime-workflow.test.mjs +++ b/config/scripts/ssh-relay-runtime-workflow.test.mjs @@ -104,6 +104,42 @@ describe('SSH relay runtime artifact workflow', () => { expect(stop.if).toBe("always() && runner.os == 'Linux'") }) + it('runs full-size POSIX system SSH against loopback OpenSSH with restricted primitives', async () => { + const workflow = parse(await readFile(workflowUrl, 'utf8')) + const steps = workflow.jobs['build-posix-runtime'].steps + const start = steps.find( + (step) => step.name === 'Start restricted loopback OpenSSH POSIX system-SSH fixture' + ) + const measure = steps.find( + (step) => step.name === 'Measure exact full-size runtime over POSIX system SSH' + ) + const stop = steps.find( + (step) => step.name === 'Stop restricted loopback OpenSSH POSIX system-SSH fixture' + ) + + expect(start).toBeDefined() + expect(measure).toBeDefined() + expect(stop).toBeDefined() + expect(start.if).toBe("runner.os == 'Linux'") + expect(start.run).toContain('ListenAddress 127.0.0.1') + expect(start.run).toContain('ForceCommand $wrapper') + expect(start.run).toContain('PasswordAuthentication no') + expect(start.run).toContain('StrictModes yes') + expect(start.run).toContain('known_hosts') + expect(start.run).toContain('StrictHostKeyChecking=yes') + for (const primitive of ['mkdir', 'chmod', 'cat', 'rm']) { + expect(start.run).toContain(`command -v ${primitive}`) + } + for (const forbidden of ['node', 'python', 'perl', 'tar', 'base64', 'sha256sum', 'shasum']) { + expect(start.run).not.toContain(`command -v ${forbidden}`) + } + expect(measure.if).toBe("runner.os == 'Linux'") + expect(measure.run).toContain('ORCA_SSH_FORCE_SYSTEM_TRANSPORT=1') + expect(measure.run).toContain('ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts') + expect(measure.run).toContain('first_output/runtime') + expect(stop.if).toBe("always() && runner.os == 'Linux'") + }) + it('uploads only the first output after two clean builds verify and compare', async () => { const source = await readFile(workflowUrl, 'utf8') const workflow = parse(source) 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 4b93ae7ff33..497df6015d3 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 @@ -43,6 +43,19 @@ immediate SIGTERM listener. No direct spawn, legacy uploader change, live claim, or product path is included. Audit only a disconnected live/full-size POSIX system-SSH proof next; legacy remains the only production/default path and SignPath remains deferred. +`E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-AUDIT-001` limits that proof to a second loopback-only stock +`sshd` on Linux x64/arm64 artifact runners, exact generated host-key trust, public-key auth, and a +forced remote `PATH` containing only `mkdir`, `chmod`, `cat`, and `rm` plus absolute `/bin/sh`. +It adds only a purpose-named full-size test and workflow/oracle wiring; no production module changes. +`E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-LOCAL-RED-001` proves the live suite skips without its seven CI +inputs while the independent workflow oracle fails only because the audited start/measure/stop +steps are absent. That bounded fixture wiring now passes the local 9/9 workflow oracle while the +purpose suite remains honestly skipped without CI inputs; broad regression gates and exact-head +Linux x64/arm64 live evidence remain required. +`E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-LOCAL-001` records that bounded package locally green: 9/9 workflow +oracle, 220 focused, 656 broad relay, 282 release-contract, typecheck, lint, 355-entry max-lines, +formatting, diff, protected-resolver, and no-product-consumer gates pass. The live case is explicitly +skipped locally and earns no proof; exact-head Linux x64/arm64 live evidence is still required. `E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001` records 25/25 purpose, 628 broad relay, 281/281 release, and all static/isolation gates. `E-M6-POSIX-SYSTEM-SSH-FILE-CI-001` proves the 25 cases on all six primary native clients; both Linux 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 27f16c28f3e..b666c057d44 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 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: disconnected POSIX system-SSH/no-tar transfer composition**. 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.
-Session checkpoint: **POSIX SYSTEM-SSH TREE/ROOT EXACT-HEAD GREEN / LIVE FULL-SIZE AUDIT NEXT — 2026-07-15, Codex implementation owner** — `E-M6-POSIX-SYSTEM-SSH-TREE-CI-001` proves the 15-case disconnected composition on all six primary Node 24 native clients at exact head `ec51e36b7`; Linux supplements, Windows x64 floor, PR Checks, computer-use, and Golden E2E pass. The artifact run remains red only for the declared Windows arm64 hosted build 26200 versus required 26100 floor mismatch after complete runtime verification. Audit only a purpose-named live/full-size loopback OpenSSH proof next; do not connect production behavior. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.
+Session checkpoint: **POSIX SYSTEM-SSH LIVE FULL-SIZE LOCAL PACKAGE GREEN / COMMIT AND EXACT-HEAD CI NEXT — 2026-07-15, Codex implementation owner** — `E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-LOCAL-001` records the bounded workflow oracle, focused/broad/release suites, typecheck, lint, max-lines, formatting, diff, protected-resolver, and no-product-consumer gates green. The purpose suite remains honestly skipped without its seven CI inputs and earns no live proof locally. Commit and push only this test/workflow/docs package, then require exact-head Linux x64/arm64 live metrics and all-six adjacent regression cells. Merging to `main` remains prohibited, SignPath is deferred, and 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) @@ -17879,8 +17879,8 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`. separate five-second abort controller. Runtime-invalid staging-root input was corrected before final GREEN so validation cannot throw an incidental `.split` type error. - Purpose command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts - --maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-control-command.test.ts - src/main/ssh/ssh-relay-runtime-posix-tree-transfer.test.ts`. Result: 2 files and 15/15 cases pass; +--maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-control-command.test.ts +src/main/ssh/ssh-relay-runtime-posix-tree-transfer.test.ts`. Result: 2 files and 15/15 cases pass; Vitest 1.95 seconds, 2.96 seconds real, 139,640,832-byte maximum RSS, 95,851,848-byte peak memory footprint, zero swaps and block I/O. Cases cover exact/no-input success, nonzero/error, pre/mid abort, timeout, graceful/forced settlement and joined failures; invalid OS/path/concurrency before @@ -17888,7 +17888,7 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`. progress, default-one/four-channel bounds, collision ownership, cancellation settlement, and cleanup failure/timeout joining. - Focused composition command: `/usr/bin/time -l pnpm exec vitest run --config - config/vitest.config.ts --maxWorkers=1` with the two new purpose suites plus +config/vitest.config.ts --maxWorkers=1` with the two new purpose suites plus `ssh-relay-runtime-posix-file-destination.test.ts`, `ssh-relay-runtime-system-ssh-file-channel.test.ts`, the source-tree/scan/stream suites, `ssh-system-fallback.test.ts`, `ssh-connection.test.ts`, and @@ -17898,13 +17898,13 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`. lease, retained-buffer/no-later-write, missing process/false kill, diagnostic, child settlement, and exact native-workflow-family contracts inherited from the composed boundaries. - Broad relay command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts - --maxWorkers=1 src/main/ssh/ssh-relay-*.test.ts`. Result: 50 files passed, four files skipped; +--maxWorkers=1 src/main/ssh/ssh-relay-*.test.ts`. Result: 50 files passed, four files skipped; 656 passed and six declared skips out of 662. Vitest 55.58 seconds, 58.05 seconds real, 254,115,840-byte maximum RSS, 96,458,248-byte peak memory footprint, zero swaps and block I/O. Skips remain the declared case-fold/full-size/live SSH cells and are not treated as this package's evidence. - Release-contract command: `/usr/bin/time -l pnpm exec vitest run --config - config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-*.test.mjs`. Result: 50 +config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-*.test.mjs`. Result: 50 files and 281/281 cases pass; Vitest 37.74 seconds, 41.85 seconds real, 190,676,992-byte maximum RSS, 97,195,384-byte peak memory footprint, zero swaps and block I/O. - Static commands/results: `/usr/bin/time -l pnpm typecheck` passes in 8.38 seconds real with @@ -17917,7 +17917,7 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`. `pnpm check:max-lines-ratchet` command; no checklist box was credited to the failed spelling. - Isolation evidence: `git diff --check` and no-index whitespace checks for all four new files are clean. `git diff --exit-code HEAD -- src/main/ssh/ssh-remote-node-resolution.ts - src/main/ssh/ssh-remote-node-resolution.test.ts` is empty, preserving the protected Milestone 0 +src/main/ssh/ssh-remote-node-resolution.test.ts` is empty, preserving the protected Milestone 0 resolver. The only implementation references to the exported tree owner are its definition and purpose test; the control owner is consumed only by that disconnected tree owner and its test. No product, settings, legacy uploader, fallback, tuple, publication, or default-path importer @@ -17971,6 +17971,115 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`. publish/launch, product/Beta/fallback/default wiring, tuple enablement, release publication, and SignPath remain absent. +### E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-AUDIT-001 — restricted-primitive full-size OpenSSH proof + +- Date/owner/base: 2026-07-15, Codex implementation owner; exact clean evidence base + `3fbf2339badb7bab0308a6daadb80e7d5444e280` after recording the disconnected tree package's + exact-head evidence. +- Existing-boundary audit: the exact tree owner already composes production `SshConnection.exec`, + the system-SSH child/channel adapter, exclusive POSIX destinations, the scanned lease-backed + source stream, and bounded owned-root cleanup. No production change is necessary for live proof. + The existing live SFTP fixture cannot force a restricted command environment because its + `internal-sftp` subsystem shares the daemon, so do not alter or weaken it. +- Live fixture: on only the Linux x64/arm64 target-native artifact jobs, start a second stock + loopback-only `sshd` on its own port/PID/log with the already-generated ephemeral Ed25519 client + and host keys. Pin the generated public host key into the runner user's `known_hosts`; retain + `StrictModes yes`, disable password/keyboard-interactive authentication, and never expose a + non-loopback listener. `ForceCommand` invokes a fixed fixture-owned POSIX wrapper whose remote + `PATH` contains symlinks only for `mkdir`, `chmod`, `cat`, and `rm`; `/bin/sh` is absolute. This + proves transfer without remote Node, Python, Perl, tar, base64, `sha256sum`, or `shasum`, while + preserving normal production system-SSH argument/channel behavior and authenticated transport. +- Purpose suite: build a scanned source tree from the exact full-size runtime identity produced in + the same native job, force the existing `SshConnection` into system transport only for the test, + and call `transferSshRelayRuntimeTreeViaPosixSystemSsh` directly. Measure serial/default-one and + explicit-four transfers; validate every remote path, byte, SHA-256, mode, file count, and total; + prove a root collision cannot alter the complete tree; cancel after the first transferred bytes, + require no later progress and owned-root removal after settlement; and prove the connection remains + usable. Record tuple/content/server/runner, bytes/files, elapsed time, peak progress concurrency, + incremental RSS, cancellation delay, and residual gaps. Enforce the existing 80 MiB incremental + RSS and 10-second cancellation ceilings under the workflow's 20-minute job budget. +- Evidence sequence: add only the skipped-without-input purpose suite plus workflow oracle first and + accept RED only when the oracle reports the three missing start/measure/stop steps. Then add the + bounded workflow fixture/wiring, require local skip/oracle GREEN plus broad/static/isolation gates, + commit, and require exact-head Linux x64/arm64 live metrics and all-six adjacent regression cells. +- Explicit residual boundary: no high-RTT host, `MaxSessions=1`, cross-client/remote family matrix, + network interruption/disk/quota/inode/noexec/AV fault injection, semantic probe/cache, optional tar + fast path, Windows transfer, remote bundled-Node verification/install/publish/launch, product/Beta/ + settings/fallback/default behavior, tuple enablement, publication, or SignPath is included. Legacy + remains the only production/default path. + +### E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-LOCAL-RED-001 — audited workflow fixture is absent + +- Date/owner/base: 2026-07-15, Codex implementation owner; audit, purpose suite, and workflow-oracle- + only diff atop exact clean evidence base `3fbf2339badb7bab0308a6daadb80e7d5444e280`. +- Command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 +src/main/ssh/ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts +config/scripts/ssh-relay-runtime-workflow.test.mjs`. +- Result: expected RED, exit 1. The purpose-named live suite declares its single case skipped because + the seven full-size/OpenSSH inputs are absent locally and earns no transfer evidence. The workflow + oracle passes 8/9 existing cases and its only failure is `expected undefined to be defined` for + the audited restricted POSIX system-SSH start step; measure and stop are consequently absent too. +- Resource evidence: Vitest 1.50 seconds, 3.73 seconds real, 143,818,752-byte maximum RSS, + 96,146,808-byte peak memory footprint, zero swaps and block I/O. +- Disposition: RED is causal and accepted. Add only the second loopback daemon, restricted primitive + wrapper, exact known-host pin, purpose measurement, and always-run cleanup steps. No production, + product/default, fallback, tuple, publication, tar optimization, Windows transfer, or SignPath + change is authorized by this evidence. + +### E-M6-POSIX-SYSTEM-SSH-TREE-LIVE-LOCAL-001 — bounded live fixture package is locally green + +- Date/owner/base/runner: 2026-07-15, Codex implementation owner; uncommitted test/workflow/docs + package atop exact base `3fbf2339badb7bab0308a6daadb80e7d5444e280` on Apple arm64, macOS 26.2 + build 25C56, Node 26.0.0, pnpm 10.24.0. The local Node version is newer than the repository's Node + 24 contract, so exact-head native workflow evidence remains mandatory. +- Package: add one purpose-named live/full-size test and three audited Linux-only artifact-workflow + steps. The fixture starts a second stock loopback `sshd`, pins the generated host key, permits only + public-key authentication, and forces a wrapper whose `PATH` contains only `mkdir`, `chmod`, `cat`, + and `rm` while invoking absolute `/bin/sh`. The test uses the exact native runtime tree and existing + production system-SSH composition to measure default-one and explicit-four transfer, exact tree + bytes/hashes/modes/paths/counts, exclusive-root collision preservation, cancellation settlement, + no later progress, owned-root cleanup, post-cancellation connection usability, latency, RSS, and + active-file count. No production module changes. +- Targeted command: `pnpm exec oxfmt --check` for the new test, workflow oracle, and artifact workflow; + `pnpm exec oxlint` for the new test and oracle; then `/usr/bin/time -l pnpm exec vitest run --config + config/vitest.config.ts --maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts + config/scripts/ssh-relay-runtime-workflow.test.mjs`. Result: formatting/lint pass; the workflow + oracle passes 9/9 while the one live case is explicitly skipped because its seven CI inputs are + absent. Vitest 12.53 seconds; 25.90 seconds real; 131,907,584-byte maximum RSS; 96,146,904-byte + peak footprint; zero swaps and block I/O. The skip earns no live/full-size transfer evidence. +- Focused composition command: `/usr/bin/time -l pnpm exec vitest run --config + config/vitest.config.ts --maxWorkers=1` with the control/tree/file-destination/system-channel, + source-tree/scan/stream, system-fallback, connection, and workflow-oracle suites. Result: 10 files, + 220 passed and one declared platform skip; Vitest 13.07 seconds; 14.67 seconds real; + 178,061,312-byte maximum RSS; 96,277,904-byte peak footprint; zero swaps and block I/O. +- Broad relay command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts + --maxWorkers=1 src/main/ssh/ssh-relay-*.test.ts`. Result: 50 files passed, five files skipped; 656 + passed and seven declared skips out of 663. Vitest 59.16 seconds; 64.28 seconds real; + 269,565,952-byte maximum RSS; 96,114,160-byte peak footprint; zero swaps and block I/O. The new + live case is one of the declared skips and is not counted as proof. +- Release-contract command: `/usr/bin/time -l pnpm exec vitest run --config + config/vitest.config.ts --maxWorkers=1 config/scripts/ssh-relay-runtime-*.test.mjs`. Result: 50 files + and 282/282 cases pass; Vitest 35.69 seconds; 39.36 seconds real; 190,201,856-byte maximum RSS; + 96,638,376-byte peak footprint; zero swaps and block I/O. +- Static evidence: `/usr/bin/time -l pnpm typecheck` passes in 6.13 seconds real with + 1,241,317,376-byte maximum RSS and 96,163,192-byte peak footprint. `/usr/bin/time -l pnpm lint` + passes in 36.12 seconds real with 2,111,471,616-byte maximum RSS and 95,868,232-byte peak footprint; + its 26 warnings are pre-existing outside this diff. Full lint proves switch exhaustiveness, 41 + reliability gates, the 355-entry max-lines ratchet with no new bypass, bundled guides, and all + localization checks. Standalone max-lines and targeted formatting pass after formatting this + ledger entry; the new test is 377 lines with no suppression. +- Isolation evidence: `git diff --check` passes; `git diff --exit-code HEAD -- + src/main/ssh/ssh-remote-node-resolution.ts src/main/ssh/ssh-remote-node-resolution.test.ts` is empty. + An asserted non-test reference search finds the tree-transfer export only in its owning production + module; this package adds no product importer. The intended diff is limited to the live test, + artifact workflow, workflow oracle, and both checklist documents. No settings, Beta, legacy + uploader, fallback, tuple, publication, default-path, or signing behavior changes. +- Residual boundary: local execution does not supply a Linux OpenSSH daemon or the exact full-size + native runtime, so exact-head Linux x64/arm64 live metrics and all-six adjacent native regression + cells remain required. High RTT, `MaxSessions=1`, cross-family remotes, Windows transfer, primitive + probe/cache, remote bundled-Node verification/install/publish/launch, product/Beta/fallback/default + wiring, tuple enablement, publication, Apple signing, and SignPath remain absent. + ## 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-runtime-posix-system-ssh-openssh-full-size.test.ts b/src/main/ssh/ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts new file mode 100644 index 00000000000..586caede435 --- /dev/null +++ b/src/main/ssh/ssh-relay-runtime-posix-system-ssh-openssh-full-size.test.ts @@ -0,0 +1,377 @@ +import { createHash } from 'node:crypto' +import { lstat, open, opendir, readFile, rm } from 'node:fs/promises' +import { join } from 'node:path' +import { performance } from 'node:perf_hooks' + +import { describe, expect, it, vi } from 'vitest' + +vi.mock('electron', () => ({ app: { getAppPath: () => process.cwd() } })) + +import type { SshTarget } from '../../shared/ssh-types' +import { SshConnection } from './ssh-connection' +import { runSshRelayRuntimePosixControlCommand } from './ssh-relay-runtime-posix-control-command' +import { transferSshRelayRuntimeTreeViaPosixSystemSsh } from './ssh-relay-runtime-posix-tree-transfer' +import { + scanSshRelayRuntimeSourceTree, + type SshRelayRuntimeScannedSourceTree +} from './ssh-relay-runtime-source-scan' +import type { SshRelayRuntimeSourceTree } from './ssh-relay-runtime-source-tree' +import { + SSH_RELAY_RUNTIME_SOURCE_STREAM_LIMITS, + type SshRelayRuntimeSourceStreamProgress +} from './ssh-relay-runtime-source-stream' +import { openSshRelayRuntimeSystemSshFileChannel } from './ssh-relay-runtime-system-ssh-file-channel' + +type MeasurementIdentity = Pick< + SshRelayRuntimeSourceTree, + 'tupleId' | 'contentId' | 'os' | 'architecture' +> & { + entries: ( + | { path: string; type: 'directory'; mode: 0o755 } + | { + path: string + type: 'file' + role: SshRelayRuntimeSourceTree['files'][number]['role'] + size: number + mode: 0o644 | 0o755 + sha256: SshRelayRuntimeSourceTree['files'][number]['sha256'] + } + )[] + archive: { expandedSize: number; fileCount: number } +} + +type Measurement = { + result: T + elapsedMs: number + baselineRss: number + peakRss: number + incrementalRssBytes: number +} + +const host = process.env.ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_HOST +const user = process.env.ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_USER +const identityFile = process.env.ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_IDENTITY +const remoteRoot = process.env.ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_REMOTE_ROOT +const runtimeRoot = process.env.ORCA_SSH_RELAY_FULL_SIZE_RUNTIME_ROOT +const identityPath = process.env.ORCA_SSH_RELAY_FULL_SIZE_IDENTITY +const port = Number.parseInt(process.env.ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_PORT ?? '', 10) +const hasLiveInput = Boolean( + host && + user && + identityFile && + remoteRoot && + runtimeRoot && + identityPath && + Number.isInteger(port) +) + +function parseIdentity(value: unknown): MeasurementIdentity { + if ( + typeof value !== 'object' || + value === null || + !('entries' in value) || + !Array.isArray(value.entries) || + !('archive' in value) || + typeof value.archive !== 'object' || + value.archive === null + ) { + throw new Error('Live POSIX system-SSH measurement identity is incomplete') + } + return value as MeasurementIdentity +} + +function sourceTree(identity: MeasurementIdentity): SshRelayRuntimeSourceTree { + const directories = identity.entries + .filter((entry) => entry.type === 'directory') + .map((entry) => ({ + ...entry, + localPath: join(runtimeRoot as string, ...entry.path.split('/')) + })) + const files = identity.entries + .filter((entry) => entry.type === 'file') + .map((entry) => ({ + ...entry, + localPath: join(runtimeRoot as string, ...entry.path.split('/')) + })) + return Object.freeze({ + tupleId: identity.tupleId, + contentId: identity.contentId, + releaseTag: 'measurement-only', + os: identity.os, + architecture: identity.architecture, + runtimeRoot: runtimeRoot as string, + directories, + files, + fileCount: identity.archive.fileCount, + expandedBytes: identity.archive.expandedSize, + assertLeaseOwned: async () => {} + }) +} + +async function measure(operation: () => Promise): Promise> { + const baselineRss = process.memoryUsage().rss + let peakRss = baselineRss + const sample = (): void => { + peakRss = Math.max(peakRss, process.memoryUsage().rss) + } + const sampler = setInterval(sample, 1) + const startedAt = performance.now() + try { + const result = await operation() + sample() + return { + result, + elapsedMs: performance.now() - startedAt, + baselineRss, + peakRss, + incrementalRssBytes: Math.max(0, peakRss - baselineRss) + } + } finally { + clearInterval(sampler) + } +} + +async function hashFile(path: string): Promise { + const handle = await open(path, 'r') + const digest = createHash('sha256') + const buffer = Buffer.allocUnsafe(64 * 1024) + let position = 0 + try { + while (true) { + const { bytesRead } = await handle.read(buffer, 0, buffer.length, position) + if (bytesRead === 0) { + break + } + digest.update(buffer.subarray(0, bytesRead)) + position += bytesRead + } + } finally { + await handle.close() + } + return `sha256:${digest.digest('hex')}` +} + +async function collectRemotePaths(root: string): Promise { + const paths: string[] = [] + const pending = [{ localPath: root, relativePath: '' }] + while (pending.length > 0) { + const current = pending.pop()! + const directory = await opendir(current.localPath) + for await (const entry of directory) { + const relativePath = current.relativePath + ? `${current.relativePath}/${entry.name}` + : entry.name + paths.push(relativePath) + if (entry.isDirectory()) { + pending.push({ localPath: join(current.localPath, entry.name), relativePath }) + } + } + } + return paths.sort() +} + +async function validateTransferredTree( + tree: SshRelayRuntimeScannedSourceTree, + stage: string +): Promise { + const expectedPaths = [...tree.directories, ...tree.files].map((entry) => entry.path).sort() + // Why: the loopback runner can inspect sshd's filesystem without adding another proof channel. + expect(await collectRemotePaths(stage)).toEqual(expectedPaths) + for (const directory of tree.directories) { + const metadata = await lstat(join(stage, ...directory.path.split('/'))) + expect(metadata.isDirectory()).toBe(true) + expect(metadata.mode & 0o777).toBe(directory.mode) + } + for (const file of tree.files) { + const path = join(stage, ...file.path.split('/')) + const metadata = await lstat(path) + expect(metadata.isFile()).toBe(true) + expect(metadata.size).toBe(file.size) + expect(metadata.mode & 0o777).toBe(file.mode) + expect(await hashFile(path)).toBe(file.sha256) + } +} + +function stagePath(name: string, identity: MeasurementIdentity): string { + return join(remoteRoot as string, `${name}-${identity.contentId.slice('sha256:'.length, 23)}`) +} + +function createProgressRecorder(): { + record: (progress: SshRelayRuntimeSourceStreamProgress) => void + snapshot: () => { bytes: number; updates: number; peakActiveFiles: number } +} { + let bytes = 0 + let updates = 0 + let peakActiveFiles = 0 + return { + record: (progress) => { + bytes = progress.bytesTransferred + updates += 1 + peakActiveFiles = Math.max(peakActiveFiles, progress.activeFiles) + }, + snapshot: () => ({ bytes, updates, peakActiveFiles }) + } +} + +async function transfer( + connection: SshConnection, + tree: SshRelayRuntimeScannedSourceTree, + stage: string, + signal: AbortSignal, + onProgress: (progress: SshRelayRuntimeSourceStreamProgress) => void, + maximumConcurrency?: number +) { + return transferSshRelayRuntimeTreeViaPosixSystemSsh({ + connection, + tree, + remoteStagingRoot: stage, + signal, + onProgress, + ...(maximumConcurrency === undefined ? {} : { maximumConcurrency }) + }) +} + +describe.skipIf(!hasLiveInput)( + 'SSH relay full-size POSIX system-SSH transfer through restricted OpenSSH', + () => { + it( + 'preserves exact bytes and modes with bounded serial, concurrent, and cancellation behavior', + { timeout: 20 * 60_000 }, + async () => { + expect(process.platform).toBe('linux') + expect(process.env.ORCA_SSH_FORCE_SYSTEM_TRANSPORT).toBe('1') + const identity = parseIdentity(JSON.parse(await readFile(identityPath as string, 'utf8'))) + expect(identity.os).toBe('linux') + const tree = await scanSshRelayRuntimeSourceTree( + sourceTree(identity), + new AbortController().signal + ) + const target: SshTarget = { + id: 'live-posix-system-ssh-measurement', + label: 'live-posix-system-ssh-measurement', + host: host as string, + port, + username: user as string, + identityFile: identityFile as string, + identitiesOnly: true, + systemSshConnectionReuse: true, + source: 'manual' + } + const connection = new SshConnection(target, { onStateChange: () => {} }) + const serialStage = stagePath('system-serial', identity) + const concurrentStage = stagePath('system-concurrent', identity) + const cancelledStage = stagePath('system-cancelled', identity) + const stages = [serialStage, concurrentStage, cancelledStage] + await Promise.all(stages.map((stage) => rm(stage, { recursive: true, force: true }))) + + try { + await connection.connect() + expect(connection.usesSystemSshTransport()).toBe(true) + + const serialProgress = createProgressRecorder() + const serial = await measure(() => + transfer( + connection, + tree, + serialStage, + new AbortController().signal, + serialProgress.record + ) + ) + await validateTransferredTree(tree, serialStage) + expect(serialProgress.snapshot().peakActiveFiles).toBe(1) + + const concurrentProgress = createProgressRecorder() + const concurrent = await measure(() => + transfer( + connection, + tree, + concurrentStage, + new AbortController().signal, + concurrentProgress.record, + 4 + ) + ) + await validateTransferredTree(tree, concurrentStage) + expect(concurrentProgress.snapshot().peakActiveFiles).toBeGreaterThan(1) + expect(concurrentProgress.snapshot().peakActiveFiles).toBeLessThanOrEqual(4) + await expect( + transfer(connection, tree, concurrentStage, new AbortController().signal, () => {}) + ).rejects.toBeTruthy() + await validateTransferredTree(tree, concurrentStage) + + const controller = new AbortController() + const cancelledProgress = createProgressRecorder() + let abortRequestedAt = 0 + const cancelled = await measure(async () => { + const outcome = transfer( + connection, + tree, + cancelledStage, + controller.signal, + (progress) => { + cancelledProgress.record(progress) + if (progress.bytesTransferred > 0 && !controller.signal.aborted) { + abortRequestedAt = performance.now() + controller.abort(new Error('live POSIX system-SSH cancellation')) + } + }, + 4 + ) + await expect(outcome).rejects.toThrow('live POSIX system-SSH cancellation') + return performance.now() - abortRequestedAt + }) + const updatesAfterSettlement = cancelledProgress.snapshot().updates + await expect(lstat(cancelledStage)).rejects.toMatchObject({ code: 'ENOENT' }) + await new Promise((resolve) => setTimeout(resolve, 500)) + await expect(lstat(cancelledStage)).rejects.toMatchObject({ code: 'ENOENT' }) + expect(cancelledProgress.snapshot().updates).toBe(updatesAfterSettlement) + + await runSshRelayRuntimePosixControlCommand({ + command: 'true', + signal: new AbortController().signal, + openChannel: (command, signal) => + openSshRelayRuntimeSystemSshFileChannel(connection, command, signal) + }) + expect(connection.getState().status).toBe('connected') + + const metrics = { + tupleId: identity.tupleId, + contentId: identity.contentId, + files: tree.fileCount, + bytes: tree.expandedBytes, + serverVersion: process.env.ORCA_SSH_RELAY_LIVE_SYSTEM_SSH_SERVER_VERSION, + runnerImage: process.env.ImageOS, + runnerVersion: process.env.ImageVersion, + runnerArchitecture: process.env.RUNNER_ARCH, + remotePrimitives: ['/bin/sh', 'mkdir', 'chmod', 'cat', 'rm'], + serialElapsedMs: serial.elapsedMs, + serialIncrementalRssBytes: serial.incrementalRssBytes, + serialPeakActiveFiles: serialProgress.snapshot().peakActiveFiles, + concurrentElapsedMs: concurrent.elapsedMs, + concurrentIncrementalRssBytes: concurrent.incrementalRssBytes, + concurrentPeakActiveFiles: concurrentProgress.snapshot().peakActiveFiles, + cancellationSettlementMs: cancelled.result, + cancellationIncrementalRssBytes: cancelled.incrementalRssBytes, + cancellationProgressUpdates: cancelledProgress.snapshot().updates + } + console.log(`ssh_relay_live_posix_system_ssh=${JSON.stringify(metrics)}`) + expect(serial.result.bytesTransferred).toBe(tree.expandedBytes) + expect(concurrent.result.bytesTransferred).toBe(tree.expandedBytes) + expect(serialProgress.snapshot().bytes).toBe(tree.expandedBytes) + expect(concurrentProgress.snapshot().bytes).toBe(tree.expandedBytes) + expect(serial.incrementalRssBytes).toBeLessThanOrEqual( + SSH_RELAY_RUNTIME_SOURCE_STREAM_LIMITS.maximumIncrementalMemoryBytes + ) + expect(concurrent.incrementalRssBytes).toBeLessThanOrEqual( + SSH_RELAY_RUNTIME_SOURCE_STREAM_LIMITS.maximumIncrementalMemoryBytes + ) + expect(cancelled.result).toBeLessThan(10_000) + } finally { + await connection.disconnect() + await Promise.all(stages.map((stage) => rm(stage, { recursive: true, force: true }))) + } + } + ) + } +)