mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
feat(ssh): add POSIX relay file destination
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -286,6 +286,7 @@ jobs:
|
||||
src/main/ssh/ssh-relay-runtime-source-tree.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-source-scan.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-source-stream.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-sftp-connection-transfer.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-sftp-session.test.ts \
|
||||
@@ -931,6 +932,7 @@ jobs:
|
||||
src/main/ssh/ssh-relay-runtime-source-tree.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-source-scan.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-source-stream.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-sftp-connection-transfer.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-sftp-session.test.ts `
|
||||
|
||||
@@ -164,6 +164,7 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
'ssh-relay-runtime-source-tree.test.ts',
|
||||
'ssh-relay-runtime-source-scan.test.ts',
|
||||
'ssh-relay-runtime-source-stream.test.ts',
|
||||
'ssh-relay-runtime-posix-file-destination.test.ts',
|
||||
'ssh-relay-runtime-sftp-file-destination.test.ts',
|
||||
'ssh-relay-runtime-sftp-connection-transfer.test.ts',
|
||||
'ssh-relay-runtime-sftp-session.test.ts',
|
||||
|
||||
+15
-9
@@ -9,15 +9,21 @@ 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 — exact full-size Linux
|
||||
x64/arm64 OpenSSH SFTP is green; repeated adjacent Windows compiler-test timeout correction is in
|
||||
progress, 2026-07-15, Codex implementation owner.** `E-M6-SFTP-LIVE-COMPOSITION-CI-001` records
|
||||
complete exact-tree/mode/exclusive-root/cancellation/RSS proof against stock OpenSSH on native Linux
|
||||
x64 and arm64. `E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-RED-001` records the only adjacent failure:
|
||||
the unchanged real Windows C# compiler integration case exceeded Vitest's five-second default twice.
|
||||
Current main already fixes that pre-existing test under reviewed commit `45a772cb4` / PR #8897 by
|
||||
scoping 15 seconds only to the real compiler cases. Apply that exact test-only patch and require a
|
||||
fresh exact-head computer-use pass before starting disconnected POSIX system-SSH transfer.
|
||||
Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — disconnected POSIX
|
||||
system-SSH/no-tar per-file boundary is locally green and exact-head CI is next, 2026-07-15, Codex
|
||||
implementation owner.** `E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001` records 25/25 purpose cases, 87 focused
|
||||
cases with one declared skip, 628 broad relay cases with six declared skips, 281/281 release
|
||||
contracts, typecheck, full lint, formatting, max-lines, diff, protected-resolver, and no-product-
|
||||
consumer gates. The command uses only the four declared POSIX primitives and cancellation is bounded
|
||||
to a 250 ms graceful / 2,000 ms total ceiling. Exact-head all-six native and adjacent Actions proof
|
||||
is required before the next package.
|
||||
`E-M6-SFTP-LIVE-COMPOSITION-CI-001` records complete exact-tree/mode/exclusive-root/cancellation/RSS
|
||||
proof against stock OpenSSH on native Linux x64 and arm64. The repeated unrelated Windows compiler
|
||||
timeout is closed at exact head `4945645e8` by computer-use run `29515352845` under
|
||||
`E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-001`. `E-M6-POSIX-SYSTEM-SSH-FILE-AUDIT-001` limits the next
|
||||
slice to one injected command channel and one exclusively created manifest file with bounded chunk,
|
||||
EOF, mode, cancellation, and forced-settlement contracts. No real SSH connection, remote tree,
|
||||
semantic probe, live/full-size claim, product path, mode/fallback, or default behavior is included.
|
||||
`E-M6-SFTP-LIVE-COMPOSITION-AUDIT-001` limits this package to one authenticated built-in
|
||||
`SshConnection`, one raw SFTP channel, the existing one-to-four file transfer, captured-transport-
|
||||
only force close with awaited teardown, and exact full-size Linux x64/arm64 runtime transfer against
|
||||
|
||||
+171
-17
@@ -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: close exact-head live-SFTP adjacent regression evidence before starting disconnected POSIX system-SSH transfer**. 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: **LIVE SFTP GREEN / REPEATED ADJACENT WINDOWS TIMEOUT DIAGNOSED — 2026-07-15, Codex implementation owner** — `E-M6-SFTP-LIVE-COMPOSITION-CI-001` proves exact full-size Linux x64/arm64 transfer through stock OpenSSH with complete tree/mode/exclusive-root/cancellation/RSS evidence. `E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-RED-001` records the only adjacent failure: the unchanged real Windows launcher compilation case exceeded Vitest's five-second default on both attempts. Current `main` independently fixed that exact pre-existing test in reviewed commit `45a772cb4` / PR #8897 by scoping 15 seconds only to the real compiler integration cases. Apply that exact test-only patch, rerun exact head, and do not credit Windows/POSIX system SSH, high-RTT, `MaxSessions=1`, product/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: disconnected POSIX system-SSH/no-tar per-file command and destination 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: **POSIX NO-TAR FILE BOUNDARY LOCALLY GREEN / EXACT-HEAD CI NEXT — 2026-07-15, Codex implementation owner** — `E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001` proves the isolated destination across 25 purpose cases, 628 broad relay cases, 281 release contracts, typecheck, lint, formatting, max-lines, diff, protected-resolver, and no-product-consumer gates. The module still accepts only an injected command channel and has no real SSH/tree/product caller, so exact-head all-six native and adjacent Actions evidence is required before the next package. 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,14 +150,12 @@ 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, RED, and local
|
||||
GREEN complete under E-M6-SOURCE-STREAM-AUDIT-001, E-M6-SOURCE-STREAM-LOCAL-RED-001, and
|
||||
E-M6-SOURCE-STREAM-LOCAL-001. E-M6-SOURCE-STREAM-CI-RED-001 exposes a Windows-only test-oracle
|
||||
mismatch; its POSIX-only correction is locally green under
|
||||
E-M6-SOURCE-STREAM-WINDOWS-ORACLE-CORRECTION-LOCAL-001. Commit/push it, then require fresh
|
||||
exact-head all-six native/full-size and adjacent CI. Do not import SSH, open channels, construct
|
||||
remote paths, create remote staging, or add product/settings/fallback/tuple/publication/default
|
||||
behavior.
|
||||
- Active package: **Local implementation complete / exact-head CI pending — 2026-07-15, Codex
|
||||
implementation owner** — the disconnected POSIX no-tar per-file command/destination boundary is
|
||||
green under E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001. It consumes the existing source-stream
|
||||
destination interface and an injected command-channel factory only. Do not import or modify the
|
||||
legacy tar/base64 directory uploader, wire `SshConnection`, create a remote tree, add a semantic
|
||||
probe, 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
|
||||
@@ -17410,14 +17408,171 @@ src/main/ssh/ssh-relay-runtime-sftp-tree-transfer.test.ts` — PASS, 15/15 in 1.
|
||||
overlap this package. The same workflow's Ubuntu job passes, and the independent all-six native
|
||||
artifact jobs plus PR Checks and Golden E2E pass.
|
||||
- Independent diagnosis: current `origin/main` commit
|
||||
`45a772cb42f8525ce0189fe63a509e562c5b6928` (`test(windows): allow native launcher compilation
|
||||
time`, reviewed PR #8897) changes only the two real Windows compiler integration tests and scopes a
|
||||
`45a772cb42f8525ce0189fe63a509e562c5b6928`
|
||||
(`test(windows): allow native launcher compilation time`, reviewed PR #8897) changes only the two
|
||||
real Windows compiler integration tests and scopes a
|
||||
15-second timeout to them because cold hosted `csc.exe` startup exceeds the unit default. It is
|
||||
not an SFTP change and changes no production behavior. This feature branch predates that commit.
|
||||
- Disposition: apply the exact reviewed test-only patch without broad timeout relaxation, then
|
||||
require a fresh exact-head computer-use pass before advancing. No adjacent green evidence is
|
||||
credited from either failed attempt.
|
||||
|
||||
### E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-001 — scoped upstream test budget closes exact-head adjacent gate
|
||||
|
||||
- Date/owner/head/run: 2026-07-15, Codex implementation owner; exact evidence/test-only commit
|
||||
`4945645e81af971d8ba874d83ee5975abfd6e79d`, computer-use run
|
||||
[29515352845](https://github.com/stablyai/orca/actions/runs/29515352845).
|
||||
- Windows result: job
|
||||
[87678882484](https://github.com/stablyai/orca/actions/runs/29515352845/job/87678882484) passes the
|
||||
401 applicable tests with four declared skips, including both real Windows C# compiler integration
|
||||
cases under their exact reviewed 15-second budgets. It then passes native verification, CLI and
|
||||
packaged Electron builds, daemon boot, Windows daemon workspace-close repro, and Windows
|
||||
computer-use E2E in 4 minutes 38 seconds. No product source or timeout outside the two compiler
|
||||
cases changed.
|
||||
- Control result: Ubuntu job
|
||||
[87678882558](https://github.com/stablyai/orca/actions/runs/29515352845/job/87678882558) passes its
|
||||
unit/native/build/daemon/Linux E2E path in 2 minutes 49 seconds. Local macOS runs passed three
|
||||
applicable launcher cases with the two Windows cases declared skipped plus the eight-case relay
|
||||
workflow oracle; formatting and diff gates passed before commit.
|
||||
- Disposition: the repeated adjacent RED is resolved by the exact independently reviewed upstream
|
||||
test-only correction. Prior live SFTP evidence remains anchored to `a72c32f63`; this commit changes
|
||||
no SFTP/runtime/product code. The separately triggered all-six artifact rerun is redundant control
|
||||
evidence and may continue while the next disconnected package begins.
|
||||
|
||||
### E-M6-SFTP-LIVE-COMPOSITION-CONTROL-CI-001 — redundant exact-head control classified
|
||||
|
||||
- Date/owner/head: 2026-07-15, Codex implementation owner; exact test/docs-only head
|
||||
`4945645e81af971d8ba874d83ee5975abfd6e79d` after the adjacent correction above.
|
||||
- Runtime artifact run: [29515352928](https://github.com/stablyai/orca/actions/runs/29515352928).
|
||||
All six primary native jobs pass: Windows arm64 [87678883773](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883773),
|
||||
Windows x64 [87678883819](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883819),
|
||||
macOS arm64 [87678883853](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883853),
|
||||
Linux arm64 [87678883887](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883887),
|
||||
Linux x64 [87678883950](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883950),
|
||||
and macOS x64 [87678883952](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883952).
|
||||
Both Linux oldest-userland supplements pass in jobs 87680141129 and 87680141145, as does the
|
||||
Windows x64 floor in job 87681071541.
|
||||
- Declared residual: only Windows arm64 floor job
|
||||
[87681071534](https://github.com/stablyai/orca/actions/runs/29515352928/job/87681071534) fails after
|
||||
complete runtime smoke because the hosted runner reports build 26200 while the immutable contract
|
||||
requires build 26100; platform and architecture pass, `osBuild` fails. This is the already recorded
|
||||
runner gap, not a test-only correction or SFTP regression.
|
||||
- Adjacent controls: PR Checks run
|
||||
[29515352911](https://github.com/stablyai/orca/actions/runs/29515352911) passes lint, typecheck, Git
|
||||
compatibility, tests, unpacked app build, and packaged CLI smoke. Golden E2E run
|
||||
[29515352860](https://github.com/stablyai/orca/actions/runs/29515352860) passes both macOS job
|
||||
87678882828 and Linux job 87678882845.
|
||||
- Disposition: this closes classification of the redundant control run only. Live SFTP evidence
|
||||
remains anchored to `a72c32f63`; no POSIX system-SSH claim or later-package evidence is inferred.
|
||||
|
||||
### E-M6-POSIX-SYSTEM-SSH-FILE-AUDIT-001 — isolated no-tar file channel boundary
|
||||
|
||||
- Date/owner/base: 2026-07-15, Codex implementation owner; clean exact base
|
||||
`4945645e81af971d8ba874d83ee5975abfd6e79d` after the adjacent gate above.
|
||||
- Existing-code audit: `system-ssh-file-transfer.ts` is the proven legacy/default uploader and uses
|
||||
local plus remote tar on POSIX and a whole-tree JSON/base64 buffer on Windows. Those paths remain
|
||||
available but violate the new runtime bootstrap contract and must not be reused, narrowed, or
|
||||
modified in this slice. `ssh-relay-runtime-source-stream.ts` already owns the exact scanned source,
|
||||
one 64 KiB buffer per worker, hash/size/snapshot checks, and joined destination cleanup.
|
||||
- New boundary: add one purpose-named POSIX file-destination module that implements only
|
||||
`SshRelayRuntimeSourceDestination`. It receives an absolute POSIX manifest path, authenticated
|
||||
mode (`0644` or `0755`), the exact caller signal, and an injected command-channel factory. The
|
||||
generated POSIX `sh` command uses only `umask`, `set -C`, byte-preserving `cat`, and `chmod`; it
|
||||
shell-quotes the path, exclusively creates the file with mode `0600` during transfer, and applies
|
||||
the authenticated final mode only after stdin EOF and successful `cat` settlement. It never uses
|
||||
Node, Python, Perl, tar, base64, a checksum tool, a whole-file buffer, or a remote download.
|
||||
- Lifecycle contract: one write may be active; each write resolves only after the channel accepts
|
||||
the exact non-empty chunk so the source worker may safely reuse its buffer. Close sends EOF once
|
||||
and awaits a successful remote exit before declaring completion. Abort is idempotent, settles any
|
||||
active write, requests graceful channel termination, escalates after a short fixed grace to forced
|
||||
termination, and fails closed if the bounded total settlement window expires. A later tree owner,
|
||||
not this file boundary, owns removal of an incomplete proved-owned path after the channel settles.
|
||||
- Layer-A proof: purpose tests must cover hostile/relative/newline/NUL paths, invalid modes, exact
|
||||
shell quoting and forbidden-command absence, exclusive/restrictive/final-mode command order,
|
||||
zero-byte EOF, multi-chunk buffer callback order, concurrent-operation rejection, remote nonzero
|
||||
exit, pre/mid-write/mid-close cancellation, graceful and forced settlement, final timeout, cleanup
|
||||
failure propagation, idempotent abort, and no write after cancellation. Pin the purpose suite once
|
||||
in both native workflow command families before implementation.
|
||||
- Explicit residual boundary: no real SSH process or connection, directory/tree staging, primitive
|
||||
semantic probe, `MaxSessions=1`, one-versus-four policy, tar optimization, full-size/live transfer,
|
||||
bundled-Node remote verification/install/publish/launch, product/Beta/settings/fallback/tuple/
|
||||
publication/default behavior, Windows PowerShell path, or SignPath is included. Legacy remains the
|
||||
sole production/default path.
|
||||
|
||||
### E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-RED-001 — missing production boundary fails as designed
|
||||
|
||||
- Date/owner/base: 2026-07-15, Codex implementation owner; exact uncommitted test/workflow state on
|
||||
base `4945645e81af971d8ba874d83ee5975abfd6e79d` after
|
||||
`E-M6-POSIX-SYSTEM-SSH-FILE-AUDIT-001`.
|
||||
- Command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
|
||||
--maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts
|
||||
config/scripts/ssh-relay-runtime-workflow.test.mjs`.
|
||||
- Result: expected RED, exit 1. The new purpose suite collects zero cases and fails solely with
|
||||
`Cannot find module './ssh-relay-runtime-posix-file-destination'`; that production module does not
|
||||
exist yet. The independent release-workflow oracle passes all 8/8 cases, proving the purpose suite
|
||||
appears exactly once in each native command family before implementation.
|
||||
- Resource evidence: Vitest duration 671 ms; `/usr/bin/time -l` reports 1.86 seconds real,
|
||||
132,153,344 bytes maximum resident set size, zero swaps, and zero block input/output operations.
|
||||
- Disposition: RED is causally narrow and accepted. Implement only the audited injected-channel
|
||||
destination, then require purpose GREEN plus focused, broad, static, isolation, and exact-head
|
||||
native workflow evidence. No live SSH, tree transfer, product path, or default-path claim follows
|
||||
from this evidence.
|
||||
|
||||
### E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-TYPE-RED-001 — test-only type boundary corrected
|
||||
|
||||
- Date/owner/state: 2026-07-15, Codex implementation owner; first static pass on the uncommitted
|
||||
implementation atop `4945645e81af971d8ba874d83ee5975abfd6e79d`.
|
||||
- Command/result: `/usr/bin/time -l pnpm typecheck` — expected gate RED after runtime tests were
|
||||
already green, exit 1 in 3.23 seconds real with 1,032,896,512-byte maximum RSS. TypeScript reports
|
||||
only three purpose-test errors: the intentionally invalid numeric mode crosses the production
|
||||
literal union, and two zero-argument `vi.fn` channel factories expose no command tuple to the
|
||||
assertions. No production line fails typechecking.
|
||||
- Correction: constrain the deliberate runtime-invalid mode at the test call boundary and declare
|
||||
the two mock command/signal parameters. The 25-case purpose suite and repeated typecheck below pass
|
||||
after that test-only correction. No product, transport, or runtime behavior changed.
|
||||
|
||||
### E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001 — isolated file channel boundary is locally green
|
||||
|
||||
- Date/owner/environment/base: 2026-07-15, Codex implementation owner; local macOS arm64, Node
|
||||
26.0.0, pnpm 10.24.0, uncommitted package atop exact base
|
||||
`4945645e81af971d8ba874d83ee5975abfd6e79d`.
|
||||
- Implementation: the 318-line purpose-named module accepts one absolute normalized POSIX manifest
|
||||
path, authenticated `0644`/`0755`, exact signal, and injected channel factory. Its exact command
|
||||
uses only `umask 077`, `set -C`, byte-preserving `cat`, and `chmod`; single-quote escaping is
|
||||
covered. It awaits every non-empty write callback, sends EOF once, requires successful remote
|
||||
settlement, and bounds cancellation to a 250 ms graceful window plus a 2,000 ms total ceiling with
|
||||
force-close escalation. Cancellation never sends EOF and does not release an active retained
|
||||
chunk until channel settlement or the fail-closed ceiling.
|
||||
- Purpose command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
|
||||
--maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts` — PASS, 25/25 in
|
||||
275 ms Vitest / 1.31 seconds real, 132,743,168-byte maximum RSS, zero swaps, and zero block I/O.
|
||||
Cases cover both final modes; exact/forbidden command content; hostile paths; empty/write-error/
|
||||
retained chunks; concurrent operations; zero-byte EOF; nonzero remote exit; pre-open, mid-open,
|
||||
mid-write, and mid-close cancellation; graceful/forced/timeout settlement; cleanup failure; and
|
||||
idempotent abort/no later EOF or write.
|
||||
- Focused command: purpose, source stream/scan/tree, and native-workflow oracle — PASS, five files,
|
||||
87 cases plus one declared platform skip in 7.49 seconds Vitest / 8.98 seconds real with
|
||||
144,556,032-byte maximum RSS. The workflow oracle passes 8/8 and pins the suite exactly once in
|
||||
both POSIX and PowerShell native command families.
|
||||
- Broad commands: `src/main/ssh/ssh-relay-*.test.ts` — PASS, 47 files plus four declared skipped,
|
||||
628 cases plus six declared skips in 25.04 seconds Vitest / 26.50 seconds real with
|
||||
244,596,736-byte maximum RSS; `config/scripts/ssh-relay-runtime-*.test.mjs` — PASS, 50 files and
|
||||
281/281 cases in 22.34 seconds Vitest / 24.48 seconds real with 190,545,920-byte maximum RSS. Only
|
||||
the established stale-lock diagnostic and local Node 26 module deprecation appear.
|
||||
- Static/isolation commands: repeated `/usr/bin/time -l pnpm typecheck` passes in 3.32 seconds real
|
||||
with 1,241,726,976-byte maximum RSS. Targeted `oxlint`, targeted `oxfmt --check`, standalone
|
||||
max-lines ratchet, `git diff --check`, protected-resolver empty diff, and no non-test consumer pass.
|
||||
Full `pnpm lint` passes in 18.10 seconds real with 2,052,751,360-byte maximum RSS: switch
|
||||
exhaustiveness, 41 reliability gates, the 355-entry max-lines ratchet, bundled-skill verification,
|
||||
9,837 localization references, locale parity, and zero-candidate localization coverage pass; only
|
||||
pre-existing unrelated warnings remain. Module/test sizes are 318/325 lines with no bypass.
|
||||
- Residual: this is an injected callback-channel contract, not a real SSH process, `SshConnection`
|
||||
adapter, tree/root staging owner, semantic primitive probe, tar optimization, `MaxSessions=1`,
|
||||
full-size/live transfer, or Windows PowerShell path. It has no non-test consumer. Remote bundled-
|
||||
Node verification/install/publish/launch, product/Beta/fallback/tuple/publication/default behavior,
|
||||
and SignPath remain absent; legacy remains the sole production/default path. Exact-head all-six
|
||||
native and adjacent Actions evidence is required before the next bounded package.
|
||||
|
||||
## Accepted Gaps
|
||||
|
||||
No product gap is accepted merely because it appears in this list. Each entry requires explicit
|
||||
@@ -17476,11 +17631,10 @@ The project is not complete until every applicable item below is checked with ev
|
||||
|
||||
## Next Required Action
|
||||
|
||||
Apply current-main commit `45a772cb4`'s exact scoped Windows compiler-test timeout patch, commit and
|
||||
push this evidence checkpoint, and require a fresh exact-head computer-use pass. Then audit the next
|
||||
smallest disconnected POSIX system-SSH/no-tar transfer package before changing code. Do not check
|
||||
the broad Milestone 6 SFTP item yet: Windows remote, cross-client, high-RTT, `MaxSessions=1`, live
|
||||
channel/handle, and fault-injection cells remain open. Do not add an Electron/startup/product
|
||||
Commit and push the locally green isolated POSIX no-tar per-file boundary, then require exact-head
|
||||
all-six native artifact jobs, both Linux supplements, the Windows x64 floor, PR Checks, Golden E2E,
|
||||
and computer-use evidence before auditing the next system-SSH adapter/tree slice. Do not
|
||||
check the broad Milestone 6 SFTP or POSIX system-SSH items yet. Do not add an Electron/startup/product
|
||||
importer, per-target 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,
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
openSshRelayRuntimePosixFileDestination,
|
||||
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS,
|
||||
type SshRelayRuntimePosixFileChannel
|
||||
} from './ssh-relay-runtime-posix-file-destination'
|
||||
|
||||
type Callback = (error?: Error) => void
|
||||
|
||||
function createChannel(): {
|
||||
channel: SshRelayRuntimePosixFileChannel
|
||||
resolve: () => void
|
||||
reject: (error: Error) => void
|
||||
} {
|
||||
let resolveSettled: () => void = () => {}
|
||||
let rejectSettled: (error: Error) => void = () => {}
|
||||
const settled = new Promise<void>((resolve, reject) => {
|
||||
resolveSettled = resolve
|
||||
rejectSettled = reject
|
||||
})
|
||||
const channel: SshRelayRuntimePosixFileChannel = {
|
||||
write: vi.fn((_chunk: Buffer, callback: Callback) => callback()),
|
||||
end: vi.fn(),
|
||||
settled,
|
||||
requestClose: vi.fn(),
|
||||
forceClose: vi.fn()
|
||||
}
|
||||
return { channel, resolve: resolveSettled, reject: rejectSettled }
|
||||
}
|
||||
|
||||
function openDestination(
|
||||
channel: SshRelayRuntimePosixFileChannel,
|
||||
options: {
|
||||
remotePath?: string
|
||||
mode?: number
|
||||
signal?: AbortSignal
|
||||
openChannel?: (command: string, signal: AbortSignal) => Promise<SshRelayRuntimePosixFileChannel>
|
||||
} = {}
|
||||
) {
|
||||
return openSshRelayRuntimePosixFileDestination({
|
||||
remotePath: options.remotePath ?? "/owned stage/bin/no'de",
|
||||
mode: (options.mode ?? 0o755) as 0o644 | 0o755,
|
||||
signal: options.signal ?? new AbortController().signal,
|
||||
openChannel: options.openChannel ?? vi.fn(async () => channel)
|
||||
})
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
describe('SSH relay runtime POSIX no-tar file destination', () => {
|
||||
it('builds one exclusive restrictive command with exact quoting and final mode order', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
const openChannel = vi.fn(async (_command: string, _signal: AbortSignal) => channel)
|
||||
const destination = await openDestination(channel, {
|
||||
signal: controller.signal,
|
||||
openChannel
|
||||
})
|
||||
|
||||
expect(openChannel).toHaveBeenCalledWith(
|
||||
"umask 077; set -C; cat > '/owned stage/bin/no'\\''de' && chmod 0755 '/owned stage/bin/no'\\''de'",
|
||||
controller.signal
|
||||
)
|
||||
const command = openChannel.mock.calls[0]?.[0] ?? ''
|
||||
for (const forbidden of ['node ', 'python', 'perl', 'tar ', 'base64', 'sha256sum', 'shasum']) {
|
||||
expect(command.toLowerCase()).not.toContain(forbidden)
|
||||
}
|
||||
|
||||
const closing = destination.close()
|
||||
expect(channel.end).toHaveBeenCalledOnce()
|
||||
resolve()
|
||||
await closing
|
||||
})
|
||||
|
||||
it('authenticates a non-executable final mode in the same command', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const openChannel = vi.fn(async (_command: string, _signal: AbortSignal) => channel)
|
||||
const destination = await openDestination(channel, {
|
||||
remotePath: '/owned-stage/目录/manifest.json',
|
||||
mode: 0o644,
|
||||
openChannel
|
||||
})
|
||||
|
||||
expect(openChannel.mock.calls[0]?.[0]).toBe(
|
||||
"umask 077; set -C; cat > '/owned-stage/目录/manifest.json' && chmod 0644 '/owned-stage/目录/manifest.json'"
|
||||
)
|
||||
const closing = destination.close()
|
||||
resolve()
|
||||
await closing
|
||||
})
|
||||
|
||||
it.each([
|
||||
['', 0o755],
|
||||
['relative/file', 0o755],
|
||||
['/', 0o755],
|
||||
['/owned//file', 0o755],
|
||||
['/owned/../file', 0o755],
|
||||
['/owned/./file', 0o755],
|
||||
['/owned/file/', 0o755],
|
||||
['/owned/file\nnext', 0o755],
|
||||
['/owned/file\0next', 0o755],
|
||||
['/owned/file', 0o600]
|
||||
])('rejects hostile path %j or mode %d before opening a channel', async (remotePath, mode) => {
|
||||
const { channel } = createChannel()
|
||||
const openChannel = vi.fn(async () => channel)
|
||||
|
||||
await expect(openDestination(channel, { remotePath, mode, openChannel })).rejects.toThrow(
|
||||
/invalid/i
|
||||
)
|
||||
expect(openChannel).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends EOF once for a zero-byte file and awaits remote settlement', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const destination = await openDestination(channel)
|
||||
let settled = false
|
||||
const closing = destination.close().then(() => {
|
||||
settled = true
|
||||
})
|
||||
|
||||
await Promise.resolve()
|
||||
expect(channel.end).toHaveBeenCalledOnce()
|
||||
expect(settled).toBe(false)
|
||||
resolve()
|
||||
await closing
|
||||
await expect(destination.close()).rejects.toThrow(/closed/i)
|
||||
expect(channel.end).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('awaits exact chunk callbacks and rejects concurrent operations', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
let firstCallback: Callback | undefined
|
||||
vi.mocked(channel.write).mockImplementationOnce((_chunk, callback) => {
|
||||
firstCallback = callback
|
||||
})
|
||||
const destination = await openDestination(channel)
|
||||
const first = Buffer.from('first')
|
||||
let settled = false
|
||||
const writing = destination.write(first).then(() => {
|
||||
settled = true
|
||||
})
|
||||
|
||||
await Promise.resolve()
|
||||
expect(channel.write).toHaveBeenCalledWith(first, expect.any(Function))
|
||||
expect(settled).toBe(false)
|
||||
await expect(destination.write(Buffer.from('second'))).rejects.toThrow(/concurrent/i)
|
||||
await expect(destination.close()).rejects.toThrow(/concurrent/i)
|
||||
firstCallback?.()
|
||||
await writing
|
||||
await destination.write(Buffer.from('second'))
|
||||
const closing = destination.close()
|
||||
resolve()
|
||||
await closing
|
||||
})
|
||||
|
||||
it('rejects empty chunks and propagates channel write failures', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
vi.mocked(channel.write).mockImplementationOnce((_chunk, callback) => {
|
||||
callback(new Error('channel write failed'))
|
||||
})
|
||||
const destination = await openDestination(channel)
|
||||
|
||||
await expect(destination.write(Buffer.alloc(0))).rejects.toThrow(/empty/i)
|
||||
await expect(destination.write(Buffer.from('payload'))).rejects.toThrow('channel write failed')
|
||||
const closing = destination.close()
|
||||
resolve()
|
||||
await closing
|
||||
})
|
||||
|
||||
it('propagates remote nonzero settlement on close', async () => {
|
||||
const { channel, reject } = createChannel()
|
||||
const destination = await openDestination(channel)
|
||||
const closing = destination.close()
|
||||
reject(new Error('remote cat failed (exit 1)'))
|
||||
|
||||
await expect(closing).rejects.toThrow('remote cat failed')
|
||||
})
|
||||
|
||||
it('rejects pre-open cancellation without opening a channel', async () => {
|
||||
const { channel } = createChannel()
|
||||
const controller = new AbortController()
|
||||
const reason = new Error('cancelled before open')
|
||||
controller.abort(reason)
|
||||
const openChannel = vi.fn(async () => channel)
|
||||
|
||||
await expect(openDestination(channel, { signal: controller.signal, openChannel })).rejects.toBe(
|
||||
reason
|
||||
)
|
||||
expect(openChannel).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('settles a channel returned after mid-open cancellation', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
const reason = new Error('cancelled during open')
|
||||
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
|
||||
const openChannel = vi.fn(async () => {
|
||||
controller.abort(reason)
|
||||
return channel
|
||||
})
|
||||
|
||||
await expect(openDestination(channel, { signal: controller.signal, openChannel })).rejects.toBe(
|
||||
reason
|
||||
)
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
expect(channel.forceClose).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('holds a retained write buffer until cancellation settles the channel', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
const reason = new Error('cancelled during write')
|
||||
vi.mocked(channel.write).mockImplementation(() => {})
|
||||
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
const chunk = Buffer.alloc(64 * 1024, 7)
|
||||
let settled = false
|
||||
const writing = destination.write(chunk).finally(() => {
|
||||
settled = true
|
||||
})
|
||||
|
||||
controller.abort(reason)
|
||||
await Promise.resolve()
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
await expect(writing).rejects.toBe(reason)
|
||||
expect(settled).toBe(true)
|
||||
await expect(destination.abort(reason)).resolves.toBeUndefined()
|
||||
await expect(destination.write(Buffer.from('later'))).rejects.toThrow(/closed/i)
|
||||
expect(channel.write).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('cancels a close only after the command channel settles', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
const reason = new Error('cancelled during close')
|
||||
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
const closing = destination.close()
|
||||
|
||||
controller.abort(reason)
|
||||
await expect(destination.abort(reason)).resolves.toBeUndefined()
|
||||
await expect(closing).rejects.toBe(reason)
|
||||
expect(channel.end).toHaveBeenCalledOnce()
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('settles ordinary cancellation during the graceful window', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
|
||||
controller.abort(new Error('cancelled'))
|
||||
await expect(destination.abort(controller.signal.reason)).resolves.toBeUndefined()
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
expect(channel.forceClose).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('forces a channel that does not settle during the graceful window', async () => {
|
||||
vi.useFakeTimers()
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
vi.mocked(channel.forceClose).mockImplementation(() => resolve())
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
|
||||
controller.abort(new Error('cancelled'))
|
||||
const aborting = destination.abort(controller.signal.reason)
|
||||
await vi.advanceTimersByTimeAsync(
|
||||
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.gracefulCloseMs
|
||||
)
|
||||
await expect(aborting).resolves.toBeUndefined()
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
expect(channel.forceClose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('fails closed when forced channel settlement reaches the total ceiling', async () => {
|
||||
vi.useFakeTimers()
|
||||
const { channel } = createChannel()
|
||||
const controller = new AbortController()
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
|
||||
controller.abort(new Error('cancelled'))
|
||||
const aborting = destination.abort(controller.signal.reason)
|
||||
await vi.advanceTimersByTimeAsync(SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.totalCloseMs)
|
||||
|
||||
await expect(aborting).rejects.toThrow(/settlement timed out/i)
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
expect(channel.forceClose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('propagates cleanup request failures after the channel settles', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
vi.mocked(channel.requestClose).mockImplementation(() => {
|
||||
resolve()
|
||||
throw new Error('graceful close failed')
|
||||
})
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
|
||||
controller.abort(new Error('cancelled'))
|
||||
await expect(destination.abort(controller.signal.reason)).rejects.toThrow(
|
||||
'graceful close failed'
|
||||
)
|
||||
})
|
||||
|
||||
it('makes abort idempotent and never sends EOF after cancellation', async () => {
|
||||
const { channel, resolve } = createChannel()
|
||||
const controller = new AbortController()
|
||||
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
|
||||
const destination = await openDestination(channel, { signal: controller.signal })
|
||||
const reason = new Error('cancelled')
|
||||
|
||||
controller.abort(reason)
|
||||
const first = destination.abort(reason)
|
||||
const second = destination.abort(reason)
|
||||
expect(first).toBe(second)
|
||||
await first
|
||||
expect(channel.requestClose).toHaveBeenCalledOnce()
|
||||
expect(channel.end).not.toHaveBeenCalled()
|
||||
await expect(destination.close()).rejects.toThrow(/closed/i)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,318 @@
|
||||
import type { SshRelayRuntimeSourceDestination } from './ssh-relay-runtime-source-stream'
|
||||
|
||||
type ChannelWriteCallback = (error?: Error) => void
|
||||
|
||||
export type SshRelayRuntimePosixFileChannel = Readonly<{
|
||||
write: (chunk: Buffer, callback: ChannelWriteCallback) => void
|
||||
end: () => void
|
||||
settled: Promise<void>
|
||||
requestClose: () => void
|
||||
forceClose: () => void
|
||||
}>
|
||||
|
||||
export type OpenSshRelayRuntimePosixFileDestinationOptions = Readonly<{
|
||||
remotePath: string
|
||||
mode: 0o644 | 0o755
|
||||
signal: AbortSignal
|
||||
openChannel: (command: string, signal: AbortSignal) => Promise<SshRelayRuntimePosixFileChannel>
|
||||
}>
|
||||
|
||||
export const SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS = Object.freeze({
|
||||
gracefulCloseMs: 250,
|
||||
totalCloseMs: 2_000
|
||||
})
|
||||
|
||||
type DestinationState = 'open' | 'closing' | 'complete' | 'aborting' | 'aborted'
|
||||
|
||||
type ChannelSettlement = Readonly<{ ok: true } | { ok: false; error: unknown }>
|
||||
|
||||
type AbortOutcome = Readonly<{ reason: unknown }>
|
||||
|
||||
function validateRemotePath(remotePath: string): void {
|
||||
const segments = remotePath.split('/')
|
||||
if (
|
||||
remotePath === '/' ||
|
||||
!remotePath.startsWith('/') ||
|
||||
remotePath.includes('\0') ||
|
||||
remotePath.includes('\n') ||
|
||||
remotePath.includes('\r') ||
|
||||
segments.slice(1).some((segment) => segment === '' || segment === '.' || segment === '..')
|
||||
) {
|
||||
throw new Error('SSH relay runtime POSIX file destination path is invalid')
|
||||
}
|
||||
}
|
||||
|
||||
function validateOptions(options: OpenSshRelayRuntimePosixFileDestinationOptions): void {
|
||||
if (
|
||||
!options ||
|
||||
typeof options.remotePath !== 'string' ||
|
||||
typeof options.openChannel !== 'function' ||
|
||||
!options.signal
|
||||
) {
|
||||
throw new Error('SSH relay runtime POSIX file destination input is invalid')
|
||||
}
|
||||
validateRemotePath(options.remotePath)
|
||||
if (options.mode !== 0o644 && options.mode !== 0o755) {
|
||||
throw new Error('SSH relay runtime POSIX file destination mode is invalid')
|
||||
}
|
||||
}
|
||||
|
||||
function validateChannel(channel: SshRelayRuntimePosixFileChannel): void {
|
||||
if (
|
||||
!channel ||
|
||||
typeof channel.write !== 'function' ||
|
||||
typeof channel.end !== 'function' ||
|
||||
typeof channel.settled?.then !== 'function' ||
|
||||
typeof channel.requestClose !== 'function' ||
|
||||
typeof channel.forceClose !== 'function'
|
||||
) {
|
||||
throw new Error('SSH relay runtime POSIX file channel is invalid')
|
||||
}
|
||||
}
|
||||
|
||||
function quotePosixShellArgument(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\\''")}'`
|
||||
}
|
||||
|
||||
function buildCommand(remotePath: string, mode: 0o644 | 0o755): string {
|
||||
const quotedPath = quotePosixShellArgument(remotePath)
|
||||
const finalMode = mode.toString(8).padStart(4, '0')
|
||||
// Why: noclobber plus umask makes authenticated staging exclusive and non-readable until EOF.
|
||||
return `umask 077; set -C; cat > ${quotedPath} && chmod ${finalMode} ${quotedPath}`
|
||||
}
|
||||
|
||||
function cleanupFailure(failures: readonly unknown[]): unknown | undefined {
|
||||
if (failures.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return failures.length === 1
|
||||
? failures[0]
|
||||
: new AggregateError(failures, 'SSH relay runtime POSIX file channel cleanup failed')
|
||||
}
|
||||
|
||||
function waitForSettlement(
|
||||
settlement: Promise<ChannelSettlement>,
|
||||
timeoutMs: number
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => resolve(false), timeoutMs)
|
||||
void settlement.then(() => {
|
||||
clearTimeout(timeout)
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function settleCancelledChannel(
|
||||
channel: SshRelayRuntimePosixFileChannel,
|
||||
settlement: Promise<ChannelSettlement>
|
||||
): Promise<void> {
|
||||
const failures: unknown[] = []
|
||||
try {
|
||||
channel.requestClose()
|
||||
} catch (error) {
|
||||
failures.push(error)
|
||||
}
|
||||
|
||||
const graceful = await waitForSettlement(
|
||||
settlement,
|
||||
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.gracefulCloseMs
|
||||
)
|
||||
if (!graceful) {
|
||||
try {
|
||||
channel.forceClose()
|
||||
} catch (error) {
|
||||
failures.push(error)
|
||||
}
|
||||
const forced = await waitForSettlement(
|
||||
settlement,
|
||||
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.totalCloseMs -
|
||||
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.gracefulCloseMs
|
||||
)
|
||||
if (!forced) {
|
||||
failures.unshift(new Error('SSH relay runtime POSIX file channel settlement timed out'))
|
||||
}
|
||||
}
|
||||
|
||||
const failure = cleanupFailure(failures)
|
||||
if (failure !== undefined) {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
function createDestination(
|
||||
options: OpenSshRelayRuntimePosixFileDestinationOptions,
|
||||
channel: SshRelayRuntimePosixFileChannel
|
||||
): SshRelayRuntimeSourceDestination {
|
||||
const { signal } = options
|
||||
const channelSettlement: Promise<ChannelSettlement> = channel.settled.then(
|
||||
() => ({ ok: true }),
|
||||
(error: unknown) => ({ ok: false, error })
|
||||
)
|
||||
let state: DestinationState = 'open'
|
||||
let activeOperation: Promise<void> | undefined
|
||||
let abortPromise: Promise<void> | undefined
|
||||
let abortReason: unknown
|
||||
let resolveAbortOutcome: (outcome: AbortOutcome) => void = () => {}
|
||||
const abortOutcome = new Promise<AbortOutcome>((resolve) => {
|
||||
resolveAbortOutcome = resolve
|
||||
})
|
||||
|
||||
const assertWritable = (): void => {
|
||||
if (state !== 'open') {
|
||||
throw new Error('SSH relay runtime POSIX file destination is closed')
|
||||
}
|
||||
if (activeOperation) {
|
||||
throw new Error('SSH relay runtime POSIX file destination has a concurrent operation')
|
||||
}
|
||||
}
|
||||
|
||||
const runExclusive = (operation: () => Promise<void>): Promise<void> => {
|
||||
const pending = operation()
|
||||
activeOperation = pending
|
||||
void pending
|
||||
.finally(() => {
|
||||
if (activeOperation === pending) {
|
||||
activeOperation = undefined
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
return pending
|
||||
}
|
||||
|
||||
const waitForOperation = async (operation: Promise<void>): Promise<void> => {
|
||||
const result = await Promise.race([
|
||||
operation.then(
|
||||
() => ({ kind: 'complete' as const }),
|
||||
(error: unknown) => ({ kind: 'failed' as const, error })
|
||||
),
|
||||
abortOutcome.then((outcome) => ({ kind: 'aborted' as const, outcome }))
|
||||
])
|
||||
if (result.kind === 'aborted') {
|
||||
throw result.outcome.reason
|
||||
}
|
||||
if (state === 'aborting' || state === 'aborted') {
|
||||
const outcome = await abortOutcome
|
||||
throw outcome.reason
|
||||
}
|
||||
if (result.kind === 'failed') {
|
||||
throw result.error
|
||||
}
|
||||
}
|
||||
|
||||
const performAbort = async (): Promise<void> => {
|
||||
const reason = abortReason
|
||||
let failure: unknown
|
||||
try {
|
||||
await settleCancelledChannel(channel, channelSettlement)
|
||||
} catch (error) {
|
||||
failure = error
|
||||
} finally {
|
||||
state = 'aborted'
|
||||
signal.removeEventListener('abort', onSignalAbort)
|
||||
// Why: retained transport buffers are released only after the channel has settled or timed out.
|
||||
resolveAbortOutcome({ reason })
|
||||
}
|
||||
if (failure !== undefined) {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
const abort = (reason: unknown): Promise<void> => {
|
||||
if (abortPromise) {
|
||||
return abortPromise
|
||||
}
|
||||
if (state === 'complete' || state === 'aborted') {
|
||||
abortPromise = Promise.resolve()
|
||||
return abortPromise
|
||||
}
|
||||
state = 'aborting'
|
||||
abortReason = reason
|
||||
abortPromise = performAbort()
|
||||
return abortPromise
|
||||
}
|
||||
|
||||
function onSignalAbort(): void {
|
||||
void abort(signal.reason).catch(() => {})
|
||||
}
|
||||
|
||||
const write = (chunk: Buffer): Promise<void> => {
|
||||
try {
|
||||
assertWritable()
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
if (!Buffer.isBuffer(chunk) || chunk.length === 0) {
|
||||
return Promise.reject(new Error('SSH relay runtime POSIX file destination chunk is empty'))
|
||||
}
|
||||
return runExclusive(async () => {
|
||||
signal.throwIfAborted()
|
||||
// Why: the callback is the byte-retention boundary before a source worker reuses its buffer.
|
||||
const accepted = new Promise<void>((resolve, reject) => {
|
||||
channel.write(chunk, (error) => (error ? reject(error) : resolve()))
|
||||
})
|
||||
await waitForOperation(accepted)
|
||||
signal.throwIfAborted()
|
||||
})
|
||||
}
|
||||
|
||||
const close = (): Promise<void> => {
|
||||
try {
|
||||
assertWritable()
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
state = 'closing'
|
||||
return runExclusive(async () => {
|
||||
signal.throwIfAborted()
|
||||
channel.end()
|
||||
const settled = await Promise.race([
|
||||
channelSettlement,
|
||||
abortOutcome.then((outcome) => ({ ok: false as const, error: outcome.reason }))
|
||||
])
|
||||
if (state === 'aborting' || state === 'aborted') {
|
||||
const outcome = await abortOutcome
|
||||
throw outcome.reason
|
||||
}
|
||||
if (!settled.ok) {
|
||||
throw settled.error
|
||||
}
|
||||
signal.throwIfAborted()
|
||||
state = 'complete'
|
||||
signal.removeEventListener('abort', onSignalAbort)
|
||||
})
|
||||
}
|
||||
|
||||
signal.addEventListener('abort', onSignalAbort, { once: true })
|
||||
if (signal.aborted) {
|
||||
onSignalAbort()
|
||||
}
|
||||
return Object.freeze({ write, close, abort })
|
||||
}
|
||||
|
||||
export async function openSshRelayRuntimePosixFileDestination(
|
||||
options: OpenSshRelayRuntimePosixFileDestinationOptions
|
||||
): Promise<SshRelayRuntimeSourceDestination> {
|
||||
validateOptions(options)
|
||||
options.signal.throwIfAborted()
|
||||
const channel = await options.openChannel(
|
||||
buildCommand(options.remotePath, options.mode),
|
||||
options.signal
|
||||
)
|
||||
validateChannel(channel)
|
||||
const destination = createDestination(options, channel)
|
||||
try {
|
||||
options.signal.throwIfAborted()
|
||||
return destination
|
||||
} catch (error) {
|
||||
try {
|
||||
await destination.abort(error)
|
||||
} catch (cleanupError) {
|
||||
throw new AggregateError(
|
||||
[error, cleanupError],
|
||||
'SSH relay runtime POSIX file open cancellation cleanup failed'
|
||||
)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user