mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* test: audit repository visibility and session-search CLI coverage * test: verify service identities and public skill-sharing output * test: verify terminal retry commands and unsuccessful navigation attempts * test: exercise bulk-close refusal and printed legacy prompt receipts * test: audit host discovery and agent launch recovery identities * test: audit ACP replay, lifecycle, and hook delivery coverage * test: audit automation errors and build artifact guards * test: audit mobile bridge and bundle lifecycle coverage * docs: reconcile audit tracker with input-ownership changes --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
278 lines
10 KiB
JavaScript
278 lines
10 KiB
JavaScript
import {
|
|
cpSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
symlinkSync,
|
|
writeFileSync
|
|
} from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join, resolve } from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { NODE_RUNTIME_PIN } from '../../src/shared/node-runtime-pin.ts'
|
|
import { sha256Of } from './orcad-prebuild-slot-contents.mjs'
|
|
import {
|
|
validateWindowsPrebuildCache,
|
|
windowsPrebuildCacheIdentity,
|
|
WINDOWS_PREBUILD_CACHE_INPUTS
|
|
} from './orcad-windows-prebuild-cache.mjs'
|
|
import { peImage } from './windows-pe-image-fixture.mjs'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const { CYGWIN_BREAKAWAY_MARKER } = require('./node-pty-job-ownership.cjs')
|
|
const temporary = []
|
|
afterEach(() => {
|
|
for (const dir of temporary.splice(0)) {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
function fixture(arch = 'x64') {
|
|
const dir = mkdtempSync(join(tmpdir(), 'orca-windows-prebuild-cache-'))
|
|
temporary.push(dir)
|
|
const sourceDir = join(dir, 'source')
|
|
const prebuildsDir = join(dir, 'prebuilds')
|
|
const slot = `win32-${arch}`
|
|
const slotDir = join(prebuildsDir, slot)
|
|
const write = (path, bytes) => {
|
|
mkdirSync(dirname(path), { recursive: true })
|
|
writeFileSync(path, bytes)
|
|
}
|
|
write(join(sourceDir, 'package.json'), '{"version":"1.1.0"}')
|
|
write(join(sourceDir, 'binding.gyp'), '--no-as-needed,-l:libutil.so.1')
|
|
write(join(sourceDir, 'src/unix/pty.cc'), '.symver openpty,openpty@')
|
|
write(join(sourceDir, 'src/win/conpty.cc'), 'L"msys-2.0.dll"')
|
|
mkdirSync(join(sourceDir, 'third_party/conpty/1', `win10-${arch}`), { recursive: true })
|
|
const files = {}
|
|
for (const [file, bytes] of [
|
|
['conpty.node', Buffer.concat([peImage({ arch }), CYGWIN_BREAKAWAY_MARKER])],
|
|
['conpty_console_list.node', peImage({ arch })],
|
|
['conpty/conpty.dll', peImage({ arch })],
|
|
['conpty/OpenConsole.exe', peImage({ arch })],
|
|
['pty.node', peImage({ arch })],
|
|
['winpty.dll', peImage({ arch })],
|
|
['winpty-agent.exe', peImage({ arch })]
|
|
]) {
|
|
write(join(slotDir, file), bytes)
|
|
files[file] = sha256Of(join(slotDir, file))
|
|
if (file !== 'conpty.node' && !file.startsWith('conpty/')) {
|
|
write(join(sourceDir, 'prebuilds', slot, file), bytes)
|
|
}
|
|
if (file.startsWith('conpty/')) {
|
|
write(
|
|
join(sourceDir, 'third_party/conpty/1', `win10-${arch}`, file.slice('conpty/'.length)),
|
|
bytes
|
|
)
|
|
}
|
|
}
|
|
const entry = { platform: 'win32', arch, libc: 'none', glibc: null, napi: 8, files }
|
|
const manifest = {
|
|
schemaVersion: 2,
|
|
module: 'node-pty',
|
|
version: '1.1.0',
|
|
napi: 8,
|
|
nodeHeaders: NODE_RUNTIME_PIN.version,
|
|
slots: { [slot]: entry }
|
|
}
|
|
const save = () => writeFileSync(join(prebuildsDir, 'manifest.json'), JSON.stringify(manifest))
|
|
save()
|
|
return { sourceDir, prebuildsDir, slot, slotDir, entry, manifest, save, platform: 'win32', arch }
|
|
}
|
|
|
|
describe('Windows server prebuild cache validation', () => {
|
|
it.each(['x64', 'arm64'])('accepts a complete current %s payload', (arch) => {
|
|
const f = fixture(arch)
|
|
expect(validateWindowsPrebuildCache(f)).toBe(f.slot)
|
|
expect(() => validateWindowsPrebuildCache({ ...f, platform: 'linux' })).toThrow(
|
|
/requires win32/
|
|
)
|
|
})
|
|
|
|
it.each([
|
|
['module', 'upstream'],
|
|
['version', '0.0.0'],
|
|
['napi', 10],
|
|
['nodeHeaders', '24.0.0'],
|
|
['schemaVersion', 1]
|
|
])('refuses a changed manifest %s', (field, value) => {
|
|
const f = fixture()
|
|
f.manifest[field] = value
|
|
f.save()
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/manifest does not match/)
|
|
})
|
|
|
|
it.each([
|
|
['arch', 'arm64'],
|
|
['platform', 'linux'],
|
|
['libc', 'glibc'],
|
|
['glibc', '2.28'],
|
|
['napi', 10]
|
|
])('refuses a changed slot %s', (field, value) => {
|
|
const f = fixture()
|
|
f.entry[field] = value
|
|
f.save()
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/metadata does not match/)
|
|
})
|
|
|
|
it('refuses missing, extra and unlisted payloads before trusting their hashes', () => {
|
|
const f = fixture()
|
|
delete f.entry.files['conpty/conpty.dll']
|
|
f.save()
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/exactly the current ConPTY payload/)
|
|
const extra = fixture()
|
|
writeFileSync(join(extra.slotDir, 'conpty.pdb'), 'upstream symbols')
|
|
expect(() => validateWindowsPrebuildCache(extra)).toThrow(/exactly the current ConPTY payload/)
|
|
const missing = fixture()
|
|
rmSync(join(missing.slotDir, 'conpty/OpenConsole.exe'))
|
|
expect(() => validateWindowsPrebuildCache(missing)).toThrow(
|
|
/exactly the current ConPTY payload/
|
|
)
|
|
})
|
|
|
|
it('refuses modified bytes even when the file inventory is complete', () => {
|
|
const f = fixture()
|
|
writeFileSync(join(f.slotDir, 'conpty/conpty.dll'), 'tampered')
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/sha256 does not match/)
|
|
})
|
|
|
|
it('refuses unexpected empty directories and payload symlinks', () => {
|
|
const f = fixture()
|
|
mkdirSync(join(f.slotDir, 'upstream'))
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/exactly the current ConPTY payload/)
|
|
rmSync(join(f.slotDir, 'upstream'), { recursive: true })
|
|
const path = join(f.slotDir, 'conpty.node')
|
|
const target = join(f.prebuildsDir, 'outside.node')
|
|
cpSync(path, target)
|
|
rmSync(path)
|
|
symlinkSync(target, path)
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/cannot contain symlinks/)
|
|
})
|
|
|
|
it('compares vendored payload bytes with the current installed dependency', () => {
|
|
const f = fixture()
|
|
writeFileSync(
|
|
join(f.sourceDir, 'third_party/conpty/1/win10-x64/conpty.dll'),
|
|
'different vendor payload'
|
|
)
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/differs from the current vendored/)
|
|
})
|
|
|
|
it.each([
|
|
[
|
|
'wrong PE architecture',
|
|
Buffer.concat([peImage({ arch: 'arm64' }), CYGWIN_BREAKAWAY_MARKER]),
|
|
/targets win32-x64/
|
|
],
|
|
['old breakaway policy', peImage({ arch: 'x64' }), /predates the Cygwin/],
|
|
[
|
|
'post-baseline N-API import',
|
|
Buffer.concat([
|
|
peImage({ arch: 'x64' }),
|
|
CYGWIN_BREAKAWAY_MARKER,
|
|
Buffer.from('node_api_symbol_for')
|
|
]),
|
|
/above N-API 8/
|
|
]
|
|
])('refuses %s even with a matching cached digest', (_name, bytes, error) => {
|
|
const f = fixture()
|
|
writeFileSync(join(f.slotDir, 'conpty.node'), bytes)
|
|
f.entry.files['conpty.node'] = sha256Of(join(f.slotDir, 'conpty.node'))
|
|
f.save()
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(error)
|
|
})
|
|
|
|
it('checks both current source guards independently of cached payload bytes', () => {
|
|
const f = fixture()
|
|
writeFileSync(join(f.sourceDir, 'src/win/conpty.cc'), 'upstream')
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/source.*does not carry/)
|
|
writeFileSync(join(f.sourceDir, 'src/win/conpty.cc'), 'L"msys-2.0.dll"')
|
|
writeFileSync(join(f.sourceDir, 'binding.gyp'), 'upstream')
|
|
expect(() => validateWindowsPrebuildCache(f)).toThrow(/patch is not applied/)
|
|
})
|
|
})
|
|
|
|
describe('Windows server prebuild cache inputs', () => {
|
|
const options = { platform: 'win32', arch: 'x64', imageOS: 'win22', imageVersion: '20260927.1.0' }
|
|
|
|
it('separates host architecture and compiler image without fallback keys', () => {
|
|
const identity = windowsPrebuildCacheIdentity(options)
|
|
expect(identity.key).toMatch(/^orcad-windows-prebuild-v1-win32-x64-[a-f0-9]{64}$/)
|
|
expect(windowsPrebuildCacheIdentity(options)).toEqual(identity)
|
|
for (const override of [
|
|
{ arch: 'arm64' },
|
|
{ imageOS: 'win11' },
|
|
{ imageVersion: '20261001.1.0' }
|
|
]) {
|
|
expect(windowsPrebuildCacheIdentity({ ...options, ...override }).key).not.toBe(identity.key)
|
|
}
|
|
expect(() => windowsPrebuildCacheIdentity({ ...options, imageVersion: '' })).toThrow(
|
|
/ImageVersion/
|
|
)
|
|
})
|
|
|
|
const compilerInputs = [
|
|
...[
|
|
'run-process',
|
|
'spawn-resolution',
|
|
'process-tree-termination',
|
|
'process-tree-kill-gate',
|
|
'spawn-observer',
|
|
'bounded-output-sink',
|
|
'growing-byte-buffer',
|
|
'child-termination-reporter',
|
|
'process-spec',
|
|
'windows-command-line',
|
|
'windows-cmd-shim-resolution',
|
|
'windows-system-binary',
|
|
'windows-tree-kill'
|
|
].map((name) => `src/packages/process-host/src/${name}.ts`),
|
|
'src/packages/process-host/.gitignore',
|
|
'src/packages/process-host/package.json',
|
|
'src/packages/process-host/scripts/build-dist.mjs',
|
|
'src/packages/process-host/tsconfig.json'
|
|
]
|
|
const cacheInputs = [...new Set([...WINDOWS_PREBUILD_CACHE_INPUTS, ...compilerInputs])]
|
|
|
|
it('reuses native binaries across app versions but invalidates every other manifest field', () => {
|
|
const repository = mkdtempSync(join(tmpdir(), 'orca-prebuild-version-key-'))
|
|
temporary.push(repository)
|
|
const source = resolve(import.meta.dirname, '../..')
|
|
for (const relative of cacheInputs) {
|
|
mkdirSync(dirname(join(repository, relative)), { recursive: true })
|
|
cpSync(join(source, relative), join(repository, relative))
|
|
}
|
|
const path = join(repository, 'package.json')
|
|
const manifest = JSON.parse(readFileSync(path, 'utf8'))
|
|
const before = windowsPrebuildCacheIdentity({ ...options, repository }).key
|
|
writeFileSync(path, JSON.stringify({ ...manifest, version: '999.0.0' }))
|
|
expect(windowsPrebuildCacheIdentity({ ...options, repository }).key).toBe(before)
|
|
for (const field of Object.keys(manifest).filter((field) => field !== 'version')) {
|
|
writeFileSync(path, JSON.stringify({ ...manifest, [field]: 'changed' }))
|
|
expect(windowsPrebuildCacheIdentity({ ...options, repository }).key).not.toBe(before)
|
|
}
|
|
})
|
|
|
|
it.each(cacheInputs)('invalidates a changed %s input', (file) => {
|
|
const repository = mkdtempSync(join(tmpdir(), 'orca-prebuild-key-'))
|
|
temporary.push(repository)
|
|
const source = resolve(import.meta.dirname, '../..')
|
|
for (const relative of cacheInputs) {
|
|
mkdirSync(dirname(join(repository, relative)), { recursive: true })
|
|
cpSync(join(source, relative), join(repository, relative))
|
|
}
|
|
const before = windowsPrebuildCacheIdentity({ ...options, repository }).key
|
|
writeFileSync(
|
|
join(repository, file),
|
|
file === 'package.json'
|
|
? JSON.stringify({
|
|
...JSON.parse(readFileSync(join(repository, file), 'utf8')),
|
|
engines: { node: 'changed' }
|
|
})
|
|
: `${readFileSync(join(repository, file), 'utf8')}\nchanged input`
|
|
)
|
|
expect(windowsPrebuildCacheIdentity({ ...options, repository }).key).not.toBe(before)
|
|
})
|
|
})
|