diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml
index 3c2b0e75914..7c18c15a716 100644
--- a/.github/workflows/ssh-relay-runtime-artifacts.yml
+++ b/.github/workflows/ssh-relay-runtime-artifacts.yml
@@ -285,6 +285,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-sftp-file-destination.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 \
@@ -840,6 +841,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-sftp-file-destination.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 `
diff --git a/config/scripts/ssh-relay-runtime-workflow.test.mjs b/config/scripts/ssh-relay-runtime-workflow.test.mjs
index 3b99c61f563..53ff1bf7bfb 100644
--- a/config/scripts/ssh-relay-runtime-workflow.test.mjs
+++ b/config/scripts/ssh-relay-runtime-workflow.test.mjs
@@ -143,6 +143,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-sftp-file-destination.test.ts',
'ssh-relay-compiled-manifest-trust.test.ts',
'ssh-relay-official-manifest.test.ts',
'ssh-relay-manifest-accepted-keys.test.ts',
diff --git a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md
index 3bb3dd0b586..e7240664173 100644
--- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md
+++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md
@@ -9,9 +9,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 — bounded client source
-streaming is exact-head green; the smallest disconnected SFTP adapter audit is next, 2026-07-15,
-Codex implementation owner.**
+Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — disconnected SFTP
+per-file destination is locally green; exact-head native proof is next, 2026-07-15, Codex
+implementation owner.** `E-M6-SFTP-FILE-DESTINATION-AUDIT-001` fixes exclusive `wx` open, callback-bounded
+positional writes, explicit POSIX mode repair/handle-stat proof, and joined close-before-unlink
+cleanup. It deliberately borrows but does not acquire/end an SFTP session and does not create
+directories, clean a whole staging tree, or publish/launch anything. Session-wide cancellation,
+full-size live SFTP, product/default wiring, and SignPath remain later gates.
+`E-M6-SFTP-FILE-DESTINATION-LOCAL-RED-001` records the expected missing-module failure for the
+12-case purpose contract; the 7/7 native workflow oracle already pins it once in both command
+families. Protected resolvers remain untouched and no production importer exists.
+`E-M6-SFTP-FILE-DESTINATION-LOCAL-001` passes the hardened 13-case purpose suite, 103 focused cases
+with one declared skip, 579 broad relay cases with five declared skips, 280/280 workflow cases,
+typecheck, full lint, targeted format, max-lines, diff, protected-resolver, and no-product-import
+gates. It proves the exact per-file lifecycle only; SFTP session/tree orchestration, live/full-size
+remote proof, and every product/default path remain open.
`E-M6-SOURCE-STREAM-CI-001` closes exact commit `df39c287d`: all six primary native clients pass the
27-case source-stream suite (Windows has only the declared POSIX-mode skip), exact 85–125 MB trees
stream in 103.663–530.937 ms with 0–5,492,736 incremental RSS bytes, both Linux supplements and the
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 ace0e2e78a2..7b27094bb36 100644
--- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md
+++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md
@@ -8,8 +8,8 @@ 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: bounded client source streaming is exact-head green; the smallest disconnected SFTP adapter audit is next**. 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: **BOUNDED SOURCE STREAM EXACT-HEAD GREEN / SFTP ADAPTER AUDIT NEXT — 2026-07-15, Codex implementation owner** — `E-M6-SOURCE-STREAM-CI-001` closes all-six native purpose and exact full-size client-stream proof. The first adjacent computer-use Windows attempt exposed only two unrelated five-second launcher timeouts under `E-M6-SOURCE-STREAM-ADJACENT-CI-RED-001`; its same-SHA failed-job rerun is fully green without changing timeouts or code. Audit the smallest SFTP destination/session boundary before writing its RED tests. Do not 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.
+Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: disconnected SFTP per-file destination is locally green; exact-head native proof is next**. 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: **SFTP PER-FILE DESTINATION LOCAL GREEN — 2026-07-15, Codex implementation owner** — `E-M6-SFTP-FILE-DESTINATION-LOCAL-001` passes the hardened 13-case purpose contract, focused/broad/workflow tests, and all static/isolation gates. It proves exclusive remote open, exact callback-bounded offsets, in-flight cancellation joining, explicit POSIX mode repair/handle-stat proof, Windows mode skip, close-before-unlink cleanup, and idempotent aggregation. Commit/push this disconnected package and require exact-head all-six native/adjacent CI before advancing to SFTP session/tree orchestration. The adapter borrows but does not acquire/end an SFTP session and does not create directories or publish/launch a tree; session-wide cancellation and whole-staging cleanup remain later gates. Do not 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.
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)
@@ -16738,6 +16738,91 @@ runtime source stream destination is incomplete`. The contract step failed after
install/launch step, product/Beta/fallback/tuple/publication/default path, and SignPath remain
open. Legacy remains the sole production/default path and every tuple remains disabled.
+### E-M6-SFTP-FILE-DESTINATION-AUDIT-001 — exclusive bounded SFTP file lifecycle boundary
+
+- Date/owner: 2026-07-15, Codex implementation owner.
+- Exact input boundary: accept only a caller-owned SFTP file primitive set, one already validated
+ exact remote staging-file path, the signed `0o644` or `0o755` mode, an explicit POSIX-mode flag,
+ and the same required caller-owned `AbortSignal` used by the source streamer. Do not derive any
+ remote path from a client-native local path and do not acquire or end the SFTP session here.
+- Open/write contract: check cancellation before open; use only exclusive `wx` open; once the handle
+ exists this adapter owns that new file. Each `write(chunk)` issues exactly one positional SFTP
+ write and resolves only from its callback, making that callback the source stream's reusable
+ 64-KiB buffer-lifetime boundary. Writes are sequential, offsets are exact, empty/concurrent/late
+ writes are rejected, and no chunk/tree concatenation or base64 representation exists.
+- Completion contract: for POSIX remotes, apply the signed mode through the owned handle, re-read
+ handle attributes, and require the exact permission bits before closing. Windows/non-POSIX
+ remotes skip POSIX mode operations explicitly. A close is successful only after the remote handle
+ callback settles and a final signal check passes.
+- Failure/cancellation contract: an open failure never unlinks because no ownership was obtained.
+ Any later write, mode, verification, close, or cancellation failure leaves the destination
+ abortable; abort attempts owned-handle close before unlink, joins cleanup failures, and is
+ idempotent. Cancellation during an in-flight SFTP callback remains joined until that callback is
+ forced to settle by the later session owner, preventing reuse of a possibly retained source
+ buffer.
+- Required purpose RED: missing destination module; exclusive open and exact offsets; deferred
+ callback/buffer lifetime; pre-open and post-open cancellation; write/mode/stat/close failures;
+ POSIX exact-mode verification; explicit Windows mode skip; no deletion on failed exclusive open;
+ close-before-unlink order; joined/idempotent cleanup; illegal concurrent/empty/late operations;
+ and path-free adapter-authored diagnostics. Add the suite exactly once to both native artifact
+ command families before implementation.
+- Explicit residual boundary: this is a per-file primitive, not an SFTP transfer. SFTP session
+ acquisition/end/error ownership, bounded tree concurrency, remote directory creation, remote-path
+ policy, session-wide timeout/cancellation, full-staging cleanup, real server behavior, full-size
+ transfer, mode behavior across servers, and later remote tree verification/install/publish/launch
+ all remain open. No production importer, setting, fallback, tuple, release, or default path may be
+ added by this package; legacy remains the sole production path.
+
+### E-M6-SFTP-FILE-DESTINATION-LOCAL-RED-001 — destination lifecycle is absent
+
+- Date/owner/environment: 2026-07-15, Codex implementation owner; local macOS arm64, Node 26.0.0,
+ pnpm 10.24.0, atop exact checkpoint commit `705bca2db` plus the purpose/workflow/checklist RED diff.
+- Purpose command: `pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1
+src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts` — expected RED, exit 1: one failed
+ suite, zero collected tests, because `./ssh-relay-runtime-sftp-file-destination` does not exist.
+ The 260-line source contract declares 12 cases including two parameterized failure cells.
+- Native workflow oracle: `pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1
+config/scripts/ssh-relay-runtime-workflow.test.mjs` — PASS, 7/7 in 356 ms. The workflow source and
+ oracle already require the purpose suite exactly once in both POSIX and PowerShell native command
+ families, so implementation cannot become locally green while native coverage remains omitted.
+- Static precondition: `git diff --check` passes and the test remains below the max-lines ratchet at
+ 260 lines. Protected resolver files remain untouched. No production importer, SFTP session owner,
+ remote directory/tree orchestrator, product caller, setting, fallback, tuple, publication, or
+ default behavior exists in the RED diff.
+
+### E-M6-SFTP-FILE-DESTINATION-LOCAL-001 — per-file SFTP lifecycle is locally green
+
+- Date/owner/environment: 2026-07-15, Codex implementation owner; local macOS arm64, Node 26.0.0,
+ pnpm 10.24.0, atop exact checkpoint commit `705bca2db` plus the implementation/checklist diff.
+- Implementation: the 210-line `ssh-relay-runtime-sftp-file-destination.ts` accepts only the audited
+ primitive boundary. It opens with `wx` and signed mode, awaits one SFTP positional-write callback
+ per source chunk before advancing the exact offset, checks the same signal around every callback,
+ repairs and verifies POSIX permission bits before close, and skips those operations only from the
+ explicit non-POSIX flag. Adapter-authored state/input errors contain no remote path.
+- Purpose command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
+--maxWorkers=1 src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts` — PASS, 13/13 in
+ 431 ms Vitest / 2.36 seconds wall, 133,005,312-byte maximum RSS, zero swaps. The additional
+ hardening case proves cancellation during a pending write cannot settle or begin cleanup until the
+ SFTP callback releases the borrowed source buffer; it then closes before unlinking.
+- Focused command: the destination, source stream/scan/tree, acquisition/cache population and
+ resolution, integration, and workflow-oracle ten-file command — PASS, 103 tests with one declared
+ platform skip in 8.28 seconds.
+- Broad commands: `src/main/ssh/ssh-relay-*.test.ts` — PASS, 579 tests with five declared platform/
+ full-size skips in 27.25 seconds; `config/scripts/ssh-relay-runtime-*.test.mjs` — PASS, 280/280 in
+ 20.82 seconds. The first quoted-glob invocations matched no files and were discarded as command
+ syntax mistakes; the unquoted exact checklist commands above are the credited results.
+- Static/isolation commands: `pnpm typecheck`, targeted `pnpm exec oxlint`, `pnpm lint`, targeted
+ `pnpm exec oxfmt --check`, `pnpm run check:max-lines-ratchet`, `git diff --check`, the protected-
+ resolver diff gate, and the no-non-test-importer `rg` gate pass. Full lint includes switch
+ exhaustiveness, styled-scrollbar, 41 reliability, bundled-skill, max-lines, and localization gates;
+ only pre-existing unrelated warnings remain. The module/test are 210/289 lines with no bypass.
+- Residual: this is callback-contract proof with mocked primitives, not a live SFTP server, session,
+ SSH connection, remote directory/staging tree, bounded multi-file transfer, session-wide abort/
+ timeout, whole-tree cleanup, or full-size remote measurement. It has no non-test importer. Exact-
+ head all-six Node 24 and adjacent Actions evidence is required before auditing the session/tree
+ adapter. Product/Beta/fallback/tuple/publication/default behavior and SignPath remain absent;
+ legacy remains the sole production path.
+
## 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-sftp-file-destination.test.ts b/src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts
new file mode 100644
index 00000000000..61b39bde8e0
--- /dev/null
+++ b/src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts
@@ -0,0 +1,289 @@
+import { describe, expect, it, vi } from 'vitest'
+
+import {
+ openSshRelayRuntimeSftpFileDestination,
+ type SshRelayRuntimeSftpFileOperations
+} from './ssh-relay-runtime-sftp-file-destination'
+
+type Callback = (error?: Error) => void
+
+function createOperations(options: { mode?: number } = {}): {
+ operations: SshRelayRuntimeSftpFileOperations
+ events: string[]
+ handle: Buffer
+} {
+ const handle = Buffer.from('owned-handle')
+ const events: string[] = []
+ const operations = {
+ open: vi.fn(
+ (
+ _path: string,
+ _flags: string,
+ _attributes: { mode: number },
+ callback: (error: Error | undefined, value: Buffer) => void
+ ) => {
+ events.push('open')
+ callback(undefined, handle)
+ }
+ ),
+ write: vi.fn(
+ (
+ _handle: Buffer,
+ _buffer: Buffer,
+ _offset: number,
+ _length: number,
+ _position: number,
+ callback: Callback
+ ) => {
+ events.push('write')
+ callback()
+ }
+ ),
+ fchmod: vi.fn((_handle: Buffer, _mode: number, callback: Callback) => {
+ events.push('fchmod')
+ callback()
+ }),
+ fstat: vi.fn(
+ (_handle: Buffer, callback: (error: Error | undefined, value: { mode: number }) => void) => {
+ events.push('fstat')
+ callback(undefined, { mode: options.mode ?? 0o100755 })
+ }
+ ),
+ close: vi.fn((_handle: Buffer, callback: Callback) => {
+ events.push('close')
+ callback()
+ }),
+ unlink: vi.fn((_path: string, callback: Callback) => {
+ events.push('unlink')
+ callback()
+ })
+ } as unknown as SshRelayRuntimeSftpFileOperations
+ return { operations, events, handle }
+}
+
+function openDestination(
+ operations: SshRelayRuntimeSftpFileOperations,
+ options: { signal?: AbortSignal; enforcePosixMode?: boolean; remotePath?: string } = {}
+) {
+ return openSshRelayRuntimeSftpFileDestination({
+ operations,
+ remotePath: options.remotePath ?? '/owned-staging/bin/node',
+ mode: 0o755,
+ enforcePosixMode: options.enforcePosixMode ?? true,
+ signal: options.signal ?? new AbortController().signal
+ })
+}
+
+describe('SSH relay runtime SFTP file destination', () => {
+ it('opens exclusively and awaits each exact positional write callback', async () => {
+ const { operations, handle } = createOperations()
+ let firstCallback: Callback | undefined
+ vi.mocked(operations.write).mockImplementationOnce(
+ (_handle, _buffer, _offset, _length, _position, callback) => {
+ firstCallback = callback
+ }
+ )
+
+ const destination = await openDestination(operations)
+ const firstChunk = Buffer.from('abc')
+ let firstSettled = false
+ const firstWrite = destination.write(firstChunk).then(() => {
+ firstSettled = true
+ })
+ await Promise.resolve()
+ expect(firstSettled).toBe(false)
+ expect(operations.open).toHaveBeenCalledWith(
+ '/owned-staging/bin/node',
+ 'wx',
+ { mode: 0o755 },
+ expect.any(Function)
+ )
+ expect(operations.write).toHaveBeenNthCalledWith(
+ 1,
+ handle,
+ firstChunk,
+ 0,
+ 3,
+ 0,
+ expect.any(Function)
+ )
+
+ firstCallback?.()
+ await firstWrite
+ const secondChunk = Buffer.from('de')
+ await destination.write(secondChunk)
+ expect(operations.write).toHaveBeenNthCalledWith(
+ 2,
+ handle,
+ secondChunk,
+ 0,
+ 2,
+ 3,
+ expect.any(Function)
+ )
+ })
+
+ it('repairs and verifies exact POSIX mode before closing the owned handle', async () => {
+ const { operations, events, handle } = createOperations()
+ const destination = await openDestination(operations)
+ await destination.close()
+
+ expect(operations.fchmod).toHaveBeenCalledWith(handle, 0o755, expect.any(Function))
+ expect(operations.fstat).toHaveBeenCalledWith(handle, expect.any(Function))
+ expect(events).toEqual(['open', 'fchmod', 'fstat', 'close'])
+ })
+
+ it('skips POSIX mode operations only when the caller explicitly disables them', async () => {
+ const { operations, events } = createOperations()
+ const destination = await openDestination(operations, { enforcePosixMode: false })
+ await destination.close()
+
+ expect(events).toEqual(['open', 'close'])
+ expect(operations.fchmod).not.toHaveBeenCalled()
+ expect(operations.fstat).not.toHaveBeenCalled()
+ })
+
+ it('rejects a mode mismatch and removes the owned incomplete file on abort', async () => {
+ const { operations, events } = createOperations({ mode: 0o100644 })
+ const destination = await openDestination(operations)
+ await expect(destination.close()).rejects.toThrow(/mode/i)
+ await destination.abort(new Error('mode mismatch'))
+
+ expect(events).toEqual(['open', 'fchmod', 'fstat', 'close', 'unlink'])
+ })
+
+ it.each(['fchmod', 'fstat'] as const)('fails closed when %s fails', async (operation) => {
+ const { operations } = createOperations()
+ vi.mocked(operations[operation]).mockImplementationOnce((...args: unknown[]) => {
+ const callback = args.at(-1) as Callback
+ callback(new Error(`${operation} failed`))
+ })
+ const destination = await openDestination(operations)
+
+ await expect(destination.close()).rejects.toThrow(`${operation} failed`)
+ await destination.abort(new Error(`${operation} failed`))
+ expect(operations.close).toHaveBeenCalledOnce()
+ expect(operations.unlink).toHaveBeenCalledOnce()
+ })
+
+ it('does not unlink a pre-existing file when exclusive open fails', async () => {
+ const { operations } = createOperations()
+ vi.mocked(operations.open).mockImplementationOnce((...args: unknown[]) => {
+ const callback = args.at(-1) as (error: Error) => void
+ callback(new Error('already exists'))
+ })
+
+ await expect(openDestination(operations)).rejects.toThrow('already exists')
+ expect(operations.close).not.toHaveBeenCalled()
+ expect(operations.unlink).not.toHaveBeenCalled()
+ })
+
+ it('rejects pre-open cancellation without creating a remote file', async () => {
+ const { operations } = createOperations()
+ const controller = new AbortController()
+ controller.abort(new Error('cancelled before open'))
+
+ await expect(openDestination(operations, { signal: controller.signal })).rejects.toThrow(
+ 'cancelled before open'
+ )
+ expect(operations.open).not.toHaveBeenCalled()
+ })
+
+ it('cleans an exclusively opened file when cancellation wins the open callback', async () => {
+ const { operations, events, handle } = createOperations()
+ const controller = new AbortController()
+ vi.mocked(operations.open).mockImplementationOnce((...args: unknown[]) => {
+ const callback = args.at(-1) as (error: undefined, value: Buffer) => void
+ controller.abort(new Error('cancelled after open'))
+ callback(undefined, handle)
+ })
+
+ await expect(openDestination(operations, { signal: controller.signal })).rejects.toThrow(
+ 'cancelled after open'
+ )
+ expect(events).toEqual(['close', 'unlink'])
+ })
+
+ it('joins cancellation to the in-flight write callback before cleanup', async () => {
+ const { operations, events } = createOperations()
+ const controller = new AbortController()
+ let writeCallback: Callback | undefined
+ vi.mocked(operations.write).mockImplementationOnce(
+ (_handle, _buffer, _offset, _length, _position, callback) => {
+ events.push('write')
+ writeCallback = callback
+ }
+ )
+ const destination = await openDestination(operations, { signal: controller.signal })
+ let settled = false
+ const write = destination.write(Buffer.from('bytes')).finally(() => {
+ settled = true
+ })
+
+ controller.abort(new Error('cancelled during write'))
+ await Promise.resolve()
+ expect(settled).toBe(false)
+ expect(operations.close).not.toHaveBeenCalled()
+ writeCallback?.()
+ await expect(write).rejects.toThrow('cancelled during write')
+ await destination.abort(controller.signal.reason)
+ expect(events).toEqual(['open', 'write', 'close', 'unlink'])
+ })
+
+ it('propagates a write failure and then closes before unlinking', async () => {
+ const { operations, events } = createOperations()
+ vi.mocked(operations.write).mockImplementationOnce((...args: unknown[]) => {
+ const callback = args.at(-1) as Callback
+ events.push('write')
+ callback(new Error('remote write failed'))
+ })
+ const destination = await openDestination(operations)
+
+ await expect(destination.write(Buffer.from('bytes'))).rejects.toThrow('remote write failed')
+ await destination.abort(new Error('remote write failed'))
+ expect(events).toEqual(['open', 'write', 'close', 'unlink'])
+ })
+
+ it('joins close and unlink cleanup failures and makes abort idempotent', async () => {
+ const { operations } = createOperations()
+ vi.mocked(operations.close).mockImplementation((_handle, callback) =>
+ callback(new Error('close cleanup failed'))
+ )
+ vi.mocked(operations.unlink).mockImplementation((_path, callback) =>
+ callback(new Error('unlink cleanup failed'))
+ )
+ const destination = await openDestination(operations)
+
+ const firstAbort = destination.abort(new Error('primary'))
+ const secondAbort = destination.abort(new Error('secondary'))
+ expect(firstAbort).toBe(secondAbort)
+ await expect(firstAbort).rejects.toMatchObject({
+ errors: [
+ expect.objectContaining({ message: 'close cleanup failed' }),
+ expect.objectContaining({ message: 'unlink cleanup failed' })
+ ]
+ })
+ expect(operations.close).toHaveBeenCalledOnce()
+ expect(operations.unlink).toHaveBeenCalledOnce()
+ })
+
+ it('rejects empty, concurrent, and late writes with path-free diagnostics', async () => {
+ const { operations } = createOperations()
+ let writeCallback: Callback | undefined
+ vi.mocked(operations.write).mockImplementationOnce(
+ (_handle, _buffer, _offset, _length, _position, callback) => {
+ writeCallback = callback
+ }
+ )
+ const secretPath = '/home/private-user/staging/node'
+ const destination = await openDestination(operations, { remotePath: secretPath })
+
+ await expect(destination.write(Buffer.alloc(0))).rejects.not.toThrow(secretPath)
+ const pending = destination.write(Buffer.from('first'))
+ await expect(destination.write(Buffer.from('second'))).rejects.toThrow(/concurrent/i)
+ writeCallback?.()
+ await pending
+ await destination.close()
+ await expect(destination.write(Buffer.from('late'))).rejects.toThrow(/closed/i)
+ })
+})
diff --git a/src/main/ssh/ssh-relay-runtime-sftp-file-destination.ts b/src/main/ssh/ssh-relay-runtime-sftp-file-destination.ts
new file mode 100644
index 00000000000..2d62d610975
--- /dev/null
+++ b/src/main/ssh/ssh-relay-runtime-sftp-file-destination.ts
@@ -0,0 +1,210 @@
+import type { SshRelayRuntimeSourceDestination } from './ssh-relay-runtime-source-stream'
+
+type SftpCallback = (error?: Error) => void
+
+export type SshRelayRuntimeSftpFileOperations = Readonly<{
+ open: (
+ path: string,
+ flags: 'wx',
+ attributes: { mode: number },
+ callback: (error: Error | undefined, handle: Buffer) => void
+ ) => void
+ write: (
+ handle: Buffer,
+ buffer: Buffer,
+ offset: number,
+ length: number,
+ position: number,
+ callback: SftpCallback
+ ) => void
+ fchmod: (handle: Buffer, mode: number, callback: SftpCallback) => void
+ fstat: (
+ handle: Buffer,
+ callback: (error: Error | undefined, attributes: { mode: number }) => void
+ ) => void
+ close: (handle: Buffer, callback: SftpCallback) => void
+ unlink: (path: string, callback: SftpCallback) => void
+}>
+
+export type OpenSshRelayRuntimeSftpFileDestinationOptions = Readonly<{
+ operations: SshRelayRuntimeSftpFileOperations
+ remotePath: string
+ mode: 0o644 | 0o755
+ enforcePosixMode: boolean
+ signal: AbortSignal
+}>
+
+type DestinationState = 'open' | 'closed' | 'complete' | 'aborting' | 'aborted'
+
+function waitForSftpCallback(register: (callback: SftpCallback) => void): Promise {
+ return new Promise((resolve, reject) => {
+ register((error) => (error ? reject(error) : resolve()))
+ })
+}
+
+function waitForSftpValue(
+ register: (callback: (error: Error | undefined, value: T) => void) => void
+): Promise {
+ return new Promise((resolve, reject) => {
+ register((error, value) => (error ? reject(error) : resolve(value)))
+ })
+}
+
+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 SFTP file cleanup failed')
+}
+
+function validateOptions(options: OpenSshRelayRuntimeSftpFileDestinationOptions): void {
+ if (!options.operations || typeof options.remotePath !== 'string' || options.remotePath === '') {
+ throw new Error('SSH relay runtime SFTP file destination input is invalid')
+ }
+ if (options.remotePath.includes('\0')) {
+ throw new Error('SSH relay runtime SFTP file destination path is invalid')
+ }
+ if (options.mode !== 0o644 && options.mode !== 0o755) {
+ throw new Error('SSH relay runtime SFTP file destination mode is invalid')
+ }
+}
+
+function createDestination(
+ options: OpenSshRelayRuntimeSftpFileDestinationOptions,
+ handle: Buffer
+): SshRelayRuntimeSourceDestination {
+ const { operations, remotePath, mode, enforcePosixMode, signal } = options
+ let state: DestinationState = 'open'
+ let position = 0
+ let activeOperation: Promise | undefined
+ let abortPromise: Promise | undefined
+
+ const assertWritable = (): void => {
+ if (state !== 'open') {
+ throw new Error('SSH relay runtime SFTP file destination is closed')
+ }
+ if (activeOperation) {
+ throw new Error('SSH relay runtime SFTP file destination has a concurrent operation')
+ }
+ }
+
+ const runExclusive = (operation: () => Promise): Promise => {
+ const pending = operation()
+ activeOperation = pending
+ void pending
+ .finally(() => {
+ if (activeOperation === pending) {
+ activeOperation = undefined
+ }
+ })
+ .catch(() => {})
+ return pending
+ }
+
+ const write = (chunk: Buffer): Promise => {
+ try {
+ assertWritable()
+ } catch (error) {
+ return Promise.reject(error)
+ }
+ if (!Buffer.isBuffer(chunk) || chunk.length === 0) {
+ return Promise.reject(new Error('SSH relay runtime SFTP file destination chunk is empty'))
+ }
+ return runExclusive(async () => {
+ signal.throwIfAborted()
+ // Why: the callback is the remote-consumption boundary; resolving earlier could let the
+ // source worker reuse its one buffer while ssh2 still retains the chunk view.
+ await waitForSftpCallback((callback) =>
+ operations.write(handle, chunk, 0, chunk.length, position, callback)
+ )
+ position += chunk.length
+ signal.throwIfAborted()
+ })
+ }
+
+ const close = (): Promise => {
+ try {
+ assertWritable()
+ } catch (error) {
+ return Promise.reject(error)
+ }
+ return runExclusive(async () => {
+ signal.throwIfAborted()
+ if (enforcePosixMode) {
+ await waitForSftpCallback((callback) => operations.fchmod(handle, mode, callback))
+ signal.throwIfAborted()
+ const attributes = await waitForSftpValue<{ mode: number }>((callback) =>
+ operations.fstat(handle, callback)
+ )
+ if (!Number.isInteger(attributes.mode) || (attributes.mode & 0o777) !== mode) {
+ throw new Error('SSH relay runtime SFTP file mode verification failed')
+ }
+ }
+ signal.throwIfAborted()
+ await waitForSftpCallback((callback) => operations.close(handle, callback))
+ state = 'closed'
+ signal.throwIfAborted()
+ state = 'complete'
+ })
+ }
+
+ const performAbort = async (): Promise => {
+ if (state === 'complete' || state === 'aborted') {
+ return
+ }
+ const pending = activeOperation
+ if (pending) {
+ await pending.catch(() => {})
+ }
+ const handleNeedsClose = state !== 'closed'
+ state = 'aborting'
+ const failures: unknown[] = []
+ if (handleNeedsClose) {
+ await waitForSftpCallback((callback) => operations.close(handle, callback)).catch((error) =>
+ failures.push(error)
+ )
+ }
+ await waitForSftpCallback((callback) => operations.unlink(remotePath, callback)).catch(
+ (error) => failures.push(error)
+ )
+ state = 'aborted'
+ const failure = cleanupFailure(failures)
+ if (failure !== undefined) {
+ throw failure
+ }
+ }
+
+ const abort = (_reason: unknown): Promise => {
+ abortPromise ??= performAbort()
+ return abortPromise
+ }
+
+ return Object.freeze({ write, close, abort })
+}
+
+export async function openSshRelayRuntimeSftpFileDestination(
+ options: OpenSshRelayRuntimeSftpFileDestinationOptions
+): Promise {
+ validateOptions(options)
+ options.signal.throwIfAborted()
+ const handle = await waitForSftpValue((callback) =>
+ options.operations.open(options.remotePath, 'wx', { mode: options.mode }, callback)
+ )
+ const destination = createDestination(options, handle)
+ try {
+ options.signal.throwIfAborted()
+ return destination
+ } catch (error) {
+ try {
+ await destination.abort(error)
+ } catch (cleanupError) {
+ throw new AggregateError(
+ [error, cleanupError],
+ 'SSH relay runtime SFTP file open cancellation cleanup failed'
+ )
+ }
+ throw error
+ }
+}