feat(booter): complete P2P POSIX roadmap items and doc alignment

- ADRs under docs/adr/; KERNEL_CONTRACT + env appendix + handbook updates
- socketMsgSurface schema 5; replication_snapshot schema 2; syscall/example + tests
- MBR pacing docs/microbench; export mbrReadTimeoutMsForDisk; shell passthrough envs
- verify-ctx-api-feature-bits: syscall schema vs posix-compliance-matrix
- Wasm hostname import, disk.os snapshot hints, kernel-ext resolution docs
- coreutils nice + matrix/dashboard; syscalls.example ctxApiVersion 1.51.1
- Seeder kernel rsync parity; assorted booter/protocol doc fixes
This commit is contained in:
Raven Scott
2026-04-05 15:04:59 -04:00
parent 39dbf0f1be
commit 169df862f7
62 changed files with 1584 additions and 509 deletions
+2 -1
View File
@@ -5,7 +5,7 @@ Authoritative **version alignment** with protocol and telemetry schema numbers l
## Maintenance
- **Host filesystem**: first-party booter and seeder sources use **`import … from '#host-fs'`** / **`#host-path`** (and seeder **`#host-fs-promises`**) so Bare resolves **`bare-fs`** / **`bare-path`** and Node resolves the built-in **`fs`** / **`path`** modules via the default **`imports`** condition. **`bare-fs`** is a direct **`dependencies`** entry of **`bare-os-booter`**. CI: **`scripts/verify-pear-no-static-node-import.mjs`** rejects bare **`fs`** / **`path`** specifiers.
- **POSIX / proc**: **`/proc/bare_os/syscalls.json`** schema **10** with **`socketMsgSurface` schema 4** (`logicalScmRightsOnSend`); includes **`posixXsh`** (**`posix_fadvise`** simulated), **`fdModel`**, **`errnoHints`**; JSON text **cached** until **`vfs.bareOsClearWarmReadCaches`**. **`/proc/bare_os/process_table.json`** schema **7**; **`/proc/bare_os/protomux.json`** wire text schema **2**; **`/dev/shm`** in-memory named segments. **`/proc/bare_os/security_posture.json`** schema **4****`vaultCryptoPrimitives`**, **`bareCryptoReportedVersion`**, **`peerAdmission`** (**denylist**, **`requireCaps`**, NDJSON audit env pointers). **`/proc/bare_os_features`**: **`pearIpcConservativeAdvertisement`** (channel names only; no host wiring claim). Protocol export **`BARE_OS_POSIX_PROFILE_*`**.
- **POSIX / proc**: **`/proc/bare_os/syscalls.json`** schema **10** with **`socketMsgSurface` schema 5** (`logicalScmRightsOnSend`); includes **`posixXsh`** (**`posix_fadvise`** simulated), **`fdModel`**, **`errnoHints`**; JSON text **cached** until **`vfs.bareOsClearWarmReadCaches`**. **`/proc/bare_os/process_table.json`** schema **7**; **`/proc/bare_os/protomux.json`** wire text schema **2**; **`/dev/shm`** in-memory named segments. **`/proc/bare_os/security_posture.json`** schema **5** adds **`blindPeeringOperatorSketch`** env gate pointers; **`vaultCryptoPrimitives`**, **`bareCryptoReportedVersion`**, **`peerAdmission`**. **`/proc/bare_os_features`**: **`protomuxAppChannel`** advertisement + **`pearIpcConservativeAdvertisement`**. Protocol export **`BARE_OS_POSIX_PROFILE_*`**.
- **Blind-relay / pairing / geo proc** (default): **`bareOsProcBlindPeerRelayHintsMinimal`** returns **schema 1** with **`operatorRedacted: true`**. The legacy export **`bareOsProcBlindPeerRelayHintsStub`** was removed; use **`bareOsProcBlindPeerRelayHintsMinimal`** only.
- **Vendored bundles**: post-esbuild **`scripts/sanitize-bare-bundles.mjs`** (from **`bare-os-bare-libs/build.mjs`**) strips CI-forbidden markers and replaces **`node:`** requires in **`bareDev.js`** with **`bare-*`** modules.
- **Boot hooks (naming)**: canonical **`bareOsRegisterBootStepHook`**, **`bareOsInvokeBootStepHooks`**, **`bareOsEmitBareBootStepHint`**; legacy **`*BootPhase*`** methods remain thin wrappers (**no `bareOsCtxApiVersion` bump**). See [docs/reference/naming-alias-matrix.md](../../docs/reference/naming-alias-matrix.md).
@@ -18,6 +18,7 @@ Authoritative **version alignment** with protocol and telemetry schema numbers l
| Version | Booter (workspace) | Notes |
| ------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1.51.1 | 0.1.0 | **`disk.os` `bare_os.replication_snapshot`** JSON **schema 2** — merges seed snapshot hints / manifest slice / chain + **`BARE_OS_CORESTORE_SNAPSHOT_JSON`**. Shell **`BARE_OS_SHELL_NOUNSET`** / **`set -u`** / **`set -o nounset`**. Wasm **`BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT`** → **`env.bare_os_hostname_peek`**. **`ctx.bareOsTrySpawnHostSubprocess`** merges default **`timeoutMs`** from **`BARE_OS_BARE_SUBPROCESS_TIMEOUT_MS`**. **`/proc/bare_os_features`**: **`protomuxAppChannel`**. **`/proc/bare_os/security_posture.json`** schema **5** (**`blindPeeringOperatorSketch`**). Swarm disk: **`BARE_OS_MBR_READ_TIMEOUT_MS`**, **`BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE`**. Kernel **`kernel-ext-resolution.json`** schema **3** + **`BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS`**. Seeder multisig logs via **`seedLog`**. |
| 1.51.0 | 0.1.0 | **`disk.os` `bare_os.replication_operator_sketch`** JSON **schema 5** — optional **`corestoreOperatorSketch`** from host **`BARE_OS_CORESTORE_STATS_JSON`**. **`/proc/bare_os/syscalls.json`** schema **10****`ctx.bareOsSyscall('posix_fadvise')`** simulated no-op; **`getconf`** / **`posix-compliance-matrix`** syscall op list includes **`posix_fadvise`**. **`hrpc_allowlist_sketch`** proc **schema 3** + **`routeTableSchemaVersion`** ( **`bare-os-hrpc-route-table.js`** ). **`export -p`** shell builtin. Optional **`kernel.ext.d` `provides`** version conflict detection (strict boot). Runtime caps: **`swarm_health`** proc paths. |
| 1.50.0 | 0.1.0 | Optional **`BARE_OS_SHELL_READ_BUILTIN`** **`read`** builtin (bounded stdin / **`readLine`**). Expanded **`bareOsGetconfSysconf`** **`_SC_BARE_OS_*`** runtime caps (pipeline, glob, swarm, socket queues). **`pathconf`**: **`BARE_OS_PERSONAL_ACCT_PREFIX`** + union — **`_PC_NO_TRUNC`** stays **1** under **`/.bare-os/acct/…`**. Socket bridge **`SO_SNDBUF`/`SO_RCVBUF`** logical **`getsockopt`/`setsockopt`**. **`metrics_live.replicationLive`** schema **3** + **`corestoreSnapshotSurface`**; **`bootBudget*`** schema **2** + **`bootBudgetTelemetry`**. Wasm **`BARE_OS_WASM_KERNEL_MONOTONIC_MS`** → **`env.bare_os_monotonic_ms`**. MQ priority tie-break FIFO (**`posixMessageQueues` schema 2**). Blind-relay swarm proc **schema 3** (**`blindPeerStack`**). Hyperbee guest hint **schema 2** (**`hyperbee2ReadSurface`**). **`/run/bare-os/boot-perf.json`**: **`bootBudgetStrict`** / **`bootPolicyStrict`**. |
| 1.49.0 | 0.1.0 | **`ctx.bareOsTrySpawnHostSubprocess`**: optional **`BARE_OS_CTX_BARE_SUBPROCESS_SPAWN`** prefers **`ctx.bare.bareSubprocess.spawn`** when merged. **`/proc/bare_os/extensions.json`** schema **9** (**`resolverModule`**, per-entry **`loadMs`**). **`bareOsPosixPoll`** zero-timeout path includes **`pollClock`**; **`bareOsSyscall('select')`** forwards **`pollClock`** when **`timeoutMs` > 0. **`disk.os` `replication_operator_sketch`** adds **`peerPrioritySketch`** from **`BARE_OS_REPLICATION_PEER_PRIORITY_JSON`**. Warm-cache explicit invalidation counts replication reasons (**`vfs.warm_read_cache_invalidate_replication`**). Cooperative shell job yield (**`suspend-job`** / **`fg`**) + **`foregroundPgid`** on **`fg`**. Peer admission **schema 2** (**`BARE_OS_PEER_DENYLIST_HEX`**, **`BARE_OS_PEER_REQUIRE_CAPS_JSON`**, optional **`BARE_OS_PEER_ADMISSION_AUDIT_NDJSON`** / **`BARE_OS_PEER_ADMISSION_AUDIT_RATE_MS`**). Host env passthrough adds **`BARE_OS_CTX_BARE_SUBPROCESS_SPAWN`**, **`BARE_OS_REPLICATION_PEER_PRIORITY_JSON`**, peer-policy keys. |
+60 -3
View File
@@ -606,6 +606,8 @@ async function executeKernel(disk, store, swarm, initSource) {
'BARE_OS_SHELL_READ_BUILTIN',
'BARE_OS_SHELL_READ_MAX_BYTES',
'BARE_OS_SHELL_POSIX_MODE',
'BARE_OS_SHELL_ERREXIT',
'BARE_OS_SHELL_NOUNSET',
'BARE_OS_TELEMETRY_NDJSON',
'BARE_OS_TELEMETRY_OTEL_JSONL',
'BARE_OS_PROC_POLL_MS',
@@ -642,12 +644,14 @@ async function executeKernel(disk, store, swarm, initSource) {
'BARE_OS_BOOT_CAPABILITY_CONTRACT_DEBUG',
'BARE_OS_BOOT_EXT_RESOLUTION_TRACE',
'BARE_OS_KERNEL_EXT_GRAPH',
'BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS',
'BARE_OS_BOOT_RC_RESOLUTION_TRACE',
'BARE_OS_WARM_REBOOT',
'BARE_OS_WARM_REBOOT_EXTRA_CYCLES',
'BARE_OS_WASM_KERNEL',
'BARE_OS_WASM_KERNEL_SYSCALL',
'BARE_OS_WASM_KERNEL_MONOTONIC_MS',
'BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT',
'BARE_OS_POSIX_FD_SIM',
'BARE_OS_POSIX_FD_SIM_MAX_BYTES',
'BARE_OS_POSIX_SOCKET_FD_BRIDGE',
@@ -1469,6 +1473,17 @@ async function executeKernel(disk, store, swarm, initSource) {
).trim() || undefined,
note: 'OTA / Pear stage hints for operators; compare pear-runtime-updater docs on Holepunch.'
},
protomuxAppChannel: {
schema: 1,
protocolName: 'bare-os-app-v1',
envGate: 'BARE_OS_PROTOMUX_APP_CHANNEL',
hostEnabled:
shellEnv.BARE_OS_PROTOMUX_APP_CHANNEL === '1' ||
shellEnv.BARE_OS_PROTOMUX_APP_CHANNEL === 'true',
operatorMetric: 'disk.os replication_operator_sketch.protomuxOperatorSketch.protomuxAppChannelRxTotal',
note:
'Optional second Protomux channel paired alongside bare-os-v1 for app-tier traffic; control plane stays on PROTOCOL_NAME.'
},
pearIpcConservativeAdvertisement: {
schema: 2,
note:
@@ -1551,8 +1566,8 @@ async function executeKernel(disk, store, swarm, initSource) {
10
)
return `${JSON.stringify({
schema: 4,
note: 'Non-secret security posture snapshot; no vault or key material.',
schema: 5,
note: 'Non-secret security posture snapshot; no vault or key material. Schema 5 adds blindPeeringOperatorSketch env gates.',
bootPolicyMerged: bootPol,
bootPolicyStrict: strict,
sandboxScript:
@@ -1613,6 +1628,21 @@ async function executeKernel(disk, store, swarm, initSource) {
schema: 1,
note: 'Replication RPC may carry attestation blobs; guests verify only when operator policy enables pinned validators.'
},
blindPeeringOperatorSketch: {
schema: 1,
blindPeerTopologyV3JsonEnv: 'BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON',
blindPeerRouterJsonEnv: 'BARE_OS_BLIND_PEER_ROUTER_JSON',
blindPeeringJsonPresent: !!(
shellEnv.BARE_OS_BLIND_PEER_ROUTER_JSON &&
String(shellEnv.BARE_OS_BLIND_PEER_ROUTER_JSON).trim()
),
blindTopologyV3JsonPresent: !!(
shellEnv.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON &&
String(shellEnv.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON).trim()
),
note:
'Non-secret env gates only; JSON bodies are not echoed here. Wire blind-peering / blind-peer-router operator configs on the host, then read sketches via disk.os replication_operator_sketch.'
},
peerAdmission: {
schema: 2,
allowlistConfigured: !!(
@@ -4328,6 +4358,20 @@ async function executeKernel(disk, store, swarm, initSource) {
...(opts && typeof opts === 'object' ? opts : {}),
stdio: 'pipe'
}
const envT = Math.floor(
Number(
spawnOpts.timeoutMs != null
? spawnOpts.timeoutMs
: shellEnv.BARE_OS_BARE_SUBPROCESS_TIMEOUT_MS
)
)
if (
spawnOpts.timeoutMs == null &&
Number.isFinite(envT) &&
envT > 0
) {
spawnOpts.timeoutMs = Math.min(envT, 3_600_000)
}
const pushJob = (backend) => {
if (!Array.isArray(this.bareOsHostSubprocessJobs)) {
this.bareOsHostSubprocessJobs = []
@@ -6732,7 +6776,7 @@ async function executeKernel(disk, store, swarm, initSource) {
flags: 0,
controllen: 0,
note:
'TCP bridge: single flat buffer; recv ancillary controllen 0 (socketMsgSurface schema 4).'
'TCP bridge: single flat buffer; recv ancillary controllen 0 (socketMsgSurface schema 5).'
}
}
return base
@@ -9111,6 +9155,19 @@ async function executeKernel(disk, store, swarm, initSource) {
typeof disk.seedHttpDhtProxyHint === 'object'
? disk.seedHttpDhtProxyHint
: null,
seedSnapshotHints:
disk.seedSnapshotHints && typeof disk.seedSnapshotHints === 'object'
? disk.seedSnapshotHints
: null,
seedSnapshotManifestSlice:
disk.seedSnapshotManifestSlice &&
typeof disk.seedSnapshotManifestSlice === 'object'
? disk.seedSnapshotManifestSlice
: null,
seedSnapshotChain:
disk.seedSnapshotChain && typeof disk.seedSnapshotChain === 'object'
? disk.seedSnapshotChain
: null,
swarmPeerCount:
disk.peers && typeof disk.peers.size === 'number'
? disk.peers.size
@@ -2,4 +2,4 @@
* Semantic version of the booter `ctx` contract for custom kernels.
* Bump when adding/removing/renaming documented `ctx` fields or changing behavior.
*/
export const BARE_OS_CTX_API_VERSION = '1.51.0'
export const BARE_OS_CTX_API_VERSION = '1.51.1'
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* Stable shapes for custom kernels and `/bin` utilities (best-effort; full `ctx` is assembled in `index.js`).
* Match `BARE_OS_CTX_API_VERSION` in `bare-os-ctx-api.js` (stock **1.51.0**).
* Match `BARE_OS_CTX_API_VERSION` in `bare-os-ctx-api.js` (stock **1.51.1**).
*/
export interface BareOsAbortOpts {
@@ -66,6 +66,9 @@ function sleepMs(ms) {
* seedReplicationStatus?: Record<string, unknown> | null
* seedMirrorDriveHintV2?: Record<string, unknown> | null
* seedHttpDhtProxyHint?: Record<string, unknown> | null
* seedSnapshotHints?: Record<string, unknown> | null
* seedSnapshotManifestSlice?: Record<string, unknown> | null
* seedSnapshotChain?: Record<string, unknown> | null
* swarmPeerCount?: number | null
* swarmConnectionBudget?: Record<string, unknown> | null
* protomuxOperatorSketch?: Record<string, unknown> | null
@@ -195,17 +198,28 @@ export function createBareOsDiskOsBridge(opts) {
const drive = opts.drive
if (!drive) {
return JSON.stringify({
schema: 1,
schema: 2,
ok: false,
reason: 'no_system_drive'
})
}
const env = globalThis.process?.env
const parseEnvJson = (key) => {
const raw = String(env?.[key] ?? '').trim()
if (!raw) return null
try {
const o = JSON.parse(raw)
return o && typeof o === 'object' ? o : null
} catch {
return { schema: 1, error: 'invalid_json', key }
}
}
try {
const id = /** @type {{ id?: Uint8Array }} */ (drive).id
const dk = /** @type {{ discoveryKey?: Uint8Array }} */ (drive)
.discoveryKey
return JSON.stringify({
schema: 1,
schema: 2,
ok: true,
writable: !!drive.writable,
idPrefix:
@@ -216,11 +230,30 @@ export function createBareOsDiskOsBridge(opts) {
dk && dk.byteLength
? b4a.toString(dk, 'hex').slice(0, 16)
: null,
note: 'Non-secret length-capped hex prefixes for operator correlation only.'
seedSnapshotHints:
opts.seedSnapshotHints &&
typeof opts.seedSnapshotHints === 'object'
? opts.seedSnapshotHints
: null,
seedSnapshotManifestSlice:
opts.seedSnapshotManifestSlice &&
typeof opts.seedSnapshotManifestSlice === 'object'
? opts.seedSnapshotManifestSlice
: null,
seedSnapshotChain:
opts.seedSnapshotChain &&
typeof opts.seedSnapshotChain === 'object'
? opts.seedSnapshotChain
: null,
corestoreSnapshotEnv: parseEnvJson(
'BARE_OS_CORESTORE_SNAPSHOT_JSON'
),
note:
'Schema 2: adds optional seed snapshot hints/manifest slice/chain (non-secret) plus BARE_OS_CORESTORE_SNAPSHOT_JSON merge for corestore-snapshotstyle operator signals. Hex prefixes unchanged.'
})
} catch (e) {
return JSON.stringify({
schema: 1,
schema: 2,
ok: false,
error: (e && /** @type {Error} */ (e).message) || String(e)
})
@@ -27,6 +27,62 @@ function defaultNdjsonPath() {
* @param {string} human Human-readable message (no secrets).
* @param {string} [detail] Optional short detail (error.message, etc.).
*/
/**
* Host-side informational line (boot trace off): stderr when mode is stderr; optional NDJSON append.
* @param {string} code
* @param {string} human
* @param {string} [detail]
*/
export function bareOsHostBooterInfo(code, human, detail) {
const env = globalThis.process?.env || {}
const mode = String(env.BARE_OS_HOST_BOOTER_LOG || 'stderr')
.trim()
.toLowerCase()
if (
mode === '0' ||
mode === 'false' ||
mode === 'off' ||
mode === 'no' ||
mode === 'silent'
) {
return
}
const d = detail != null ? String(detail).slice(0, 4000) : ''
const line = `[bare-os-booter][${code}] ${human}${d ? `: ${d}` : ''}`
if (mode === 'ndjson') {
const logPath = String(env.BARE_OS_HOST_BOOTER_LOG_PATH || '').trim() || defaultNdjsonPath()
try {
fs.mkdirSync(path.dirname(logPath), { recursive: true })
fs.appendFileSync(
logPath,
`${JSON.stringify({
type: 'booterHostInfo',
code: String(code).slice(0, 128),
message: String(human).slice(0, 2000),
detail: d || undefined,
ts: Date.now()
})}\n`,
'utf8'
)
} catch {
/* fall through */
}
}
if (mode === 'ndjson' && env.BARE_OS_HOST_BOOTER_LOG_STDERR !== '1') {
return
}
const err = globalThis.process?.stderr
if (err && typeof err.write === 'function') {
err.write(`${line}\n`)
} else if (typeof console.info === 'function') {
console.info(line)
}
}
export function bareOsHostBooterWarn(code, human, detail) {
const env = globalThis.process?.env || {}
const mode = String(env.BARE_OS_HOST_BOOTER_LOG || 'stderr')
@@ -29,11 +29,11 @@ export function buildBareOsSyscallsProcJson(p) {
opsDetail: [...BARE_OS_SYSCALL_OPS_DETAIL_FULL],
posixXsh: {
schema: 2,
note: 'POSIX.1 XSH-style names: logical names in opsDetail; schema 10 adds posix_fadvise as a simulated ctx.bareOsSyscall no-op (Issue 7-style advice hint). Socket family are ctx.bareOsSyscall ops returning ENOSYS-shaped results (not kernel socket FDs) unless BARE_OS_POSIX_SOCKET_FD_BRIDGE. readv/writev are partial posix-pipe facades. getsockopt/setsockopt: ENOSYS without bridge fd; with bridge, partial SO_KEEPALIVE/TCP_NODELAY and optional SO_RCVBUF/SO_SNDBUF on logical fds when the Bare socket implementation exposes buffer sizes. nanosleep-shaped delay via ctx.bareOsSyscall("nanosleep"); clock_gettime via ctx.bareOsSyscall("clock_gettime") (REALTIME wall; MONOTONIC via bare-hrtime or performance.now sketch). SOCK_DGRAM bridge supports passive bind(2), optional connect(2) after bind, send/sendmsg/recv/recvfrom/recvmsg with bounded queue (BARE_OS_POSIX_DGRAM_RECVQ_MAX); bound-without-connect send requires port+host syscall args. sendmsg/recvmsg: non-empty ancillary control is ENOTSUP (socketMsgSurface schema 4) unless BARE_OS_POSIX_SOCKET_SCM_RIGHTS=1, then only SCM_RIGHTS-shaped `{ fds: number[] }` logical fd dup is accepted (see bare-os-socket-scm-rights.js and syscall-socket-contract.md).',
note: 'POSIX.1 XSH-style names: logical names in opsDetail; schema 10 adds posix_fadvise as a simulated ctx.bareOsSyscall no-op (Issue 7-style advice hint). Socket family are ctx.bareOsSyscall ops returning ENOSYS-shaped results (not kernel socket FDs) unless BARE_OS_POSIX_SOCKET_FD_BRIDGE. readv/writev are partial posix-pipe facades. getsockopt/setsockopt: ENOSYS without bridge fd; with bridge, partial SO_KEEPALIVE/TCP_NODELAY and optional SO_RCVBUF/SO_SNDBUF on logical fds when the Bare socket implementation exposes buffer sizes. nanosleep-shaped delay via ctx.bareOsSyscall("nanosleep"); clock_gettime via ctx.bareOsSyscall("clock_gettime") (REALTIME wall; MONOTONIC via bare-hrtime or performance.now sketch). SOCK_DGRAM bridge supports passive bind(2), optional connect(2) after bind, send/sendmsg/recv/recvfrom/recvmsg with bounded queue (BARE_OS_POSIX_DGRAM_RECVQ_MAX); bound-without-connect send requires port+host syscall args. sendmsg/recvmsg: non-empty ancillary control is ENOTSUP (socketMsgSurface schema 5) unless BARE_OS_POSIX_SOCKET_SCM_RIGHTS=1, then only SCM_RIGHTS-shaped `{ fds: number[] }` logical fd dup is accepted on SOCK_STREAM and SOCK_DGRAM bridge fds (same prepareBridgeSendmsg path; see bare-os-socket-scm-rights.js and syscall-socket-contract.md).',
namesCsv: bareOsPosixXshOpsCsv()
},
socketMsgSurface: {
schema: 4,
schema: 5,
ancillaryControl: {
supported: false,
errno: 'ENOTSUP',
@@ -45,7 +45,7 @@ export function buildBareOsSyscallsProcJson(p) {
'msgHdr.cmsgs'
],
note:
'Binary/kernel ancillary buffers remain ENOTSUP. When BARE_OS_POSIX_SOCKET_SCM_RIGHTS=1, only JSON-shaped `{ fds: number[] }` entries in cmsgs/msgHdr.cmsgs are accepted (see logicalScmRightsOnSend).'
'Binary/kernel ancillary buffers remain ENOTSUP. When BARE_OS_POSIX_SOCKET_SCM_RIGHTS=1, only JSON-shaped `{ fds: number[] }` entries in cmsgs/msgHdr.cmsgs are accepted (see logicalScmRightsOnSend). Schema 5 documents SOCK_STREAM sendmsg parity with SOCK_DGRAM (logical dup on connected TCP bridge fds).'
},
logicalScmRightsOnSend: {
env: 'BARE_OS_POSIX_SOCKET_SCM_RIGHTS',
@@ -82,7 +82,11 @@ export function buildBareOsSyscallsProcJson(p) {
fds: 'stdio 02 reserved; extras via ctx.bareOsRegisterLogicalFd'
},
read: {
mapsTo: ['vfs.readFile', 'bareOsBinReadStdin', 'IPC fifo consumer'],
mapsTo: [
'vfs.readFile',
'bareOsBinReadStdin',
'IPC fifo consumer (/run/bare-os/ipc/*): blocking until push or AbortSignal (AbortError); backlog bounded via BARE_OS_IPC_MAX_BYTES'
],
partial: true
},
write: {
@@ -81,6 +81,7 @@ function writeCStrToMemory(memory, ptr, outCap, text) {
* **`BARE_OS_WASM_KERNEL_SYSCALL=1`** (or **`opts.wasmSyscallImports: true`**) and **`ctx.bareOsPathconf`** exists.
* Also **`env.bare_os_wall_time_ms32`**: low 32 bits of wall time (`Date.now()`, signed i32 wrap) for bounded guest timing.
* With **`BARE_OS_WASM_KERNEL_MONOTONIC_MS=1`**, adds **`env.bare_os_monotonic_ms`**: floored monotonic milliseconds (`performance.now` when available), i32-shaped.
* With **`BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT=1`** (requires syscall imports), adds **`env.bare_os_hostname_peek`**: writes session **`HOSTNAME`** (or **`bare-os`**) NUL-terminated into Wasm memory (**bounded**).
*
* @param {Uint8Array} source
* @param {Record<string, unknown>} ctx
@@ -140,6 +141,10 @@ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_MONOTONIC_MS === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_MONOTONIC_MS === 'true')
const wantHostname =
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT === 'true')
const pathconfFn = ctx && typeof ctx.bareOsPathconf === 'function'
? ctx.bareOsPathconf.bind(ctx)
: null
@@ -192,6 +197,21 @@ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
const m = parseInt(String(shellEnv.UMASK || '022'), 8)
return Number.isFinite(m) ? m & 0o777 : 0o22
}
if (wantHostname) {
envImports.bare_os_hostname_peek = (outPtr, outCap) => {
const vfsEnv =
ctx &&
ctx.vfs &&
ctx.vfs.env &&
typeof ctx.vfs.env === 'object'
? /** @type {Record<string, string>} */ (ctx.vfs.env)
: {}
const h = String(
vfsEnv.HOSTNAME || shellEnv.HOSTNAME || 'bare-os'
).slice(0, 256)
return writeCStrToMemory(memory, Number(outPtr), Number(outCap), h)
}
}
}
const importObject = {
env: envImports
@@ -218,7 +238,8 @@ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
wasmSyscallImports: !!wantSyscall,
wasmPathconfImport: !!(wantSyscall && pathconfFn),
wasmWallClockMs32Import: !!wantSyscall,
wasmMonotonicMsImport: !!wantMonotonicMs
wasmMonotonicMsImport: !!wantMonotonicMs,
wasmHostnamePeekImport: !!(wantSyscall && wantHostname)
}
} catch (e) {
return { ok: false, reason: String(e && e.message ? e.message : e) }
+2 -2
View File
@@ -67,8 +67,8 @@ function gitAuthor(ctx) {
}
}
function printHelp(console) {
console.log(`bare-os git (isomorphic-git) — not full GNU git.
function printHelp(sink) {
sink.log(`bare-os git (isomorphic-git) — not full GNU git.
Usage: git [--git-dir <path>] [-C <path>] <command> [args]
+65 -4
View File
@@ -12,6 +12,35 @@ import {
} from './bare-os-theme-presets.js'
import { pathnameExpandShellWord, bareOsFnmatch } from './shell-glob.js'
/** @see execShellLine — nounset violations surface as exit status 1 */
export const BARE_OS_SHELL_NOUNSET_ERROR = 'BARE_OS_SHELL_NOUNSET'
/**
* @param {Record<string, string> | null | undefined} env
*/
function shellNounsetEnvOn(env) {
return (
env &&
(env.BARE_OS_SHELL_NOUNSET === '1' || env.BARE_OS_SHELL_NOUNSET === 'true')
)
}
/**
* @param {string} name
* @param {Record<string, string>} env
*/
function shellCheckUnboundParam(name, env) {
if (!shellNounsetEnvOn(env) || !name) return
const n = name.trim()
if (n === '?' || /^\d+$/.test(n)) return
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)) return
if (!Object.prototype.hasOwnProperty.call(env, n)) {
const e = new Error(`unbound variable: ${n}`)
e.code = BARE_OS_SHELL_NOUNSET_ERROR
throw e
}
}
const SHELL_BUILTINS = new Set([
'alias',
'unalias',
@@ -821,7 +850,9 @@ function expandParamBracedInner(inner, env) {
return v.startsWith(pref) ? v.slice(pref.length) : v
}
}
return env[inner] ?? ''
const keySimple = inner.trim()
shellCheckUnboundParam(keySimple, env)
return env[keySimple] ?? ''
}
/**
@@ -868,7 +899,9 @@ export function expandWord(s, env) {
) {
out += expandParamBracedInner(inner, env)
} else {
out += env[inner] ?? ''
const ik = inner.trim()
shellCheckUnboundParam(ik, env)
out += env[ik] ?? ''
}
j = end + 1
continue
@@ -879,7 +912,9 @@ export function expandWord(s, env) {
continue
}
if (/[0-9]/.test(s[j + 1] ?? '')) {
out += env[s[j + 1]] ?? ''
const pn = s[j + 1]
shellCheckUnboundParam(pn, env)
out += env[pn] ?? ''
j += 2
continue
}
@@ -887,6 +922,7 @@ export function expandWord(s, env) {
while (k < s.length && /[A-Za-z0-9_]/.test(s[k])) k++
const name = s.slice(j + 1, k)
if (name) {
shellCheckUnboundParam(name, env)
out += env[name] ?? ''
j = k
} else {
@@ -1595,6 +1631,22 @@ async function execParsedPipeline(ctx, pipeline) {
env.BARE_OS_SHELL_ERREXIT = '1'
} else if (args.length === 1 && args[0] === '+e') {
delete env.BARE_OS_SHELL_ERREXIT
} else if (
args.length === 2 &&
args[0] === '-o' &&
args[1] === 'nounset'
) {
env.BARE_OS_SHELL_NOUNSET = '1'
} else if (
args.length === 2 &&
args[0] === '+o' &&
args[1] === 'nounset'
) {
delete env.BARE_OS_SHELL_NOUNSET
} else if (args.length === 1 && args[0] === '-u') {
env.BARE_OS_SHELL_NOUNSET = '1'
} else if (args.length === 1 && args[0] === '+u') {
delete env.BARE_OS_SHELL_NOUNSET
} else if (
args.length === 2 &&
args[0] === '-o' &&
@@ -1610,7 +1662,7 @@ async function execParsedPipeline(ctx, pipeline) {
} else {
origErr.call(
ctx.console,
'set: unsupported arguments (only -f / +f / -e / +e / -o errexit / +o errexit / -o pipefail / +o pipefail)'
'set: unsupported arguments (only -f / +f / -e / +e / -u / +u / -o errexit|nounset|pipefail / +o errexit|nounset|pipefail)'
)
ctx.exitCode = 1
}
@@ -2774,6 +2826,15 @@ export async function execShellLine(ctx, line) {
}
try {
return await execShellLineInner(ctx, raw)
} catch (e) {
const code = e && /** @type {{ code?: string }} */ (e).code
if (code === BARE_OS_SHELL_NOUNSET_ERROR) {
ctx.console.error('shell: ' + ((e && e.message) || 'unbound variable'))
ctx.exitCode = 1
syncBareOsExitStatusEnv(ctx)
return 'ok'
}
throw e
} finally {
if (savedEnv != null && vfs) vfs.env = savedEnv
}
+31 -7
View File
@@ -4,7 +4,10 @@ import {
PROTOCOL_NAME,
PROTOCOL_APP_CHANNEL_NAME
} from 'bare-os-protocol/constants.js'
import { bareOsHostBooterWarn } from './bare-os-host-booter-log.js'
import {
bareOsHostBooterInfo,
bareOsHostBooterWarn
} from './bare-os-host-booter-log.js'
/**
* Host-side booter log for swarm disk (stderr JSON when BARE_OS_BOOT_TRACE=json|ndjson; else stderr or console.warn).
@@ -39,13 +42,34 @@ function emitSwarmDiskHostLog(level, message, detail = null) {
)
return
}
const line = `[bare-os-booter][swarm-disk] ${message}`
if (err && typeof err.write === 'function') err.write(`${line}\n`)
else if (typeof console.info === 'function') console.info(line)
bareOsHostBooterInfo('swarm_disk', message, detail ? JSON.stringify(detail) : '')
}
/** Max wait for block 0 (MBR) bytes from swarm peers before failing boot. */
const MBR_READ_TIMEOUT_MS = 60_000
/** Default max wait for block 0 (MBR) before failing boot (override **`BARE_OS_MBR_READ_TIMEOUT_MS`**). */
const MBR_READ_TIMEOUT_MS_DEFAULT = 60_000
/**
* Max wait (ms) for block 0 / MBR replication before boot failure.
* Exported for microbench / unit introspection (same rules as booter).
* @param {SwarmDisk} disk
*/
export function mbrReadTimeoutMsForDisk(disk) {
const env = globalThis.process?.env
const raw = String(env?.BARE_OS_MBR_READ_TIMEOUT_MS ?? '').trim()
if (raw) {
const n = Math.floor(Number(raw))
if (Number.isFinite(n) && n >= 3000) return Math.min(n, 600_000)
}
const adapt =
env?.BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE === '1' ||
env?.BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE === 'true'
if (adapt && disk && disk.peers && typeof disk.peers.size === 'number') {
const n = disk.peers.size
if (n <= 1) return Math.min(120_000, MBR_READ_TIMEOUT_MS_DEFAULT + 30_000)
if (n >= 4) return Math.max(45_000, MBR_READ_TIMEOUT_MS_DEFAULT - 15_000)
}
return MBR_READ_TIMEOUT_MS_DEFAULT
}
export class SwarmDisk {
constructor() {
@@ -456,7 +480,7 @@ export class SwarmDisk {
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error('MBR read timeout')),
MBR_READ_TIMEOUT_MS
mbrReadTimeoutMsForDisk(this)
)
this.pendingReads.set(index, (data) => {
clearTimeout(timeout)
+47 -2
View File
@@ -1383,7 +1383,7 @@ test('buildBareOsSyscallsProcJson exposes posixLike fd mapping', async (t) => {
t.ok(j.posixXsh.namesCsv.includes('sendmsg'))
t.ok(j.posixXsh.namesCsv.includes('recvmsg'))
t.ok(j.posixXsh.namesCsv.includes('shutdown'))
t.is(j.socketMsgSurface?.schema, 4)
t.is(j.socketMsgSurface?.schema, 5)
t.is(j.socketMsgSurface?.logicalScmRightsOnSend?.syscallResultField, 'scmRightsLocalDup')
t.is(j.socketMsgSurface?.ancillaryControl?.supported, false)
t.is(j.socketMsgSurface?.ancillaryControl?.errno, 'ENOTSUP')
@@ -1563,7 +1563,7 @@ test('disk.os replication_snapshot and replication_operator_sketch RPCs', async
})
const snap = await bridge.execRpc('bare_os', 'replication_snapshot', [])
const sj = JSON.parse(snap)
t.is(sj.schema, 1)
t.is(sj.schema, 2)
t.is(sj.ok, true)
t.ok(typeof sj.writable === 'boolean')
const op = await bridge.execRpc('bare_os', 'replication_operator_sketch', [])
@@ -2874,6 +2874,24 @@ test('execShellLine BARE_OS_SHELL_ERREXIT skips after failed command', async (t)
rmSync(dir, { recursive: true, force: true })
})
test('execShellLine BARE_OS_SHELL_NOUNSET rejects unbound variable', async (t) => {
const dir = testCorestoreDir('nounset')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pex-nu'))
await drive.ready()
await personal.ready()
const ctx = testCtx(drive, personal)
const errs = []
ctx.console.error = (m) => errs.push(String(m))
ctx.vfs.env.BARE_OS_SHELL_NOUNSET = '1'
await execShellLine(ctx, 'echo $UNBOUND_XYZ')
t.is(ctx.exitCode, 1)
t.ok(errs.some((e) => /unbound variable/.test(e)))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('execShellLine BARE_OS_SHELL_PIPEFAIL uses first failing stage exit', async (t) => {
const dir = testCorestoreDir('pipefail')
const store = new Corestore(dir)
@@ -6820,6 +6838,33 @@ test('bareOsWasmKernelInstantiate optional bare_os_monotonic_ms import', async (
t.ok(r.wasmMonotonicMsImport)
})
test('bareOsWasmKernelInstantiate optional bare_os_hostname_peek import flag', async (t) => {
const { bareOsWasmKernelInstantiate } = await import('./lib/bare-os-wasm-kernel.js')
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
t.pass('skip: no WebAssembly')
return
}
const wasmMinimal = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
])
const ctx = {
bareOsPathconf: () => '4096',
vfs: { env: { HOSTNAME: 'guest.test' } }
}
const r = await bareOsWasmKernelInstantiate(wasmMinimal, ctx, {
wasmSyscallImports: true,
shellEnv: {
BARE_OS_WASM_KERNEL_SYSCALL: '1',
BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT: '1'
}
})
if (!r.ok) {
t.pass('instantiate may fail on minimal module')
return
}
t.ok(r.wasmHostnamePeekImport)
})
test('bareOsAppendVaultRotationCheckpoint appends vault_save row', async (t) => {
const { bareOsAppendVaultRotationCheckpoint } = await import(
'./lib/bare-os-vault-rotation-audit.js'
@@ -81,6 +81,7 @@ export const COREUTILS_COMMANDS = [
'mount',
'mv',
'nano',
'nice',
'nl',
'nohup',
'nproc',
@@ -0,0 +1,21 @@
{
"name": "nice",
"section": 1,
"title": "invoke a utility with an altered scheduling priority (simulated)",
"synopsis": [
"nice [-n increment] utility [argument...]"
],
"description": "Bare OS implementation: records BARE_OS_SIMULATED_NICE on the session env and runs the utility via ctx.runBinCommand. The host JavaScript runtime does not change OS niceness; scripts use this for POSIX script compatibility.",
"options": [],
"keywords": [
"nice",
"bare-os",
"coreutils"
],
"examples": [
{
"caption": "run with default increment",
"code": "nice make"
}
]
}
+39
View File
@@ -0,0 +1,39 @@
async function run(ctx, argv) {
let inc = 10
let i = 1
if (argv[i] === '-n') {
const n = Number.parseInt(String(argv[i + 1] ?? ''), 10)
if (!Number.isFinite(n) || n < 0 || n > 39) {
ctx.console.error('nice: invalid adjustment')
ctx.exitCode = 125
return
}
inc = n
i += 2
} else if (argv[i] && /^-\d+$/.test(argv[i])) {
const n = Number.parseInt(argv[i].slice(1), 10)
if (!Number.isFinite(n) || n < 0 || n > 39) {
ctx.console.error('nice: invalid adjustment')
ctx.exitCode = 125
return
}
inc = n
i++
}
const cmd = argv.slice(i)
if (!cmd.length) {
ctx.console.error('usage: nice [-n increment] utility [argument...]')
ctx.exitCode = 125
return
}
if (typeof ctx.runBinCommand !== 'function') {
ctx.console.error('nice: runBinCommand is not available')
ctx.exitCode = 125
return
}
const env = ctx.vfs?.env
if (env && typeof env === 'object') {
env.BARE_OS_SIMULATED_NICE = String(inc)
}
await ctx.runBinCommand(cmd)
}
+1 -1
View File
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
return false
}
var BARE_OS_HELP_BIN_SPACED = "arch awk baretop base32 base64 basename basenc btop bundlebee cat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help hostid hostname hrpc iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum mkdir mkfifo mktemp mount mv nano nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathchk pear-runtime-matrix pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault sed seq setfacl sh sha1sum sha256sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen stat sum sync systemctl tac tail tar tar tee test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs xattr yes"
var BARE_OS_HELP_BIN_SPACED = "arch awk baretop base32 base64 basename basenc btop bundlebee cat chgrp chmod chown cksum clear cmp comm cp crontab curl cut date dd df diff dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf getfacl git git-pear grep groups hdms head help hostid hostname hrpc iconv id install join journalctl jq kernel-boot-diff kernel-doctor kernel-explain kernel-fsck kernel-home-snapshot kernel-manifest-validate kernel-preflight kernel-triage kill link ln logger login logname logout ls man md5sum mkdir mkfifo mktemp mount mv nano nice nl nohup nproc numfmt od oidc-publish openssl openssl paste patch pathchk pear-runtime-matrix pr printenv printf procstat ps pwd readlink realpath rev rm rmdir savevault sed seq setfacl sh sha1sum sha256sum sha512sum shuf sidecar sleep sort split ssh-keygen ssh-keygen stat sum sync systemctl tac tail tar tar tee test theme time timeout touch tr true truncate tsort tty ulimit umount uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs xattr yes"
async function run(ctx, argv) {
ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
+128
View File
@@ -0,0 +1,128 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
let inc = 10
let i = 1
if (argv[i] === '-n') {
const n = Number.parseInt(String(argv[i + 1] ?? ''), 10)
if (!Number.isFinite(n) || n < 0 || n > 39) {
ctx.console.error('nice: invalid adjustment')
ctx.exitCode = 125
return
}
inc = n
i += 2
} else if (argv[i] && /^-\d+$/.test(argv[i])) {
const n = Number.parseInt(argv[i].slice(1), 10)
if (!Number.isFinite(n) || n < 0 || n > 39) {
ctx.console.error('nice: invalid adjustment')
ctx.exitCode = 125
return
}
inc = n
i++
}
const cmd = argv.slice(i)
if (!cmd.length) {
ctx.console.error('usage: nice [-n increment] utility [argument...]')
ctx.exitCode = 125
return
}
if (typeof ctx.runBinCommand !== 'function') {
ctx.console.error('nice: runBinCommand is not available')
ctx.exitCode = 125
return
}
const env = ctx.vfs?.env
if (env && typeof env === 'object') {
env.BARE_OS_SIMULATED_NICE = String(inc)
}
await ctx.runBinCommand(cmd)
}
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-05T18:43:53.989Z",
"generatedAt": "2026-04-05T19:03:41.000Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
@@ -316,6 +316,10 @@
"name": "nano",
"tier": "tier1_bin"
},
{
"name": "nice",
"tier": "tier1_bin"
},
{
"name": "nl",
"tier": "tier1_bin"
@@ -1,6 +1,6 @@
{
"schemaVersion": 10,
"ctxApiVersion": "1.51.0",
"ctxApiVersion": "1.51.1",
"posixProfile": {
"id": "bare-os-posix-like",
"version": "1.0.15"
+50 -4
View File
@@ -1821,6 +1821,38 @@ async function runKernelExtDropins(ctx, opts = {}) {
return false
}
}
/** @param {string} ver */
function bareOsKernelExtSemverValid(ver) {
const s = String(ver || '').trim()
return /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/.test(
s
)
}
/** @type {string[]} */
const providesInvalidSemver = []
for (const e of collected) {
for (const pr of e.provides || []) {
if (!bareOsKernelExtSemverValid(pr.version)) {
providesInvalidSemver.push(`${e.extId}:${pr.name}@${pr.version}`)
}
}
}
providesInvalidSemver.sort()
if (providesInvalidSemver.length) {
bootStructuredLog(
ctx,
'warn',
'kernelExt.providesInvalidSemver',
`[kernel.ext.d] provides[].version must look like semver (major.minor.patch): ${providesInvalidSemver.join('; ')}`
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'provides_invalid_semver',
items: providesInvalidSemver
})
return false
}
}
/** @type {Map<string, { extId: string, version: string }>} */
const provideNameToOwner = new Map()
/** @type {string[]} */
@@ -1927,20 +1959,27 @@ async function runKernelExtDropins(ctx, opts = {}) {
strictPol &&
(ctx.env?.BARE_OS_BOOT_EXT_RESOLUTION_TRACE === '1' ||
ctx.env?.BARE_OS_BOOT_EXT_RESOLUTION_TRACE === 'true')
const resolutionAlways =
ctx.env?.BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS === '1' ||
ctx.env?.BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS === 'true'
const extResolutionFailure = strictPol && !topo.ok
if (
ctx.vfs &&
typeof ctx.vfs.writeFile === 'function' &&
ctx.b4a &&
(extResolutionFailure || traceOn)
(extResolutionFailure || traceOn || resolutionAlways)
) {
try {
const cycleSorted = topo.ok
? []
: [...topo.cycleExtIds].sort((a, b) => a.localeCompare(b))
const provideSnapshot = {}
for (const [k, v] of provideNameToOwner) {
provideSnapshot[k] = v
}
const body =
JSON.stringify({
schema: 2,
schema: 3,
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
bootPolicyStrict: !!strictPol,
@@ -1954,9 +1993,16 @@ async function runKernelExtDropins(ctx, opts = {}) {
ordered: ordered.map((e) => ({
extId: e.extId,
file: e.file,
dependsOn: e.dependsOn
dependsOn: e.dependsOn,
provides: e.provides || []
})),
cycleExtIds: topo.ok ? [] : cycleSorted
cycleExtIds: topo.ok ? [] : cycleSorted,
providesInvalidSemver,
providesVersionConflicts:
providesVersionConflicts.length > 0
? providesVersionConflicts
: undefined,
provideNameToOwner: provideSnapshot
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/kernel-ext-resolution.json',
@@ -1,18 +1,18 @@
{
"version": 1,
"bundles": [
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{
"path": "/lib/bare/bundles/hypercoreIdEncoding.js",
"keys": [
@@ -25,36 +25,36 @@
"compactEncoding"
]
},
{
"path": "/lib/bare/bundles/bareUrl.js",
"keys": [
"bareUrl"
]
},
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{
"path": "/lib/bare/bundles/bareEvents.js",
"keys": [
"bareEvents"
]
},
{
"path": "/lib/bare/bundles/bareEncoding.js",
"keys": [
"bareEncoding"
]
},
{
"path": "/lib/bare/bundles/bareUrl.js",
"keys": [
"bareUrl"
]
},
{
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"barePath"
]
},
{
"path": "/lib/bare/bundles/bareEvents.js",
"keys": [
"bareEvents"
]
},
{
"path": "/lib/bare/bundles/bareAbort.js",
"keys": [
@@ -68,15 +68,9 @@
]
},
{
"path": "/lib/bare/bundles/bareAnsiEscapes.js",
"path": "/lib/bare/bundles/bareCrypto.js",
"keys": [
"bareAnsiEscapes"
]
},
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
"bareCrypto"
]
},
{
@@ -86,33 +80,15 @@
]
},
{
"path": "/lib/bare/bundles/bareCrypto.js",
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareCrypto"
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareAppKit.js",
"path": "/lib/bare/bundles/bareAnsiEscapes.js",
"keys": [
"bareAppKit"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
"bareAsyncHooks"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
"bareAnsiEscapes"
]
},
{
@@ -121,18 +97,48 @@
"fetch"
]
},
{
"path": "/lib/bare/bundles/bareAppKit.js",
"keys": [
"bareAppKit"
]
},
{
"path": "/lib/bare/bundles/bareApk.js",
"keys": [
"bareApk"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
"bareAsyncHooks"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/bareBmp.js",
"keys": [
"bareBmp"
]
},
{
"path": "/lib/bare/bundles/bareBuffer.js",
"keys": [
"bareBuffer"
]
},
{
"path": "/lib/bare/bundles/bareBundleCompile.js",
"keys": [
@@ -145,12 +151,6 @@
"bareBundle"
]
},
{
"path": "/lib/bare/bundles/bareBuffer.js",
"keys": [
"bareBuffer"
]
},
{
"path": "/lib/bare/bundles/bareBluetoothApple.js",
"keys": [
@@ -169,12 +169,6 @@
"bareBoot"
]
},
{
"path": "/lib/bare/bundles/bareBundleId.js",
"keys": [
"bareBundleId"
]
},
{
"path": "/lib/bare/bundles/bareConsole.js",
"keys": [
@@ -187,6 +181,12 @@
"bareDebugLog"
]
},
{
"path": "/lib/bare/bundles/bareBundleId.js",
"keys": [
"bareBundleId"
]
},
{
"path": "/lib/bare/bundles/bareChannel.js",
"keys": [
@@ -205,36 +205,36 @@
"bareDelta"
]
},
{
"path": "/lib/bare/bundles/bareDns.js",
"keys": [
"bareDns"
]
},
{
"path": "/lib/bare/bundles/bareDiagnosticsChannel.js",
"keys": [
"bareDiagnosticsChannel"
]
},
{
"path": "/lib/bare/bundles/bareDns.js",
"keys": [
"bareDns"
]
},
{
"path": "/lib/bare/bundles/bareEnv.js",
"keys": [
"bareEnv"
]
},
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{
"path": "/lib/bare/bundles/bareDgram.js",
"keys": [
"bareDgram"
]
},
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
@@ -272,15 +272,15 @@
]
},
{
"path": "/lib/bare/bundles/bareFileLogger.js",
"path": "/lib/bare/bundles/bareGtk.js",
"keys": [
"bareFileLogger"
"bareGtk"
]
},
{
"path": "/lib/bare/bundles/bareHeif.js",
"path": "/lib/bare/bundles/bareFileLogger.js",
"keys": [
"bareHeif"
"bareFileLogger"
]
},
{
@@ -290,9 +290,9 @@
]
},
{
"path": "/lib/bare/bundles/bareGtk.js",
"path": "/lib/bare/bundles/bareHeif.js",
"keys": [
"bareGtk"
"bareHeif"
]
},
{
@@ -313,12 +313,6 @@
"bareIco"
]
},
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{
"path": "/lib/bare/bundles/bareHttp1.js",
"keys": [
@@ -326,9 +320,9 @@
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareInspect"
"bareImageResample"
]
},
{
@@ -338,9 +332,15 @@
]
},
{
"path": "/lib/bare/bundles/bareIpc.js",
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareIpc"
"bareInspect"
]
},
{
"path": "/lib/bare/bundles/bareJpeg.js",
"keys": [
"bareJpeg"
]
},
{
@@ -350,9 +350,9 @@
]
},
{
"path": "/lib/bare/bundles/bareJpeg.js",
"path": "/lib/bare/bundles/bareIpc.js",
"keys": [
"bareJpeg"
"bareIpc"
]
},
{
@@ -379,6 +379,12 @@
"bareLink"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareMake.js",
"keys": [
@@ -397,18 +403,6 @@
"bareModuleResolve"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [
"bareModuleTraverse"
]
},
{
"path": "/lib/bare/bundles/bareNdk.js",
"keys": [
@@ -421,12 +415,6 @@
"bareNative"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareMedia"
]
},
{
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
@@ -434,9 +422,9 @@
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [
"bareNet"
"bareModuleTraverse"
]
},
{
@@ -452,15 +440,15 @@
]
},
{
"path": "/lib/bare/bundles/barePack.js",
"path": "/lib/bare/bundles/bareNet.js",
"keys": [
"barePack"
"bareNet"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareDev"
"bareMedia"
]
},
{
@@ -470,9 +458,9 @@
]
},
{
"path": "/lib/bare/bundles/barePng.js",
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePng"
"barePack"
]
},
{
@@ -482,9 +470,9 @@
]
},
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"path": "/lib/bare/bundles/barePng.js",
"keys": [
"bareNodeRuntime"
"barePng"
]
},
{
@@ -493,30 +481,36 @@
"barePipe"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{
"path": "/lib/bare/bundles/barePunycode.js",
"keys": [
"barePunycode"
]
},
{
"path": "/lib/bare/bundles/bareQuerystring.js",
"keys": [
"bareQuerystring"
]
},
{
"path": "/lib/bare/bundles/bareQueueMicrotask.js",
"keys": [
"bareQueueMicrotask"
]
},
{
"path": "/lib/bare/bundles/barePrebuild.js",
"keys": [
"barePrebuild"
]
},
{
"path": "/lib/bare/bundles/bareQuerystring.js",
"keys": [
"bareQuerystring"
]
},
{
"path": "/lib/bare/bundles/bareRealm.js",
"keys": [
@@ -524,21 +518,9 @@
]
},
{
"path": "/lib/bare/bundles/bareProcess.js",
"path": "/lib/bare/bundles/bareQueueMicrotask.js",
"keys": [
"bareProcess"
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareRepl"
]
},
{
"path": "/lib/bare/bundles/bareRuntime.js",
"keys": [
"bareRuntime"
"bareQueueMicrotask"
]
},
{
@@ -548,9 +530,15 @@
]
},
{
"path": "/lib/bare/bundles/bareRpc.js",
"path": "/lib/bare/bundles/bareProcess.js",
"keys": [
"bareRpc"
"bareProcess"
]
},
{
"path": "/lib/bare/bundles/bareRuntime.js",
"keys": [
"bareRuntime"
]
},
{
@@ -560,9 +548,9 @@
]
},
{
"path": "/lib/bare/bundles/bareSignals.js",
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareSignals"
"bareRepl"
]
},
{
@@ -572,9 +560,15 @@
]
},
{
"path": "/lib/bare/bundles/bareRun.js",
"path": "/lib/bare/bundles/bareRpc.js",
"keys": [
"bareRun"
"bareRpc"
]
},
{
"path": "/lib/bare/bundles/bareSignals.js",
"keys": [
"bareSignals"
]
},
{
@@ -584,9 +578,9 @@
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"path": "/lib/bare/bundles/bareRun.js",
"keys": [
"bareStdio"
"bareRun"
]
},
{
@@ -608,9 +602,9 @@
]
},
{
"path": "/lib/bare/bundles/bareStructuredClone.js",
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStructuredClone"
"bareStdio"
]
},
{
@@ -619,12 +613,6 @@
"bareSvg"
]
},
{
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
"bareSystemLogger"
]
},
{
"path": "/lib/bare/bundles/bareTap.js",
"keys": [
@@ -632,15 +620,9 @@
]
},
{
"path": "/lib/bare/bundles/bareTcp.js",
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
"bareTcp"
]
},
{
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareTiff"
"bareSystemLogger"
]
},
{
@@ -650,9 +632,9 @@
]
},
{
"path": "/lib/bare/bundles/bareThread.js",
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareThread"
"bareTiff"
]
},
{
@@ -661,6 +643,12 @@
"bareTimers"
]
},
{
"path": "/lib/bare/bundles/bareTcp.js",
"keys": [
"bareTcp"
]
},
{
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
@@ -668,15 +656,27 @@
]
},
{
"path": "/lib/bare/bundles/bareType.js",
"path": "/lib/bare/bundles/bareStructuredClone.js",
"keys": [
"bareType"
"bareStructuredClone"
]
},
{
"path": "/lib/bare/bundles/bareUnpack.js",
"path": "/lib/bare/bundles/bareThread.js",
"keys": [
"bareUnpack"
"bareThread"
]
},
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{
"path": "/lib/bare/bundles/bareType.js",
"keys": [
"bareType"
]
},
{
@@ -691,12 +691,6 @@
"bareUiKit"
]
},
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{
"path": "/lib/bare/bundles/bareV8.js",
"keys": [
@@ -704,9 +698,9 @@
]
},
{
"path": "/lib/bare/bundles/bareWalkHandles.js",
"path": "/lib/bare/bundles/bareUnpack.js",
"keys": [
"bareWalkHandles"
"bareUnpack"
]
},
{
@@ -715,12 +709,30 @@
"bareVm"
]
},
{
"path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [
"bareWalkHandles"
]
},
{
"path": "/lib/bare/bundles/bareUnionBundle.js",
"keys": [
"bareUnionBundle"
]
},
{
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [
"bareV8ToIstanbul"
]
},
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
@@ -739,24 +751,12 @@
"bareWebKit"
]
},
{
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [
"bareV8ToIstanbul"
]
},
{
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareWebKitGtk"
]
},
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{
"path": "/lib/bare/bundles/bareWinUi.js",
"keys": [
@@ -770,9 +770,9 @@
]
},
{
"path": "/lib/bare/bundles/bareZmq.js",
"path": "/lib/bare/bundles/bareWorker.js",
"keys": [
"bareZmq"
"bareWorker"
]
},
{
@@ -782,9 +782,9 @@
]
},
{
"path": "/lib/bare/bundles/bareWorker.js",
"path": "/lib/bare/bundles/bareZmq.js",
"keys": [
"bareWorker"
"bareZmq"
]
},
{
@@ -1595,8 +1595,8 @@
],
"bundleProvenance": {
"schemaVersion": 1,
"generatedAt": "2026-04-05T18:43:55.029Z",
"gitCommit": "8bde7451914ae7534418f5c06def978ca5cec733",
"generatedAt": "2026-04-05T19:03:42.280Z",
"gitCommit": "39dbf0f1be135931add4414edfc66b593559be83",
"nodeVersion": "v22.22.0",
"bundleTier": "all",
"normativeManifest": "packages/bare-os-booter/lib/bare-module-manifest.json",
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1775414633988,
"atMs": 1775415820999,
"commands": [
"arch",
"awk",
@@ -80,6 +80,7 @@
"mount",
"mv",
"nano",
"nice",
"nl",
"nohup",
"nproc",
@@ -507,6 +507,38 @@ async function runKernelExtDropins(ctx, opts = {}) {
return false
}
}
/** @param {string} ver */
function bareOsKernelExtSemverValid(ver) {
const s = String(ver || '').trim()
return /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/.test(
s
)
}
/** @type {string[]} */
const providesInvalidSemver = []
for (const e of collected) {
for (const pr of e.provides || []) {
if (!bareOsKernelExtSemverValid(pr.version)) {
providesInvalidSemver.push(`${e.extId}:${pr.name}@${pr.version}`)
}
}
}
providesInvalidSemver.sort()
if (providesInvalidSemver.length) {
bootStructuredLog(
ctx,
'warn',
'kernelExt.providesInvalidSemver',
`[kernel.ext.d] provides[].version must look like semver (major.minor.patch): ${providesInvalidSemver.join('; ')}`
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'provides_invalid_semver',
items: providesInvalidSemver
})
return false
}
}
/** @type {Map<string, { extId: string, version: string }>} */
const provideNameToOwner = new Map()
/** @type {string[]} */
@@ -613,20 +645,27 @@ async function runKernelExtDropins(ctx, opts = {}) {
strictPol &&
(ctx.env?.BARE_OS_BOOT_EXT_RESOLUTION_TRACE === '1' ||
ctx.env?.BARE_OS_BOOT_EXT_RESOLUTION_TRACE === 'true')
const resolutionAlways =
ctx.env?.BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS === '1' ||
ctx.env?.BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS === 'true'
const extResolutionFailure = strictPol && !topo.ok
if (
ctx.vfs &&
typeof ctx.vfs.writeFile === 'function' &&
ctx.b4a &&
(extResolutionFailure || traceOn)
(extResolutionFailure || traceOn || resolutionAlways)
) {
try {
const cycleSorted = topo.ok
? []
: [...topo.cycleExtIds].sort((a, b) => a.localeCompare(b))
const provideSnapshot = {}
for (const [k, v] of provideNameToOwner) {
provideSnapshot[k] = v
}
const body =
JSON.stringify({
schema: 2,
schema: 3,
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
bootPolicyStrict: !!strictPol,
@@ -640,9 +679,16 @@ async function runKernelExtDropins(ctx, opts = {}) {
ordered: ordered.map((e) => ({
extId: e.extId,
file: e.file,
dependsOn: e.dependsOn
dependsOn: e.dependsOn,
provides: e.provides || []
})),
cycleExtIds: topo.ok ? [] : cycleSorted
cycleExtIds: topo.ok ? [] : cycleSorted,
providesInvalidSemver,
providesVersionConflicts:
providesVersionConflicts.length > 0
? providesVersionConflicts
: undefined,
provideNameToOwner: provideSnapshot
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/kernel-ext-resolution.json',
File diff suppressed because one or more lines are too long
@@ -2,6 +2,7 @@ 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>}
@@ -51,16 +52,20 @@ export async function logPearMultisigKernelHint(kernelRoot) {
const raw = await readFile(f, 'utf8')
const j = JSON.parse(raw)
if (!pearMultisigShapeOk(j)) {
console.warn(
'[seeder] pear.multisig.json: expected { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length'
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
console.log(
`[seeder] pear.multisig.json OK (${signers.length} signers, quorum ${quorum})`
seedLog(
'info',
`pear.multisig.json OK (${signers.length} signers, quorum ${quorum})`,
{ code: 'pear_multisig_ok' }
)
const env = globalThis.process?.env
if (
@@ -69,8 +74,10 @@ export async function logPearMultisigKernelHint(kernelRoot) {
) {
const spawnSync = await loadSpawnSync()
if (!spawnSync) {
console.warn(
'[seeder] hyper-multisig verify skipped (no subprocess: install bare-subprocess or use Node)'
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
@@ -80,17 +87,22 @@ export async function logPearMultisigKernelHint(kernelRoot) {
stdio: ['ignore', 'pipe', 'pipe']
})
if (r.error) {
console.warn(
'[seeder] hyper-multisig verify skipped (CLI not on PATH):',
r.error.message
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)
console.warn('[seeder] hyper-multisig verify failed:', msg || `exit ${r.status}`)
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 {
console.log('[seeder] hyper-multisig verify OK')
seedLog('info', 'hyper-multisig verify OK', { code: 'hyper_multisig_verify_ok' })
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'ok'
}
} else if (envOut) {
@@ -98,7 +110,9 @@ export async function logPearMultisigKernelHint(kernelRoot) {
}
} catch (e) {
if (/** @type {NodeJS.ErrnoException} */ (e).code === 'ENOENT') return
console.warn('[seeder] pear.multisig.json:', e?.message || e)
seedLog('warn', 'pear.multisig.json: ' + (e?.message || String(e)), {
code: 'pear_multisig_read_error'
})
}
}
@@ -120,19 +134,25 @@ export async function logPearMultisigKernelHintFromPearAppDrive(appDrive) {
try {
const j = JSON.parse(raw)
if (!pearMultisigShapeOk(j)) {
console.warn(
'[seeder] pear.multisig.json: expected { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length'
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
console.log(
`[seeder] pear.multisig.json OK (${signers.length} signers, quorum ${quorum}) [from Pear bundle]`
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) {
console.warn('[seeder] pear.multisig.json (bundle):', e?.message || e)
seedLog('warn', 'pear.multisig.json (bundle): ' + (e?.message || String(e)), {
code: 'pear_multisig_bundle_parse'
})
}
}