- Extend bundle throw governance and guest boot trace; seeder parity hints

- fcntl F_GETFL/F_SETFL + posixPoll; bump POSIX profile to 1.0.3, syscalls example
- bareOsSendSignal: negative target = logical pgid; process_table docs
- IPC: max channel quota, telemetry; metrics_live.ipcTelemetry; BARE_OS_IPC_MAX_CHANNELS
- VFS: bareOsClearWarmReadCaches; invalidate on batch bin/lib/bare puts; ctx helpers
- Swarm: enforce BARE_OS_PEER_ALLOWLIST_HEX in shouldAttemptPeer
- Replication JSON reader, boot-ready bareStdlib resolutionWallMs, vault audit mirror docs
- sync-holepunch-clones.mjs report; kernel-extension resolver/test alignment; protomux tests
- pathconf union/mirror; getconf tests; golden tests for test/expr/printf
- docs: contract bump checklist, handbook/env appendix, package refs, protocol CHANGELOG
This commit is contained in:
Raven Scott
2026-04-04 23:04:09 -04:00
parent 985eadfda3
commit 48f973634a
57 changed files with 1831 additions and 609 deletions
+3 -1
View File
@@ -16,7 +16,9 @@ This project is **experimental research software**. APIs described here follow t
**Architecture contract (boundaries):** [`docs/architecture/KERNEL_CONTRACT.md`](../docs/architecture/KERNEL_CONTRACT.md) — booter vs guest image, **`disk.os`** RPC, boot step IDs.
**Kernel source set:** [`docs/reference/kernel-extensions.md`](../docs/reference/kernel-extensions.md) — first-party paths (`kernel/init.js`, `kernel/lib/boot`, built `/bin`) vs vendored **`kernel/lib/bare/bundles`** (Holepunch IIFEs), CI allowlists, and seeder parity. Hand-authored code must stay free of incomplete markers; bundles are tracked separately until allowlists are empty.
**Kernel source set:** [`docs/reference/kernel-extensions.md`](../docs/reference/kernel-extensions.md) — first-party paths (`kernel/init.js`, `kernel/lib/boot`, built `/bin`) vs vendored **`kernel/lib/bare/bundles`** (Holepunch IIFEs), CI allowlists, and seeder parity. Hand-authored code must stay free of incomplete markers; bundles are gated by **`verify-bundle-markers.mjs`**, **`verify-bundle-throws.mjs`** ( **`new Error` messages** with *not implemented* / abstract-base-class semantics — see [`docs/audit/bundle-throw-allowlist.json`](../docs/audit/bundle-throw-allowlist.json)), and **`sanitize-bare-bundles.mjs`**. Prefer shrinking the throw allowlist via upstream bundle refreshes over silent drift.
**Local Holepunch clones:** run **`node scripts/sync-holepunch-clones.mjs`** to emit **`docs/audit/holepunch-clone-sync-report.json`** comparing **`bare-module-manifest.json`** names to optional checkouts under **`BARE_OS_HOLEPUNCH_CLONES_ROOT`** (default **`~/dev/pearcli/holepunch-repos/holepunchto_repos`**).
---
+16
View File
@@ -74,6 +74,22 @@ If you must quote a banned substring for wire-compat or third-party context, add
---
## Kernel tree vs seeder mirror
**Canonical tree:** [`kernel/`](../kernel/) (hand-authored **`kernel/lib/init/init-main.js`**, **`kernel/lib/boot/*.js`**, **`kernel/etc/**`**, staged **`kernel/bin/**`**, **`kernel/lib/bare/**`**).
**Mirror:** [`packages/bare-os-seeder/kernel/`](../packages/bare-os-seeder/kernel/) must be **byte-identical** to **`kernel/`** (CI: [scripts/verify-kernel-seeder-parity.mjs](../scripts/verify-kernel-seeder-parity.mjs)).
Workflow after changing boot logic:
1. Edit **`kernel/lib/init/init-main.js`** (and optional **`kernel/lib/boot/*.js`**).
2. Run **`node scripts/bundle-kernel-init.mjs`** to regenerate root **`kernel/init.js`**.
3. Sync the mirror: **`rsync -a --delete kernel/ packages/bare-os-seeder/kernel/`** from the repo root.
Do not edit the seeder copy first; it will be overwritten by parity checks.
---
## Before you open a PR
```bash
+13
View File
@@ -118,6 +118,19 @@ How-to for code inside and around the image.
| [Machine-readable data](data/README.md) | Roadmap JSON, terminology allowlist |
| [Holepunch catalog](bare-holepunch-catalog.json) | Optional `ctx.bare` bundle metadata |
### Contract bump checklist
When you change **`BARE_OS_CTX_API_VERSION`**, **`/proc`** JSON schemas, **`BARE_OS_PROTOCOL_*`** / package semver, **`BARE_OS_POSIX_PROFILE_VERSION`**, or kernel capability words, update every dependent artifact in one pass:
1. [README.md](../README.md) and [DOCUMENTATION.md](../DOCUMENTATION.md) if the version string or install story changes.
2. [Handbook](../handbook/README.md) chapters touched by behavior (especially ch.34, 6, 9).
3. [Users manual](../users-manual/README.md) and [Developer guide](../developer-guide/README.md) for operator or extension workflows.
4. [docs/reference/compatibility-matrix.md](reference/compatibility-matrix.md) and any affected topic pages under [docs/reference/](reference/README.md).
5. Package changelogs: [packages/bare-os-booter/CHANGELOG.md](../packages/bare-os-booter/CHANGELOG.md), [packages/bare-os-protocol/CHANGELOG.md](../packages/bare-os-protocol/CHANGELOG.md) when applicable.
6. [docs/schemas/](schemas/) and [docs/architecture/POSIX_DECLARED_PROFILE.md](architecture/POSIX_DECLARED_PROFILE.md) when proc, telemetry, or POSIX profiles move.
7. [kernel/etc/bare-os/syscalls.example.json](../kernel/etc/bare-os/syscalls.example.json) and the [seeder mirror](../packages/bare-os-seeder/kernel/) after kernel contract edits; run **`node scripts/verify-kernel-seeder-parity.mjs`**.
8. Regenerate **`docs/reference/ctx-client-helper.generated.ts`** via root **`pretest`** ( **`gen-ctx-client-helper.mjs`** ).
---
## Root stubs and Pear
+5 -3
View File
@@ -6,7 +6,7 @@ This document is the **normative contract** for how closely the stock Bare OS ke
| Constant | Value |
| -------- | ----- |
| `BARE_OS_POSIX_PROFILE_VERSION` | `1.0.1` |
| `BARE_OS_POSIX_PROFILE_VERSION` | `1.0.3` |
| `BARE_OS_POSIX_PROFILE_ID` | `bare-os-posix-like` |
| `BARE_OS_POSIX_PROFILE_REFERENCE` | Open Group Issue 7 index URL |
@@ -18,7 +18,7 @@ This document is the **normative contract** for how closely the stock Bare OS ke
Bare OS does **not** expose a C ABI. The following **observability contracts** stand in for “system calls”:
- **`/proc/bare_os/syscalls.json`** — Declares **fd model**, **signal model**, **errno hints**, and **operation detail** (schema version in-file). **`ops`** lists invocable **`ctx.bareOsSyscall`** names; **`opsDetail`** also carries **POSIX.1 XSH** logical names (**`open`**, **`close`**, **`read`**, **`write`**, **`lseek`**, **`pipe`**, **`dup`**, **`dup2`**, **`fcntl`**) with **`posixAlignment`** (**`simulated`**, **`partial`**, **`ENOTSUP`**) and **`mapsTo`** for traceability. The **`posixXsh`** object summarizes those XSH names. Unsupported operations are listed with **ENOTSUP** / **ENOSYS**-style semantics where applicable.
- **`/proc/bare_os/syscalls.json`** — Declares **fd model**, **signal model**, **errno hints**, and **operation detail** (schema version in-file). **`ops`** lists invocable **`ctx.bareOsSyscall`** names; **`opsDetail`** also carries **POSIX.1 XSH** logical names (**`open`**, **`close`**, **`read`**, **`write`**, **`lseek`**, **`pipe`**, **`dup`**, **`dup2`**, **`fcntl`**, **`poll`**) with **`posixAlignment`** (**`simulated`**, **`partial`**, **`ENOTSUP`**) and **`mapsTo`** for traceability. The **`posixXsh`** object summarizes those XSH names. **`fcntl`** is implemented as **`ctx.bareOsSyscall('fcntl', { fd, cmd, arg })`** with **`F_GETFL`** / **`F_SETFL`** (Linux-shaped **`O_APPEND`** **`0x400`**, **`O_NONBLOCK`** **`0x800`**). **`poll`** is approximated by **`ctx.bareOsPosixPoll`** / **`posixPoll`** on simulated pipe FDs. Unsupported operations are listed with **ENOTSUP** / **ENOSYS**-style semantics where applicable.
- **`/proc/bare_os/process_table.json`** — **PGID** / **SID**-style fields, **signal routing**, and initd binding for the **logical** process table (not host PIDs).
**Explicit non-goals**
@@ -53,11 +53,13 @@ Two logical drives (**system** + **personal**) form one namespace; **`.bareos_em
**Rename:** No atomic cross-drive **`rename`**; **`mv`** implements copy + remove where needed (handbook ch.9).
**`pathconf` / `getconf`:** Live **`getconf _PC_* /path`** delegates to **`ctx.bareOsPathconf`**. Union read prefixes (**`BARE_OS_VFS_UNION_PREFIXES`**) and read-only **mirror** paths (**`/mirror/…`**) adjust documented **`_PC_NO_TRUNC`** and **`_PC_CHOWN_RESTRICTED`** hints (not a certifiable pathconf implementation).
---
## 5. Signals and exit statuses
Signal names accepted by **`kill`** and shell builtins follow POSIX **names** where listed in proc JSON. **Exit codes** for pipelines and **`timeout`** follow documented stock behavior (see handbook and booter tests).
Signal names accepted by **`kill`** and shell builtins follow POSIX **names** where listed in proc JSON. **`ctx.bareOsSendSignal`** and **`/bin/kill`** also accept a **negative** numeric operand as a logical **process group id** (**`pgid`**) for **shell background jobs** (active rows only; **`kill -0 -pgid`** checks existence). **Exit codes** for pipelines and **`timeout`** follow documented stock behavior (see handbook and booter tests).
---
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"generatedAt": "2026-04-05T02:38:13.083Z",
"generatedAt": "2026-04-05T03:03:09.364Z",
"normativeManifest": "packages/bare-os-booter/lib/bare-module-manifest.json",
"buildTool": "packages/bare-os-bare-libs/build.mjs",
"bundles": [
+19 -3
View File
@@ -1,5 +1,21 @@
{
"schemaVersion": 1,
"description": "Vendored bundles that still contain throw new Error(\"not implemented\") until upstream removes them. Target: empty.",
"entries": []
"schemaVersion": 2,
"description": "Vendored bundles that intentionally contain new Error(...) messages with 'not implemented' / abstract-base-class semantics from upstream Bare/Node-compat streams. Re-run after bare-os-bare-libs rebuild; trim when upstream removes throws.",
"entries": [
{
"bundleFile": "bareDev.js",
"rationale": "Duplex/transform stream base classes from upstream use _read/_write/_transform and abstract readByte/seek errors when subclasses omit hooks — standard Node stream pattern, not Bare OS stubs.",
"triggerCondition": "Subclass Readable/Writable/Duplex without overriding _read/_write/_transform; RandomAccessReader subclasses without readByte/seek"
},
{
"bundleFile": "bareMedia.js",
"rationale": "Image pipeline defers ICO encoder path; throws only when ICO output is requested.",
"triggerCondition": "ICO encoding write path when encoder not shipped in bundle"
},
{
"bundleFile": "bareIco.js",
"rationale": "ICO encode entrypoint matches bareMedia deferral; decode works, encode is unsupported until upstream implements.",
"triggerCondition": "ICO encode API invoked"
}
]
}
+1 -1
View File
@@ -14,7 +14,7 @@ Single place to compare **protocol**, **booter `ctx` API**, **feature-bit docume
## Version artifacts
**`bareOsCtxApiVersion`** (current stock: **`1.34.0`**) — Defined in [`packages/bare-os-booter/lib/bare-os-ctx-api.js`](../../packages/bare-os-booter/lib/bare-os-ctx-api.js). This is the semver string for the documented **`ctx`** contract; bump it when stable guest-visible behavior changes.
**`bareOsCtxApiVersion`** (current stock: **`1.36.0`**) — Defined in [`packages/bare-os-booter/lib/bare-os-ctx-api.js`](../../packages/bare-os-booter/lib/bare-os-ctx-api.js). This is the semver string for the documented **`ctx`** contract; bump it when stable guest-visible behavior changes.
**`BARE_OS_KERNEL_FEATURE_BITS_DOC`** (current stock: **`15`**) — Exported from [`packages/bare-os-protocol/lib/kernel-feature-bits.js`](../../packages/bare-os-protocol/lib/kernel-feature-bits.js). It versions the feature-bit documentation and related governance; keep it aligned with [`developer-guide/adr/001-kernel-feature-bits-governance.md`](../../developer-guide/adr/001-kernel-feature-bits-governance.md).
@@ -1,16 +1,16 @@
// Auto-generated by scripts/gen-ctx-client-helper.mjs — do not edit by hand.
/** Canonical `ctx` API semver from `packages/bare-os-booter/lib/bare-os-ctx-api.js`. */
export const BARE_OS_CTX_API_CLIENT_VERSION = '1.34.0' as const
export const BARE_OS_CTX_API_CLIENT_VERSION = '1.36.0' as const
/** Relative path to the hand-maintained TypeScript contract (schema source of truth). */
export const BARE_OS_CTX_DTS_SOURCE = 'packages/bare-os-booter/lib/bare-os-ctx.d.ts' as const
/** Approximate count of `bareOs…` members in the DTS (diagnostic only; regenerate on contract edits). */
export const BARE_OS_CTX_DTS_BAREOS_MEMBER_APPROX = 141 as const
export const BARE_OS_CTX_DTS_BAREOS_MEMBER_APPROX = 146 as const
/** Byte length of `bare-os-ctx.d.ts` when this file was generated. */
export const BARE_OS_CTX_DTS_BYTES = 17146 as const
export const BARE_OS_CTX_DTS_BYTES = 17869 as const
/** Pointers for external client generators (OpenAPI / JSON Schema follow-ups). */
export const BARE_OS_CTX_SCHEMA_HINT = {
@@ -27,7 +27,7 @@ The list below is one **bullet per variable** in the form **name — component
- `BARE_OS_NO_SPLASH` — Booter — If `1`, skip TTY splash (plain logs / non-TTY behavior unchanged)
- `BARE_OS_LOCAL_SEED` — paths — Overrides local seed path helper (`defaultLocalSeedCorestorePath`); booter does not local-boot
- `BARE_OS_SKIP_REPL` — Booter — If `1`, readline returns null — non-interactive exit
- `BARE_OS_BOOT_TRACE` — Stock kernel (bundled **`/boot/init.js`** from [`kernel/lib/init/init-main.js`](../../kernel/lib/init/init-main.js) via [`scripts/bundle-kernel-init.mjs`](../../scripts/bundle-kernel-init.mjs)) — If `1` or `true`, log each boot phase duration on stderr as `[boot] phase: Nms`; if `json`, log `{"phase":"…","ms":n}` per phase; **`ndjson`** adds **`sessionId`** / **`ts`** (same shape as `ctx.bareOsEmitBootEvent`)
- `BARE_OS_BOOT_TRACE` — Stock kernel (bundled **`/boot/init.js`** from [`kernel/lib/init/init-main.js`](../../kernel/lib/init/init-main.js) via [`scripts/bundle-kernel-init.mjs`](../../scripts/bundle-kernel-init.mjs)) — If `1` or `true`, log each boot phase duration on stderr as `[boot] phase: Nms`; if `json` / **`ndjson`**, log structured phase lines (**`bootTraceSchemaVersion` 2**, **`step`** / **`stage`** / **`phase`**) per completed stage; **`ndjson`** adds **`sessionId`** / **`ts`**. When `json` or **`ndjson`**, guest **`os-release`**, **`motd`**, and **`banner`** text also emit extra stderr lines with **`type":"bootOutput"`** and a capped **`textPreview`** (the live session output still uses **`ctx.console.log`** only).
- `BARE_OS_BOOT_SAFE_MODE` — Stock kernel — **`1`** / **`true`** skips **`rc.d`**, **`kernel.ext.d`**, **`onboot`** after boot policy merge.
- `BARE_OS_BOOT_TRANSACTION_JOURNAL` — Stock kernel — **`1`**, **`true`**, or **`ndjson`** appends phase records to **`/run/bare-os/boot-transaction.ndjson`** when **`ctx.vfs`** supports it.
- `BARE_OS_BOOT_CHECKPOINT` — Stock kernel — **`1`** / **`true`** writes **`/run/bare-os/boot-checkpoint.json`** after each completed boot phase.
@@ -50,6 +50,7 @@ The list below is one **bullet per variable** in the form **name — component
- `BARE_OS_VFS_UNION_PREFIXES` — Booter / VFS — Comma-separated logical path prefixes where **`readFile`** may union system + overlay sources.
- `BARE_OS_VFS_UNION_WRITE_DENY` — Booter / VFS — When **`1`** / **`true`**, block **`writeFile`** / **`unlink`** on paths under union read prefixes (read-only union overlay).
- `BARE_OS_IPC_CHANNEL_MAX_BYTES` — Booter / IPC — Optional JSON map **`{"fifoName": maxBytes}`** (or legacy comma form) capping buffered bytes per FIFO name under **`/run/bare-os/ipc/`**.
- `BARE_OS_IPC_MAX_CHANNELS` — Booter / IPC — Cap distinct FIFO channel keys (default **4096**); exceeding the quota makes **`mkfifo`** fail with **`bare-os ipc: channel quota exceeded`**. Telemetry appears under **`ipcTelemetry.telemetry`** in **`/proc/bare_os/metrics_live.json`**.
- `BARE_OS_VFS_BIN_CACHE` — VFS — When **`1`** / **`true`**, LRU read cache for **`/bin`**; invalidated on **`vfs.watch`** when enabled.
- `BARE_OS_VFS_BIN_CACHE_BLAKE2B` — VFS — When **`1`** / **`true`** with **`BARE_OS_VFS_BIN_CACHE`**, **`/bin`** cache entries are keyed by **BLAKE2b** content digests (via **`bare-crypto`**) for deduplication across paths within the LRU budget.
- `BARE_OS_VFS_ENFORCE_ACL` — VFS — When **`1`** / **`true`**, **`PATH.bare_acl`** sidecars (same text as **`getfacl` / `setfacl`**) can deny reads/writes on the personal drive. Lines **`user::`**, **`user:UID:`**, **`group::`**, **`group:GID:`**, **`other::`**, and **`mask::`** are interpreted with Linux-style **mask** capping named users, named groups, and **`group::`**; **`user::`** and **`other::`** are not masked. Object owner/group default to **`UID` / `GID`**; override with **`BARE_OS_ACL_OBJECT_UID`** / **`BARE_OS_ACL_OBJECT_GID`** when inode metadata differs. See **`bare-os-vfs-acl-enforce.js`**.
+3
View File
@@ -15,6 +15,8 @@ Bare OS splits **hand-authored kernel code** from **vendored Holepunch runtime b
**Seeder parity:** [`kernel/`](../../kernel/) and [`packages/bare-os-seeder/kernel/`](../../packages/bare-os-seeder/kernel/) must be byte-identical ([`scripts/verify-kernel-seeder-parity.mjs`](../../scripts/verify-kernel-seeder-parity.mjs)). After editing `kernel/`, sync the seeder tree before commit.
**`kernel.ext.d` ordering:** The Kahn topological sort in [`kernel/lib/init/init-main.js`](../../kernel/lib/init/init-main.js) must stay aligned with [`packages/bare-os-booter/lib/kernel-extension-resolver.js`](../../packages/bare-os-booter/lib/kernel-extension-resolver.js) (**`topologicalOrderKernelExtensions`**); CI covers this via the Brittle test **`kernel extension topological order matches guest Kahn tie-break`** in **`packages/bare-os-booter/test.js`**.
## Governance (feature bits doc v14)
New capability bits are governed by [ADR 001 — Kernel feature bit governance](../../developer-guide/adr/001-kernel-feature-bits-governance.md). **`BARE_OS_KERNEL_FEATURE_BITS_DOC`** must bump when semantics change. Bits **2830** on word 1 are assigned (see ADR). **Word 2 (`bits2`)** carries the extended seeding platform surface; always mask with **`>>> 0`**. Avoid `1 << 31` on word 1 in JS without `>>> 0` discipline.
@@ -48,6 +50,7 @@ Seed RPC **`bare_os.capabilities`** includes **`protocolPackageVersion`**, optio
- Command subst — `BARE_OS_SHELL_CMDSUBST=1` — Enable bounded `$(…)` in words (`BARE_OS_SHELL_CMDSUBST_MAX_BYTES`)
- Union writes — `BARE_OS_VFS_UNION_WRITE_DENY` — Comma/colon list of path prefixes blocked for writes under union prefixes
- IPC — `BARE_OS_IPC_CHANNEL_MAX_BYTES` — JSON map `{"fifoName": 65536}` per-channel backlog cap
- IPC — `BARE_OS_IPC_MAX_CHANNELS` — Upper bound on distinct FIFO names (default 4096); telemetry in **`metrics_live.ipcTelemetry`**
- Initd journal — `BARE_OS_INITD_JOURNAL_MAX_LINES` — Cap NDJSON lines per unit (default 400, max 20000)
- Telemetry — `BARE_OS_TELEMETRY_NDJSON` — Logical path; append NDJSON mirror (`telemetrySchemaVersion` / `lifecycleSchemaVersion` **10**); optional **`sessionForkGeneration`**, **`bareModuleCryptoStagingProbeId`**, **`pearInspectLoggerTlsProbeId`**, **`hypercorePackHrpcLifecycleProbeId`**, **`bareModuleProbeClass`**, **`bareRpcProbeClass`**; optional correlation via **`BARE_OS_BOOT_ATTEMPT_ID`** → **`bootAttemptId`**
- Telemetry OTel — `BARE_OS_TELEMETRY_OTEL_JSONL` — Logical path; append OTLP-inspired JSON lines (`otlSchemaVersion` **8**, optional **`scopeMetrics`** sketch; see [`otel-bare-os-jsonl.schema.json`](../schemas/otel-bare-os-jsonl.schema.json))
+5 -1
View File
@@ -32,6 +32,10 @@ Former **DOCUMENTATION.md** §§12.112.9 (runtime, VFS, shell, identity). [Re
See also [Kernel subsystem map](../architecture/kernel-subsystems.md) and [ADR 0001](../adr/0001-kernel-subsystem-boundaries.md).
### 12.2b Corestore / swarm suspendresume bridge
- **[`lib/corestore-host-lifecycle.js`](../../packages/bare-os-booter/lib/corestore-host-lifecycle.js)** — Registers one-shot hooks so guest **`ctx.bareOsRegisterSuspendHook`** / **`bareOsRegisterResumeHook`** call host **`corestore`** / **`hyperswarm`** **`suspend()`** / **`resume()`** when those methods exist (mobile sleep, Pear lifecycle). **`bareOsCorestoreSnapshotOperatorHint`** remains a documentation-only sketch (guest does not invoke **`corestore-snapshot`**).
### 12.3 [packages/bare-os-booter/index.js](../../packages/bare-os-booter/index.js)
**Imports** — Hyperswarm, Protomux, protocol, `./lib/swarm-disk.js`, `./lib/kernel-runner.js`, `./lib/bare-os-kernel-loader.js`, `./lib/bare-os-capability-registry.js`, `./lib/bare-os-lifecycle-manager.js`, `./lib/vfs.js`, `./lib/shell.js`, `./lib/paths.js`, `./lib/bare-os-ipc.js`, `./lib/bare-os-runtime-caps.js`, stdio/readline/repl/boot-splash helpers, `./lib/identity-session.js`.
@@ -47,7 +51,7 @@ See also [Kernel subsystem map](../architecture/kernel-subsystems.md) and [ADR 0
**`executeKernel(disk, store, swarm, initSource)`** (`store`/`swarm` unused but kept for signature symmetry / future use)
- Builds `shellEnv` with **guest** defaults: `USER`/`LOGNAME`=`guest`, `HOME`/`PWD`=`/home/guest`, `UID`/`GID`=`65534`, `BARE_OS_IDENTITY=guest`, `BARE_OS_EXIT_STATUS`=`0`, `BARE_OS_CTX_API_VERSION`, `PATH=/bin`, `SHELL`, `HOSTNAME`, `0`. When the host sets any of the keys listed under “host → session passthrough” in [Environment variables and POSIX appendix](environment-and-posix-appendix.md#14-environment-variables-complete-list), those values are copied into `shellEnv`. Sets **`BARE_OS_BOOT_PROFILE_RESOLVED`** from **`BARE_OS_BOOT_PROFILE`** or the first line of **`/etc/bare-os/profile`**, and **`BARE_OS_SESSION_ID`** (random UUID). Seeds **`/run/bare-os/boot.json`** fields **`imageDigest`**, **`pearChannel`**, **`pearRelease`** from **`BARE_OS_IMAGE_DIGEST`**, **`BARE_OS_PEAR_CHANNEL`** / **`PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`** when present.
- **`createBareOsIpc({ maxFifoBytes, perChannelMaxBytes?, ipcRpcToken?, enableFanout?, maxJsonRpcLineBytes? })`** ([`bare-os-ipc.js`](../../packages/bare-os-booter/lib/bare-os-ipc.js)) — FIFOs under **`/run/bare-os/ipc/<name>`**; optional per-name byte caps from **`BARE_OS_IPC_CHANNEL_MAX_BYTES`**; JSON-RPC with optional token and line cap; fan-out **`fanoutPublish`/`fanoutSubscribe`**; **`stats`** includes fan-out counts when caps expose **`features.ipcFanout`**.
- **`createBareOsIpc({ maxFifoBytes, maxChannels?, perChannelMaxBytes?, ipcRpcToken?, enableFanout?, maxJsonRpcLineBytes? })`** ([`bare-os-ipc.js`](../../packages/bare-os-booter/lib/bare-os-ipc.js)) — FIFOs under **`/run/bare-os/ipc/<name>`**; optional per-name byte caps from **`BARE_OS_IPC_CHANNEL_MAX_BYTES`**; optional global channel cap **`BARE_OS_IPC_MAX_CHANNELS`**; JSON-RPC with optional token and line cap; fan-out **`fanoutPublish`/`fanoutSubscribe`**; **`stats`** includes fan-out counts, quota telemetry, and operator backpressure snapshots when caps expose **`features.ipcFanout`**.
- **`createVfs(drive, personalDrive, shellEnv, vfsMountRef, vfsOptions)`** → `ctx.vfs` (same `env` object as `ctx.env`). **`vfsOptions`** supply **`procSnapshot`**, dynamic **`/proc/*`** and **`/sys/*`** text (quotas JSON, **`bare_os_features`** with **`bits`****`bits5`** when advertised, **`bare_os_net_summary`**, **`bare_os_host_os`**, **`bare_os_sync_window`**, **`bare_os_debug`**, net/disk stubs, session stats), **`bootProfileText`**, **`sessionText`**, **`initdRunText`**, **`bootReadyJsonText`**, mount map for **`/proc/mounts`**, **`bootStartedMs`** for **`/proc/uptime`**, etc. Exposes **`vfs.watch(logicalPath)`** for Hyperdrive-backed paths when **`BARE_OS_VFS_WATCH`** is not **`0`**.
- **`applyGuestEnv(ctx)`** then **`ensureGuestHome(ctx)`** — normalizes `ctx.identity` and seeds `/.bare/` (and a guest marker) on the personal drive.
- **`createReadLine()`** always resolves stdio first and returns `stdout` (may be `null`) alongside `readLine` so the kernel can write to the **same** stream as the REPL (including `bare-stdio` under Pear).
@@ -32,6 +32,8 @@ Re-exports from `./lib/seed-rpc-methods.js`: `BARE_OS_SEED_RPC_METHODS`, `BARE_O
Re-exports **`BARE_OS_KERNEL_FEATURES_STOCK_WORD_*`** stock masks and **`BARE_OS_FEATURE5_*`** … **`FEATURE11_*`** from `./lib/kernel-feature-bits.js` (alongside earlier words). Re-exports wire v2 helpers from **`./lib/kernel-capability-wire.js`** (**`kernelCapabilityWords`** shape, **`BARE_OS_KERNEL_CAPABILITY_WORD_KEYS`**, …). **`BARE_OS_PROTOCOL_PACKAGE_VERSION`** is published from `./lib/protocol-meta.js` for **`ctx.bareOsProtocolPackageVersion`** comparisons.
When Capability word **6** (replication operator surface) is advertised, the stock booter also exposes **`ctx.bareOsReadReplicationOperatorJson()`** as a typed convenience reader over the same JSON as **`/proc/bare_os/replication`** (feature-bit alignment is documented in the capability index).
### 10.3 [packages/bare-os-protocol/constants.js](../../packages/bare-os-protocol/constants.js)
- `PROTOCOL_NAME``'bare-os-v1'` — Protomux channel name
+3 -3
View File
@@ -10,20 +10,20 @@
"shell": {
"grammar": "line_at_a_time_subset",
"pipes": "simulated_capture",
"job_control": "logical_jobs_only",
"job_control": "logical_jobs_only; kill / bareOsSendSignal negative target = pgid on shellBackgroundJobs",
"loop_control": "BARE_OS_SHELL_LOOP_CONTROL for break continue in while for"
},
"vfs": {
"fifo": "in_memory_ipc_under_run",
"union": "BARE_OS_VFS_UNION_PREFIXES",
"system_ro_alias": "BARE_OS_VFS_SYSTEM_RO_ALIAS",
"bin_cache": "BARE_OS_VFS_BIN_CACHE",
"bin_cache": "BARE_OS_VFS_BIN_CACHE; warm cache clear via ctx.bareOsInvalidateWarmReadCaches / vfs.bareOsClearWarmReadCaches; bareOsVfsBatchWrite clears on bin/lib/bare puts",
"lib_bare_cache": "BARE_OS_VFS_LIB_BARE_CACHE with bin cache"
},
"synthetic_proc": {
"syscalls_json_schema": 4,
"process_table_schema": 4,
"paths": ["/proc/bare_os/syscalls.json", "/proc/bare_os/process_table.json"],
"posix_xsh_surface": "open,close,read,write,lseek,pipe,dup,dup2,fcntl documented in syscalls.json opsDetail + posixXsh.namesCsv"
"posix_xsh_surface": "open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll in syscalls.json opsDetail + posixXsh.namesCsv; ctx.bareOsSyscall fcntl+posixPoll; ctx.bareOsPosixPoll"
}
}
+21 -19
View File
@@ -8,32 +8,34 @@
"/proc/bare_os/syscalls.json"
],
"bareOsSyscallOps": [
"readFile",
"writeFile",
"readdir",
"mkdir",
"stat",
"unlink",
"chmod",
"access",
"chdir",
"getcwd",
"readlink",
"symlink",
"chmod",
"exists",
"fcntl",
"fdatasync",
"fsync",
"ftruncate",
"getcwd",
"kill",
"link",
"lstat",
"rmdir",
"mkdir",
"mount",
"pathconf",
"umount",
"kill",
"posixPoll",
"readFile",
"readdir",
"readlink",
"rename",
"link",
"access",
"utimes",
"rmdir",
"stat",
"symlink",
"truncate",
"ftruncate",
"fsync",
"fdatasync"
"umount",
"unlink",
"utimes",
"writeFile"
],
"utilitiesRecent": [
"dd",
+1 -1
View File
@@ -46,7 +46,7 @@ The personal drive still persists: guest data is **not** anonymous to the drive
**What this means in practice:** the **passphrase** never sits on disk; the blob stores **salt + iterations + ciphertext**. Unlocking derives a key from the passphrase, decrypts the **signing secret**, and keeps derived session state in memory. **`login --new`** creates a new account; **`login`** decrypts an existing one. Legacy v1 blobs are rejected with a message to recreate.
**Vault (`savevault`, `logout --save`):** selected files are snapshotted into **`/.bare/vault/`** as **AEAD-protected / sealed** blobs at rest (path hashing + keys derived from the unlocked session material in the implementation). Vault ciphertext is only as good as your **passphrase**, **backups**, and **who can replicate** your personal drive.
**Vault (`savevault`, `logout --save`):** selected files are snapshotted into **`/.bare/vault/`** as **AEAD-protected / sealed** blobs at rest (path hashing + keys derived from the unlocked session material in the implementation). Vault ciphertext is only as good as your **passphrase**, **backups**, and **who can replicate** your personal drive. NDJSON checkpoints still append under **`/.bare/vault-rotation-audit.ndjson`**; when **`ctx.bareOsAuditLogAppendBatch`** exists, the booter mirrors **`identity_unlock`** / **`vault_save`** rows into the host audit chain as **`identity.vault_audit_mirror`** for hash-chained accountability.
---
+1 -1
View File
@@ -75,7 +75,7 @@ The **prompt** (`[user@host:path] > `) is applied by the booters readline lay
| **Primary goal** | CI / doctor preflight without side effects | Recover from a bad rollout by skipping selected stages |
| **Key artifact** | stderr / trace only | **`/run/bare-os/boot-rollback.marker`** JSON merged into boot policy skips |
- **`BARE_OS_REQUIRE_CTX_API_MIN`** / **`BARE_OS_BOOT_ABI_STRICT`** — Compare **`ctx.bareOsCtxApiVersion`** to a minimum semver when strict.
- **`BARE_OS_BOOT_TRACE=ndjson`** — One JSON object per boot phase on stderr (**`type`**, **`phase`**, **`ms`**, **`sessionId`**, **`ts`**).
- **`BARE_OS_BOOT_TRACE=ndjson`** (or **`json`**) — One JSON object per completed boot stage on stderr (**`bootTraceSchemaVersion`**: **2**, **`type":"boot"`**, **`step`** / **`stage`** / **`phase`**, **`ms`**, plus **`sessionId`** / **`ts`** for **`ndjson`**). Guest **`os-release`**, **`motd`**, and **`banner`** lines additionally emit **`type":"bootOutput"`** records with a preview of the text (**`textPreview`**); interactive output remains **`ctx.console.log`**.
- **`BARE_OS_KERNEL_SELFTEST`** — After boot snippets, run a short trusted **`execLine`** checklist.
- **`BARE_OS_SELFTEST_FORMAT=tap`** — Same self-test emits **TAP** lines on stderr (CI-friendly).
- **`BARE_OS_BOOT_ALLOWLIST=1`** — Only run boot snippet lines whose first shell token is listed in **`/etc/bare-os/boot.allow`** (plus safe builtins); distributors can start from **`etc/bare-os/boot.allow.example`** on the system image. Pair with **`BARE_OS_BOOT_STRICT`** to exit on the first disallowed or failing line.
@@ -63,6 +63,7 @@ The authoritative script lives in root **[`package.json`](../package.json)** und
- **`npm run build -w bare-os-coreutils`** — Produces **`/bin`** outputs and **`man.json`**. Failure usually means stale or broken utilities, TypeScript or syntax errors, or a bad build script.
- **`npm run build -w bare-os-bare-libs`** — Builds **`ctx.bare`** bundles. Failure means esbuild errors or bad imports under **`kernel/lib/bare/`**.
- **`node scripts/verify-kernel-seeder-parity.mjs`** — Ensures **`kernel/`** matches **`packages/bare-os-seeder/kernel/`**. Failure means the Pear seeder would ship the wrong tree; sync before release.
- **`node scripts/sync-holepunch-clones.mjs`** — Optional maintainer report under **`docs/audit/holepunch-clone-sync-report.json`** (local **`holepunchto_repos`** vs **`bare-module-manifest.json`** names).
- Additional verifiers (ctx API, capability contracts, roadmap wave rows, doc links, man coverage, compatibility matrix strings, Pear import rules, bare manifest smoke) run in the same **`pretest`** chain—read **`package.json`** for the full ordered list.
If any step fails, scroll the **`npm test`** output for the first non-zero exit; each script prints a specific error string.
+3 -3
View File
@@ -42,9 +42,9 @@ We borrow **names and muscle memory** from POSIX because that makes scripts port
## 1. Executive summary: what is _not_ POSIX here
- **Full POSIX `sh` grammar** — Bare uses a **line-at-a-time** shell: builtins plus **`/bin`**; lists with **`;`** (outside compound statements), **`&&`**, and **`|`** pipelines; bounded **`if`** … **`fi`**; bounded **`while`** / **`for`** (iteration cap **`BARE_OS_SHELL_LOOP_MAX`**, default **10000**); optional **`break`** / **`continue`** when **`BARE_OS_SHELL_LOOP_CONTROL=1`**; bounded **`case`** … **`esac`** (**`BARE_OS_SHELL_CASE_MAX_BRANCHES`**, default **32**). Optional bounded **`$(…)`** when **`BARE_OS_SHELL_CMDSUBST=1`** (see §3). There are **no subshells** and no full **`sh`** grammar.
- **Processes, `fork`, pipes as OS primitives** — Pipelines are **simulated** by capturing **`console.log`** (and merged **`console.error`** when **`2>&1`** is used) into the next command. With **`BARE_OS_POSIX_FD_SIM=1`**, extensions may also use **`ctx.bareOsPosixFdSimPipe`** / **`bareOsPosixFdSimRead`** / **`bareOsPosixFdSimWrite`** for bounded in-memory pipe pairs surfaced as logical FDs in **`/proc/self/fd`** (not full kernel pipes).
- **Processes, `fork`, pipes as OS primitives** — Pipelines are **simulated** by capturing **`console.log`** (and merged **`console.error`** when **`2>&1`** is used) into the next command. With **`BARE_OS_POSIX_FD_SIM=1`**, extensions may also use **`ctx.bareOsPosixFdSimPipe`** / **`bareOsPosixFdSimRead`** / **`bareOsPosixFdSimWrite`** for bounded in-memory pipe pairs surfaced as logical FDs in **`/proc/self/fd`** (not full kernel pipes). Logical FDs support **`ctx.bareOsSyscall('fcntl', { fd, cmd: 'F_GETFL' | 'F_SETFL', arg })`** (Linux-shaped **`O_APPEND`** **`0x400`**, **`O_NONBLOCK`** **`0x800`**) and readiness via **`ctx.bareOsPosixPoll`** / **`ctx.bareOsSyscall('posixPoll', { fds, timeoutMs })`** (see **`/proc/bare_os/syscalls.json`**).
- **`chown` / `chgrp` / real UIDs across users** — **Single-session** identity; metadata carries **uid/gid** for display and checks, not a multi-user kernel.
- **FIFOs, `mknod`, real devices** — No kernel FIFOs or **`mknod`**; **`mkfifo`** creates **in-memory** channels under **`/run/bare-os/ipc/<name>`** (see **`bare-os-ipc.js`**).
- **FIFOs, `mknod`, real devices** — No kernel FIFOs or **`mknod`**; **`mkfifo`** creates **in-memory** channels under **`/run/bare-os/ipc/<name>`** (see **`bare-os-ipc.js`**). Optional **`BARE_OS_IPC_MAX_CHANNELS`** caps distinct channel keys; **`metrics_live.ipcTelemetry`** mirrors FIFO depth, fan-out counts, and quota counters.
- **Full POSIX/GNU `xargs`** — Bounded implementation: **`ctx.runBinCommand`** only; **`-0`**, **`-n`** (and **`-nN`**), optional **`-P`** parallelism with effective cap **`min(requested, BARE_OS_XARGS_MAX_PROCS, 32)`** (default **8** when env unset; sequential when **`0`**); stdin, token, and invocation caps (see **`src/xargs.js`**).
- **`getconf` / live `sysconf`** — Fixed name table plus **`-a`**; values are Bare constants, not host kernel queries (see **`src/getconf.js`**). **`getconf NAME /path`** delegates to **`ctx.bareOsPathconf`** for **`_PC_*`** variables ( **`_PC_CHOWN_RESTRICTED`** is **`0`** under **`/mirror/`** read-only aux mounts).
- **Byte-identical `sed` / `awk` / `grep`** — Implementations are **JavaScript**; regex and edge cases differ from GNU or strict POSIX.
@@ -90,7 +90,7 @@ When HDMS or the booter attaches extra Hyperdrives on **`disk.auxiliaryDrives`**
### 2.5.1 Read-through cache (`/bin`, optional `/lib/bare`)
With **`BARE_OS_VFS_BIN_CACHE=1`**, the booter keeps a bounded LRU of system-drive **`readFile`** results for **`/bin/*`**. Add **`BARE_OS_VFS_LIB_BARE_CACHE=1`** to extend the same warm path to **`/lib/bare/*`** (bundle resolution hot paths). Hit, miss, and per-prefix counters appear under **`warmReadCache`** in **`/proc/bare_os/metrics_live.json`** (and the coalesced **`ctx.bareOsReadProcMetricsLive()`** view) when the cache is enabled.
With **`BARE_OS_VFS_BIN_CACHE=1`**, the booter keeps a bounded LRU of system-drive **`readFile`** results for **`/bin/*`**. Add **`BARE_OS_VFS_LIB_BARE_CACHE=1`** to extend the same warm path to **`/lib/bare/*`** (bundle resolution hot paths). Hit, miss, and per-prefix counters appear under **`warmReadCache`** in **`/proc/bare_os/metrics_live.json`** (and the coalesced **`ctx.bareOsReadProcMetricsLive()`** view) when the cache is enabled. After live replication or policy changes, call **`ctx.bareOsInvalidateWarmReadCaches(reason)`** (or rely on **`ctx.bareOsVfsBatchWrite`** when batch **`put`** paths touch **`bin/`** or **`lib/bare/`**) so operators do not read stale bytes from warm entries.
### 2.6 Copy and move
+2 -2
View File
@@ -139,10 +139,10 @@ const CONF = {
_PC_2_SYMLINKS: '1',
/** Comma-separated `ctx.bareOsSyscall` op names implemented in stock booter. */
BARE_OS_SYSCALL_OPS:
'readFile,writeFile,readdir,mkdir,stat,unlink,chmod,chdir,getcwd,readlink,symlink,exists,lstat,rmdir,mount,umount,kill,rename,link,access,utimes,truncate,ftruncate,fsync,fdatasync,pathconf',
'access,chdir,chmod,exists,fcntl,fdatasync,fsync,ftruncate,getcwd,kill,link,lstat,mkdir,mount,pathconf,posixPoll,readFile,readdir,readlink,rename,rmdir,stat,symlink,truncate,umount,unlink,utimes,writeFile',
/** POSIX.1 XSH-style names documented in `/proc/bare_os/syscalls.json` opsDetail (not separate ctx ops). */
BARE_OS_POSIX_XSH_OPS:
'open,close,read,write,lseek,pipe,dup,dup2,fcntl',
'open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll',
/** Encodings accepted by `/bin/iconv` (subset; case-insensitive names). */
BARE_OS_ICONV_ENCODINGS: 'UTF-8,ISO-8859-1,UTF-16LE,UTF-16BE',
/** Synthetic process table JSON path (logical VFS). */
+3 -3
View File
@@ -1,9 +1,9 @@
{
"schemaVersion": 4,
"ctxApiVersion": "1.34.0",
"ctxApiVersion": "1.36.0",
"posixProfile": {
"id": "bare-os-posix-like",
"version": "1.0.1"
"version": "1.0.3"
},
"ops": ["readFile", "writeFile"],
"opsDetail": [
@@ -21,7 +21,7 @@
"posixXsh": {
"schema": 1,
"note": "POSIX.1 XSH names in opsDetail with posixAlignment; not ctx.bareOsSyscall op strings.",
"namesCsv": "open,close,read,write,lseek,pipe,dup,dup2,fcntl"
"namesCsv": "open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll"
},
"errnoHints": {
"ENOENT": 2,
+74 -12
View File
@@ -44,8 +44,8 @@ async function invokeCtxBootHooks(ctx, ev) {
* BARE_OS_ONBOOT (newline-separated) or, if unset, every such line from /etc/bare-os/onboot
* in file order (trusted). Then readLine yields EOF.
*
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**.
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2). Guest **`os-release`**, **`motd`**, and **`banner`** lines also emit **`type":"bootOutput"`** previews on stderr (stdout body still goes through **`ctx.console.log`** only).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**, plus **`type":"bootOutput"`** for guest-visible text previews.
*
* BARE_OS_BOOT_MINIMAL=1 or true: skip profile rc, rc, rc.d, rc.local, kernel.d (recovery shell).
* BARE_OS_BOOT_SAFE_MODE=1 or true: after boot policy merge, skip rc.d, kernel.ext.d, onboot (lighter recovery).
@@ -123,6 +123,62 @@ function isBootTraceNdjson(ctx) {
return ctx.env && ctx.env.BARE_OS_BOOT_TRACE === 'ndjson'
}
/**
* Guest-visible boot text via **`ctx.console.log`** only (booter session / var-log mirror).
* When **`BARE_OS_BOOT_TRACE`** is **`json`** or **`ndjson`**, append one stderr JSON line per emission (**`type":"bootOutput"`**, preview-capped) for CI and hosts.
* @param {Record<string, unknown>} ctx
* @param {string} step
* @param {string} text
*/
function bootGuestOutLine(ctx, step, text) {
const line = String(text)
const out = ctx.console
if (out && typeof out.log === 'function') out.log(line)
else if (
globalThis.console &&
typeof globalThis.console.log === 'function'
) {
globalThis.console.log(line)
}
if (!wantBootTrace(ctx)) return
const prev =
isBootTraceNdjson(ctx) && line.length > 400
? line.slice(0, 400) + '\u2026'
: isBootTraceJson(ctx) && line.length > 200
? line.slice(0, 200) + '\u2026'
: line
const errFn =
out && typeof out.error === 'function'
? out.error.bind(out)
: globalThis.console && typeof globalThis.console.error === 'function'
? globalThis.console.error.bind(globalThis.console)
: null
if (!errFn) return
if (isBootTraceNdjson(ctx)) {
errFn(
JSON.stringify({
type: 'bootOutput',
bootTraceSchemaVersion: 2,
step,
stream: 'stdout',
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
ts: Date.now(),
textPreview: prev
})
)
} else if (isBootTraceJson(ctx)) {
errFn(
JSON.stringify({
bootTraceSchemaVersion: 2,
type: 'bootOutput',
step,
stream: 'stdout',
textPreview: prev
})
)
}
}
/**
* @param {Record<string, unknown>} ctx
*/
@@ -1792,12 +1848,14 @@ async function runRcLines(ctx, text) {
* @param {Record<string, unknown>} ctx
*/
async function printOsRelease(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const rel = await drive.get('/etc/os-release')
if (rel) console.log(b4a.toString(rel))
if (rel) bootGuestOutLine(ctx, 'os-release', b4a.toString(rel))
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -1805,12 +1863,14 @@ async function printOsRelease(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printMotd(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const motd = await drive.get('/etc/motd')
if (motd) console.log(b4a.toString(motd).trimEnd())
if (motd) bootGuestOutLine(ctx, 'motd', b4a.toString(motd).trimEnd())
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -2444,12 +2504,12 @@ async function runBareOsRcDir(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printSessionBanner(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
console.log(b4a.toString(buf).trimEnd())
bootGuestOutLine(ctx, 'banner', b4a.toString(buf).trimEnd())
return
}
} catch {
@@ -2459,12 +2519,14 @@ async function printSessionBanner(ctx) {
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock) | shell: cd, export, if/fi, && || ;, |, &, jobs/fg, <<<, exit | services: systemctl list-units, journalctl -u UNIT | try: help, ls /bin, crontab -l'
if (ctx.bareOsSkipRepl) {
console.log(
bootGuestOutLine(
ctx,
'banner',
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
console.log(defaultBanner)
bootGuestOutLine(ctx, 'banner', defaultBanner)
}
/**
+216 -216
View File
@@ -7,18 +7,18 @@
"b4a"
]
},
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{
"path": "/lib/bare/bundles/hypercoreIdEncoding.js",
"keys": [
"hypercoreIdEncoding"
]
},
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{
"path": "/lib/bare/bundles/compactEncoding.js",
"keys": [
@@ -38,9 +38,9 @@
]
},
{
"path": "/lib/bare/bundles/bareEncoding.js",
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"bareEncoding"
"barePath"
]
},
{
@@ -50,9 +50,9 @@
]
},
{
"path": "/lib/bare/bundles/barePath.js",
"path": "/lib/bare/bundles/bareEncoding.js",
"keys": [
"barePath"
"bareEncoding"
]
},
{
@@ -73,12 +73,6 @@
"bareAnsiEscapes"
]
},
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareReadline.js",
"keys": [
@@ -91,6 +85,18 @@
"bareCrypto"
]
},
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
@@ -103,30 +109,24 @@
"bareAppKit"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/bareApk.js",
"keys": [
"bareApk"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/bareBmp.js",
"keys": [
@@ -145,6 +145,12 @@
"bareBuffer"
]
},
{
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
"keys": [
"bareBundleEvaluate"
]
},
{
"path": "/lib/bare/bundles/bareBundle.js",
"keys": [
@@ -157,48 +163,42 @@
"bareBluetoothApple"
]
},
{
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
"keys": [
"bareBundleEvaluate"
]
},
{
"path": "/lib/bare/bundles/bareBoot.js",
"keys": [
"bareBoot"
]
},
{
"path": "/lib/bare/bundles/bareConsole.js",
"keys": [
"bareConsole"
]
},
{
"path": "/lib/bare/bundles/bareBundleId.js",
"keys": [
"bareBundleId"
]
},
{
"path": "/lib/bare/bundles/bareConsole.js",
"keys": [
"bareConsole"
]
},
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{
"path": "/lib/bare/bundles/bareDelta.js",
"keys": [
"bareDelta"
]
},
{
"path": "/lib/bare/bundles/bareChannel.js",
"keys": [
"bareChannel"
]
},
{
"path": "/lib/bare/bundles/bareDelta.js",
"keys": [
"bareDelta"
]
},
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
@@ -229,6 +229,12 @@
"bareExif"
]
},
{
"path": "/lib/bare/bundles/bareDgram.js",
"keys": [
"bareDgram"
]
},
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
@@ -241,12 +247,6 @@
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareDgram.js",
"keys": [
"bareDgram"
]
},
{
"path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [
@@ -254,9 +254,9 @@
]
},
{
"path": "/lib/bare/bundles/bareFormData.js",
"path": "/lib/bare/bundles/bareGif.js",
"keys": [
"bareFormData"
"bareGif"
]
},
{
@@ -266,9 +266,15 @@
]
},
{
"path": "/lib/bare/bundles/bareGif.js",
"path": "/lib/bare/bundles/bareFormData.js",
"keys": [
"bareGif"
"bareFormData"
]
},
{
"path": "/lib/bare/bundles/bareHrtime.js",
"keys": [
"bareHrtime"
]
},
{
@@ -289,12 +295,6 @@
"bareFileLogger"
]
},
{
"path": "/lib/bare/bundles/bareHrtime.js",
"keys": [
"bareHrtime"
]
},
{
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
@@ -319,18 +319,18 @@
"bareImageResample"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{
"path": "/lib/bare/bundles/bareHttp1.js",
"keys": [
"bareHttp1"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{
"path": "/lib/bare/bundles/bareHttps.js",
"keys": [
@@ -355,6 +355,12 @@
"bareIpc"
]
},
{
"path": "/lib/bare/bundles/bareInspector.js",
"keys": [
"bareInspector"
]
},
{
"path": "/lib/bare/bundles/bareLief.js",
"keys": [
@@ -374,9 +380,9 @@
]
},
{
"path": "/lib/bare/bundles/bareInspector.js",
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
"bareInspector"
"bareModule"
]
},
{
@@ -385,60 +391,30 @@
"bareMake"
]
},
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
"bareModule"
]
},
{
"path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [
"bareModuleTraverse"
]
},
{
"path": "/lib/bare/bundles/bareNdk.js",
"keys": [
"bareNdk"
]
},
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{
"path": "/lib/bare/bundles/bareNative.js",
"keys": [
"bareNative"
]
},
{
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
@@ -446,15 +422,33 @@
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [
"bareNet"
"bareModuleTraverse"
]
},
{
"path": "/lib/bare/bundles/barePackDrive.js",
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"barePackDrive"
"bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"keys": [
"bareNet"
]
},
{
@@ -470,9 +464,9 @@
]
},
{
"path": "/lib/bare/bundles/barePng.js",
"path": "/lib/bare/bundles/barePackDrive.js",
"keys": [
"barePng"
"barePackDrive"
]
},
{
@@ -481,12 +475,30 @@
"barePipe"
]
},
{
"path": "/lib/bare/bundles/barePng.js",
"keys": [
"barePng"
]
},
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{
"path": "/lib/bare/bundles/barePrebuild.js",
"keys": [
"barePrebuild"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{
"path": "/lib/bare/bundles/barePunycode.js",
"keys": [
@@ -494,9 +506,9 @@
]
},
{
"path": "/lib/bare/bundles/barePrebuild.js",
"path": "/lib/bare/bundles/bareProcess.js",
"keys": [
"barePrebuild"
"bareProcess"
]
},
{
@@ -517,108 +529,102 @@
"bareRealm"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{
"path": "/lib/bare/bundles/bareProcess.js",
"keys": [
"bareProcess"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
]
},
{
"path": "/lib/bare/bundles/bareRuntime.js",
"keys": [
"bareRuntime"
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareRepl"
]
},
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{
"path": "/lib/bare/bundles/bareSemver.js",
"keys": [
"bareSemver"
]
},
{
"path": "/lib/bare/bundles/bareRpc.js",
"keys": [
"bareRpc"
]
},
{
"path": "/lib/bare/bundles/bareSignals.js",
"keys": [
"bareSignals"
]
},
{
"path": "/lib/bare/bundles/bareRun.js",
"keys": [
"bareRun"
]
},
{
"path": "/lib/bare/bundles/bareSemver.js",
"keys": [
"bareSemver"
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareRepl"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
]
},
{
"path": "/lib/bare/bundles/bareSignals.js",
"keys": [
"bareSignals"
]
},
{
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareSidecar"
]
},
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareSvg.js",
"keys": [
"bareSvg"
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStdio"
]
},
{
"path": "/lib/bare/bundles/bareStringDecoder.js",
"keys": [
"bareStringDecoder"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{
"path": "/lib/bare/bundles/bareStructuredClone.js",
"keys": [
"bareStructuredClone"
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStdio"
]
},
{
"path": "/lib/bare/bundles/bareSvg.js",
"keys": [
"bareSvg"
]
},
{
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareTiff"
]
},
{
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
@@ -638,9 +644,9 @@
]
},
{
"path": "/lib/bare/bundles/bareTiff.js",
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
"bareTiff"
"bareTpl"
]
},
{
@@ -661,30 +667,12 @@
"bareTimers"
]
},
{
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
"bareTpl"
]
},
{
"path": "/lib/bare/bundles/bareType.js",
"keys": [
"bareType"
]
},
{
"path": "/lib/bare/bundles/bareUiKit.js",
"keys": [
"bareUiKit"
]
},
{
"path": "/lib/bare/bundles/bareUnpack.js",
"keys": [
"bareUnpack"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
@@ -697,6 +685,12 @@
"bareTty"
]
},
{
"path": "/lib/bare/bundles/bareUnpack.js",
"keys": [
"bareUnpack"
]
},
{
"path": "/lib/bare/bundles/bareV8.js",
"keys": [
@@ -704,9 +698,9 @@
]
},
{
"path": "/lib/bare/bundles/bareUnionBundle.js",
"path": "/lib/bare/bundles/bareUiKit.js",
"keys": [
"bareUnionBundle"
"bareUiKit"
]
},
{
@@ -715,6 +709,12 @@
"bareWalkHandles"
]
},
{
"path": "/lib/bare/bundles/bareUnionBundle.js",
"keys": [
"bareUnionBundle"
]
},
{
"path": "/lib/bare/bundles/bareVm.js",
"keys": [
@@ -727,30 +727,30 @@
"bareWebKit"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
"bareWebp"
]
},
{
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareWebKitGtk"
]
},
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
"bareWebp"
]
},
{
"path": "/lib/bare/bundles/bareWhich.js",
"keys": [
"bareWhich"
]
},
{
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareWebKitGtk"
]
},
{
"path": "/lib/bare/bundles/bareWinUi.js",
"keys": [
@@ -769,18 +769,18 @@
"bareV8ToIstanbul"
]
},
{
"path": "/lib/bare/bundles/bareZlib.js",
"keys": [
"bareZlib"
]
},
{
"path": "/lib/bare/bundles/bareWs.js",
"keys": [
"bareWs"
]
},
{
"path": "/lib/bare/bundles/bareZlib.js",
"keys": [
"bareZlib"
]
},
{
"path": "/lib/bare/bundles/bareZmq.js",
"keys": [
@@ -1595,8 +1595,8 @@
],
"bundleProvenance": {
"schemaVersion": 1,
"generatedAt": "2026-04-05T02:38:13.070Z",
"gitCommit": "8a6c1e7b0947b1a70fc837892c76d67222e44119",
"generatedAt": "2026-04-05T03:03:09.352Z",
"gitCommit": "985eadfda39fa8a847791ff5c7f8f40d4434d9ac",
"nodeVersion": "v22.22.0",
"bundleTier": "all",
"normativeManifest": "packages/bare-os-booter/lib/bare-module-manifest.json",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1775356692404,
"atMs": 1775358188597,
"commands": [
"arch",
"awk",
+74 -12
View File
@@ -14,8 +14,8 @@
* BARE_OS_ONBOOT (newline-separated) or, if unset, every such line from /etc/bare-os/onboot
* in file order (trusted). Then readLine yields EOF.
*
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**.
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2). Guest **`os-release`**, **`motd`**, and **`banner`** lines also emit **`type":"bootOutput"`** previews on stderr (stdout body still goes through **`ctx.console.log`** only).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**, plus **`type":"bootOutput"`** for guest-visible text previews.
*
* BARE_OS_BOOT_MINIMAL=1 or true: skip profile rc, rc, rc.d, rc.local, kernel.d (recovery shell).
* BARE_OS_BOOT_SAFE_MODE=1 or true: after boot policy merge, skip rc.d, kernel.ext.d, onboot (lighter recovery).
@@ -93,6 +93,62 @@ function isBootTraceNdjson(ctx) {
return ctx.env && ctx.env.BARE_OS_BOOT_TRACE === 'ndjson'
}
/**
* Guest-visible boot text via **`ctx.console.log`** only (booter session / var-log mirror).
* When **`BARE_OS_BOOT_TRACE`** is **`json`** or **`ndjson`**, append one stderr JSON line per emission (**`type":"bootOutput"`**, preview-capped) for CI and hosts.
* @param {Record<string, unknown>} ctx
* @param {string} step
* @param {string} text
*/
function bootGuestOutLine(ctx, step, text) {
const line = String(text)
const out = ctx.console
if (out && typeof out.log === 'function') out.log(line)
else if (
globalThis.console &&
typeof globalThis.console.log === 'function'
) {
globalThis.console.log(line)
}
if (!wantBootTrace(ctx)) return
const prev =
isBootTraceNdjson(ctx) && line.length > 400
? line.slice(0, 400) + '\u2026'
: isBootTraceJson(ctx) && line.length > 200
? line.slice(0, 200) + '\u2026'
: line
const errFn =
out && typeof out.error === 'function'
? out.error.bind(out)
: globalThis.console && typeof globalThis.console.error === 'function'
? globalThis.console.error.bind(globalThis.console)
: null
if (!errFn) return
if (isBootTraceNdjson(ctx)) {
errFn(
JSON.stringify({
type: 'bootOutput',
bootTraceSchemaVersion: 2,
step,
stream: 'stdout',
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
ts: Date.now(),
textPreview: prev
})
)
} else if (isBootTraceJson(ctx)) {
errFn(
JSON.stringify({
bootTraceSchemaVersion: 2,
type: 'bootOutput',
step,
stream: 'stdout',
textPreview: prev
})
)
}
}
/**
* @param {Record<string, unknown>} ctx
*/
@@ -1762,12 +1818,14 @@ async function runRcLines(ctx, text) {
* @param {Record<string, unknown>} ctx
*/
async function printOsRelease(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const rel = await drive.get('/etc/os-release')
if (rel) console.log(b4a.toString(rel))
if (rel) bootGuestOutLine(ctx, 'os-release', b4a.toString(rel))
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -1775,12 +1833,14 @@ async function printOsRelease(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printMotd(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const motd = await drive.get('/etc/motd')
if (motd) console.log(b4a.toString(motd).trimEnd())
if (motd) bootGuestOutLine(ctx, 'motd', b4a.toString(motd).trimEnd())
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -2414,12 +2474,12 @@ async function runBareOsRcDir(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printSessionBanner(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
console.log(b4a.toString(buf).trimEnd())
bootGuestOutLine(ctx, 'banner', b4a.toString(buf).trimEnd())
return
}
} catch {
@@ -2429,12 +2489,14 @@ async function printSessionBanner(ctx) {
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock) | shell: cd, export, if/fi, && || ;, |, &, jobs/fg, <<<, exit | services: systemctl list-units, journalctl -u UNIT | try: help, ls /bin, crontab -l'
if (ctx.bareOsSkipRepl) {
console.log(
bootGuestOutLine(
ctx,
'banner',
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
console.log(defaultBanner)
bootGuestOutLine(ctx, 'banner', defaultBanner)
}
/**
File diff suppressed because one or more lines are too long
+2
View File
@@ -15,6 +15,8 @@ Authoritative **version alignment** with protocol and telemetry schema numbers l
| Version | Booter (workspace) | Notes |
| ------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1.36.0 | 0.1.0 | **`ctx.bareOsSendSignal`** negative target = synthetic **process group** (shell job **`pgid`**); **`ctx.bareOsReadReplicationOperatorJson`**, **`ctx.bareOsInvalidateWarmReadCaches`**, **`vfs.bareOsClearWarmReadCaches`**; **`bareOsVfsBatchWrite`** clears warm **`/bin`** / **`/lib/bare`** caches on matching puts; **`metrics_live.ipcTelemetry`** (FIFO quota / counters via **`BARE_OS_IPC_MAX_CHANNELS`**); swarm **`shouldAttemptPeer`** honors **`BARE_OS_PEER_ALLOWLIST_HEX`**; **`bareOsPathconf`** union/mirror **`_PC_NO_TRUNC`** / **`_PC_CHOWN_RESTRICTED`**; boot-ready **`subsystems.bareStdlib.resolutionWallMs`**. |
| 1.35.0 | 0.1.0 | **`ctx.bareOsSyscall('fcntl', …)`** (**`F_GETFL`** / **`F_SETFL`**, **`O_APPEND`** **`0x400`**, **`O_NONBLOCK`** **`0x800`**) + **`bareOsLogicalFdFlags`**; **`ctx.bareOsPosixPoll`** / **`bareOsPosixPollProbe`** + **`bareOsSyscall('posixPoll', …)`**; **`bareOsPosixFdSimRead`** honors **`O_NONBLOCK`** (**`EAGAIN`** when empty); **`/proc/bare_os/syscalls.json`** **`posixXsh.namesCsv`** includes **`poll`**; **`getconf`** **`BARE_OS_SYSCALL_OPS`** / **`BARE_OS_POSIX_XSH_OPS`** updated; protocol **`BARE_OS_POSIX_PROFILE_VERSION` `1.0.2`**. |
| 1.34.0 | 0.1.0 | **`ctx.bareOsPosixFdSimPipe`** / **`bareOsPosixFdSimRead`** / **`Write`** / **`Dup`** when **`BARE_OS_POSIX_FD_SIM`**; **`ctx.bareOsSigaction`** (**`IGNORE`** / **`DEFAULT`**) + **`process_table.sigactionSurface`**; expanded POSIX signal names; **`ctx.bareOsWasmKernelInstantiate`** (bounded **`Memory`** + timeout); **`/proc/bare_os/syscalls.json`** **`posixXsh`** + XSH **`opsDetail`** rows; **`bareOsPathconf`** **`_PC_*`** coverage; protocol **`BARE_OS_POSIX_PROFILE_VERSION` `1.0.1`**. |
| 1.33.0 | 0.1.0 | **`ctx.bareOsListMirrorMounts`**, **`bareOsVaultAuditSummary`**, **`bareOsWasmKernelCompile`** ( **`BARE_OS_WASM_KERNEL`** ); **`/proc/bare_os/syscalls.json`** documents **`fdModel.posixLike`** mapping; **`process_table`** **`exitStatusModel`**; pipeline exit = last stage ( **`BARE_OS_SHELL_PIPEFAIL`** for bash-like early stop); script errors stop pipeline; **`corestore_snapshot`** operator proc id; Pear inspect + **`bareOsDiagnosticsEmit`**; IPC **`stats.ipcBackpressure`**. |
| 1.32.1 | 0.1.0 | **`/proc/bare_os/process_table.json`** schema **4**: **`signalRouting`**, **`initdBinding`**, initd units as logical pids **5100+**; **`bareOsSendSignal`** accepts **41008999** (shell jobs / initd) without session exit. |
+381 -28
View File
@@ -806,8 +806,17 @@ async function executeKernel(disk, store, swarm, initSource) {
syncWindowProcState.active ? '1' : '0'
const ipcPerChannelMax = parseIpcChannelMaxBytesMap(shellEnv)
const ipcMaxChannelsRaw = Number.parseInt(
String(shellEnv.BARE_OS_IPC_MAX_CHANNELS || '').trim(),
10
)
const ipcMaxChannelsResolved =
Number.isFinite(ipcMaxChannelsRaw) && ipcMaxChannelsRaw > 0
? Math.min(65536, ipcMaxChannelsRaw)
: undefined
const bareOsIpc = createBareOsIpc({
maxFifoBytes: ipcMaxResolved,
maxChannels: ipcMaxChannelsResolved,
ipcRpcToken: ipcRpcTok,
enableFanout: ipcFanoutOn,
maxJsonRpcLineBytes: ipcJsonMaxResolved,
@@ -2152,7 +2161,11 @@ async function executeKernel(disk, store, swarm, initSource) {
ndjsonSchema: 'docs/schemas/telemetry-ndjson-record.schema.json',
otelSchema: 'docs/schemas/otel-bare-os-jsonl.schema.json'
},
warmReadCache: warmReadCacheStatsRef.current
warmReadCache: warmReadCacheStatsRef.current,
ipcTelemetry:
bareOsIpc && typeof bareOsIpc.stats === 'function'
? bareOsIpc.stats()
: null
})}\n`
}
return metricsLiveCache.text
@@ -2251,6 +2264,7 @@ async function executeKernel(disk, store, swarm, initSource) {
/** @type {Record<string, unknown>} */
const bareLibrary = {}
const bareCtxResolutionT0 = Date.now()
if (bareOsBareModulesEnabled(shellEnv)) {
await maybeMergeBareFromDrive(shellEnv, vfs, bareLibrary)
if (bareOsBareHostImportsEnabled(shellEnv)) {
@@ -2260,6 +2274,21 @@ async function executeKernel(disk, store, swarm, initSource) {
if (lockWarn.length) bootReadyStateRef.bareModuleLockWarnings = lockWarn
primeGlobalFetchFromBareLibrary(bareLibrary)
}
Object.assign(bootReadyStateRef.subsystems, {
bareStdlib: bareOsBareModulesEnabled(shellEnv)
? {
ready: true,
atMs: Date.now(),
resolutionWallMs: Date.now() - bareCtxResolutionT0,
exportCount: Object.keys(bareLibrary).length
}
: {
ready: false,
atMs: Date.now(),
resolutionWallMs: 0,
exportCount: 0
}
})
const hdmsController = new HdmsController()
disk.hdmsController = hdmsController
@@ -2373,6 +2402,46 @@ async function executeKernel(disk, store, swarm, initSource) {
'PROF'
])
/**
* @param {Record<string, unknown>} ctx
* @param {number} pid
* @param {string} sig normalized signal short name (e.g. TERM)
*/
function deliverBareOsVirtualSignalToPid(ctx, pid, sig) {
const extendedSynthetic =
Number.isFinite(pid) && pid >= 4100 && pid < 9000
if (sig === '0') {
return { ok: true, delivered: false, exists: true }
}
if (
ctx.bareOsLogicalSigaction &&
ctx.bareOsLogicalSigaction[sig] === 'IGNORE'
) {
return {
ok: true,
delivered: false,
ignored: true,
atMs: Date.now()
}
}
const atMs = Date.now()
bareOsVirtualSignalState.set(pid, { signal: sig, atMs })
if (typeof globalThis.process?.emit === 'function') {
try {
globalThis.process.emit('bare-os:signal', { pid, signal: sig, atMs })
} catch {
/* ignore */
}
}
const noSessionExit =
extendedSynthetic || !BARE_OS_SESSION_EXIT_SIGNALS.has(sig)
if (!noSessionExit) {
const signum = BARE_OS_SIGNAL_EXIT[sig] || 15
ctx.requestBooterExit(128 + signum)
}
return { ok: true, delivered: true, atMs }
}
/**
* Built-in hrpc bridge with strict validation and optional host event emission.
* Hosts can still replace `ctx.bareOsHrpcRequest`, but the stock handler is functional.
@@ -2487,6 +2556,8 @@ async function executeKernel(disk, store, swarm, initSource) {
bareOsBootStartedMs: bootStartedMs,
/** Logical open-file targets for `/proc/self/fd/*` beyond stdio (see `bareOsRegisterLogicalFd`). */
bareOsLogicalFds: /** @type {Record<string, string>} */ ({}),
/** Per-FD **`fcntl` F_SETFL** bits (**`O_APPEND`**, **`O_NONBLOCK`**) ORed with access mode from target; keys are decimal fd strings. */
bareOsLogicalFdFlags: /** @type {Record<string, number>} */ ({}),
/** In-memory POSIX pipe simulation (`BARE_OS_POSIX_FD_SIM`); see `bareOsPosixFdSimPipe`. */
bareOsPosixFdSimState: {
nextFd: 10,
@@ -2962,7 +3033,7 @@ async function executeKernel(disk, store, swarm, initSource) {
},
/**
* POSIX-like signal dispatch over synthetic process IDs.
* Targets: numeric pid (**13** session stack, **4100+** shell jobs, **5100+** initd logical rows) or `kernel` / `booter` / `shell`.
* Targets: numeric pid (**13** session stack, **4100+** shell jobs, **5100+** initd logical rows), **negative process group** (e.g. `-300` all running shell jobs with `pgid` 300), or `kernel` / `booter` / `shell`.
* Signals: HUP, INT, KILL, TERM, PIPE, CHLD, USR1, USR2; `PIPE`/`CHLD`/`USR*` update state only (no session exit).
*/
bareOsSendSignal(target, signal = 'TERM') {
@@ -2972,11 +3043,80 @@ async function executeKernel(disk, store, swarm, initSource) {
}
const raw = String(target == null ? '' : target).trim()
let pid = Number.parseInt(raw, 10)
let pgidTarget = null
if (Number.isFinite(pid) && pid < 0) {
pgidTarget = -pid
pid = NaN
}
if (!Number.isFinite(pid)) {
if (pgidTarget == null) {
if (raw === 'kernel') pid = 1
else if (raw === 'booter') pid = 2
else if (raw === 'shell') pid = 3
}
}
if (pgidTarget != null) {
const live = bareOsInteractiveCtxRef.ctx
const list =
live &&
live.shellBackgroundJobs &&
typeof live.shellBackgroundJobs === 'object' &&
Array.isArray(live.shellBackgroundJobs.list)
? live.shellBackgroundJobs.list
: []
const byPgid = list.filter(
(j) =>
j &&
typeof j.pgid === 'number' &&
Math.floor(j.pgid) === Math.floor(pgidTarget)
)
const active = byPgid.filter((j) => !j.done)
if (sig === '0') {
if (byPgid.length === 0) {
throw new Error('bareOsSendSignal: unknown process group')
}
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: false,
exists: true,
jobCount: byPgid.length
}
}
if (active.length === 0) {
throw new Error('bareOsSendSignal: unknown process group')
}
if (
this.bareOsLogicalSigaction &&
this.bareOsLogicalSigaction[sig] === 'IGNORE'
) {
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: false,
ignored: true,
atMs: Date.now()
}
}
const atMs = Date.now()
/** @type {number[]} */
const pids = []
for (const j of active) {
const jp = 4100 + j.id
pids.push(jp)
deliverBareOsVirtualSignalToPid(this, jp, sig)
}
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: true,
atMs,
pids
}
}
const extendedSynthetic = Number.isFinite(pid) && pid >= 4100 && pid < 9000
if (
!Number.isFinite(pid) ||
@@ -2988,37 +3128,24 @@ async function executeKernel(disk, store, swarm, initSource) {
if (sig === '0') {
return { ok: true, pid, signal: sig, delivered: false, exists: true }
}
if (
this.bareOsLogicalSigaction &&
this.bareOsLogicalSigaction[sig] === 'IGNORE'
) {
const r = deliverBareOsVirtualSignalToPid(this, pid, sig)
if (r.ignored) {
return {
ok: true,
pid,
signal: sig,
delivered: false,
ignored: true,
atMs: Date.now()
atMs: r.atMs
}
}
const atMs = Date.now()
bareOsVirtualSignalState.set(pid, { signal: sig, atMs })
if (typeof globalThis.process?.emit === 'function') {
try {
globalThis.process.emit('bare-os:signal', { pid, signal: sig, atMs })
} catch {
/* ignore */
return {
ok: true,
pid,
signal: sig,
delivered: !!r.delivered,
atMs: r.atMs
}
}
const noSessionExit =
extendedSynthetic ||
sig === '0' ||
!BARE_OS_SESSION_EXIT_SIGNALS.has(sig)
if (!noSessionExit) {
const signum = BARE_OS_SIGNAL_EXIT[sig] || 15
this.requestBooterExit(128 + signum)
}
return { ok: true, pid, signal: sig, delivered: true, atMs }
},
/**
* Try to spawn a host child via **`bare-subprocess`** when the addon is available (Bare hosts).
@@ -3068,6 +3195,9 @@ async function executeKernel(disk, store, swarm, initSource) {
if (this.bareOsLogicalFds && typeof this.bareOsLogicalFds === 'object') {
delete this.bareOsLogicalFds[k]
}
if (this.bareOsLogicalFdFlags && typeof this.bareOsLogicalFdFlags === 'object') {
delete this.bareOsLogicalFdFlags[k]
}
},
bareOsPosixFdSimCapBytes() {
const raw = String(shellEnv.BARE_OS_POSIX_FD_SIM_MAX_BYTES || '').trim()
@@ -3177,7 +3307,12 @@ async function executeKernel(disk, store, swarm, initSource) {
const pairId = m[1]
const st = this.bareOsPosixFdSimState
const q = st.queues[pairId]
if (!q || q.length === 0) return { ok: true, data: '', eof: false }
if (!q || q.length === 0) {
const nonblock =
(this.bareOsLogicalFdFlags[k] & 0x800) === 0x800
if (nonblock) return { ok: false, reason: 'EAGAIN' }
return { ok: true, data: '', eof: false }
}
let chunk = q.shift()
if (!chunk) return { ok: true, data: '', eof: false }
if (chunk.length > cap) {
@@ -3189,6 +3324,83 @@ async function executeKernel(disk, store, swarm, initSource) {
st.byteTotals[pairId] = Math.max(0, (st.byteTotals[pairId] || 0) - used)
return { ok: true, data: chunk, eof: false }
},
/**
* Non-blocking readiness probe for logical FDs (simulated pipes + stdout/stderr write).
* @param {{ fds?: Array<{ fd: number, events?: string }>, timeoutMs?: number }} [spec]
* @returns {{ ok: true, ready: Array<{ fd: number, revents: string }>, waitedMs: number, timedOut?: boolean }}
*/
bareOsPosixPollProbe(spec = {}) {
const fds = Array.isArray(spec.fds) ? spec.fds : []
/** @type {Array<{ fd: number, revents: string }>} */
const ready = []
for (const entry of fds.slice(0, 64)) {
const fd = Number(entry && entry.fd)
if (!Number.isFinite(fd) || fd < 0) continue
const ev = String(entry.events || 'rw').toLowerCase()
const wantR = ev.includes('r')
const wantW = ev.includes('w')
const k = String(fd >>> 0)
const t = this.bareOsLogicalFds[k]
const pipeR = /^posix-pipe:(\d+):r$/.exec(String(t || ''))
const pipeW = /^posix-pipe:(\d+):w$/.exec(String(t || ''))
if (wantR && pipeR) {
const st = this.bareOsPosixFdSimState
const q = st.queues[pipeR[1]]
if (q && q.length > 0) ready.push({ fd, revents: 'r' })
}
if (wantW && pipeW) {
const st = this.bareOsPosixFdSimState
const pairId = pipeW[1]
const prev = st.byteTotals[pairId] || 0
const cap = this.bareOsPosixFdSimCapBytes()
if (prev < cap) ready.push({ fd, revents: 'w' })
}
if (wantW && (k === '1' || k === '2')) {
ready.push({ fd, revents: 'w' })
}
}
return { ok: true, ready, waitedMs: 0 }
},
/**
* Poll logical FDs with bounded wait (simulated **`poll(2)`** subset for **`BARE_OS_POSIX_FD_SIM`** pipes).
* @param {{ fds?: Array<{ fd: number, events?: string }>, timeoutMs?: number }} [spec]
*/
async bareOsPosixPoll(spec = {}) {
const maxWait = Math.min(
60000,
Math.max(0, Math.floor(Number(spec && spec.timeoutMs) || 0))
)
if (maxWait <= 0) {
const once = this.bareOsPosixPollProbe(spec)
return {
ok: true,
ready: once.ready,
waitedMs: 0,
timedOut: false
}
}
const t0 = Date.now()
const interval = 10
while (Date.now() - t0 <= maxWait) {
const once = this.bareOsPosixPollProbe(spec)
if (once.ready.length) {
return {
ok: true,
ready: once.ready,
waitedMs: Date.now() - t0,
timedOut: false
}
}
if (Date.now() - t0 >= maxWait) break
await new Promise((res) => setTimeout(res, interval))
}
return {
ok: true,
ready: [],
waitedMs: Date.now() - t0,
timedOut: true
}
},
/** @type {Record<string, unknown>[]} */
bareOsSnapshotHandles: [],
/** @param {Record<string, unknown>} desc */
@@ -3210,7 +3422,14 @@ async function executeKernel(disk, store, swarm, initSource) {
*/
bareOsPathconf(pathArg, name) {
const k = String(name || '').trim()
const p = String(pathArg || '/').trim() || '/'
const p = vfs.resolveLogical(String(pathArg || '/').trim() || '/')
const rawUnion = String(shellEnv.BARE_OS_VFS_UNION_PREFIXES || '')
const underUnion = rawUnion
.split(',')
.map((s) => s.trim())
.filter((x) => x.startsWith('/'))
.some((pre) => p === pre || p.startsWith(pre + '/'))
const underMirror = p === '/mirror' || p.startsWith('/mirror/')
/** @type {Record<string, number>} */
const table = {
PATH_MAX: 4096,
@@ -3229,7 +3448,11 @@ async function executeKernel(disk, store, swarm, initSource) {
_POSIX_VERSION: 200809,
_POSIX_THREAD_ATTR_STACKSIZE: 65536
}
if (k === '_PC_CHOWN_RESTRICTED' && p.startsWith('/mirror/')) {
if (k === '_PC_CHOWN_RESTRICTED') {
if (underMirror) return 0
return 1
}
if (k === '_PC_NO_TRUNC' && (underMirror || underUnion)) {
return 0
}
if (!k || table[k] === undefined) {
@@ -3500,6 +3723,95 @@ async function executeKernel(disk, store, swarm, initSource) {
note: 'Best-effort no-op unless the VFS exposes a flush primitive.'
}
}
if (name === 'fcntl') {
const O_ACCMODE = 3
const O_APPEND = 0x400
const O_NONBLOCK = 0x800
const SETFL_MASK = O_APPEND | O_NONBLOCK
const fd = Number(args.fd)
if (!Number.isFinite(fd) || fd < 0 || fd > 65535) {
throw new Error('bareOsSyscall: fcntl: invalid fd')
}
const k = String(fd >>> 0)
const target = this.bareOsLogicalFds[k]
const isStd = k === '0' || k === '1' || k === '2'
if (!isStd && (target == null || target === '')) {
const err = /** @type {Error & { code?: string }} */ (
new Error('EBADF: fcntl')
)
err.code = 'EBADF'
throw err
}
const baseAcc = () => {
if (k === '0') return 0
if (k === '1' || k === '2') return 2
const s = String(target || '')
if (/posix-pipe:\d+:r$/.test(s)) return 0
if (/posix-pipe:\d+:w$/.test(s)) return 1
return 2
}
let cmd = args.cmd
if (typeof cmd === 'string') {
const u = String(cmd).toUpperCase()
if (u === 'F_GETFL' || u === 'GETFL') cmd = 3
else if (u === 'F_SETFL' || u === 'SETFL') cmd = 4
else {
const err = /** @type {Error & { code?: string }} */ (
new Error('ENOTSUP: fcntl cmd')
)
err.code = 'ENOTSUP'
throw err
}
}
cmd = Number(cmd)
if (cmd === 3) {
const acc = baseAcc() & O_ACCMODE
const extra = (this.bareOsLogicalFdFlags[k] || 0) & SETFL_MASK
return {
ok: true,
op: 'fcntl',
cmd: 'F_GETFL',
fd,
flags: acc | extra
}
}
if (cmd === 4) {
if (!this.bareOsLogicalFdFlags || typeof this.bareOsLogicalFdFlags !== 'object') {
this.bareOsLogicalFdFlags = {}
}
const arg = Number(args.arg ?? args.value ?? 0) >>> 0
this.bareOsLogicalFdFlags[k] = arg & SETFL_MASK
const acc = baseAcc() & O_ACCMODE
const extra = this.bareOsLogicalFdFlags[k] & SETFL_MASK
return {
ok: true,
op: 'fcntl',
cmd: 'F_SETFL',
fd,
flags: acc | extra
}
}
const err2 = /** @type {Error & { code?: string }} */ (
new Error('ENOTSUP: fcntl cmd')
)
err2.code = 'ENOTSUP'
throw err2
}
if (name === 'posixPoll') {
const timeoutMs = Math.max(
0,
Math.min(60000, Math.floor(Number(args.timeoutMs) || 0))
)
if (timeoutMs > 0) {
return this.bareOsPosixPoll({
fds: Array.isArray(args.fds) ? args.fds : [],
timeoutMs
})
}
return this.bareOsPosixPollProbe({
fds: Array.isArray(args.fds) ? args.fds : []
})
}
if (name === 'kill') {
return this.bareOsSendSignal(args.target, String(args.signal || 'TERM'))
}
@@ -3520,7 +3832,21 @@ async function executeKernel(disk, store, swarm, initSource) {
* @param {{ path: string, buf: Uint8Array | ArrayBuffer, opts?: Record<string, unknown> }[]} puts
*/
async bareOsVfsBatchWrite(puts) {
return bareOsVfsBatchPut(disk.drive, puts)
await bareOsVfsBatchPut(disk.drive, puts)
if (vfs?.bareOsClearWarmReadCaches && Array.isArray(puts)) {
for (const row of puts) {
const p = String(row?.path || '').replace(/^\/+/, '')
if (
p === 'bin' ||
p.startsWith('bin/') ||
p === 'lib/bare' ||
p.startsWith('lib/bare/')
) {
vfs.bareOsClearWarmReadCaches()
break
}
}
}
},
/**
* Hyperdrive `diff` collector (API varies by stack version; see handbook).
@@ -3662,6 +3988,33 @@ async function executeKernel(disk, store, swarm, initSource) {
return {}
}
},
/**
* Parse `/proc/bare_os/replication` (same JSON as the VFS pseudo file).
* @returns {Promise<Record<string, unknown> | null>}
*/
async bareOsReadReplicationOperatorJson() {
if (!vfs || typeof vfs.readFile !== 'function') return null
try {
const b = await vfs.readFile('/proc/bare_os/replication')
if (!b) return null
const t = b4a.toString(b, 'utf8').trim()
if (!t) return null
return /** @type {Record<string, unknown>} */ (JSON.parse(t))
} catch {
return null
}
},
/**
* Invalidate `/bin` and `/lib/bare` warm read caches after replication or policy changes.
* @param {string} [reason]
*/
bareOsInvalidateWarmReadCaches(reason) {
if (vfs && typeof vfs.bareOsClearWarmReadCaches === 'function') {
vfs.bareOsClearWarmReadCaches()
}
bareOsKernelMetricInc('vfs.warm_read_cache_invalidate_explicit')
return { ok: true, reason: String(reason || '').slice(0, 256) }
},
/**
* Batch-read `/proc/bare_os/*` mirrors for baretop(1). Keys must stay aligned with
* `BARE_TOP_SNAPSHOT_PROC_ENTRIES` in packages/bare-os-coreutils/lib/baretop-snapshot.js.
@@ -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.34.0'
export const BARE_OS_CTX_API_VERSION = '1.36.0'
+23
View File
@@ -130,6 +130,10 @@ export interface BareOsKernelContext {
bareOsIpc?: BareOsIpc
bareOsGetResourceStatus(): BareOsResourceStatus
bareOsReadProcMetricsLive(): Record<string, unknown>
bareOsReadReplicationOperatorJson?(): Promise<Record<string, unknown> | null>
bareOsInvalidateWarmReadCaches?(
reason?: string
): { ok: boolean; reason: string }
/**
* Optional batch `/proc/bare_os/*` read for baretop(1); keys match baretop-snapshot preamble.
*/
@@ -342,6 +346,8 @@ export interface BareOsKernelContext {
}
/** FD → logical VFS/IPC target; see `/proc/bare_os/syscalls.json` fdModel. */
bareOsLogicalFds?: Record<string, string>
/** `fcntl` F_SETFL bits (O_APPEND | O_NONBLOCK) per decimal fd key. */
bareOsLogicalFdFlags?: Record<string, number>
/** Shell `trap` COMMAND → SIGNAL map (logical; delivery is synthetic). */
shellTrapHandlers?: Record<string, string>
bareOsLogicalSigaction?: Record<string, string>
@@ -373,6 +379,23 @@ export interface BareOsKernelContext {
readFd: number,
maxBytes?: number
): { ok: boolean; data?: string; eof?: boolean; reason?: string }
bareOsPosixPollProbe?(spec?: {
fds?: Array<{ fd: number; events?: string }>
timeoutMs?: number
}): {
ok: true
ready: Array<{ fd: number; revents: string }>
waitedMs: number
}
bareOsPosixPoll?(spec?: {
fds?: Array<{ fd: number; events?: string }>
timeoutMs?: number
}): Promise<{
ok: true
ready: Array<{ fd: number; revents: string }>
waitedMs: number
timedOut?: boolean
}>
bareOsSigaction?(
signal: string,
mode: 'IGNORE' | 'DEFAULT' | 'IGN' | 'DEF'
+44 -3
View File
@@ -136,7 +136,7 @@ function assertSafeIpcName(name) {
const DEFAULT_JSON_RPC_MAX = 256 * 1024
/**
* @param {{ maxFifoBytes?: number, perChannelMaxBytes?: Map<string, number>, ipcRpcToken?: string | null, enableFanout?: boolean, maxJsonRpcLineBytes?: number }} [opts]
* @param {{ maxFifoBytes?: number, maxChannels?: number, perChannelMaxBytes?: Map<string, number>, ipcRpcToken?: string | null, enableFanout?: boolean, maxJsonRpcLineBytes?: number }} [opts]
*/
export function createBareOsIpc(opts = {}) {
const maxFifoBytes =
@@ -144,9 +144,22 @@ export function createBareOsIpc(opts = {}) {
? Math.min(opts.maxFifoBytes, 16 * 1024 * 1024)
: 1024 * 1024
const maxChannels =
typeof opts.maxChannels === 'number' && opts.maxChannels > 0
? Math.min(65536, Math.floor(opts.maxChannels))
: 4096
const perChannelMax =
opts.perChannelMaxBytes instanceof Map ? opts.perChannelMaxBytes : null
/** @type {{ fifoCreates: number, fifoCreateDeniedQuota: number, fifoPushDenied: number, mqFull: number }} */
const telemetry = {
fifoCreates: 0,
fifoCreateDeniedQuota: 0,
fifoPushDenied: 0,
mqFull: 0
}
const limitFor = (name) => {
if (perChannelMax && perChannelMax.has(name)) {
const n = perChannelMax.get(name)
@@ -185,7 +198,14 @@ export function createBareOsIpc(opts = {}) {
*/
create(name, opts) {
assertSafeIpcName(name)
if (!channels.has(name)) channels.set(name, new FifoChannel())
if (!channels.has(name)) {
if (channels.size >= maxChannels) {
telemetry.fifoCreateDeniedQuota++
throw new Error('bare-os ipc: channel quota exceeded')
}
channels.set(name, new FifoChannel())
telemetry.fifoCreates++
}
if (opts && opts.scope != null && String(opts.scope).trim()) {
channelScopes.set(name, String(opts.scope).trim().slice(0, 64))
}
@@ -221,7 +241,14 @@ export function createBareOsIpc(opts = {}) {
if (!Number.isFinite(n) || n < 0 || n > 0xffffffff) {
throw new Error('invalid pgid')
}
if (!channels.has(channelName)) channels.set(channelName, new FifoChannel())
if (!channels.has(channelName)) {
if (channels.size >= maxChannels) {
telemetry.fifoCreateDeniedQuota++
throw new Error('bare-os ipc: channel quota exceeded')
}
channels.set(channelName, new FifoChannel())
telemetry.fifoCreates++
}
channelScopes.set(channelName, 'pgid:' + String(Math.floor(n)))
},
@@ -260,7 +287,18 @@ export function createBareOsIpc(opts = {}) {
push(name, buf) {
const ch = channels.get(name)
if (!ch) throw new Error('no such fifo: ' + name)
try {
ch.push(buf, limitFor(name))
} catch (e) {
if (
e &&
typeof e === 'object' &&
String(/** @type {Error} */ (e).message || '').includes('backlog')
) {
telemetry.fifoPushDenied++
}
throw e
}
},
/**
@@ -412,6 +450,7 @@ export function createBareOsIpc(opts = {}) {
throw new Error('bare-os ipc mq: message exceeds maxBytes')
}
if (q.msgs.length >= q.maxmsg) {
telemetry.mqFull++
throw new Error('bare-os ipc mq: queue full (maxmsg)')
}
if (q.curBytes + u8.byteLength > q.maxmsg * q.maxBytes) {
@@ -467,6 +506,8 @@ export function createBareOsIpc(opts = {}) {
}
return {
channelCount: channels.size,
maxChannels,
telemetry: { ...telemetry },
queuedBytesTotal,
fanoutTopicCount: fanouts.size,
fanoutSubscribersTotal,
@@ -148,7 +148,8 @@ export function bareOsProcessTableSnapshot(opts = {}) {
schema: 1,
bareOsSendSignal:
'Delivers to synthetic pids 13 (kernel, booter, shell), shell job rows (41xx), and initd logical rows (51xx) when keyed by pid; see bareOsSendSignal implementation.',
killUtility: '/bin/kill maps names kernel|booter|shell and numeric pids to the same routing.',
killUtility:
'/bin/kill maps names kernel|booter|shell, numeric pids, and **negative pgid** (e.g. `kill -TERM -300` for logical group 300) to the same routing.',
trap:
'Shell builtins use ctx.shellTrapHandlers; signals are synthetic (no host kill).',
sessionShellPgid: 3
@@ -181,7 +182,7 @@ export function bareOsProcessTableSnapshot(opts = {}) {
setpgidAnalog:
'Background job start assigns pgid on shellBackgroundJobs rows where implemented.',
killpgAnalog:
'Virtual signal delivery consults shell job lists; not a full killpg(2) emulation.'
'Negative numeric targets index **`shellBackgroundJobs`** by **`pgid`**; active jobs only (zombies excluded except **`kill -0`** existence checks). Not a full killpg(2) emulation.'
},
ipcProcessGroups:
opts.ipcStats &&
@@ -4,8 +4,12 @@
*
* Tunable via optional env (read by caller): BARE_OS_SWARM_BAN_FAIL_THRESHOLD,
* BARE_OS_SWARM_BAN_MS_INITIAL, BARE_OS_SWARM_BAN_MS_MAX, BARE_OS_SWARM_EWMA_ALPHA.
* Optional **`BARE_OS_PEER_ALLOWLIST_HEX`** is enforced here so swarm scheduling aligns with
* **`ctx.bareOsEvaluatePeerAdmission`** (deny before reconnect budget is spent).
*/
import { evaluateBareOsPeerAdmission } from './bare-os-peer-admission.js'
const DEFAULT_FAILS_BEFORE_BAN = 4
const DEFAULT_BAN_MS_INITIAL = 5000
const DEFAULT_BAN_MS_MAX = 300000
@@ -69,6 +73,7 @@ export class BareOsSwarmPeerPolicyEngine {
*/
constructor(swarm, opts = {}) {
this.swarm = swarm
this._env = opts.env && typeof opts.env === 'object' ? opts.env : null
this._cfg = readSwarmPolicyEnv(opts.env)
/** @type {Map<string, PeerState>} */
this._peers = new Map()
@@ -149,6 +154,10 @@ export class BareOsSwarmPeerPolicyEngine {
*/
shouldAttemptPeer(peerKey) {
const k = this._key(peerKey)
if (this._env) {
const adm = evaluateBareOsPeerAdmission(this._env, k)
if (adm.verdict === 'deny') return false
}
const st = this._peers.get(k)
const now = Date.now()
if (!st) return true
@@ -37,7 +37,9 @@ export const BARE_OS_SYSCALL_OPS_DETAIL = Object.freeze([
{ name: 'ftruncate', category: 'fs', stability: 'experimental' },
{ name: 'fsync', category: 'fs', stability: 'experimental' },
{ name: 'fdatasync', category: 'fs', stability: 'experimental' },
{ name: 'pathconf', category: 'fs', stability: 'experimental' }
{ name: 'pathconf', category: 'fs', stability: 'experimental' },
{ name: 'fcntl', category: 'fs', stability: 'stable' },
{ name: 'posixPoll', category: 'fs', stability: 'experimental' }
])
/**
@@ -122,8 +124,23 @@ export const BARE_OS_POSIX_XSH_OPS_DETAIL = Object.freeze([
category: 'fs',
stability: 'stable',
posixAlignment: 'partial',
mapsTo: ['F_GETFL noop where honored', 'other cmds ENOTSUP'],
errnoHint: 'ENOTSUP'
mapsTo: [
'ctx.bareOsSyscall("fcntl")',
'F_GETFL',
'F_SETFL',
'O_APPEND',
'O_NONBLOCK',
'bareOsLogicalFdFlags'
],
errnoHint: 'EBADF'
},
{
name: 'poll',
category: 'fs',
stability: 'experimental',
posixAlignment: 'partial',
mapsTo: ['ctx.bareOsPosixPoll', 'ctx.bareOsSyscall("posixPoll")'],
errnoHint: 'EINVAL'
}
])
@@ -29,7 +29,7 @@ export function buildBareOsSyscallsProcJson(p) {
posixXsh: {
schema: 1,
note: 'POSIX.1 XSH names in opsDetail with posixAlignment; not additional ctx.bareOsSyscall op strings.',
namesCsv: 'open,close,read,write,lseek,pipe,dup,dup2,fcntl'
namesCsv: 'open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll'
},
errnoHints: bareOsErrnoTableForProc(),
fdModel: {
@@ -71,7 +71,22 @@ export function buildBareOsSyscallsProcJson(p) {
partial: true
},
fcntl: {
mapsTo: ['F_GETFL noop where honored; other cmds ENOTSUP at VFS layer'],
mapsTo: [
'ctx.bareOsSyscall("fcntl")',
'F_GETFL',
'F_SETFL',
'O_APPEND 0x400',
'O_NONBLOCK 0x800',
'bareOsLogicalFdFlags per fd string key'
],
partial: true
},
poll: {
mapsTo: [
'ctx.bareOsPosixPoll',
'ctx.bareOsPosixPollProbe',
'ctx.bareOsSyscall("posixPoll")'
],
partial: true
}
}
@@ -4,6 +4,28 @@
import b4a from 'b4a'
import { bareOsAppendVaultRotationCheckpoint } from './bare-os-vault-rotation-audit.js'
/**
* Mirror vault NDJSON checkpoints into the tamper-evident host audit chain when the booter exposes it.
* @param {Record<string, unknown>} ctx
* @param {Record<string, unknown>} row
*/
function mirrorVaultCheckpointToAuditChain(ctx, row) {
if (typeof ctx.bareOsAuditLogAppendBatch !== 'function') return
try {
ctx.bareOsAuditLogAppendBatch([
{
type: 'identity.vault_audit_mirror',
schema: 1,
vaultRotationAuditPath: '/.bare/vault-rotation-audit.ndjson',
...row,
atMs: Date.now()
}
])
} catch {
/* optional chain */
}
}
import { loadBarerc } from './shell.js'
import {
ACCOUNT_PATH,
@@ -200,6 +222,10 @@ export async function unlockIdentity(ctx, passphrase) {
kind: 'identity_unlock',
user: String(ctx.vfs?.env?.USER || '')
})
mirrorVaultCheckpointToAuditChain(ctx, {
kind: 'identity_unlock',
user: String(ctx.vfs?.env?.USER || '')
})
} catch {
/* ignore audit failures */
}
@@ -422,6 +448,10 @@ export async function saveVaultToDrive(ctx) {
kind: 'vault_save',
fileCount
})
mirrorVaultCheckpointToAuditChain(ctx, {
kind: 'vault_save',
fileCount
})
} catch (e) {
ctx.console?.error?.(
'[bare-os] vault-save audit: ' + (e?.message || e)
@@ -4,6 +4,11 @@
*
* Enforcement at boot remains in `kernel/init.js` (`kernel.ext.d`); this module provides
* shared deterministic resolution for booter tooling, tests, and strict preflight.
*
* **Keep in sync:** `topologicalOrderKernelExtensions` / `kernelExtensionDependencyDepth`
* must match `topologicalOrderKernelExtEntries` / `kernelExtDependencyDepth` in
* `kernel/lib/init/init-main.js` (same Kahn sort and depth walk). CI: booter test
* `kernel extension topological order matches resolver module`.
*/
/**
+20
View File
@@ -20,6 +20,7 @@ import {
S_IFLNK,
S_IFREG
} from './vfs-posix-meta.js'
import { bareOsKernelMetricInc } from './bare-os-kernel-metrics.js'
/**
* Policy-oriented path class for VFS routing (system vs personal vs pseudo vs mount vs volatile).
@@ -778,6 +779,23 @@ export function createVfs(
}
}
}
/** Drop `/bin` and `/lib/bare` warm read caches (replication / OTA safety). */
function bareOsClearWarmReadCaches() {
if (binReadCache) binReadCache.clear()
if (binDigestCache) binDigestCache.clear()
if (binPathToBlakeDigest) binPathToBlakeDigest.clear()
if (binDigestRefcount) binDigestRefcount.clear()
if (binBlake2bLruPaths) binBlake2bLruPaths.length = 0
if (warmReadCacheStats) {
warmReadCacheStats.hits = 0
warmReadCacheStats.misses = 0
warmReadCacheStats.binHits = 0
warmReadCacheStats.libBareHits = 0
}
bareOsKernelMetricInc('vfs.warm_read_cache_clear')
}
const sysClassNetLoText =
typeof vfsOptions.sysClassNetLoText === 'function'
? vfsOptions.sysClassNetLoText
@@ -4366,6 +4384,8 @@ export function createVfs(
env,
bareOsClearWarmReadCaches,
async chdir(userPath) {
const abs = resolveLogical(userPath)
assertNotBootPolicyDenyVfs(abs, 'chdir')
+61 -8
View File
@@ -18,6 +18,8 @@ import {
} from './lib/vfs.js'
import { buildBareOsHypercorePackHrpcLifecycleProcJson } from './lib/bare-os-proc-hypercore-pack-hrpc-lifecycle.js'
import { createBareOsIpc } from './lib/bare-os-ipc.js'
import { topologicalOrderKernelExtensions } from './lib/kernel-extension-resolver.js'
import { createBareOsProtomuxAliasRegistry } from './lib/bare-os-protomux-alias-registry.js'
import { bareOsHttpUrlAllowed } from './lib/bare-os-http-policy.js'
import {
bareOsParseBareAclText,
@@ -483,13 +485,22 @@ test('stock kernel init.js BARE_OS_BOOT_TRACE=json emits boot trace JSON on stde
}
})
.filter(Boolean)
t.ok(phases.length >= 3)
t.ok(phases.some((o) => o.phase === 'rc.local'))
t.ok(phases.some((o) => o.step === 'rc.local'))
t.ok(phases.some((o) => o.phase === 'kernel.d'))
t.ok(phases.some((o) => o.step === 'kernel.d'))
t.ok(phases.every((o) => o.bootTraceSchemaVersion === 2))
t.ok(phases.every((o) => typeof o.ms === 'number'))
const bootSteps = phases.filter(
(o) => o && typeof o.ms === 'number' && o.type !== 'bootOutput'
)
t.ok(bootSteps.length >= 3)
t.ok(bootSteps.some((o) => o.phase === 'rc.local'))
t.ok(bootSteps.some((o) => o.step === 'rc.local'))
t.ok(bootSteps.some((o) => o.phase === 'kernel.d'))
t.ok(bootSteps.some((o) => o.step === 'kernel.d'))
t.ok(bootSteps.every((o) => o.bootTraceSchemaVersion === 2))
t.ok(bootSteps.every((o) => typeof o.ms === 'number'))
t.ok(
phases.some(
(o) => o && o.type === 'bootOutput' && o.step === 'banner'
),
'bootOutput trace for guest-visible banner'
)
})
test('stock kernel BARE_OS_RC_D_SKIP skips matching rc.d snippets', async (t) => {
@@ -840,9 +851,11 @@ test('buildBareOsSyscallsProcJson exposes posixLike fd mapping', async (t) => {
t.ok(j.fdModel && j.fdModel.posixLike && j.fdModel.posixLike.pipe)
t.ok(j.fdModel.posixLike.read && j.fdModel.posixLike.write)
t.ok(j.fdModel.posixLike.fcntl)
t.ok(j.fdModel.posixLike.poll)
t.ok(j.posixXsh && j.posixXsh.schema === 1 && j.posixXsh.namesCsv.includes('open'))
t.ok(j.posixXsh.namesCsv.includes('poll'))
const xsh = j.opsDetail.filter((r) => r.posixAlignment)
t.ok(xsh.length >= 9)
t.ok(xsh.length >= 10)
})
test('ls -l long listing uses session user and regular file mode', async (t) => {
@@ -5427,6 +5440,46 @@ test('protomux and hyperswarm versions match repo lock contract', async (t) => {
t.is(hs, snap.hyperswarm)
})
test('kernel extension topological order matches guest Kahn tie-break', async (t) => {
const entries = [
{ file: 'b.json', extId: 'b', scripts: ['/x'], dependsOn: ['a'] },
{ file: 'a.json', extId: 'a', scripts: ['/y'], dependsOn: [] }
]
const topo = topologicalOrderKernelExtensions(entries)
t.ok(topo.ok)
t.is(
topo.ordered.map((e) => e.file).join(','),
'a.json,b.json',
'filename order breaks ties; dependsOn edge b→a'
)
})
test('protomux alias registry snapshot matches proc wire contract shape', async (t) => {
const reg = createBareOsProtomuxAliasRegistry()
reg.register('guestChannel', 'wireChannel')
const rt = reg.snapshot()
t.is(rt.schema, 2)
t.is(rt.aliases.guestChannel, 'wireChannel')
t.ok(Array.isArray(rt.changeLogTail))
t.ok(rt.reverseIndex && typeof rt.reverseIndex === 'object')
})
test('createBareOsIpc enforces maxChannels quota', async (t) => {
const ipc = createBareOsIpc({ maxChannels: 2, maxFifoBytes: 4096 })
ipc.create('a')
ipc.create('b')
let threw = false
try {
ipc.create('c')
} catch {
threw = true
}
t.ok(threw)
const st = ipc.stats()
t.is(st.maxChannels, 2)
t.ok(st.telemetry.fifoCreateDeniedQuota >= 1)
})
async function readBuiltBin(name) {
const fs = await import('node:fs/promises')
const p = path.join(__dirname, '../../kernel/bin', name)
+1 -1
View File
@@ -6,6 +6,6 @@
"description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)",
"scripts": {
"build": "node ./scripts/ensure-man-pages.mjs && node ./build.mjs",
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs"
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs"
}
}
@@ -396,8 +396,9 @@ EXTRA.getconf = {
}
EXTRA.mkfifo = {
description:
'FIFO special files are not implemented on Hyperdrive. The command reports failure.',
bareOsNotes: 'Documented stub; no real pipes as kernel objects.'
'Creates in-memory FIFO endpoints under /run/bare-os/ipc/<name> (not Hyperdrive specials).',
bareOsNotes:
'Uses ctx.bareOsIpc.create; optional BARE_OS_IPC_NAMESPACE prefixes keys. BARE_OS_IPC_MAX_CHANNELS caps distinct channels; see metrics_live.ipcTelemetry.'
}
EXTRA.chmod = {
synopsis: [
+2 -2
View File
@@ -50,10 +50,10 @@ const CONF = {
_PC_2_SYMLINKS: '1',
/** Comma-separated `ctx.bareOsSyscall` op names implemented in stock booter. */
BARE_OS_SYSCALL_OPS:
'readFile,writeFile,readdir,mkdir,stat,unlink,chmod,chdir,getcwd,readlink,symlink,exists,lstat,rmdir,mount,umount,kill,rename,link,access,utimes,truncate,ftruncate,fsync,fdatasync,pathconf',
'access,chdir,chmod,exists,fcntl,fdatasync,fsync,ftruncate,getcwd,kill,link,lstat,mkdir,mount,pathconf,posixPoll,readFile,readdir,readlink,rename,rmdir,stat,symlink,truncate,umount,unlink,utimes,writeFile',
/** POSIX.1 XSH-style names documented in `/proc/bare_os/syscalls.json` opsDetail (not separate ctx ops). */
BARE_OS_POSIX_XSH_OPS:
'open,close,read,write,lseek,pipe,dup,dup2,fcntl',
'open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll',
/** Encodings accepted by `/bin/iconv` (subset; case-insensitive names). */
BARE_OS_ICONV_ENCODINGS: 'UTF-8,ISO-8859-1,UTF-16LE,UTF-16BE',
/** Synthetic process table JSON path (logical VFS). */
@@ -0,0 +1,114 @@
/**
* Live pathconf: union and read-only mirror paths affect _PC_NO_TRUNC / _PC_CHOWN_RESTRICTED.
*/
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'brittle'
import b4a from 'b4a'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
async function loadBin(name) {
const runtime = await readFile(
path.join(__dirname, '../lib/runtime.js'),
'utf8'
)
const body = await readFile(
path.join(__dirname, `../src/${name}.js`),
'utf8'
)
return new AsyncFunction(
'ctx',
'argv',
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
)
}
function bareOsPathconfFromIndexLogic(shellEnv, vfs) {
return (pathArg, name) => {
const k = String(name || '').trim()
const p = vfs.resolveLogical(String(pathArg || '/').trim() || '/')
const rawUnion = String(shellEnv.BARE_OS_VFS_UNION_PREFIXES || '')
const underUnion = rawUnion
.split(',')
.map((s) => s.trim())
.filter((x) => x.startsWith('/'))
.some((pre) => p === pre || p.startsWith(pre + '/'))
const underMirror = p === '/mirror' || p.startsWith('/mirror/')
const table = {
_PC_NAME_MAX: 255,
_PC_NO_TRUNC: 1,
_PC_CHOWN_RESTRICTED: 1
}
if (k === '_PC_CHOWN_RESTRICTED') {
if (underMirror) return 0
return 1
}
if (k === '_PC_NO_TRUNC' && (underMirror || underUnion)) return 0
if (table[k] === undefined) throw new Error('unknown: ' + k)
return table[k]
}
}
test('getconf pathconf _PC_NO_TRUNC is 0 under union prefix', async (t) => {
const run = await loadBin('getconf')
const logs = []
const shellEnv = {
BARE_OS_VFS_UNION_PREFIXES: '/system'
}
const vfs = {
resolveLogical(p) {
return String(p || '/')
}
}
const ctx = {
b4a,
exitCode: 0,
env: shellEnv,
vfs,
bareOsPathconf: bareOsPathconfFromIndexLogic(shellEnv, vfs),
console: {
log(s) {
logs.push(String(s))
},
error(s) {
logs.push(String(s))
}
}
}
await run(ctx, ['getconf', '_PC_NO_TRUNC', '/system/bin/foo'])
t.is(ctx.exitCode, 0)
t.is(logs.pop(), '0')
})
test('getconf pathconf _PC_CHOWN_RESTRICTED is 0 on mirror tree', async (t) => {
const run = await loadBin('getconf')
const logs = []
const shellEnv = {}
const vfs = {
resolveLogical(p) {
return String(p || '/')
}
}
const ctx = {
b4a,
exitCode: 0,
env: shellEnv,
vfs,
bareOsPathconf: bareOsPathconfFromIndexLogic(shellEnv, vfs),
console: {
log(s) {
logs.push(String(s))
},
error(s) {
logs.push(String(s))
}
}
}
await run(ctx, ['getconf', '_PC_CHOWN_RESTRICTED', '/mirror/aux0/x'])
t.is(ctx.exitCode, 0)
t.is(logs.pop(), '0')
})
@@ -0,0 +1,78 @@
/**
* Golden-style checks for high-traffic XCU utilities (bounded POSIX Issue 7 alignment).
*/
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'brittle'
import b4a from 'b4a'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
/**
* @param {string} name
*/
async function loadBin(name) {
const runtime = await readFile(
path.join(__dirname, '../lib/runtime.js'),
'utf8'
)
const body = await readFile(
path.join(__dirname, `../src/${name}.js`),
'utf8'
)
return new AsyncFunction(
'ctx',
'argv',
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
)
}
function createCtx() {
const logs = []
return {
b4a,
exitCode: 0,
vfs: { async stat() { return null } },
console: {
log: (m) => logs.push(String(m)),
error: (m) => logs.push(String(m))
},
_logs: logs
}
}
test('/bin/test: string equality and unary -z', async (t) => {
const run = await loadBin('test')
const c1 = createCtx()
await run(c1, ['test', 'ab', '=', 'ab'])
t.is(c1.exitCode, 0)
const c2 = createCtx()
await run(c2, ['test', 'ab', '!=', 'cd'])
t.is(c2.exitCode, 0)
const c3 = createCtx()
await run(c3, ['test', '-z', ''])
t.is(c3.exitCode, 0)
})
test('/bin/expr: integer arithmetic and comparison', async (t) => {
const run = await loadBin('expr')
const c = createCtx()
await run(c, ['expr', '3', '+', '4'])
t.is(c.exitCode, 0)
t.is(c._logs.join('\n'), '7')
const c2 = createCtx()
await run(c2, ['expr', '5', '>', '2'])
t.is(c2.exitCode, 0)
t.is(c2._logs.join('\n'), '1')
})
test('/bin/printf: %d and literal percent', async (t) => {
const run = await loadBin('printf')
const c = createCtx()
await run(c, ['printf', '%d%%\n', '42'])
t.is(c.exitCode, 0)
t.is(c._logs.join(''), '42%\n')
})
+1
View File
@@ -4,6 +4,7 @@ Cross-package **version alignment** (ctx API, feature-bits doc, lifecycle schema
## 0.9.0
- **`BARE_OS_POSIX_PROFILE_VERSION` `1.0.3`** — Documents **`pathconf`** union/mirror hints and logical **`kill`** process-group negatives in the declared profile (handbook + appendix).
- **`lib/protomux-channel-schema.js`**: **`BARE_OS_PROTOMUX_CHANNEL_SCHEMA_VERSION`** handshake marker; **`BARE_OS_PROTOMUX_CORK_HINT_VERSION`** documents cork/uncork batching expectations; both re-exported from **`lib/channel.js`** for strict channel versioning.
- **Breaking (wire):** **`bare_os.capabilities`** and related surfaces use **`kernelCapabilityWords`** (semantic camelCase keys) plus **`kernelCapabilityWireVersion: 2`** instead of top-level **`bits`** … **`bits11`**. Numeric masks and bit positions are unchanged; peers must upgrade together.
- **`lib/kernel-capability-wire.js`**: **`BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION`**, **`buildKernelCapabilityWordsObject`**, **`getKernelCapabilityWords`**, **`readKernelCapabilityWord`**, **`BARE_OS_KERNEL_CAPABILITY_WORD_KEYS`**.
@@ -4,7 +4,7 @@
*/
/** Semver for the documented POSIX-like surface (handbook ch.9 + environment appendix). */
export const BARE_OS_POSIX_PROFILE_VERSION = '1.0.1'
export const BARE_OS_POSIX_PROFILE_VERSION = '1.0.3'
/** Short identifier for telemetry and `/proc` mirrors. */
export const BARE_OS_POSIX_PROFILE_ID = 'bare-os-posix-like'
+2 -2
View File
@@ -139,10 +139,10 @@ const CONF = {
_PC_2_SYMLINKS: '1',
/** Comma-separated `ctx.bareOsSyscall` op names implemented in stock booter. */
BARE_OS_SYSCALL_OPS:
'readFile,writeFile,readdir,mkdir,stat,unlink,chmod,chdir,getcwd,readlink,symlink,exists,lstat,rmdir,mount,umount,kill,rename,link,access,utimes,truncate,ftruncate,fsync,fdatasync,pathconf',
'access,chdir,chmod,exists,fcntl,fdatasync,fsync,ftruncate,getcwd,kill,link,lstat,mkdir,mount,pathconf,posixPoll,readFile,readdir,readlink,rename,rmdir,stat,symlink,truncate,umount,unlink,utimes,writeFile',
/** POSIX.1 XSH-style names documented in `/proc/bare_os/syscalls.json` opsDetail (not separate ctx ops). */
BARE_OS_POSIX_XSH_OPS:
'open,close,read,write,lseek,pipe,dup,dup2,fcntl',
'open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll',
/** Encodings accepted by `/bin/iconv` (subset; case-insensitive names). */
BARE_OS_ICONV_ENCODINGS: 'UTF-8,ISO-8859-1,UTF-16LE,UTF-16BE',
/** Synthetic process table JSON path (logical VFS). */
@@ -1,9 +1,9 @@
{
"schemaVersion": 4,
"ctxApiVersion": "1.34.0",
"ctxApiVersion": "1.36.0",
"posixProfile": {
"id": "bare-os-posix-like",
"version": "1.0.1"
"version": "1.0.3"
},
"ops": ["readFile", "writeFile"],
"opsDetail": [
@@ -21,7 +21,7 @@
"posixXsh": {
"schema": 1,
"note": "POSIX.1 XSH names in opsDetail with posixAlignment; not ctx.bareOsSyscall op strings.",
"namesCsv": "open,close,read,write,lseek,pipe,dup,dup2,fcntl"
"namesCsv": "open,close,read,write,lseek,pipe,dup,dup2,fcntl,poll"
},
"errnoHints": {
"ENOENT": 2,
+74 -12
View File
@@ -44,8 +44,8 @@ async function invokeCtxBootHooks(ctx, ev) {
* BARE_OS_ONBOOT (newline-separated) or, if unset, every such line from /etc/bare-os/onboot
* in file order (trusted). Then readLine yields EOF.
*
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**.
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2). Guest **`os-release`**, **`motd`**, and **`banner`** lines also emit **`type":"bootOutput"`** previews on stderr (stdout body still goes through **`ctx.console.log`** only).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**, plus **`type":"bootOutput"`** for guest-visible text previews.
*
* BARE_OS_BOOT_MINIMAL=1 or true: skip profile rc, rc, rc.d, rc.local, kernel.d (recovery shell).
* BARE_OS_BOOT_SAFE_MODE=1 or true: after boot policy merge, skip rc.d, kernel.ext.d, onboot (lighter recovery).
@@ -123,6 +123,62 @@ function isBootTraceNdjson(ctx) {
return ctx.env && ctx.env.BARE_OS_BOOT_TRACE === 'ndjson'
}
/**
* Guest-visible boot text via **`ctx.console.log`** only (booter session / var-log mirror).
* When **`BARE_OS_BOOT_TRACE`** is **`json`** or **`ndjson`**, append one stderr JSON line per emission (**`type":"bootOutput"`**, preview-capped) for CI and hosts.
* @param {Record<string, unknown>} ctx
* @param {string} step
* @param {string} text
*/
function bootGuestOutLine(ctx, step, text) {
const line = String(text)
const out = ctx.console
if (out && typeof out.log === 'function') out.log(line)
else if (
globalThis.console &&
typeof globalThis.console.log === 'function'
) {
globalThis.console.log(line)
}
if (!wantBootTrace(ctx)) return
const prev =
isBootTraceNdjson(ctx) && line.length > 400
? line.slice(0, 400) + '\u2026'
: isBootTraceJson(ctx) && line.length > 200
? line.slice(0, 200) + '\u2026'
: line
const errFn =
out && typeof out.error === 'function'
? out.error.bind(out)
: globalThis.console && typeof globalThis.console.error === 'function'
? globalThis.console.error.bind(globalThis.console)
: null
if (!errFn) return
if (isBootTraceNdjson(ctx)) {
errFn(
JSON.stringify({
type: 'bootOutput',
bootTraceSchemaVersion: 2,
step,
stream: 'stdout',
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
ts: Date.now(),
textPreview: prev
})
)
} else if (isBootTraceJson(ctx)) {
errFn(
JSON.stringify({
bootTraceSchemaVersion: 2,
type: 'bootOutput',
step,
stream: 'stdout',
textPreview: prev
})
)
}
}
/**
* @param {Record<string, unknown>} ctx
*/
@@ -1792,12 +1848,14 @@ async function runRcLines(ctx, text) {
* @param {Record<string, unknown>} ctx
*/
async function printOsRelease(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const rel = await drive.get('/etc/os-release')
if (rel) console.log(b4a.toString(rel))
if (rel) bootGuestOutLine(ctx, 'os-release', b4a.toString(rel))
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -1805,12 +1863,14 @@ async function printOsRelease(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printMotd(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const motd = await drive.get('/etc/motd')
if (motd) console.log(b4a.toString(motd).trimEnd())
if (motd) bootGuestOutLine(ctx, 'motd', b4a.toString(motd).trimEnd())
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -2444,12 +2504,12 @@ async function runBareOsRcDir(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printSessionBanner(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
console.log(b4a.toString(buf).trimEnd())
bootGuestOutLine(ctx, 'banner', b4a.toString(buf).trimEnd())
return
}
} catch {
@@ -2459,12 +2519,14 @@ async function printSessionBanner(ctx) {
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock) | shell: cd, export, if/fi, && || ;, |, &, jobs/fg, <<<, exit | services: systemctl list-units, journalctl -u UNIT | try: help, ls /bin, crontab -l'
if (ctx.bareOsSkipRepl) {
console.log(
bootGuestOutLine(
ctx,
'banner',
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
console.log(defaultBanner)
bootGuestOutLine(ctx, 'banner', defaultBanner)
}
/**
@@ -7,18 +7,18 @@
"b4a"
]
},
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{
"path": "/lib/bare/bundles/hypercoreIdEncoding.js",
"keys": [
"hypercoreIdEncoding"
]
},
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{
"path": "/lib/bare/bundles/compactEncoding.js",
"keys": [
@@ -38,9 +38,9 @@
]
},
{
"path": "/lib/bare/bundles/bareEncoding.js",
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"bareEncoding"
"barePath"
]
},
{
@@ -50,9 +50,9 @@
]
},
{
"path": "/lib/bare/bundles/barePath.js",
"path": "/lib/bare/bundles/bareEncoding.js",
"keys": [
"barePath"
"bareEncoding"
]
},
{
@@ -73,12 +73,6 @@
"bareAnsiEscapes"
]
},
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareReadline.js",
"keys": [
@@ -91,6 +85,18 @@
"bareCrypto"
]
},
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
@@ -103,30 +109,24 @@
"bareAppKit"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/bareApk.js",
"keys": [
"bareApk"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/bareBmp.js",
"keys": [
@@ -145,6 +145,12 @@
"bareBuffer"
]
},
{
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
"keys": [
"bareBundleEvaluate"
]
},
{
"path": "/lib/bare/bundles/bareBundle.js",
"keys": [
@@ -157,48 +163,42 @@
"bareBluetoothApple"
]
},
{
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
"keys": [
"bareBundleEvaluate"
]
},
{
"path": "/lib/bare/bundles/bareBoot.js",
"keys": [
"bareBoot"
]
},
{
"path": "/lib/bare/bundles/bareConsole.js",
"keys": [
"bareConsole"
]
},
{
"path": "/lib/bare/bundles/bareBundleId.js",
"keys": [
"bareBundleId"
]
},
{
"path": "/lib/bare/bundles/bareConsole.js",
"keys": [
"bareConsole"
]
},
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{
"path": "/lib/bare/bundles/bareDelta.js",
"keys": [
"bareDelta"
]
},
{
"path": "/lib/bare/bundles/bareChannel.js",
"keys": [
"bareChannel"
]
},
{
"path": "/lib/bare/bundles/bareDelta.js",
"keys": [
"bareDelta"
]
},
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
@@ -229,6 +229,12 @@
"bareExif"
]
},
{
"path": "/lib/bare/bundles/bareDgram.js",
"keys": [
"bareDgram"
]
},
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
@@ -241,12 +247,6 @@
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareDgram.js",
"keys": [
"bareDgram"
]
},
{
"path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [
@@ -254,9 +254,9 @@
]
},
{
"path": "/lib/bare/bundles/bareFormData.js",
"path": "/lib/bare/bundles/bareGif.js",
"keys": [
"bareFormData"
"bareGif"
]
},
{
@@ -266,9 +266,15 @@
]
},
{
"path": "/lib/bare/bundles/bareGif.js",
"path": "/lib/bare/bundles/bareFormData.js",
"keys": [
"bareGif"
"bareFormData"
]
},
{
"path": "/lib/bare/bundles/bareHrtime.js",
"keys": [
"bareHrtime"
]
},
{
@@ -289,12 +295,6 @@
"bareFileLogger"
]
},
{
"path": "/lib/bare/bundles/bareHrtime.js",
"keys": [
"bareHrtime"
]
},
{
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
@@ -319,18 +319,18 @@
"bareImageResample"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{
"path": "/lib/bare/bundles/bareHttp1.js",
"keys": [
"bareHttp1"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{
"path": "/lib/bare/bundles/bareHttps.js",
"keys": [
@@ -355,6 +355,12 @@
"bareIpc"
]
},
{
"path": "/lib/bare/bundles/bareInspector.js",
"keys": [
"bareInspector"
]
},
{
"path": "/lib/bare/bundles/bareLief.js",
"keys": [
@@ -374,9 +380,9 @@
]
},
{
"path": "/lib/bare/bundles/bareInspector.js",
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
"bareInspector"
"bareModule"
]
},
{
@@ -385,60 +391,30 @@
"bareMake"
]
},
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
"bareModule"
]
},
{
"path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [
"bareModuleTraverse"
]
},
{
"path": "/lib/bare/bundles/bareNdk.js",
"keys": [
"bareNdk"
]
},
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{
"path": "/lib/bare/bundles/bareNative.js",
"keys": [
"bareNative"
]
},
{
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
@@ -446,15 +422,33 @@
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [
"bareNet"
"bareModuleTraverse"
]
},
{
"path": "/lib/bare/bundles/barePackDrive.js",
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"barePackDrive"
"bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"keys": [
"bareNet"
]
},
{
@@ -470,9 +464,9 @@
]
},
{
"path": "/lib/bare/bundles/barePng.js",
"path": "/lib/bare/bundles/barePackDrive.js",
"keys": [
"barePng"
"barePackDrive"
]
},
{
@@ -481,12 +475,30 @@
"barePipe"
]
},
{
"path": "/lib/bare/bundles/barePng.js",
"keys": [
"barePng"
]
},
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{
"path": "/lib/bare/bundles/barePrebuild.js",
"keys": [
"barePrebuild"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{
"path": "/lib/bare/bundles/barePunycode.js",
"keys": [
@@ -494,9 +506,9 @@
]
},
{
"path": "/lib/bare/bundles/barePrebuild.js",
"path": "/lib/bare/bundles/bareProcess.js",
"keys": [
"barePrebuild"
"bareProcess"
]
},
{
@@ -517,108 +529,102 @@
"bareRealm"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{
"path": "/lib/bare/bundles/bareProcess.js",
"keys": [
"bareProcess"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
]
},
{
"path": "/lib/bare/bundles/bareRuntime.js",
"keys": [
"bareRuntime"
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareRepl"
]
},
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{
"path": "/lib/bare/bundles/bareSemver.js",
"keys": [
"bareSemver"
]
},
{
"path": "/lib/bare/bundles/bareRpc.js",
"keys": [
"bareRpc"
]
},
{
"path": "/lib/bare/bundles/bareSignals.js",
"keys": [
"bareSignals"
]
},
{
"path": "/lib/bare/bundles/bareRun.js",
"keys": [
"bareRun"
]
},
{
"path": "/lib/bare/bundles/bareSemver.js",
"keys": [
"bareSemver"
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareRepl"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
]
},
{
"path": "/lib/bare/bundles/bareSignals.js",
"keys": [
"bareSignals"
]
},
{
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareSidecar"
]
},
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareSvg.js",
"keys": [
"bareSvg"
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStdio"
]
},
{
"path": "/lib/bare/bundles/bareStringDecoder.js",
"keys": [
"bareStringDecoder"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{
"path": "/lib/bare/bundles/bareStructuredClone.js",
"keys": [
"bareStructuredClone"
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStdio"
]
},
{
"path": "/lib/bare/bundles/bareSvg.js",
"keys": [
"bareSvg"
]
},
{
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareTiff"
]
},
{
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
@@ -638,9 +644,9 @@
]
},
{
"path": "/lib/bare/bundles/bareTiff.js",
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
"bareTiff"
"bareTpl"
]
},
{
@@ -661,30 +667,12 @@
"bareTimers"
]
},
{
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
"bareTpl"
]
},
{
"path": "/lib/bare/bundles/bareType.js",
"keys": [
"bareType"
]
},
{
"path": "/lib/bare/bundles/bareUiKit.js",
"keys": [
"bareUiKit"
]
},
{
"path": "/lib/bare/bundles/bareUnpack.js",
"keys": [
"bareUnpack"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
@@ -697,6 +685,12 @@
"bareTty"
]
},
{
"path": "/lib/bare/bundles/bareUnpack.js",
"keys": [
"bareUnpack"
]
},
{
"path": "/lib/bare/bundles/bareV8.js",
"keys": [
@@ -704,9 +698,9 @@
]
},
{
"path": "/lib/bare/bundles/bareUnionBundle.js",
"path": "/lib/bare/bundles/bareUiKit.js",
"keys": [
"bareUnionBundle"
"bareUiKit"
]
},
{
@@ -715,6 +709,12 @@
"bareWalkHandles"
]
},
{
"path": "/lib/bare/bundles/bareUnionBundle.js",
"keys": [
"bareUnionBundle"
]
},
{
"path": "/lib/bare/bundles/bareVm.js",
"keys": [
@@ -727,30 +727,30 @@
"bareWebKit"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
"bareWebp"
]
},
{
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareWebKitGtk"
]
},
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
"bareWebp"
]
},
{
"path": "/lib/bare/bundles/bareWhich.js",
"keys": [
"bareWhich"
]
},
{
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareWebKitGtk"
]
},
{
"path": "/lib/bare/bundles/bareWinUi.js",
"keys": [
@@ -769,18 +769,18 @@
"bareV8ToIstanbul"
]
},
{
"path": "/lib/bare/bundles/bareZlib.js",
"keys": [
"bareZlib"
]
},
{
"path": "/lib/bare/bundles/bareWs.js",
"keys": [
"bareWs"
]
},
{
"path": "/lib/bare/bundles/bareZlib.js",
"keys": [
"bareZlib"
]
},
{
"path": "/lib/bare/bundles/bareZmq.js",
"keys": [
@@ -1595,8 +1595,8 @@
],
"bundleProvenance": {
"schemaVersion": 1,
"generatedAt": "2026-04-05T02:38:13.070Z",
"gitCommit": "8a6c1e7b0947b1a70fc837892c76d67222e44119",
"generatedAt": "2026-04-05T03:03:09.352Z",
"gitCommit": "985eadfda39fa8a847791ff5c7f8f40d4434d9ac",
"nodeVersion": "v22.22.0",
"bundleTier": "all",
"normativeManifest": "packages/bare-os-booter/lib/bare-module-manifest.json",
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1775356692404,
"atMs": 1775358188597,
"commands": [
"arch",
"awk",
@@ -14,8 +14,8 @@
* BARE_OS_ONBOOT (newline-separated) or, if unset, every such line from /etc/bare-os/onboot
* in file order (trusted). Then readLine yields EOF.
*
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**.
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2). Guest **`os-release`**, **`motd`**, and **`banner`** lines also emit **`type":"bootOutput"`** previews on stderr (stdout body still goes through **`ctx.console.log`** only).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**, plus **`type":"bootOutput"`** for guest-visible text previews.
*
* BARE_OS_BOOT_MINIMAL=1 or true: skip profile rc, rc, rc.d, rc.local, kernel.d (recovery shell).
* BARE_OS_BOOT_SAFE_MODE=1 or true: after boot policy merge, skip rc.d, kernel.ext.d, onboot (lighter recovery).
@@ -93,6 +93,62 @@ function isBootTraceNdjson(ctx) {
return ctx.env && ctx.env.BARE_OS_BOOT_TRACE === 'ndjson'
}
/**
* Guest-visible boot text via **`ctx.console.log`** only (booter session / var-log mirror).
* When **`BARE_OS_BOOT_TRACE`** is **`json`** or **`ndjson`**, append one stderr JSON line per emission (**`type":"bootOutput"`**, preview-capped) for CI and hosts.
* @param {Record<string, unknown>} ctx
* @param {string} step
* @param {string} text
*/
function bootGuestOutLine(ctx, step, text) {
const line = String(text)
const out = ctx.console
if (out && typeof out.log === 'function') out.log(line)
else if (
globalThis.console &&
typeof globalThis.console.log === 'function'
) {
globalThis.console.log(line)
}
if (!wantBootTrace(ctx)) return
const prev =
isBootTraceNdjson(ctx) && line.length > 400
? line.slice(0, 400) + '\u2026'
: isBootTraceJson(ctx) && line.length > 200
? line.slice(0, 200) + '\u2026'
: line
const errFn =
out && typeof out.error === 'function'
? out.error.bind(out)
: globalThis.console && typeof globalThis.console.error === 'function'
? globalThis.console.error.bind(globalThis.console)
: null
if (!errFn) return
if (isBootTraceNdjson(ctx)) {
errFn(
JSON.stringify({
type: 'bootOutput',
bootTraceSchemaVersion: 2,
step,
stream: 'stdout',
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
ts: Date.now(),
textPreview: prev
})
)
} else if (isBootTraceJson(ctx)) {
errFn(
JSON.stringify({
bootTraceSchemaVersion: 2,
type: 'bootOutput',
step,
stream: 'stdout',
textPreview: prev
})
)
}
}
/**
* @param {Record<string, unknown>} ctx
*/
@@ -1762,12 +1818,14 @@ async function runRcLines(ctx, text) {
* @param {Record<string, unknown>} ctx
*/
async function printOsRelease(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const rel = await drive.get('/etc/os-release')
if (rel) console.log(b4a.toString(rel))
if (rel) bootGuestOutLine(ctx, 'os-release', b4a.toString(rel))
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -1775,12 +1833,14 @@ async function printOsRelease(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printMotd(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
try {
const motd = await drive.get('/etc/motd')
if (motd) console.log(b4a.toString(motd).trimEnd())
if (motd) bootGuestOutLine(ctx, 'motd', b4a.toString(motd).trimEnd())
} catch (e) {
console.error((e && e.message) || String(e))
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
@@ -2414,12 +2474,12 @@ async function runBareOsRcDir(ctx) {
* @param {Record<string, unknown>} ctx
*/
async function printSessionBanner(ctx) {
const { drive, b4a, console } = ctx
const { drive, b4a } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
console.log(b4a.toString(buf).trimEnd())
bootGuestOutLine(ctx, 'banner', b4a.toString(buf).trimEnd())
return
}
} catch {
@@ -2429,12 +2489,14 @@ async function printSessionBanner(ctx) {
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock) | shell: cd, export, if/fi, && || ;, |, &, jobs/fg, <<<, exit | services: systemctl list-units, journalctl -u UNIT | try: help, ls /bin, crontab -l'
if (ctx.bareOsSkipRepl) {
console.log(
bootGuestOutLine(
ctx,
'banner',
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
console.log(defaultBanner)
bootGuestOutLine(ctx, 'banner', defaultBanner)
}
/**
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -209,11 +209,17 @@ Asserts **[`docs/audit/bundle-health.json`](../docs/audit/bundle-health.json)**
Scans vendored **`kernel/lib/bare/bundles/*.js`** for **`TODO` / `FIXME` / `XXX` / `NOT_IMPLEMENTED` / `###TODO###`** unless the basename is listed in **[`docs/audit/bundle-marker-allowlist.json`](../docs/audit/bundle-marker-allowlist.json)**. Complements **`verify-runtime-no-incomplete-markers.mjs`** (hand-authored sources only). Root **`pretest`**.
## `sync-holepunch-clones.mjs`
**Usage:** `node scripts/sync-holepunch-clones.mjs`
Writes **[`docs/audit/holepunch-clone-sync-report.json`](../docs/audit/holepunch-clone-sync-report.json)** comparing **`packages/bare-os-booter/lib/bare-module-manifest.json`** entry names to optional local clones under **`BARE_OS_HOLEPUNCH_CLONES_ROOT`** (default **`$HOME/dev/pearcli/holepunch-repos/holepunchto_repos`**). Operator-maintained; not in CI.
## `verify-bundle-throws.mjs`
**Usage:** `node scripts/verify-bundle-throws.mjs`
Scans vendored bundles for **`throw new Error("not implemented")`** (case variants) unless listed in **[`docs/audit/bundle-throw-allowlist.json`](../docs/audit/bundle-throw-allowlist.json)**. Root **`pretest`**.
Scans vendored **`kernel/lib/bare/bundles/*.js`** for **`new Error("…")`** string arguments whose message matches incomplete-implementation phrases (**`not implemented`**, **`not yet implemented`**, **`abstract method … not implemented`**), regardless of whether the call is **`throw`**, **`cb(…)`**, or **`emit("error", …)`**. Bundles listed in **[`docs/audit/bundle-throw-allowlist.json`](../docs/audit/bundle-throw-allowlist.json)** ( **`schemaVersion` 2** — per-file **`bundleFile`**, **`rationale`**, **`triggerCondition`**) are skipped. Rebuild bundles with **`npm run build -w bare-os-bare-libs`** before tightening the allowlist. Root **`pretest`**.
## `verify-kernel-program-doc.mjs`
+3
View File
@@ -25,6 +25,9 @@ run('npm', ['test'])
console.log('[release-checklist] verify-kernel-seeder-parity')
run('node', ['scripts/verify-kernel-seeder-parity.mjs'])
console.log(
'[release-checklist] If you edited kernel/lib/init/init-main.js or kernel/lib/boot/, run: node scripts/bundle-kernel-init.mjs && rsync -a --delete kernel/ packages/bare-os-seeder/kernel/ then re-run this script.'
)
if (process.env.CHECK_DOC_LINKS === '1') {
console.log('[release-checklist] doc link heuristic (CHECK_DOC_LINKS=1)')
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env node
/**
* Compare local Holepunch org clones (optional) to workspace bundle sources.
* Writes docs/audit/holepunch-clone-sync-report.json for operators.
*
* Env: BARE_OS_HOLEPUNCH_CLONES_ROOT default ~/dev/pearcli/holepunch-repos/holepunchto_repos
*/
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { execSync } from 'node:child_process'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const manifestPath = path.join(
root,
'packages/bare-os-booter/lib/bare-module-manifest.json'
)
const outPath = path.join(root, 'docs/audit/holepunch-clone-sync-report.json')
const defaultClones = path.join(
process.env.HOME || '',
'dev/pearcli/holepunch-repos/holepunchto_repos'
)
const clonesRoot = String(
process.env.BARE_OS_HOLEPUNCH_CLONES_ROOT || defaultClones
).trim()
function gitHead(dir) {
try {
return execSync('git rev-parse HEAD', {
cwd: dir,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
}).trim()
} catch {
return ''
}
}
function main() {
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
const entries = Array.isArray(manifest.entries) ? manifest.entries : []
/** @type {string[]} */
const names = []
for (const e of entries) {
const n =
e && typeof e.name === 'string'
? e.name.trim()
: e && typeof e.packageName === 'string'
? e.packageName.trim()
: ''
if (n) names.push(n)
}
const uniq = [...new Set(names)].sort()
const rootExists = fs.existsSync(clonesRoot)
const atMs = Date.now()
/** @type {Record<string, unknown>[]} */
const rows = []
for (const name of uniq) {
const cloneDir = path.join(clonesRoot, name)
const exists = fs.existsSync(cloneDir)
let pkgVersion = ''
const pj = path.join(cloneDir, 'package.json')
if (exists && fs.existsSync(pj)) {
try {
const p = JSON.parse(fs.readFileSync(pj, 'utf8'))
pkgVersion = String(p.version || '').trim()
} catch {
pkgVersion = ''
}
}
rows.push({
name,
clonePath: cloneDir,
cloneExists: exists,
packageJsonVersion: pkgVersion,
gitHead: exists ? gitHead(cloneDir) : ''
})
}
const report = {
schema: 1,
atMs,
clonesRoot,
clonesRootExists: rootExists,
manifestPath: path.relative(root, manifestPath),
entryCount: uniq.length,
rows
}
fs.mkdirSync(path.dirname(outPath), { recursive: true })
fs.writeFileSync(outPath, JSON.stringify(report, null, 2) + '\n')
console.log(
'sync-holepunch-clones: wrote',
path.relative(root, outPath),
'entries=',
rows.length
)
}
main()
+50 -16
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env node
/**
* CI: flag vendored IIFE bundles that contain throw new Error("not implemented") style throws
* (narrower than TODO markers; complements verify-bundle-markers.mjs).
* CI: flag vendored IIFE bundles that contain Error throws with incomplete-implementation
* messages (complements verify-bundle-markers.mjs). Allowlist entries carry rationale.
*
* Matches: throw new Error("…"), cb(new Error("…")), emit("error", new Error("…")), etc.
*/
import fs from 'node:fs'
import path from 'node:path'
@@ -12,48 +14,80 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const bundlesDir = path.join(root, 'kernel/lib/bare/bundles')
const allowPath = path.join(root, 'docs/audit/bundle-throw-allowlist.json')
const THROW_RE =
/throw\s+new\s+Error\s*\(\s*['"](?:not\s+implemented|Not\s+implemented)['"]\s*\)/gi
/** Message substring patterns that indicate stub / abstract upstream behavior. */
const BAD_MESSAGE_RE =
/not\s+yet\s+implemented|not\s+implemented|abstract\s+method[^'"]*not\s+implemented/i
/**
* Extract string literals from new Error("…") or new Error('…') in source (handles \\ escapes).
* @param {string} txt
* @returns {Iterable<{ index: number, message: string, raw: string }>}
*/
function *iterNewErrorStringArgs(txt) {
const re = /new\s+Error\s*\(\s*(['"])/g
let m
while ((m = re.exec(txt)) !== null) {
const q = m[1]
const start = m.index + m[0].length
let i = start
let out = ''
while (i < txt.length) {
const c = txt[i]
if (c === q) {
yield { index: m.index, message: out, raw: txt.slice(m.index, i + 1) }
break
}
if (c === '\\' && i + 1 < txt.length) {
out += txt[i + 1]
i += 2
continue
}
out += c
i++
}
}
}
function loadAllowlist() {
if (!fs.existsSync(allowPath)) return new Set()
try {
const j = JSON.parse(fs.readFileSync(allowPath, 'utf8'))
const entries = Array.isArray(j.entries) ? j.entries : []
/** @type {Set<string>} */
const out = new Set()
const allowedFiles = new Set()
for (const e of entries) {
const f = e && typeof e.bundleFile === 'string' ? e.bundleFile.trim() : ''
if (f) out.add(f)
if (f) allowedFiles.add(f)
}
return out
return allowedFiles
} catch {
return new Set()
}
}
function main() {
const allowed = loadAllowlist()
const allowedFiles = loadAllowlist()
/** @type {string[]} */
const bad = []
for (const name of fs.readdirSync(bundlesDir)) {
if (!name.endsWith('.js')) continue
if (allowed.has(name)) continue
if (allowedFiles.has(name)) continue
const abs = path.join(bundlesDir, name)
const txt = fs.readFileSync(abs, 'utf8')
THROW_RE.lastIndex = 0
const m = THROW_RE.exec(txt)
if (m) {
const line = txt.slice(0, m.index).split('\n').length
bad.push(`${name}:${line}:${m[0].slice(0, 60)}`)
for (const { index, message } of iterNewErrorStringArgs(txt)) {
if (!BAD_MESSAGE_RE.test(message)) continue
const line = txt.slice(0, index).split('\n').length
const preview = message.length > 72 ? message.slice(0, 72) + '…' : message
bad.push(`${name}:${line}: ${preview}`)
break
}
}
if (bad.length) {
console.error(
'verify-bundle-throws: disallowed "not implemented" throws in bundles (allowlist: docs/audit/bundle-throw-allowlist.json):'
'verify-bundle-throws: disallowed incomplete-implementation Error messages in bundles.'
)
console.error('Allowlist (with rationale): docs/audit/bundle-throw-allowlist.json')
for (const b of bad) console.error(' ', b)
process.exit(1)
}
+6
View File
@@ -42,6 +42,9 @@ function main() {
)
if (onlyA.length) console.error('Only in kernel/:', onlyA.join(', '))
if (onlyB.length) console.error('Only in seeder kernel/:', onlyB.join(', '))
console.error(
'Remediate from repo root: rsync -a --delete kernel/ packages/bare-os-seeder/kernel/'
)
process.exit(1)
}
/** @type {string[]} */
@@ -58,6 +61,9 @@ function main() {
'kernel/ vs packages/bare-os-seeder/kernel/: content differs for:',
diffs.join(', ')
)
console.error(
'Remediate: edit canonical kernel/ only, then: node scripts/bundle-kernel-init.mjs && rsync -a --delete kernel/ packages/bare-os-seeder/kernel/'
)
process.exit(1)
}
/** Staged /bin scripts must carry the BARE_OS_BIN_API pragma (see coreutils runtime). */