Skip to content

Kernel Decisions

Nine decisions that define how Rumpk works — and why it doesn't work like Linux.

K1: Unikernel Over Monolithic Kernel

Status: Accepted

Context

Traditional monolithic kernels mix isolation boundaries with scheduling logic: 2000+ cycle context switches, thousands of syscalls, ambient authority security. Microkernels (seL4, MINIX) trade performance for formal verification. Neither fits a system designed for 8-bit MCUs and Mars rovers in the same binary.

Decision

Rumpk is a modular unikernel: single address space, no process boundary, application carries its own OS logic via libnexus.a (the Membrane layer).

Alternatives Rejected

OptionWhy Not
Linux monolith~2000 cycle hypercall overhead, /proc complexity, ambient authority model
Full microkernel (seL4)10-100ms latencies, formal verification overhead not justified for commercial systems
Container stack (Docker)Requires underlying OS, 200MB+ runtime overhead per container

Consequences

  • Zero kernel context switches for I/O (2-5 cycle latency)
  • Deterministic, verifiable execution paths
  • POSIX compatibility requires Membrane translation layer
  • Each application instance includes its own kernel code (~280KB)

K2: Zig HAL + Nim Kernel Logic

Status: Accepted

Context

Hardware interactions demand precise memory layouts, volatile access, and atomic operations. Kernel scheduling and fiber management benefit from higher-level abstractions like pattern matching and ARC memory management. One language forces compromise in either direction.

Decision

Split the kernel into two language domains:

  • hal/ (Zig): Direct hardware — UART, VirtIO, GIC, MMU, interrupt vectors
  • core/ (Nim): Scheduler, fibers, channels, VFS, capability enforcement

They meet at the HAL struct — a C-compatible function pointer table.

Alternatives Rejected

OptionWhy Not
All ZigRequires custom async/await runtime; loses Nim's fiber elegance
All NimLow-level operations become brittle; inline asm escapes are unsafe
C for HALEqually verbose as Zig but more fragile (no comptime, no safety)

Consequences

  • Clean separation: Physics (Zig) vs. Logic (Nim)
  • Nim fibers survive HAL panics (channels persist at fixed addresses)
  • Future Janus language can slot into either layer
  • Two runtime ecosystems to maintain; ABI discipline required

K3: 12 Frozen Syscalls

Status: Superseded by K7 — Kernel Protocol Boundary (2026-07-02). Retained as historical record.

Superseded

The "12 frozen syscalls" framing has been replaced by the Kernel Protocol doctrine: the kernel boundary is a protocol of typed ION Ring packets, signed BKDL descriptors, and capability verbs, not a fixed syscall table. The trap numbers below remain valid as the current implementation surface of the Trap ABI, but applications should target libnexus rather than the syscall numbers directly. See K7 for the active decision.

Context

POSIX defines 400+ syscalls. Each is a security boundary crossing and a context switch point. Most are vestigial. Nexus routes all bulk I/O through ION Ring buffers — the kernel only needs a handful of control operations.

Decision

Exactly 12 syscalls + 1 meta-slot. Frozen forever:

pledge, unveil, read(0), write(1), map, unmap, spawn, kill, yield, time, random, halt

All data I/O goes through ION Rings (zero context switch). The syscall ABI is compiled into binaries at link time — no versioning, no compat layers.

Alternatives Rejected

OptionWhy Not
Full POSIX400+ audit points, each with versioning and compat requirements
seL4 model100+ capability operations, more complex interface
Plan 9 RPCEverything-as-RPC is flexible but adds latency

Consequences

  • Formal verification feasible (12 entry points vs. 400+)
  • Security surface trivially auditable
  • Binary ABI stable forever (no libc version wars)
  • Legacy applications require Membrane translation
  • No backfill possible — missed use cases stay missed

K4: ION Rings Over Pipes and Sockets

Status: Accepted

Context

POSIX pipes are byte-streams requiring syscalls per read/write. Sockets add TCP/IP overhead for local communication. Financial exchanges proved the Disruptor pattern achieves nanosecond latencies with zero allocations.

Decision

All IPC uses Disruptor-style ring buffers (ION Rings):

  • 128-byte fixed header + dynamic payload in shared memory
  • Pre-allocated at boot — no dynamic allocation
  • Each fiber gets dedicated RX/TX rings
  • Backpressure via atomic head/tail pointers

Alternatives Rejected

OptionWhy Not
POSIX pipesByte-oriented, syscall per operation, 2-5 µs minimum latency
POSIX socketsTCP overhead, kernel routing, ARP lookups for local IPC
Actor model (Erlang)Elegant but requires heavyweight runtime (50MB Erlang VM)

Consequences

  • ~100ns latency for local IPC (vs. 2-5 µs for pipes)
  • Zero-copy with proper buffer ownership
  • Automatic backpressure (no overflow, no unbounded queues)
  • Fixed-size rings require capacity pre-planning
  • Buffer ownership semantics require discipline

K5: Tickless Event-Driven Scheduler

Status: Accepted

Context

Traditional schedulers wake the CPU every 1-10ms via timer interrupt. This prevents deep sleep states and wastes power even when the system is completely idle. Modern hardware (V-Sync, DMA completion, network IRQs) provides natural event signals.

Decision

No periodic scheduler tick. The scheduler is an ISR, not a loop:

  • When RunQueue is empty: CPU executes WFI, power drops to leakage levels
  • Scheduling occurs only as side-effect of hardware interrupts
  • Wakes on V-Sync (120Hz), packet arrival, DMA completion
  • Software timeouts registered as explicit deadline events

Alternatives Rejected

OptionWhy Not
Fixed tick (1ms)Forces idle wake-ups, 1-3% CPU overhead on idle
Dynamic tick (Linux NO_HZ)Heuristic tuning adds complexity without dramatic benefit
Per-app event loopRequires application awareness, not transparent

Consequences

  • 5-10% power reduction on idle (mobile/embedded)
  • Events trigger immediate scheduler response (deterministic latency)
  • No fairness pathology (Spectrum model prevents starvation)
  • Software timeouts must be explicitly registered
  • Some batch workloads lose natural fairness preemption points

K6: Fibers Over Processes and Threads

Status: Accepted

Context

Processes cost 1-5 MB RAM and 1-5 µs per context switch. Threads share memory but invite race conditions and deadlocks. Nexus operates in a single address space — process isolation provides no benefit, only cost.

Decision

All concurrency uses fibers (Nim async/await):

  • ~1 KB RAM per fiber (vs. 1-5 MB per process)
  • Cooperative switching: ~100-200 ns (vs. 1-5 µs for context switch)
  • No preemption within a priority level — no race conditions by construction
  • Spectrum-based fairness: Photon > Matter > Gravity > Void

Alternatives Rejected

OptionWhy Not
Heavy processesIsolation already provided by capabilities; still pay full context switch
Shared-memory threadsLock-free data structures are hard; deadlocks and races omnipresent
Actor model (Erlang)Elegant but 50MB base runtime; too heavy for 280KB kernel

Consequences

  • Thousands of fibers practical (MCUs can run 20+, desktops run 10,000+)
  • No race conditions — shared memory with cooperative scheduling
  • 4KB default stack per fiber
  • No true parallelism on single core (concurrency only)
  • Long-running fibers must yield voluntarily

A1: Single Address Space

Status: Accepted

Context

Multiple address spaces (one per process) provide isolation but require TLB flushes on context switch (100-500 cycles). In a unikernel where isolation comes from capabilities, not virtual address separation, the overhead is pure waste.

Decision

One virtual address range for the entire system:

  • Cell A: 0x080000000x0FFFFFFF
  • Cell B: 0x100000000x17FFFFFF
  • Isolation is physical (PMP registers / MMU page tables), not VA ranges
  • No TLB flush needed when switching between cells

Alternatives Rejected

OptionWhy Not
Per-process address spacesTLB flush = 100-500 cycles per switch
ASID taggingMitigates flushes but complex hardware requirement, not universal
No isolationFast but insecure

Consequences

  • Zero TLB flush overhead on cell switches
  • Simpler kernel (no VA→PA remapping per cell)
  • Shared read-only pages between cells (deduplication)
  • Address space must be statically partitioned at boot
  • Some hardware (ARM Realm Management) conflicts with this model

A2: SysTable Frozen ABI

Status: Amended by K7 — Kernel Protocol Boundary (2026-07-02). The fixed-address idea survives; the 240-byte size and "12 syscall pointers" claims do not.

Amended

The SysTable remains a fixed-address structure (RISC-V 0x83000000, ARM64 0x50000000) with a magic-number-validated layout. Per SPEC-022 it is now 136 bytes and carries ION ring descriptors + a small set of fast-path function pointers + framebuffer info (not "12 syscall pointers + 1 meta-slot"). The larger doctrinal shift is that the SysTable is now framed as the Trap-ABI entry surface, not the application contract — see K7.

Context

Dynamic registration (UEFI-style) is flexible but requires boot negotiation. Fixed addresses are simple and can be compiled into binaries at link time. The kernel only needs a handful of function pointers.

Decision

SysTable at a fixed physical address, immutable layout:

ArchitectureAddress
RISC-V 640x83000000
ARM640x50000000

136 bytes. ION ring descriptors (RX, TX, event, command, input, semantic transport) + a small set of fast-path function pointers (fn_vfs_open, fn_vfs_read, fn_vfs_write, fn_vfs_close, fn_pledge, fn_yield) + framebuffer info. The fixed-address layout never changes across versions; the function-pointer set is allowed to migrate behind ring messages over time.

Alternatives Rejected

OptionWhy Not
Dynamic registrationBoot complexity, discovery protocol needed
Multiple SysTables per cellMemory overhead, harder to debug
Syscalls only (no table)Loses zero-copy ring optimization

Consequences

  • Apps hardcode SysTable address — no discovery code
  • Applications that target libnexus are insulated from SysTable evolution
  • Runtime structure validation trivial (magic + size check)
  • Wrong address = hard crash (no fallback)

A3: DragonflyBSD LWKT Scheduler Model

Status: Accepted

Context

Linux CFS optimizes for fairness (O(log n), preemptive). DragonflyBSD LWKT optimizes for predictability (priority-based, cooperative). Nexus needs deterministic latency, not max fairness.

Decision

Adopt DragonflyBSD's Lightweight Kernel Thread model:

  • Fixed priority per fiber (Spectrum tier: Photon > Matter > Gravity > Void)
  • Round-robin within same priority level
  • Cooperative: fibers yield voluntarily, no preemption overhead
  • Data moves between cores via ION Ring messages (no shared-memory mutexes)

Alternatives Rejected

OptionWhy Not
Linux CFSDesigned for multitenant fairness; preemption adds jitter
Rate MonotonicRequires static scheduling; less flexible for dynamic workloads
Custom from scratchHigh development risk, debugging nightmare

Consequences

  • Simpler scheduler (fewer lines = fewer bugs)
  • Predictable: high-priority fibers always run first
  • No starvation: Gravity/Void get time after Photon/Matter sleep
  • Not perfectly fair (background jobs may starve under sustained foreground load)
  • Long-running fibers must yield() voluntarily

A4: No Microkernel Message-Passing

Status: Accepted

Context

seL4 routes all inter-process communication through the kernel via formal RPC. This enables formal verification but adds ~1 µs overhead per message. Nexus already separates capability checking from data transfer.

Decision

The kernel does not implement RPC or message-passing:

  • Kernel only performs capability checks + side effects (memory allocation, pledge verification)
  • Data exchange happens via shared ION Rings — userland responsibility
  • Kernel is completely passive (no mediator, no routing, no serialization)

Alternatives Rejected

OptionWhy Not
seL4-style RPCEvery message = hypercall, ~1 µs overhead each
Shared memory without kernel helpEasy to deadlock, hard to debug
Hybrid (RPC for control, rings for data)Complexity without clear benefit over pure rings

Consequences

  • Kernel stays tiny (no RPC logic, no message routing)
  • Verification scope smaller (only capability checks, not datapath)
  • IPC latency is nanoseconds, not microseconds
  • Applications must implement their own wire protocols
  • Debugging requires understanding both capability system and app-level protocols

K7: Kernel Protocol Boundary

Status: Accepted

Date: 2026-07-02

Supersedes: K3 — 12 Frozen Syscalls

Amends: A2 — SysTable Frozen ABI

Context

K3 framed the kernel boundary as a fixed table of 12 syscalls "frozen forever." That framing conflated three different things: the CPU trap entry, the kernel's semantic surface, and the per-language calling convention. As the project matured, this conflation produced concrete costs:

  • The public syscall reference advertised POSIX-flavored entry numbers (SYS_OPEN, SYS_READ, integer fd) that contradicted the Native Interface described in SPEC-070.
  • The SysTable size drifted between 136 bytes (per SPEC-022) and 240 bytes (per older docs) with no single source of truth.
  • External readers could not tell whether Nexus was "POSIX with capabilities" or "capability-native with a POSIX shim."
  • The "frozen forever" wording blocked additive evolution: new packet types and capability verbs had no clean migration story if they had to slot into a fixed-width table.

The actual implementation had already moved on. The kernel communicates primarily through typed ION Ring packets (SPEC-022), loads code only through signed BKDL descriptors (SPEC-071), and gates every effectful operation through a 7-verb capability algebra (SPEC-051). POSIX-style syscalls survive only as a transitional shim.

Decision

The kernel boundary is a protocol, not a syscall table. The stable contract between userland and kernel is the union of:

  1. Typed ION Ring packets — the wire format for every channel interaction
  2. Signed BKDL descriptors — the spawn/load contract for every artifact
  3. Capability verb algebra — the authority surface (SPAWN, SEND, RECV, MAP, MASK, TICK, GRANT)

The CPU-facing trap ABI (ecall / svc #0 / syscall) is reframed as an implementation detail of how the protocol is mechanically submitted. It is allowed to change per architecture and per kernel version. Applications target libnexus (the canonical C ABI) or an idiomatic language binding; neither reaches for raw trap numbers.

The ABI is an implementation detail. The protocol is the contract.

Languages are transport adapters. The protocol is the only privileged surface at the kernel boundary. The SDK tiering (SPEC-075-INTERFACE-LANGUAGE-BINDINGS) classifies languages by what they may do at that boundary: Tier 2 Native (Janus, Nim), Tier 3 Bridge (Zig HAL-only, Rust FFI-only, C/C++ Membrane-only).

Alternatives Rejected

OptionWhy Not
Keep K3 "12 frozen syscalls"Conflates trap ABI with semantic surface; blocks additive evolution; contradicts SPEC-070
Reframe as "microkernel message-passing" (seL4 model)Adds ~1 µs overhead per message; rejected in A4
Reify POSIX as the native surfaceReverses the direction of SPEC-073 (Chimera bridge); POSIX is a compatibility layer, not the contract

Consequences

  • The trap ABI may evolve without breaking applications that target libnexus or a Tier-2 binding
  • New packet types and capability verbs are added by spec amendment; wire layouts are additive (read-only after ratification)
  • Authority is orthogonal to the protocol: capability bundles are validated atomically at spawn, reducing to the seven verbs
  • Compatibility bridges (Chimera POSIX, NipCell Linux, Mimic foreign-driver) translate upward to the protocol; they are not the contract
  • The SysTable (A2) remains a fixed-address entry surface, but its contents continue migrating from function pointers to ring descriptors
  • Public docs must distinguish Trap ABI / Kernel Protocol / Language Bindings; the word "syscall" in user-facing copy must be qualified by which layer it refers to

References

  • NORTH-STAR-LIBERTARIA-COMMONWEALTH.md — Protocol boundary doctrine (foundation doctrine file)
  • SPEC-070-INTERFACE-NATIVE-ABI — Native application lifecycle and channel shape
  • SPEC-071-INTERFACE-BINARY-MANIFEST — BKDL descriptor format
  • SPEC-022-KERNEL-ION-SYSTABLE — ION Ring layout, SysTable structure (136 bytes)
  • SPEC-075-INTERFACE-LANGUAGE-BINDINGS — Language tier stack
  • SPEC-073-INTERFACE-CHIMERA-POSIX-BRIDGE — POSIX translation layer

K8: Cells Own Application State

Status: Accepted

Context

Every general-purpose OS eventually grows a state daemon. Linux has systemd, D-Bus, udev, gsettings, dconf, and NetworkManager — each one a kingdom that began as a helper and accreted governing power. macOS has launchd and the defaults system. Windows has the Registry. The pattern is consistent: a kernel-side or pid-1-side service that holds application state, mediates access, and slowly becomes the actual application runtime.

The result is always the same. Applications become clients of the state daemon. The daemon gains query semantics, then scripting, then scheduling authority, then policy enforcement. The kernel starts reacting to daemon state instead of governing primitives. Two applications cannot share a knob without the daemon observing, logging, and judging the transaction. The kernel ends up doing things the applications asked it not to.

Nexus hit this threshold at M6 with the Nexus Registry Protocol (NRP). NRP was correctly designed as a protocol, not a database — but its Tier 0 Ambient authentication and Config Ledger backend made every mutation flow through a kernel-side event stream. The architectural pressure is already visible: if two userland apps want to negotiate a slider, the kernel sees the writes, logs them to the System Truth Ledger, and propagates them as system truth. That is wrong. Application state is not system truth. Application state is application truth, owned by the application, observed only by those the application has authorized.

The question is not whether to fix NRP. NRP is correct for system truth. The question is what role the kernel plays when the truth in question is not system truth at all.

Decision

The kernel governs. Cells remember.

Nexus separates two categories of truth:

text
System truth     → TruthDB governs (kernel-side, ledger-backed)
Application truth → Cells own (userland, in-cell, cell-scoped)

The kernel's role narrows to four primitives:

text
Govern   → capabilities, pledges, spectra, contracts
Route    → ION ring bytes by handle, not by content
Verify   → manifest hashes, signatures, contract validity
Survive  → hardware faults, cell crashes, reboot boundaries

The kernel does not do: store application state, observe application state, log application mutations, query application state, or schedule based on application state. Those concerns move into the cell.

Six invariants follow:

#Invariant
1TruthDB governs system truth. Cells own application truth. The two are not mixed at the storage layer.
2Application state lives in the cell's heap, persisted to a cell-private NexFS partition, replicated only via an explicit Pact subscriber.
3ION transports observations (events), never database queries. The kernel routes bytes; it cannot inspect payload.
4The scheduler reacts to events, not stored data. Data carries no scheduling semantics. Priority is event metadata, bounded by the mutator's Spectrum tier.
5Objects are passive. Cells execute. An object never calls another object. A cell subscribes, observes, and acts.
6No state daemon. No pid-1 registry. No D-Bus successor. Each cell carries its own state library linked as libnexus_clos.a.

Alternatives Rejected

OptionWhy Not
Extend NRP with a cell-local scope flagConflates system and application truth at the protocol layer. The Config Ledger write path remains; the kernel still sees every mutation. Does not satisfy invariant 2.
Run a userland closd service that all cells connect toRecreates D-Bus with new paint. A central state service inevitably accretes query semantics, scripting hooks, and policy enforcement. Violates invariant 6.
Per-cell database files, no protocolCells cannot share state. Forces every cross-cell interaction back through TruthDB, which is exactly the problem K8 exists to solve.
Put application state in NexFS as typed files (Plan-9 style)Loses transactional semantics, subscription model, and ownership tracking. Reintroduces heuristic file-scraping. Violates the spirit of ST2.
Treat all state as CRDTs from day oneOver-constrains the type system. CRDTs are opt-in for the replicated durability tier only; default concurrency model is single-writer/multi-reader.

Consequences

  • The kernel becomes smaller and more defensive. It governs; it does not remember on behalf of applications.
  • TruthDB's scope narrows to system truth. Its scope field gains cell-local and pair-contract values; cell-local writes do not enter the global STL.
  • The Cell Contract (SPEC-171 §2) gains a shares { ... } block declaring cross-cell domains and a clos { ... } block declaring durability defaults.
  • Foreign and sandboxed cells (NipCells, POSIX cells) must declare a one-time contract before sharing state. The kernel validates the contract; it does not mediate subsequent traffic.
  • NexShell discovery queries a manifest layer only — domain names, not values. Sensitive cells may declare ephemeral (manifest unpublished) or anonymous (manifest hashed, name not visible) modes.
  • Pact replication becomes a subscriber pattern: a forwarder service subscribes to replicated-tier objects and propagates them. Local CLR operations never pay network latency.
  • SPEC-173 (Cell-Local Object Store) is the mechanism spec implementing this doctrine (ACCEPTED v1.0, 2026-07-02). K8 is the law; SPEC-173 is the engineering.
  • The unikernel principle (K1) is reinforced: each cell carries its own OS logic via the Membrane, and that logic now includes the cell's own state management.

References

  • SPEC-140 — Nexus Registry Protocol (system truth tier; cell-local tier is out of scope here)
  • SPEC-170 — TruthDB Architecture (governs system truth)
  • SPEC-171 — Universal Cell Model (cells gain shares and clos blocks)
  • SPEC-173 — Cell-Local Object Store (the mechanism spec implementing K8)
  • SPEC-051 — Capability Algebra (contracts and capability-gated sharing)
  • SPEC-022 — ION Rings (transport for observations)
  • SPEC-085 — NexFS (persistent substrate for restart and persistent durability tiers)

K9: Lessons from io_uring (ION Generation 2)

Status: Accepted

Date: 2026-07-03

Amends: K4 — ION Rings Over Pipes and Sockets, A2 — SysTable Frozen ABI

Companion spec: SPEC-022B-KERNEL-ION-OBJECT-PROTOCOL (ION v2 wire + protocol)

Companion RFC: .agents/reports/2026-07-03-0843-voxis-rfc-ion-v2-object-protocol.md

Context

Linux's io_uring proved a structural point the industry had been circling for a decade: shared-memory command queues beat synchronous syscalls on the kernel boundary. The mechanism — SQ ring, kernel consumer, CQ ring — is correct. But io_uring also became a security catastrophe. Google restricted it across its entire fleet because the opcode surface grew into an unmanageable source of kernel exploits. The reason is structural: in io_uring, every opcode is a privileged kernel entry point. The opcode table is open. Each new opcode is a new audit surface. Adding opcodes freely is exactly what produced the exploit plague.

Nexus ratified two related doctrines in 2026 that already point past this:

  • K7 (Kernel Protocol Boundary, 2026-07-02) reframed the boundary as a protocol, not a syscall table: "the ABI is an implementation detail; the protocol is the contract." It explicitly committed Nexus to additive wire evolution: "new packet types by spec amendment; wire layouts are read-only after ratification."
  • K8 (Cells Own Application State) stated the load-bearing invariant: "ION transports observations (events), never database queries. The kernel routes bytes; it cannot inspect payload." And: "Objects are passive. Cells execute. A cell subscribes, observes, and acts."

Despite K7/K8, the implementation specs (SPEC-022, SPEC-151) still carry POSIX-flavored opcodes: CMD_FS_OPEN, CMD_FS_READ, CMD_NET_SEND, CMD_BLK_REQ, CMD_REG_QUERY. That is the rot. The doctrine says "async object protocol"; the wire says "async syscalls." Three live drifts compound the contradiction: IonPacket is 24B in channel.zig vs 32B in SPEC-151; SysTable is 136B in SPEC-022 vs 240B in ion.zig; the channel-ID scheme differs across the public docs (0x1000+), SPEC-022 §12 (0x0000xFFF), and SPEC-151 (0x00x7).

io_uring is therefore not the competitor. It is evidence — that the industry is converging on shared-memory command queues as the kernel boundary — and a cautionary tale about open opcode surfaces. The question K9 answers is how to take io_uring's structural wins without inheriting its security failure.

Decision

ION is re-specified as Generation 2 (magic 0x494F4E32 "ION2"): "io_uring for objects, capabilities, and transactions." v2 is a new generation, not a silent mutation of v1 — this honors K7's "wire read-only after ratification" by treating the v2 wire as a new ratified contract while v1 is frozen as historical record.

The v2 design separates three layers that io_uring (and POSIX) conflated:

Layer 3 — Authority:   7 capability verbs (SPEC-051, CLOSED)   SPAWN SEND RECV MAP MASK TICK GRANT
Layer 2 — Interaction: 5 protocol verbs (NEW, ION v2)          SUBMIT CANCEL SUBSCRIBE REPLY TRANSFER
Layer 1 — Transport:   SPSC ring + ion_submission + ion_completion (NEW wire, generation 2)

Adopted from io_uring: shared SQ/CQ rings · batched operations · completion-based API (Ticket + wait_multi) · multishot subscriptions (generalized IORING_ACCEPT_MULTISHOT) · registered buffers (capability-backed memory IDs; generalizes SPEC-022 §13 TextureRef) · zero-copy transfer · operation chaining / transactions (richer than io_uring linked SQEs because atomicity is SPEC-051 §8.5 bundle-validated).

Rejected from io_uring: POSIX syscall semantics (violates K8 invariant 3) · file-descriptor-centric design (violates K7; SPEC-051) · open opcode table as privileged kernel entry points (the exploit source; violates SPEC-051 §3 Law) · SQPOLL (violates K5 tickless / SPEC-021 Silence Doctrine: WFI is the only valid idle state).

The 5 protocol verbs are a discipline over SPEC-051's existing SEND/RECV/GRANT capability verbs on Channel caps — they add zero new authority surface. Semantic operations (OpenObject, PublishEvent, CommitTransaction, MirrorDomain, …) are typed BKDL-tier-2 payload families, gated by capability class, dispatched to the owning object. They are not opcodes. The kernel validates the capability and dispatches; it never interprets payload semantics.

The total privileged entry surface is 5 interaction verbs + 7 authority verbs = 12 primitives, both closed by law. The only additive surface is the operation-family payload registry — and adding a family adds a payload type, not an entry point. This is what makes the io_uring exploit class structurally impossible in ION v2, not merely policy-discouraged.

Alternatives Rejected

OptionWhy Not
Keep v1 opcodes (CMD_FS_*, CMD_NET_*, CMD_BLK_*)Contradicts K7 (protocol, not syscalls) and K8 (observations, not queries). Leaves the doctrine↔wire rot standing.
Reify POSIX as the native surfaceReverses SPEC-073 (Chimera bridge); POSIX is a compatibility layer, not the contract. Rejected in K7.
seL4-style microkernel message-passingAdds ~1 µs overhead per message; rejected in A4. ION v2 keeps data on shared-memory rings; the kernel stays off the hot path.
SQPOLL (kernel polling thread)Burns a CPU core; violates K5 (Tickless Event-Driven) and SPEC-021 (Silence Doctrine: WFI is the only valid idle state). The scheduler wakes fibers on events instead.
Silent mutation of v1 wireViolates K7's "wire read-only after ratification." A generation bump (new magic ION2) is the sanctioned evolution path.
Add protocol verbs as new capability verbsViolates SPEC-051 §3 Law: "The 7 Verbs are closed." The 5 protocol verbs are a discipline over existing verbs, not new authority.

Consequences

  • The io_uring exploit class is structurally impossible. The privileged entry surface is 5 closed protocol verbs + 7 closed capability verbs = 12. Operation families are additive payload, not entry points. Adding an op family does not add a kernel code path that interprets its semantics.
  • K8 invariant 3 is enforced at the wire. RegistryObserve (a subscribe/observe op family) replaces CMD_REG_QUERY. There is no opcode that encodes a database query; "observations, not queries" becomes a protocol-level discipline.
  • Multishot, registered buffers, and transactions are first-class. These are the io_uring structural wins, realized at object/capability abstraction. The Founder's Subscribe { object Window, events PositionChanged } example is a literal SUBSCRIBE with MULTISHOT flag.
  • v1 is frozen as historical record. SPEC-022 retains its body unchanged with a supersede banner. v1 wire (IONR/NXUS magic, IonPacket/CmdPacket) is frozen; no v1 mutation is permitted after v2 ratification.
  • EXEC-008, EXEC-007, EXEC-009, EXEC-004 must re-proof on v2 before v2 ships. These closed against v1; the v2 wire is a new contract. Re-proof is flagged in SPEC-022B §15, not executed in this doctrine pass. Implementation and re-proof belong in a dedicated per-feature lane.
  • SysTable size is canonicalized at 128 bytes (neither 136 nor 240), resolving the A2 drift. Contents continue the K7-directed migration from function pointers to ring descriptors.
  • K4 is amended, not superseded. K4's "ION Rings Over Pipes and Sockets" decision (the SPSC ring as sole IPC mechanism) stands. K9 replaces the opcode/wire layer on top of those rings; it does not replace the rings themselves.
  • A2 is amended. The fixed-address SysTable idea survives; the canonical v2 size and contents are defined in SPEC-022B §11.
  • SPEC-051 is non-breaking amended. The 7 verbs remain closed. The 5 protocol verbs are a discipline over SEND/RECV/GRANT on Channel caps; they add no new capability verbs. Transactions use the existing §8.5 composite-bundle grammar.

References

  • SPEC-022B-KERNEL-ION-OBJECT-PROTOCOL — ION v2 wire + protocol (the engineering)
  • .agents/reports/2026-07-03-0843-voxis-rfc-ion-v2-object-protocol.md — design rationale (the why)
  • SPEC-022-KERNEL-ION-SYSTABLE — ION v1 (historical; superseded opcode/wire layer)
  • SPEC-021-KERNEL-RUMPK-IO — Pipe/Valve, Silence Doctrine (SPSC + WFI invariants preserved)
  • SPEC-051-SECURITY-CAPABILITY-ALGEBRA — 7 closed verbs; §8.5 composite bundles
  • SPEC-071-INTERFACE-BINARY-MANIFEST — BKDL (v2 payloads are tier-2 typed)
  • SPEC-151-NIPCELL-ION-BRIDGE — NipCell bridge (amended for v2 transparency)
  • SPEC-173-CELL-LOCAL-OBJECT-STORE — subscribe/observe/act model (the consumer of SUBSCRIBE)
  • K4 — ION Rings Over Pipes and Sockets (amended)
  • K5 — Tickless Event-Driven Scheduler (forbids SQPOLL)
  • K7 — Kernel Protocol Boundary
  • K8 — Cells Own Application State
  • A2 — SysTable Frozen ABI (amended)
  • A4 — No Microkernel Message-Passing
  • Linux io_uring — structural inspiration
  • Google's io_uring usage restrictions — empirical evidence for the opcode-surface argument