#!/usr/bin/env node /** * Post-build checks for bare-os seeder/booter standalones (CI + local). * * Verifies each out/bare-os-{product}-{host}/ binary exists, records size, * rejects oversized Booter/Seeder builds (CodeRange OOM risk), and smoke-runs * the native-host booter when present. * * Usage: * node scripts/verify-standalone-build.cjs * BARE_OS_HOSTS=linux-x64,darwin-arm64 node scripts/verify-standalone-build.cjs */ 'use strict' const path = require('path') const fs = require('fs') const { spawnSync } = require('child_process') const { ALL_64, parseHostList, hostSupportsQvacNative } = require('./hosts.cjs') const root = path.resolve(__dirname, '..') const hosts = parseHostList(process.env.BARE_OS_HOSTS, ALL_64) const nativeHost = `${process.platform}-${process.arch}` /** * Soft ceilings (bytes). CI fails above these — keeps pear-runtime / dup natives out. * Booter hosts with in-process QVAC (`@qvac/llm-llamacpp`) are much larger (~250–300+ MiB); * win32-arm64 stubs QVAC and stays on the lean ceiling. */ const MAX_BYTES = { seeder: { 'darwin-arm64': 110 * 1024 * 1024, 'darwin-x64': 120 * 1024 * 1024, 'linux-x64': 145 * 1024 * 1024, 'linux-arm64': 145 * 1024 * 1024, 'win32-x64': 150 * 1024 * 1024, 'win32-arm64': 150 * 1024 * 1024, }, booter: { 'darwin-arm64': 110 * 1024 * 1024, 'darwin-x64': 120 * 1024 * 1024, 'linux-x64': 145 * 1024 * 1024, 'linux-arm64': 145 * 1024 * 1024, 'win32-x64': 150 * 1024 * 1024, 'win32-arm64': 150 * 1024 * 1024, }, } /** Ceiling when QVAC natives are packed into the booter (~294 MiB seen on linux-x64 CI). */ const MAX_BYTES_BOOTER_WITH_QVAC = 360 * 1024 * 1024 /** * @param {string} product * @param {string} host * @param {string} file */ function maxBytesFor(product, host, file) { const lean = MAX_BYTES[product]?.[host] || 160 * 1024 * 1024 if (product !== 'booter') return lean const infoPath = path.join(path.dirname(file), 'build-info.json') let stubbed = null try { const info = JSON.parse(fs.readFileSync(infoPath, 'utf8')) if (info.skipQvac === true || info.qvacStubbed === true) stubbed = true else if (info.skipQvac === false || info.qvacStubbed === false) stubbed = false } catch { /* fall through */ } if (stubbed === true) return lean if (stubbed === false || hostSupportsQvacNative(host)) { return Math.max(lean, MAX_BYTES_BOOTER_WITH_QVAC) } return lean } function binName(product, host) { const name = `bare-os-${product}` return host.startsWith('win32') ? `${name}.exe` : name } function findBinary(product, host) { const dir = path.join(root, 'out', `bare-os-${product}-${host}`) const preferred = path.join(dir, binName(product, host)) if (fs.existsSync(preferred)) return preferred if (!fs.existsSync(dir)) return null const walk = (d) => { for (const ent of fs.readdirSync(d, { withFileTypes: true })) { const p = path.join(d, ent.name) if (ent.isDirectory()) { const f = walk(p) if (f) return f } else if (ent.name === binName(product, host) || ent.name === `bare-os-${product}`) { return p } } return null } return walk(dir) } function assertSize(product, host, file) { const st = fs.statSync(file) const mb = (st.size / 1024 / 1024).toFixed(1) const max = maxBytesFor(product, host, file) console.log(`[verify] ${product}/${host}: ${mb} MiB (${file})`) if (st.size > max) { throw new Error( `${product}/${host} binary is ${mb} MiB — exceeds ${(max / 1024 / 1024).toFixed(0)} MiB ceiling (likely packed pear-runtime, duplicate natives, or unexpected QVAC growth)` ) } return st.size } /** Darwin JIT/library-validation entitlements required under Hardened Runtime. */ const REQUIRED_ENTITLEMENTS = [ 'com.apple.security.cs.allow-jit', 'com.apple.security.cs.allow-unsigned-executable-memory', 'com.apple.security.cs.disable-library-validation', ] function assertDarwinEntitlements(product, host, file) { if (!host.startsWith('darwin') || process.platform !== 'darwin') return const r = spawnSync('codesign', ['-d', '--entitlements', '-', file], { encoding: 'utf8', }) if (r.status !== 0) { throw new Error( `[verify] ${product}/${host}: codesign -d --entitlements failed: ${r.stderr || r.stdout}` ) } const out = r.stdout || '' const missing = REQUIRED_ENTITLEMENTS.filter((key) => !out.includes(key)) if (missing.length) { throw new Error( `[verify] ${product}/${host}: missing macOS entitlements: ${missing.join(', ')}` ) } console.log(`[verify] ${product}/${host}: darwin entitlements ok`) } function assertMarkers(file) { // Sample start of file as latin1 for cheap string checks on embedded bundle const fd = fs.openSync(file, 'r') try { const buf = Buffer.alloc(Math.min(fs.fstatSync(fd).size, 48 * 1024 * 1024)) fs.readSync(fd, buf, 0, buf.length, Math.max(0, fs.fstatSync(fd).size - buf.length)) const s = buf.toString('latin1') if (!s.includes('standalone.mjs') && !s.includes('bare-os-host-flags') && !s.includes('--datadir')) { // binary may compress/strip; require at least one app marker if (!s.includes('bare-os-booter') && !s.includes('bare-os-seeder')) { throw new Error(`${file}: missing bare-os markers in embed`) } } // pear-runtime stub path or real module both contain the string; prefer stub class marker if (s.includes('PearRuntimeStub') || s.includes('pear-runtime not packed')) { console.log('[verify] pear-runtime stub present (OTA not embedded)') } if (file.includes('bare-os-booter') && !s.includes('bare-os-discord-gateway-ws')) { throw new Error( `${file}: packed booter missing Discord WHATWG gateway WS bootstrap (bare-os-discord-gateway-ws)` ) } } finally { fs.closeSync(fd) } } function smokeNativeBooter(file) { if (process.platform === 'win32') { console.log('[verify] skip smoke on win32') return } const data = path.join( require('os').tmpdir(), `bare-os-verify-${process.pid}-${Date.now()}` ) fs.mkdirSync(data, { recursive: true }) const env = { ...process.env, BARE_OS_HOST_DATA: data, BARE_OS_BOOT_TIMEOUT_MS: '1500', BARE_OS_OTA_DISABLE: '1', } console.log(`[verify] smoke: ${file} --datadir ${data}`) const r = spawnSync(file, ['--datadir', data, '--no-updates'], { env, encoding: 'utf8', timeout: 20_000, }) const out = `${r.stdout || ''}${r.stderr || ''}` if (/Failed to reserve virtual memory for CodeRange/i.test(out)) { throw new Error('smoke failed: CodeRange OOM') } if (/MODULE_NOT_FOUND.*bare-os-booter/i.test(out)) { throw new Error(`smoke failed: module missing\n${out.slice(0, 800)}`) } // Expected without a seeder peer if (!/No swarm peers|swarm peers within/i.test(out) && r.status === 0 && !out.trim()) { console.warn('[verify] WARN: smoke produced little output; status', r.status) } else { console.log('[verify] smoke ok (exit', r.status, ')') } // datadir must have been applied const store = path.join(data, 'corestore', 'booter') if (!fs.existsSync(store)) { throw new Error(`smoke: --datadir did not create ${store}`) } console.log('[verify] --datadir ok →', store) } function main() { const argProducts = process.argv.slice(2).filter((a) => a === 'seeder' || a === 'booter') const products = argProducts.length ? argProducts : ['seeder', 'booter'] let checked = 0 for (const product of products) { for (const host of hosts) { const file = findBinary(product, host) if (!file) { console.warn(`[verify] WARN: missing ${product}/${host}`) continue } assertSize(product, host, file) assertMarkers(file) assertDarwinEntitlements(product, host, file) checked++ if (product === 'booter' && host === nativeHost) { smokeNativeBooter(file) } } } if (checked === 0) { throw new Error('no binaries found under out/ — run make first') } console.log(`[verify] passed (${checked} binaries)`) } try { main() } catch (err) { console.error('[verify] FAIL:', err.message || err) process.exit(1) }