feat: detect remote Linux kernel

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-16 04:12:20 -04:00
co-authored by Orca
parent 54d523a81a
commit d1eb0eb63e
8 changed files with 384 additions and 10 deletions
@@ -263,6 +263,7 @@ jobs:
src/main/ssh/ssh-relay-release-asset.test.ts \
src/main/ssh/ssh-relay-artifact-selector.test.ts \
src/main/ssh/ssh-relay-libc-detection.test.ts \
src/main/ssh/ssh-relay-linux-kernel-detection.test.ts \
src/main/ssh/ssh-relay-artifact-download.test.ts \
src/main/ssh/ssh-relay-artifact-extraction.test.ts \
src/main/ssh/ssh-relay-artifact-cache-lock-release.test.ts \
@@ -809,6 +810,7 @@ jobs:
src/main/ssh/ssh-relay-release-asset.test.ts `
src/main/ssh/ssh-relay-artifact-selector.test.ts `
src/main/ssh/ssh-relay-libc-detection.test.ts `
src/main/ssh/ssh-relay-linux-kernel-detection.test.ts `
src/main/ssh/ssh-relay-artifact-download.test.ts `
src/main/ssh/ssh-relay-artifact-extraction.test.ts `
src/main/ssh/ssh-relay-artifact-cache-lock-release.test.ts `
@@ -121,6 +121,7 @@ describe('SSH relay runtime artifact workflow', () => {
'ssh-relay-release-asset.test.ts',
'ssh-relay-artifact-selector.test.ts',
'ssh-relay-libc-detection.test.ts',
'ssh-relay-linux-kernel-detection.test.ts',
'ssh-relay-artifact-download.test.ts',
'ssh-relay-artifact-extraction.test.ts',
'ssh-relay-artifact-cache-lock-release.test.ts',
@@ -9,9 +9,10 @@ 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 5 / Work Package 4 desktop resolver/cache — audit the next smallest
remaining disconnected host-evidence boundary after exact-head acceptance of Linux libc detection,
2026-07-15, Codex implementation owner.**
Active checkpoint: **Milestone 5 / Work Package 4 desktop resolver/cache — require exact-head
all-six native CI for the disconnected marked Linux kernel probe and kernel-only distro-suffix
parser accepted locally by `E-M5-LINUX-KERNEL-DETECTION-LOCAL-001`, 2026-07-15, Codex implementation
owner.**
`E-M5-ARTIFACT-CACHE-RESOLUTION-CI-001` closes warm-cache commit
`22031fa68` on all six native clients, both Linux supplements, Windows x64 baseline, PR Checks,
Golden E2E, and computer-use; Windows arm64 remains the expected build-26200 rejection against 26100. `E-M5-ARTIFACT-CACHE-RESOLUTION-LOCAL-001` passes 9/9 purpose tests,
@@ -118,6 +119,30 @@ inside budget. Windows arm64 completes exact-byte PTY/watcher smoke, then retain
hosted build-26200 rejection against required 26100; this is the aggregate workflow's sole failure.
Real SSH/distro behavior, remaining host evidence, composition, and every production/default path
remain open; legacy remains the sole production path.
`E-M5-LINUX-KERNEL-DETECTION-AUDIT-001` fixes the next narrow boundary: one disconnected,
15-second cancellable POSIX-shell/no-Node marked `uname -r` probe plus a kernel-only numeric-prefix
parser. Common supported Rocky/RHEL releases such as `4.18.0-553.5.1.el8_10.x86_64` currently fail
the generic parser because its suffix grammar excludes `_`; the kernel parser will admit only the
bounded distro suffix alphabet `[0-9A-Za-z._+~-]` without relaxing libc, macOS, or Windows version
parsing. Missing, noisy, incomplete, duplicate, malformed, oversized, or invalid evidence returns
unknown; ordinary probe failure remains availability evidence and cancellation propagates. The
reviewed 4.18 floor is unchanged. Host-evidence composition, product callers, live SSH/distro
proof, transfer/install, settings, fallback, tuple enablement, publication, and defaults stay
absent; legacy remains the sole production path.
`E-M5-LINUX-KERNEL-DETECTION-LOCAL-RED-001` records the expected two-file failure before
implementation: the purpose suite collects zero tests because its detection module is absent, and
the existing selector passes 24 cases but fails three Rocky/RHEL assertions. Supported and
below-floor `el8_10` releases both return `unknown-kernel`, proving the underscore suffix gap. Exact
Node 24 and all-six native execution remain required after local green.
`E-M5-LINUX-KERNEL-DETECTION-LOCAL-001` is green: 19/19 purpose tests, 60/60 focused+workflow tests,
394/394 non-full-size relay tests with three declared skips, 280/280 release-script tests, typecheck,
lint, format, reliability, max-lines, localization, diff, and protected-resolver gates pass. One
15-second cancellable marked POSIX command returns only a unique strict `uname -r`; supported
Rocky/RHEL, Ubuntu, and Alpine suffixes select against the unchanged 4.18 floor, while malformed,
oversized, noisy, unavailable, or invalid evidence returns unknown and cancellation propagates.
Libc, macOS, and PowerShell grammars remain strict. This is contract evidence, not live SSH/distro
proof; full host composition and every production/default path remain absent. Exact-head all-six
native proof is next.
`E-M5-ARTIFACT-CACHE-ROOT-CI-001` closes the pure cache-root contract at exact head `aefcaa9a9`:
all six primary native Node 24 jobs, both Linux supplements, Windows x64 baseline, PR Checks, Golden
E2E, and computer-use pass; Windows arm64 build 26200 remains correctly gated against 26100. The
@@ -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 5 / Work Package 4 desktop resolver/cache — **In progress — 2026-07-15, Codex implementation owner: audit the next smallest remaining disconnected host-evidence boundary after exact-head acceptance of Linux libc detection**. Keep every capability disconnected from host-evidence composition and 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: **CI CLOSED / NEXT AUDIT — 2026-07-15, Codex implementation owner** — `E-M5-LIBC-DETECTION-CI-001` closes implementation commit `d8b17a354`: all six primary native Node 24 jobs, both Linux supplements, Windows x64 baseline, PR Checks, Golden E2E, and computer-use pass. Windows arm64 executes the full runtime smoke and retains only the declared hosted build-26200 rejection against required 26100. Every native client runs the new 15-case marked/no-Node libc contract. This remains contract evidence, not live SSH/distro evidence; no host-evidence composer, product caller, setting, transfer/install, fallback classifier, tuple enablement, release publication, or default behavior is connected. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.<br>
Current phase: Milestone 5 / Work Package 4 desktop resolver/cache — **In progress — 2026-07-15, Codex implementation owner: require exact-head all-six native CI for the disconnected marked Linux kernel probe and kernel-only distro-suffix parser accepted locally by E-M5-LINUX-KERNEL-DETECTION-LOCAL-001**. Keep every capability disconnected from host-evidence composition and 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: **LOCAL CLOSED / CI NEXT — 2026-07-15, Codex implementation owner** — `E-M5-LINUX-KERNEL-DETECTION-LOCAL-001` passes the 19-case purpose contract, selector and native-workflow gates, 394 non-full-size relay cases, 280 release-script cases, typecheck, lint, format, reliability, localization, max-lines, diff, and protected-resolver gates. The reviewed Linux 4.18 floor is unchanged. No host-evidence composer, product caller, setting, transfer/install, fallback classifier, tuple enablement, release publication, or default behavior is connected. 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)
@@ -15077,6 +15077,102 @@ config/scripts/ssh-relay-runtime-*.test.mjs` — PASS, 50 files / 280 tests in 2
settings, fallback, tuple enablement, publication, or defaults. Legacy remains the sole production
path; no tuple is enabled.
### E-M5-LINUX-KERNEL-DETECTION-AUDIT-001 — marked kernel evidence and distro suffix boundary
- Date/owner: 2026-07-15, Codex implementation owner.
- Audit: complete rereads of current OS/architecture detection, the accepted marked libc probe, the
signed-manifest selector/version parser, and their purpose tests. Common supported Rocky/RHEL 8
kernels report releases such as `4.18.0-553.5.1.el8_10.x86_64`; the selector's generic numeric
parser rejects `_` in that suffix and would return `unknown-kernel` despite a valid supported
numeric prefix. This is an implementation-detail gap, not a change to the reviewed 4.18 floor or
the primary plan.
- Finding: add one purpose-named `ssh-relay-linux-kernel-detection` module that consumes an explicit
SSH connection and optional cancellation signal, executes one 15-second POSIX-shell/no-Node
marked `uname -r`, and returns an exact bounded release string or undefined. Add a kernel-specific
parser to the selector that compares only the authenticated probe's strict numeric prefix while
admitting the bounded real-world suffix alphabet `[0-9A-Za-z._+~-]`; keep the generic libc/macOS/
PowerShell version grammar unchanged.
- Safety contract: only one complete marker-qualified line counts; arbitrary or marker-concatenated
startup noise, duplicate/unterminated markers, whitespace/control characters, oversized releases,
missing commands, and unparseable numeric prefixes yield unknown. Ordinary execution failure is
compatibility evidence; `AbortError` propagates. The parser must accept exact-minimum Rocky/RHEL,
Ubuntu, and Alpine suffixes but reject whitespace, slash, colon, and arbitrary punctuation.
- Required RED: missing purpose module plus a selector assertion showing a supported Rocky-style
`4.18.0-...el8_10...` release currently returns `unknown-kernel`; then marked valid/noisy/
malformed/bounded/unavailable/cancelled probe cases, kernel-suffix accept/reject boundaries,
existing selector regressions, and native-workflow inclusion.
- Deliberately absent: libc composition, libstdc++/GLIBCXX, macOS/Windows/translation evidence,
Electron/startup/product callers, live SSH or distro cells, cache acquisition, transfer/install,
settings, fallback, tuple enablement, release publication, and defaults. Legacy remains the sole
production path and the primary HTML plan is unchanged.
### E-M5-LINUX-KERNEL-DETECTION-LOCAL-RED-001 — kernel probe and Rocky suffix boundaries fail first
- Date/owner: 2026-07-15, Codex implementation owner.
- Source: uncommitted RED tests atop exact accepted evidence head `54d523a81`.
- Command: `pnpm vitest run src/main/ssh/ssh-relay-linux-kernel-detection.test.ts src/main/ssh/ssh-relay-artifact-selector.test.ts`.
- Expected result: FAIL, 2 failed files; the purpose suite has zero collected tests because
`ssh-relay-linux-kernel-detection` does not exist, and the selector has 24 passed / 3 failed.
Supported `4.18.0-553.5.1.el8_10.x86_64` returns legacy/`unknown-kernel`; the equivalent
below-floor `4.17.99-553.5.1.el8_10.x86_64` also returns `unknown-kernel` instead of
`kernel-too-old`.
- Environment: local macOS arm64 worktree; pnpm 10.24.0 under Node 26.0.0 emits the repository's
expected Node-24 engine warning. Exact Node 24 and all-six native execution remain required after
local green.
- Boundary: this is failure-before-implementation evidence only. It authorizes only the audited
disconnected probe, selector parser, purpose tests, and native workflow inclusion; no production
caller or behavior is connected.
### E-M5-LINUX-KERNEL-DETECTION-LOCAL-001 — disconnected marked kernel probe and strict selector
- Date/owner: 2026-07-15, Codex implementation owner.
- Implementation: `ssh-relay-linux-kernel-detection.ts` performs one 15-second cancellable POSIX
command, brackets the optional `uname -r` result with exact Orca markers, and returns only one
complete, unique, strict release or undefined. Startup noise outside the markers is ignored;
empty/multi-line/duplicate/unterminated/marker-concatenated evidence, releases above 256
characters, invalid numeric prefixes, whitespace/control characters, slash, colon, and arbitrary
punctuation are rejected. Ordinary execution failure returns undefined and `AbortError`
propagates. The selector's kernel-only parser admits the audited distro suffix alphabet after a
numeric kernel prefix and compares the prefix to the unchanged reviewed 4.18 floor. The generic
libc/macOS/PowerShell grammar remains strict, with direct underscore-regression assertions.
- Purpose command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 src/main/ssh/ssh-relay-linux-kernel-detection.test.ts` — PASS, 1 file / 19 tests in
209 ms Vitest, 1.08 s wall, 132,005,888-byte maximum RSS. Rocky/RHEL, Ubuntu, and Alpine releases;
outside noise; empty/multiple/duplicate/unterminated/concatenated/oversized/malformed evidence;
invalid suffixes; missing command; cancellation; one signal-qualified 15-second exec; marked
`uname -r`; and no Node/npm/Python/Perl/archive/checksum dependency all pass.
- Focused plus native-workflow oracle: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 src/main/ssh/ssh-relay-linux-kernel-detection.test.ts
src/main/ssh/ssh-relay-artifact-selector.test.ts
src/main/ssh/ssh-remote-platform-detection.test.ts
config/scripts/ssh-relay-runtime-workflow.test.mjs` — PASS, 4 files / 60 tests in 3.32 s Vitest,
5.26 s wall, 132,300,800-byte maximum RSS while run beside independent typecheck/format gates.
Both POSIX and PowerShell native commands now require the purpose suite exactly once. Supported
Rocky/RHEL, Ubuntu, and Alpine suffixes select; a Rocky release below 4.18 reports
`kernel-too-old`; invalid suffixes report `unknown-kernel`; libc, macOS, and PowerShell versions
do not inherit the kernel grammar.
- Broader relay command: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 --silent=true src/main/ssh/ssh-relay-*.test.ts` — PASS,
34 files / 394 tests; 3 declared full-size files/tests skipped; 15.00 s Vitest, 16.38 s wall,
246,890,496-byte maximum RSS. One preceding diagnostic invocation quoted the shell glob, selected
zero files, and exited 1; the exact checklist command above was then run unquoted and passed.
- Release-script command: `/usr/bin/time -l pnpm exec vitest run --config
config/vitest.config.ts --maxWorkers=1 --silent=true
config/scripts/ssh-relay-runtime-*.test.mjs` — PASS, 50 files / 280 tests in 18.76 s Vitest,
20.26 s wall, 190,480,384-byte maximum RSS.
- Repository gates: `pnpm run typecheck`, targeted `oxlint`, targeted `oxfmt --check`, `pnpm lint`,
and `pnpm run check:max-lines-ratchet` pass. Full lint includes switch exhaustiveness, 41
reliability gates, no new max-lines bypass, bundled skill-guide verification, 9,837 localization
references and locale parity, and zero localization coverage candidates; existing unrelated lint
warnings remain warnings. `git diff --check` passes and the protected Node/npm resolver pair has
zero diff. Local Node 26.0.0 emits only the expected Node-24 engine warning; exact Node 24 proof
remains the native CI gate.
- Boundary/residual gaps: the new detector is imported only by its purpose tests. This is contract
evidence, not live SSH/GNU/BusyBox/Rocky/Ubuntu/Alpine execution. It does not compose libc,
libstdc++/GLIBCXX, macOS/Windows/translation evidence and adds no Electron/startup/product caller,
transfer/install, mode, setting, fallback, tuple, publication, or default behavior. Legacy remains
the sole production path. Require exact-head all-six native proof before advancing.
## Accepted Gaps
No product gap is accepted merely because it appears in this list. Each entry requires explicit
@@ -15135,10 +15231,12 @@ The project is not complete until every applicable item below is checked with ev
## Next Required Action
Audit the next smallest disconnected host-evidence slice after exact-head acceptance of Linux libc
detection. Do not compose full host evidence, provision production secrets/environments, add an
Electron/startup consumer, embed unreviewed keys or manifest bytes, add SSH transfer/install, mode
wiring, fallback, tuple enablement, release publication, or default behavior.
Commit and push the locally accepted disconnected Linux kernel package, then require exact-head
Node 24 proof on all six native clients plus adjacent PR, Golden E2E, and computer-use checks before
auditing another host-evidence slice. Do not compose full host evidence, provision production
secrets/environments, add an Electron/startup consumer, embed unreviewed keys or manifest bytes, add
SSH transfer/install, mode wiring, fallback, tuple enablement, release publication, or default
behavior.
Keep Node upstream `.tar.xz` inputs, Windows ZIP, `ORCA_RELAY_PATH`, existing desktop required-assets
behavior, detached-signature byte encoding, Windows arm64 build 26100, macOS 13.5, Linux kernel 4.18,
release-cut, desktop builds, publication, and every tuple separately gated.
@@ -102,6 +102,57 @@ describe('SSH relay artifact selector', () => {
).toBe('selected')
})
it.each(['4.18.0-553.5.1.el8_10.x86_64', '5.15.0-107-generic', '6.6.15-0-lts'])(
'accepts a supported Linux distro kernel release %s',
(kernelVersion) => {
expect(
selectSshRelayArtifact(verifiedManifest(), {
...compatibleLinuxHost,
kernelVersion
}).kind
).toBe('selected')
}
)
it.each([
['4.18.0-553.5.1.el8_10.x86_64', 'selected'],
['4.17.99-553.5.1.el8_10.x86_64', 'kernel-too-old'],
['4.18.0 bad', 'unknown-kernel'],
['4.18.0/bad', 'unknown-kernel'],
['4.18.0:bad', 'unknown-kernel'],
['4.18.0@bad', 'unknown-kernel']
] as const)('classifies the kernel suffix boundary for %s', (kernelVersion, expected) => {
const result = selectSshRelayArtifact(verifiedManifest(), {
...compatibleLinuxHost,
kernelVersion
})
expect(result.kind === 'selected' ? result.kind : result.reason).toBe(expected)
})
it('keeps non-kernel version grammars strict', () => {
expect(
selectSshRelayArtifact(verifiedManifest(), {
...compatibleLinuxHost,
libc: { family: 'glibc', version: '2.28_bad' }
})
).toEqual({ kind: 'legacy', reason: 'unknown-libc' })
expect(
selectSshRelayArtifact(verifiedManifest(createSshRelayDarwinArtifactTestManifest()), {
os: 'darwin',
architecture: 'arm64',
processTranslated: false,
version: '13.5_bad'
})
).toEqual({ kind: 'legacy', reason: 'unknown-os-version' })
expect(
selectSshRelayArtifact(verifiedManifest(windowsManifest()), {
...compatibleWindowsHost,
powerShellVersion: '5.1_bad'
})
).toEqual({ kind: 'legacy', reason: 'unknown-powershell' })
})
it('selects the detected libc family when both Linux variants exist', () => {
const manifest = createSshRelayArtifactTestManifest()
const musl = structuredClone(manifest.tuples[0])
+25 -1
View File
@@ -3,6 +3,8 @@ import { sshRelayRuntimeDownloadUrl } from './ssh-relay-release-asset'
type VerifiedSshRelayRuntimeTuple = VerifiedSshRelayArtifactManifest['tuples'][number]
const LINUX_KERNEL_RELEASE_MAX_CHARS = 256
type SshRelayHostBase = {
architecture: 'x64' | 'arm64'
processTranslated: boolean
@@ -100,6 +102,24 @@ function parseNumericVersion(value: string | undefined): number[] | null {
return components.every(Number.isSafeInteger) ? components : null
}
function parseLinuxKernelVersion(value: string | undefined): number[] | null {
if (!value || value.length > LINUX_KERNEL_RELEASE_MAX_CHARS) {
return null
}
// Why: real distro kernels include `_`, `+`, and `~`; other version fields keep the stricter
// generic grammar so this compatibility exception cannot weaken unrelated host evidence.
const match = /^(\d+(?:\.\d+){1,3})(?:-[0-9A-Za-z._+~-]+)?$/.exec(value)
if (!match) {
return null
}
const components = match[1].split('.').map(Number)
return components.every(Number.isSafeInteger) ? components : null
}
export function isSshRelayLinuxKernelRelease(value: string | undefined): value is string {
return parseLinuxKernelVersion(value) !== null
}
function parseOpenSshVersion(value: string | undefined): number[] | null {
if (!value) {
return null
@@ -144,7 +164,11 @@ function selectLinux(
if (tuple.compatibility.kind !== 'linux') {
return { kind: 'legacy', reason: 'tuple-inconsistent' }
}
const kernel = meetsVersion(host.kernelVersion, tuple.compatibility.minimumKernelVersion)
const kernel = meetsVersion(
host.kernelVersion,
tuple.compatibility.minimumKernelVersion,
parseLinuxKernelVersion
)
if (kernel === null) {
return { kind: 'legacy', reason: 'unknown-kernel' }
}
@@ -0,0 +1,87 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshConnection } from './ssh-connection'
const execCommandMock = vi.hoisted(() => vi.fn())
vi.mock('./ssh-relay-deploy-helpers', () => ({
execCommand: execCommandMock
}))
const { detectSshRelayLinuxKernelRelease } = await import('./ssh-relay-linux-kernel-detection')
const conn = {} as SshConnection
const marker = '__ORCA_SSH_RELAY_KERNEL__'
function segment(...lines: string[]): string {
return [`${marker} BEGIN`, ...lines, `${marker} END`].join('\n')
}
describe('detectSshRelayLinuxKernelRelease', () => {
beforeEach(() => {
execCommandMock.mockReset()
})
it.each(['4.18.0-553.5.1.el8_10.x86_64', '5.15.0-107-generic', '6.6.15-0-lts'])(
'returns the exact marked distro kernel release %s',
async (release) => {
execCommandMock.mockResolvedValueOnce(
['Last login: ignored', segment(release), 'post-command noise'].join('\n')
)
await expect(detectSshRelayLinuxKernelRelease(conn)).resolves.toBe(release)
}
)
it.each([
['', 'empty segment'],
['6.8.0\nsecond-line', 'multiple marked lines'],
[`${marker} BEGIN\n6.8.0`, 'unterminated segment'],
[segment('x'.repeat(257)), 'oversized release'],
[segment('release-6.8.0'), 'malformed numeric prefix'],
[segment('6.8.0 generic'), 'suffix whitespace'],
[segment('6.8.0/generic'), 'suffix slash'],
[segment('6.8.0:generic'), 'suffix colon'],
[segment('6.8.0@generic'), 'unsupported suffix punctuation'],
[segment('6.8.0\tgeneric'), 'control whitespace'],
['6.8.0', 'unmarked evidence'],
[[segment('6.8.0'), segment('6.8.0')].join('\n'), 'duplicate complete segments'],
[`startup${marker} BEGIN\n6.8.0\n${marker} END`, 'marker concatenated to startup noise']
])('returns unknown for %s', async (output) => {
execCommandMock.mockResolvedValueOnce(output)
await expect(detectSshRelayLinuxKernelRelease(conn)).resolves.toBeUndefined()
})
it('classifies an unavailable probe as unknown without inventing fallback policy', async () => {
execCommandMock.mockRejectedValueOnce(new Error('remote command unavailable'))
await expect(detectSshRelayLinuxKernelRelease(conn)).resolves.toBeUndefined()
})
it('propagates cancellation and passes the signal into the single bounded probe', async () => {
const signal = new AbortController().signal
const abortError = Object.assign(new Error('cancelled'), { name: 'AbortError' })
execCommandMock.mockRejectedValueOnce(abortError)
await expect(detectSshRelayLinuxKernelRelease(conn, { signal })).rejects.toBe(abortError)
expect(execCommandMock).toHaveBeenCalledTimes(1)
expect(execCommandMock).toHaveBeenCalledWith(conn, expect.any(String), {
signal,
timeoutMs: 15_000
})
})
it('constructs one marked POSIX-shell uname probe without runtime dependencies', async () => {
execCommandMock.mockResolvedValueOnce(segment('6.8.0'))
await detectSshRelayLinuxKernelRelease(conn)
const command = execCommandMock.mock.calls[0]?.[1] ?? ''
expect(command).toContain('command -v uname')
expect(command).toContain('uname -r')
expect(command.match(/uname -r/gu)).toHaveLength(1)
expect(command).toContain(`${marker} BEGIN`)
expect(command).toContain(`${marker} END`)
expect(command).not.toMatch(/\b(?:node|npm|python|perl|tar|sha256sum|shasum)\b/u)
})
})
@@ -0,0 +1,86 @@
import {
getProcessOutputFields,
iterateProcessOutputLines
} from '../../shared/process-output-field-scanner'
import type { SshConnection } from './ssh-connection'
import { isSshRelayLinuxKernelRelease } from './ssh-relay-artifact-selector'
import { execCommand } from './ssh-relay-deploy-helpers'
const PROBE_MARKER = '__ORCA_SSH_RELAY_KERNEL__'
const PROBE_TIMEOUT_MS = 15_000
const PROBE_COMMAND = [
`printf '\n%s\n' '${PROBE_MARKER} BEGIN'`,
'if command -v uname >/dev/null 2>&1; then uname -r 2>&1 || :; fi',
`printf '%s\n' '${PROBE_MARKER} END'`
].join('\n')
function parseMarker(line: string): 'BEGIN' | 'END' | null {
const fields = getProcessOutputFields(line, 3)
if (fields.length !== 2 || fields[0] !== PROBE_MARKER) {
return null
}
return fields[1] === 'BEGIN' || fields[1] === 'END' ? fields[1] : null
}
function parseKernelRelease(output: string): string | undefined {
let activeLines: string[] | null = null
let release: string | undefined
let invalid = false
for (const line of iterateProcessOutputLines(output)) {
const marker = parseMarker(line)
if (marker === 'BEGIN') {
if (activeLines || release !== undefined) {
invalid = true
}
activeLines = []
continue
}
if (marker === 'END') {
if (!activeLines || activeLines.length !== 1 || release !== undefined) {
invalid = true
activeLines = null
continue
}
release = activeLines[0]
activeLines = null
continue
}
if (activeLines) {
if (activeLines.length === 1) {
invalid = true
} else {
activeLines.push(line)
}
}
}
if (invalid || activeLines || !isSshRelayLinuxKernelRelease(release)) {
return undefined
}
return release
}
function isAbortError(error: unknown): boolean {
return error instanceof Error && error.name === 'AbortError'
}
export async function detectSshRelayLinuxKernelRelease(
conn: SshConnection,
{ signal }: { signal?: AbortSignal } = {}
): Promise<string | undefined> {
try {
const output = await execCommand(conn, PROBE_COMMAND, {
signal,
timeoutMs: PROBE_TIMEOUT_MS
})
return parseKernelRelease(output)
} catch (error) {
// Why: cancellation must settle the caller's work instead of becoming compatibility evidence.
if (isAbortError(error)) {
throw error
}
return undefined
}
}