NumericalOS

docs/superpowers/specs/2026-08-04-numericalos-design.md

back to source

# NumericalOS -- Design

**Date:** 2026-08-04
**Status:** approved design, pre-implementation
**Repo:** `Documents/numericalos`
**Siblings:** `Documents/intrikata` (console), `Documents/intrikata-topology` (graph backend)

---

## 1. What this is

NumericalOS is a bootable Linux userspace whose init system **is** an
intrikata-topology graph. There are no unit files, no `/etc/systemd`, no
hand-authored service definitions. Every supervisable thing, every boot
ordering constraint, every health rule, and every remediation path is a node
in an M/G/S/MGS graph quartet, with ASEC swarms as the self-heal loop.

A booted NumericalOS machine joins the live topology as a first-class compute
node: it advertises its silicon and its supported ops, and the coordinator
schedules swarm members onto it.

"Covers all chip infrastructures" is the operative constraint: the same graph
boots on x86_64, aarch64, riscv64, armv7, ppc64le, s390x, and the long tail,
via the smallest userspace bootstrap that can honestly claim that range.

### Why "purely from the graph" is literal, not aspirational

The bootstrap's own architecture-detection table is generated from `ArchTarget`
nodes. Adding loongarch64 support is a node insert, not a code edit. The unit
set is derived from the ops registry rather than authored. There is exactly one
runtime input artifact -- `numos.state` -- and it is a deterministic export of the
four graphs.

---

## 2. Grounded facts this design rests on

Measured 2026-08-04 on the development machine, not assumed:

| Fact | Value | How measured |
|---|---|---|
| Registered ops | **196** | `GET /api/ops` on live IntrikataTopology |
| Live graphs | **471** | `GET /api/graphs` |
| Prior `numericalos` work | **none** | filesystem grep + graph search, both empty |
| Compiler toolchain | **absent** | `zig`/`gcc`/`cc`/`make`/`musl-gcc` all missing |
| Container toolchain | **absent** | `docker`/`podman` missing |
| WSL | **not installed** | `wsl --status` |

The absent toolchain is load-bearing: it is why the bootstrap is designed
zero-compile-first with the compiled path as an optional accelerator, rather
than the reverse.

---

## 3. Decisions and rationale

| # | Decision | Rationale |
|---|---|---|
| D1 | Real bootable OS, not a hosted metaphor | The graph is the init system; PID 1 executes it |
| D2 | Purpose = intrikata compute node | Units derive from the existing 196-op registry rather than being invented |
| D3 | Hybrid bootstrap: POSIX-sh floor + static fast path | Only option satisfying **smallest** *and* **all chips** without faking either |
| D4 | We ship no kernel | "Smallest bootstrap" scopes to userspace; BYO distro/upstream kernel |
| D5 | `curl \| sh` does not replace PID 1 by default | Silently taking over PID 1 on a live host is destructive; requires `--take-pid1` |
| D6 | Baked state is the boot floor; live graph is an overlay | A machine must boot with no network, always |
| D7 | Fail-closed on hash mismatch | An unverified init is worse than a halted machine with a named reason |

D6 deliberately mirrors the `origin_bridge: local_only` / `fallback_state:
ready-cache` pattern already running in IntrikataTopology, so NumericalOS
inherits a proven architecture rather than inventing a second one.

---

## 4. The graph quartet

| Graph | OS role | Contains |
|---|---|---|
| `META: NumericalOS` | policy -- what *may* run | admission rules, capability grants, arch policy, trust roots |
| `GENESIS: NumericalOS` | bring-up -- ordered stages | `BootPhase` chain: mount, dev, net, clock, identity, join |
| `MGS: NumericalOS` | steady state | 196 ops as `OSUnit` nodes plus supervision policy |
| `SHADOW: NumericalOS` | failure | `HealthPredicate` detectors, degraded-state definitions, runtime residuals |
| `Agent Swarm Extension Complex (v1)` *(existing)* | self-heal | SHADOW fire produces `SwarmInvocation`; remediation lands as `Contribution` |

---

## 5. Schemas

All five are new schemas in intrikata-topology. Per the established constraint,
schema creation goes through the scanner-and-regenerate path, not by hand-editing
the seed. All node writes via MCP are ASCII-only (the Windows stdio write path
mojibakes non-ASCII); Unicode content, if ever needed, goes through direct SQLite
plus cache invalidation.

### 5.1 `OSUnit`

The supervisable thing. One per op, plus a small number of seeded infrastructure
units (mount, net, clock, identity, join). Both kinds are graph nodes -- the
seeded ones are authored *as nodes, once*, never as files on the target. The
"no hand-authored service definitions" invariant in section 1 is about the
target filesystem: a NumericalOS machine has no unit files to edit.

| Field | Type | Notes |
|---|---|---|
| `name` | string | unique within graph |
| `exec` | string | argv, POSIX-quoted |
| `kind` | enum | `oneshot` \| `longrun` \| `target` |
| `requires` | string[] | hard dependency; failure propagates |
| `after` | string[] | ordering only; failure does not propagate |
| `restart` | enum | `never` \| `on-failure` \| `always` |
| `backoff_ms` | int | initial restart delay |
| `backoff_max_ms` | int | ceiling; `backoff_ms` doubles until it reaches this |
| `arch_mask` | string[] | empty = all arches |
| `resource_envelope` | object | `{cpu_pct, ram_mb, timeout_s}` |
| `health_probe` | ref | to a `HealthPredicate`, nullable |

`requires` and `after` together form the DAG. Cycles are rejected at export
time, not at boot time -- a machine must never discover a cycle as PID 1.

### 5.2 `BootPhase`

| Field | Type | Notes |
|---|---|---|
| `name` | string | e.g. `net` |
| `ordinal` | int | strict ordering; ties rejected at export |
| `required_units` | string[] | must reach ready before phase completes |
| `on_failure` | enum | `halt` \| `degrade` \| `continue` |

### 5.3 `Capability`

The join advertisement. Produced at runtime, not authored.

| Field | Type |
|---|---|
| `arch` | string (canonical) |
| `cores` | int |
| `ram_mb` | int |
| `cpu_features` | string[] |
| `ops_supported` | string[] |
| `degraded` | bool |

`degraded` is set when a `degrade`-mode phase failed, so the coordinator stops
scheduling onto a partially-broken node without the node dropping out entirely.

### 5.4 `HealthPredicate`

| Field | Type | Notes |
|---|---|---|
| `probe` | string | argv; exit 0 = healthy |
| `interval_s` | int | |
| `threshold` | int | consecutive failures before firing |
| `on_fire` | ref | ASEC swarm to invoke |
| `local_action` | enum | `none` \| `restart-unit` \| `degrade-node` |

`local_action` runs with no network. `on_fire` requires network and is queued
when offline -- never blocking.

### 5.5 `ArchTarget`

The multi-arch registry. This node type is what makes the bootstrap
graph-derived.

| Field | Type | Notes |
|---|---|---|
| `canonical` | string | e.g. `aarch64` |
| `uname_m_aliases` | string[] | e.g. `["aarch64", "arm64"]` |
| `zig_triple` | string | nullable when no static build published |
| `busybox_variant` | string | fallback identifier |
| `artifact_sha256` | string | nullable until built |

Initial seed set (canonical -> aliases):

```
x86_64        x86_64, amd64
aarch64       aarch64, arm64
riscv64       riscv64
arm           armv7l, armv6l, armhf
powerpc64le   ppc64le, powerpc64le
s390x         s390x
mips64el      mips64el
x86           i686, i386, x86
loongarch64   loongarch64
```

`loongarch64` Zig-target availability is verified at build time, not asserted
here. Where a `zig_triple` is null or unbuilt, the busybox fallback carries the
arch -- this is exactly the mechanism by which "all chip infrastructures" stays
honest.

---

## 6. `numos.state`

The single runtime input. Deterministic export of the four graphs, produced by
an exporter modeled on the proven `export_snapshot.py` scrub logic (Windows
paths, UUIDs, long hex, and username redacted before write).

`numos.state` is a line-oriented, space-delimited record format -- one record
per line, tagged by its first field, `awk`/`read`-native so `bootstrap.sh` and
`numinit.sh` can parse it with POSIX shell builtins alone:

```
V <version>
C <sha256 hex of the canonical body>
A <canonical> <aliases-csv|-> <zig_triple|-> <busybox_variant> <artifact_sha256|->
P <ordinal> <name> <on_failure> <required_units-csv|->
U <name> <kind> <restart> <backoff_ms> <backoff_max_ms> <arch_mask-csv|-> <health_probe|-> <requires-csv|-> <after-csv|-> <exec...>
X <name> <interval_s> <threshold> <local_action> <on_fire|-> <probe...>
```

One `V` line and exactly one `C` line; any number of `A`/`P`/`U`/`X` lines.
Empty lists render as `-`; comma-joined otherwise. Nullable scalars render as
`-` when absent. `exec` and `probe` are the last field on their line and may
contain embedded spaces -- they are not further escaped. Records within each
block (`A`, `P`, `U`, `X`) are sorted lexicographically by their full rendered
line, and `arch_targets`/`boot_phases`/`units` are drawn from the graph in that
sorted order -- so op-registry fetch order and node-insertion order never
affect output. `generated_at` is deliberately absent from the format: a
wall-clock value would make byte-identical export across runs impossible, so
it is not part of the hashed body and not emitted at all. The `C` line's hash
covers every line except itself (`canonical_body` in `numos/canonical.py`), so
it can be recomputed before or after stamping and get the same answer.

**Amendment 2026-08-04 (implementation).** This section originally specified
JSON. Shell cannot parse JSON without a parser, and shipping one contradicts
"smallest bootstrap," so `numos.state` is line-oriented and awk/`read`-native.
The JSON form survives as a web view, `numos.state.json`, served at
`/data/numos.state.json`. Both are emitted by one exporter; only the
line-oriented form is hashed by the `C` record and consumed at boot.

Canonicalization: records sorted within each block, no insignificant
whitespace, LF endings -- so the same graph state always yields the same hash.
`numinit` refuses to execute a state file whose recomputed hash does not match
the stamped `C` record, and (once signing lands in Spec 5) whose signature
does not verify.

---

## 7. Boot chain

### Vector 1 -- bare metal / VM

Initramfs whose `/init` is `bootstrap.sh`. No kernel shipped.

```
[kernel] -> initramfs /init = bootstrap.sh
  1. mount /proc /sys /dev
  2. ARCH=$(uname -m) -> canonical, via baked ArchTarget table
  3. resolve init artifact:
       prefer  numinit-$ARCH        (static, ~200KB)
       else    busybox-static-$ARCH + numinit.sh
  4. sha256 verify  -- FAIL-CLOSED: mismatch halts with a named reason
  5. load numos.state -> verify content_hash
  6. exec numinit as PID 1
```

This is the design of the vector, not a description of what the code does.
Which of the six steps Spec 1 actually implements is enumerated in
section 11, residual 5.

### Vector 2 -- existing Linux host

`curl numericalos.com/boot | sh` attaches a running host as a node, with
`numinit` supervised *under the existing init*. PID 1 is untouched unless
`--take-pid1` is passed explicitly. This is the practical on-ramp and the only
vector exercisable end-to-end without QEMU.

### `numinit` responsibilities as PID 1

- Reap zombies; handle `SIGCHLD`, `SIGTERM`, `SIGINT`
- Walk the `GENESIS` `BootPhase` chain by ordinal
- Per phase: resolve the `requires`/`after` DAG, start units, honor `on_failure`
- Settle into `MGS` steady state: supervise `longrun` units, restart with backoff
- Run `SHADOW` `HealthPredicate`s on interval; apply `local_action`; queue
  `on_fire` escalations until network is up
- Serve a control socket at `/run/numinit.sock` for `numctl`

### Failure semantics -- explicit, never silent

| Condition | Behavior |
|---|---|
| Artifact hash mismatch | halt, named reason on console |
| `numos.state` hash mismatch | halt, named reason on console |
| Phase failure, `on_failure=halt` | halt, naming phase and failed unit |
| Phase failure, `on_failure=degrade` | continue; set `Capability.degraded=true` |
| Phase failure, `on_failure=continue` | continue; log only |
| SHADOW fire, no network | apply `local_action`; queue escalation |
| Overlay reconcile failure | log; boot proceeds on baked floor |

---

## 8. numericalos.com

Same stack as intrikata.com -- Cloudflare Pages plus Functions plus D1 plus KV --
inheriting its deploy discipline: `--branch=main` pinning (branch-mismatch trap),
`functions/` tracked in git (a Git-triggered Pages build without Functions
silently drops every `/api/*` route), scrub-before-publish.

| Surface | Purpose |
|---|---|
| `/` | console -- single-file terminal UI, same lineage as the intrikata console |
| `/boot` | the POSIX bootstrap, `text/plain`, curl-able |
| `/artifacts/<arch>/numinit`, `.sha256` | per-arch artifacts |
| `/data/archtargets.json` | generated from `ArchTarget` nodes |
| `/data/numos.state` plus `/data/numos.state.json` | scrubbed default state export -- line-oriented (hashed, boot-consumed) plus its JSON web view |
| `POST /api/join` | Capability in, node id plus assignment out |
| `/api/nodes` | fleet view |
| `/api/attest/<id>` | checksums and attestation |

---

## 9. Testing and verification gates

### Testable on the development machine (Windows, git-bash)

- **Arch resolution.** `bootstrap.sh` is table-driven; feed synthetic `uname -m`
  values, assert canonical resolution and artifact selection, including the
  fallback branch when `zig_triple` is null.
- **Fail-closed behavior.** Corrupt an artifact hash and a `numos.state` hash;
  assert halt with the correct named reason in each case.
- **Phase ordering and DAG.** Run `numinit.sh` against a fake root with stub
  units; assert ordinal ordering, `requires`/`after` resolution, and correct
  `on_failure` handling for all three modes.
- **Restart backoff.** Assert doubling up to `backoff_max_ms`.
- **Export determinism.** Same graph state yields byte-identical `numos.state`
  and identical `content_hash` across runs.
- **Cycle rejection.** A cyclic `requires` graph is rejected at export, not boot.

### NOT verifiable here -- named gates, not silent gaps

| Claim | Gate required |
|---|---|
| "It boots" | Linux host with `qemu-system-<arch>`, per arch |
| True PID-1 semantics | same |
| Foreign-arch execution | same |
| Static artifact size and correctness | Zig toolchain download |

No "it boots" claim will be made in any surface, README, or console output
until the QEMU gate has actually run. Until then the honest status is
"bootstrap logic tested; boot unverified".

---

## 10. Scope

**In scope for Spec 1 (this document's implementation):** the five schemas, the
four graphs seeded, the exporter producing `numos.state`, `bootstrap.sh`,
`numinit.sh` (shell path), and the test suite above.

**In scope for Spec 2:** numericalos.com -- Pages, Functions, D1, console.

**Out of scope, sequenced as separate specs:**

| Spec | Scope | Prerequisite |
|---|---|---|
| 3 | Zig static `numinit` fast path | Zig download |
| 4 | QEMU multi-arch boot verification | Linux host |
| 5 | Signing and attestation | Spec 3 |

**Explicitly not doing:** shipping a kernel, building a package manager,
replacing PID 1 by default on live hosts, or claiming arch coverage that has
not been either built or covered by the busybox fallback.

---

## 11. Known residuals

Named rather than hidden, per Tarski-acknowledgement discipline:

1. **Boot unverified from this machine.** Structural, not incidental -- see the
   gate table in section 9.
2. **PID-1 signal handling in `ash` is imperfect.** The shell fallback path
   reaps on `wait` but cannot match a real static binary's signal discipline.
   This is precisely why the Zig fast path exists; the fallback trades rigor for
   reach, deliberately.
3. **Derived unit sets drift with the ops registry.** Mitigated by
   `content_hash` plus refusal-on-mismatch, but a registry change still silently
   changes what a node will run on next reconcile. Overlay-diff visibility is a
   Spec 2 console concern.
4. **`loongarch64` Zig availability unconfirmed.** Resolved at build time; the
   busybox fallback covers it either way.

5. **Boot-chain vector 1 is only partly implemented.** Section 7's numbered
   steps are the design; `boot/bootstrap.sh` as of Spec 1 covers these:

   | Step | Status in Spec 1 |
   |---|---|
   | 1. mount `/proc` `/sys` `/dev` | not in `bootstrap.sh`. The seeded phase-10 units carry it, and no unit has ever run on a machine |
   | 2. `uname -m` to canonical, via the baked table | implemented, tested |
   | 3. resolve init artifact | selection only: the artifact it *would* use is printed, nothing is fetched |
   | 4. sha256 verify the artifact | `numos_verify_sha256` implemented and tested, no caller outside tests |
   | 5. load `numos.state`, verify content hash | implemented, tested, and the shell and Python verifiers agree case for case |
   | 6. exec `numinit` as PID 1 | not implemented |

   Steps 3, 4 and 6 need published artifacts and a real Linux host, so they
   are sequenced with Specs 3 and 4 rather than stubbed. Vector 2's
   `curl | sh` on-ramp is a Spec 2 concern and is likewise not built.

6. **Steady-state supervision is implemented but not wired in.**
   `numos_supervise`, `numos_backoff_sequence`, `numos_sleep_ms` and
   `numos_sleep_arg` exist in `boot/numinit.sh` and are covered by tests,
   but `numos_main` calls `numos_run_phase` directly and nothing calls the
   supervisor. So section 7's "settle into MGS steady state: supervise
   `longrun` units, restart with backoff" does not happen: a `longrun` unit
   is started and then left alone, and `restart`, `backoff_ms` and
   `backoff_max_ms` are read from the state file only inside the unwired
   function. Restarting a unit is steady-state behavior, not boot behavior,
   and belongs to a later spec.

7. **PID-1 duties beyond the phase walk are unimplemented.** No zombie
   reaping and no `SIGCHLD`/`SIGTERM`/`SIGINT` handling; no
   `HealthPredicate` execution (`X` records render, parse and round-trip,
   but no probe is ever run, no `local_action` applied, no `on_fire`
   queued); no `/run/numinit.sock` control socket, and therefore no
   `numctl` -- which the seeded `identity` and `join` units nevertheless
   name as their exec.