Files
orca/config/scripts/ssh-hostile-hosts-workflow.test.mjs
T
8afa1db50c feat(ssh): rung B glibc 2.17 compat runtime; gate remote vault on host node:sqlite (#24148)
* feat(ssh): wire rung B to the glibc 2.17 compat runtime; gate rung C vault on full node:sqlite

- COMPAT_RELAY_RUNTIMES lists linux-x64-glibc217; rung B plans the compat slot and compat
  pinned Node when glibc is below 2.28 or rung A refused with libc_floor/missing_lib.
- The relay version folds the compat runtime's executable hash; refusals are cached per runtime.
- The orcad template stages an optional linux-x64-glibc217 target (base package + compat
  node-pty slot + compat runtime marker); the verifier and materializer accept it.
- node-pty slot loader falls back to the compat slot when the default slot is missing or
  needs a newer glibc.
- Runtime store GC keeps the compat pin beside the default one on every relay connect.
- hasNodeSqliteReaderApi (DatabaseSync + backup) gates relay session search and the relay
  OpenCode reader, which now names the host Node version in its unavailable reason; the SSH
  vault reader installs the compat Node on old-glibc hosts and uploads nothing when no
  pinned Node can run.
- Rung D: a remembered noexec reports home_noexec and never advises installing Node.

* fix(ssh): re-prove a replayed noexec after rung D so allowing exec recovers the host

* fix(ssh): keep the rung B compat runtime pinned in the relay-connect store GC

* test(ssh): mock deployment-target facts in the Windows OpenCode runtime tests

* ci(ssh): build the glibc 2.17 compat slot for the hostile-host matrix; CentOS 7 lands on rung B

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-01 05:32:05 -07:00

96 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import { NODE_RUNTIME_PIN } from '../../src/shared/node-runtime-pin.ts'
import { HOSTILE_HOST_CELLS } from '../../src/main/ssh/ssh-hostile-host-cells.ts'
const projectDir = resolve(import.meta.dirname, '../..')
const readText = (name) => readFileSync(join(projectDir, '.github/workflows', name), 'utf8')
const workflow = parse(readText('ssh-hostile-hosts.yml'))
function imageRefs(text, pattern) {
return [...new Set(text.match(pattern) ?? [])]
}
describe('SSH hostile-host workflow', () => {
it('runs on demand and on path-filtered, non-draft pull requests only', () => {
expect(Object.keys(workflow.on).sort()).toEqual(['pull_request', 'workflow_dispatch'])
expect(workflow.on.pull_request.paths).toContain('src/main/ssh/ssh-relay-*')
expect(workflow.jobs.glibc_slot.if).toContain('github.event.pull_request.draft != true')
expect(workflow.jobs.musl_slot.needs).toBe('glibc_slot')
expect(workflow.jobs.glibc217_slot.needs).toBe('musl_slot')
expect(workflow.jobs.hosts.needs).toBe('glibc217_slot')
})
// Why: the slots must come from the same builders the headless-server lanes qualify, so a
// NODE_RUNTIME_PIN or builder move cannot leave this matrix testing a stale runtime.
it('builds the slots on the headless-server lanes’ pinned builder images', () => {
const nodeServer = readText('node-server-tests.yml')
const hostile = readText('ssh-hostile-hosts.yml')
const alpine = /node:[0-9.]+-alpine@sha256:[0-9a-f]{64}/g
const manylinux = /quay\.io\/pypa\/manylinux_2_28_x86_64@sha256:[0-9a-f]{64}/g
expect(imageRefs(hostile, alpine)).toEqual(imageRefs(nodeServer, alpine))
expect(imageRefs(hostile, alpine)).toEqual([
expect.stringMatching(new RegExp(`^node:${NODE_RUNTIME_PIN.version.replaceAll('.', '\\.')}-`))
])
expect(imageRefs(hostile, manylinux)).toEqual(imageRefs(nodeServer, manylinux))
expect(imageRefs(hostile, manylinux)).toHaveLength(1)
const manylinux2014 = /quay\.io\/pypa\/manylinux2014_x86_64@sha256:[0-9a-f]{64}/g
expect(imageRefs(hostile, manylinux2014)).toEqual(imageRefs(nodeServer, manylinux2014))
expect(imageRefs(hostile, manylinux2014)).toHaveLength(1)
})
// Why: the CentOS 7 cell expects rung B, which needs the compat slot in the hosts' template.
it('builds the glibc 2.17 compat slot and hands it to the hosts job', () => {
const compat = workflow.jobs.glibc217_slot.steps.map((step) => step.run ?? '').join('\n')
expect(compat).toContain('--slot=linux-x64-glibc217 --print-runtime')
expect(compat).toContain('--slot=linux-x64-glibc217 --smoke')
const upload = workflow.jobs.glibc217_slot.steps.find((step) =>
String(step.uses).startsWith('actions/upload-artifact')
)
const download = workflow.jobs.hosts.steps.find((step) =>
String(step.uses).startsWith('actions/download-artifact')
)
expect(download.with.name).toBe(upload.with.name)
})
it('opts the matrix in and runs it against both x64 Linux slots', () => {
const steps = workflow.jobs.hosts.steps
const matrix = steps.find((step) => step.name === 'Run the hostile-host matrix')
expect(matrix.env.ORCA_RUN_SSH_HOSTILE_HOSTS).toBe('1')
expect(matrix.run).toBe('pnpm test src/main/ssh/ssh-relay-hostile-hosts.docker.test.ts')
expect(steps.map((step) => step.run ?? '').join('\n')).toContain(
'--targets linux-x64-glibc,linux-x64-musl'
)
})
// Why: each macOS cell expects its runner's own slot, so the runner, template target and cell
// must agree or the cell would test a slot the template never packaged.
it('runs each macOS cell on the runner and template target it expects', () => {
const job = workflow.jobs.macos_hosts
expect(job.if).toContain('github.event.pull_request.draft != true')
expect(job.needs).toBeUndefined()
const runners = { 'darwin-arm64': 'macos-14', 'darwin-x64': 'macos-15-intel' }
const macCells = HOSTILE_HOST_CELLS.filter((cell) => cell.host === 'local-sshd')
expect(
job.strategy.matrix.include.map(({ os, target, cell }) => ({ os, target, cell }))
).toEqual(
macCells.map((cell) => ({
os: runners[cell.expect.target],
target: cell.expect.target,
cell: cell.id
}))
)
const run = job.steps.map((step) => step.run ?? '').join('\n')
expect(run).toContain('--targets ${{ matrix.target }}')
expect(run).toContain('--require-slots ${{ matrix.target }}')
const cellStep = job.steps.find((step) => step.name === 'Run the macOS hostile-host cell')
expect(cellStep.env).toEqual({
ORCA_RUN_SSH_HOSTILE_HOSTS: '1',
ORCA_SSH_HOSTILE_HOST_CELLS: '${{ matrix.cell }}'
})
expect(cellStep.run).toBe('pnpm test src/main/ssh/ssh-relay-hostile-hosts.docker.test.ts')
})
})