import { readFile } from '#host-fs-promises' import path from '#host-path' import b4a from 'b4a' import { pearMultisigShapeOk } from 'bare-os-protocol/bare-os-pear-multisig-shape.js' import { seedLog } from './host-logger.mjs' /** * @returns {Promise<((cmd: string, args: string[], opts?: object) => object) | null>} */ async function loadSpawnSync() { try { const m = await import('bare-subprocess') if (m && typeof m.spawnSync === 'function') return m.spawnSync } catch { /* bare-subprocess missing or native addon unavailable */ } try { const m = await import('child_process') if (m && typeof m.spawnSync === 'function') return m.spawnSync } catch { /* Node child_process unavailable */ } return null } /** * @param {object} r spawnSync result (Node or bare-subprocess) * @param {'stdout' | 'stderr'} key */ function pipeText(r, key) { const v = r[key] if (v == null) return '' if (typeof v === 'string') return v try { return b4a.toString(v) } catch { return String(v) } } /** * When `pear.multisig.json` exists next to the kernel tree, validate shape and log status. * Mirrors Holepunch `pear-multisig-link` style metadata (operational hint, not cryptography). * Subprocess: prefers **`bare-subprocess`** (Pear/Bare); falls back to **`child_process`** on Node. * Do not use **`node:child_process`** — it is not resolvable under Bare. * @param {string} kernelRoot */ export async function logPearMultisigKernelHint(kernelRoot) { const envOut = globalThis.process?.env const f = path.join(kernelRoot, 'pear.multisig.json') try { const raw = await readFile(f, 'utf8') const j = JSON.parse(raw) if (!pearMultisigShapeOk(j)) { seedLog( 'warn', 'pear.multisig.json: expected { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length', { code: 'pear_multisig_shape' } ) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped' return } const signers = j.signers const quorum = j.quorum seedLog( 'info', `pear.multisig.json OK (${signers.length} signers, quorum ${quorum})`, { code: 'pear_multisig_ok' } ) const env = globalThis.process?.env if ( env?.BARE_OS_HYPER_MULTISIG_VERIFY === '1' || env?.BARE_OS_HYPER_MULTISIG_VERIFY === 'true' ) { const spawnSync = await loadSpawnSync() if (!spawnSync) { seedLog( 'warn', 'hyper-multisig verify skipped (no subprocess: install bare-subprocess or use Node)', { code: 'hyper_multisig_no_subprocess' } ) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped' return } const r = spawnSync('hyper-multisig', ['verify', f], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) if (r.error) { seedLog( 'warn', 'hyper-multisig verify skipped (CLI not on PATH): ' + String(r.error.message || r.error), { code: 'hyper_multisig_cli_missing' } ) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped' } else if (r.status !== 0) { const msg = (pipeText(r, 'stderr') || pipeText(r, 'stdout')).trim().slice(0, 400) seedLog('warn', 'hyper-multisig verify failed: ' + (msg || `exit ${r.status}`), { code: 'hyper_multisig_verify_failed', status: r.status }) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'failed' } else { seedLog('info', 'hyper-multisig verify OK', { code: 'hyper_multisig_verify_ok' }) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'ok' } } else if (envOut) { envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped' } } catch (e) { if (/** @type {NodeJS.ErrnoException} */ (e).code === 'ENOENT') return seedLog('warn', 'pear.multisig.json: ' + (e?.message || String(e)), { code: 'pear_multisig_read_error' }) } } /** * Same shape checks as {@link logPearMultisigKernelHint}, reading **`/kernel/pear.multisig.json`** * via Pear app drive IPC (**`pear-appdrive`**). Subprocess **`hyper-multisig verify`** is skipped. * @param {{ get: (key: string) => Promise }} appDrive */ export async function logPearMultisigKernelHintFromPearAppDrive(appDrive) { const envOut = globalThis.process?.env let raw try { const buf = await appDrive.get('/kernel/pear.multisig.json') if (!buf) return raw = b4a.toString(buf, 'utf8') } catch { return } try { const j = JSON.parse(raw) if (!pearMultisigShapeOk(j)) { seedLog( 'warn', 'pear.multisig.json: expected { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length', { code: 'pear_multisig_shape_bundle' } ) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped' return } const signers = j.signers const quorum = j.quorum seedLog( 'info', `pear.multisig.json OK (${signers.length} signers, quorum ${quorum}) [from Pear bundle]`, { code: 'pear_multisig_ok_bundle' } ) if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped' } catch (e) { seedLog('warn', 'pear.multisig.json (bundle): ' + (e?.message || String(e)), { code: 'pear_multisig_bundle_parse' }) } }