Documentation index

Fifteen documents, grouped by what you are trying to do. Start with the row that matches your reason for being here.

If you want to...Read
Understand the claimWHITEPAPER.md
Judge whether the claim holdsREVIEWER_GUIDE.md
Know what is not true yetSTATUS.md
Run itGETTING_STARTED.md

Understanding it

Judging it

Running and building on it

Direction and process

Governance

Note: ../SECURITY.md at the repository root is the GitHub vulnerability reporting policy. The design document is docs/SECURITY_MODEL.md above.

Dezh: an intent-native, effect-accountable OS substrate

Whitepaper v1 · research prototype · QEMU-only · see STATUS.md for the honest state and SECURITY_MODEL.md#threat-model for what is and is not defended.

Abstract

Dezh is a from-scratch, capability-secure operating-system substrate built around one non-negotiable thesis: no ambient authority. Every principal — including an autonomous AI agent — starts with zero access and can act only through an explicit, unforgeable, attenuable capability for a specific resource and operation. On top of that base, Dezh adds an intent-to-effect runtime: authority may only be derived through a declared intent, every effect is recorded on the authorization path itself with a reversibility class, and a whole agent mission can be forecast, attributed, and rolled back honestly — retracting what is reversible, compensating what is compensatable, and refusing with an explanation what is not. The whole chain is enforced by a small kernel, Sv39 paging, and user-space services, and is exercised end-to-end in CI on a RISC-V kernel under QEMU (with a second x86_64 kernel proving ISA portability of the program format).

The contribution is not any single mechanism — capabilities, provenance, and sagas are all decades old (see RELATED_WORK.md). It is their recombination made unbypassable by the absence of ambient authority underneath, aimed at a principal the OS literature has not yet claimed: the autonomous agent whose every effect must be accountable and reversible.

1. Problem

General-purpose systems make compatibility and convenience the default authority model: a process inherits broad filesystem access, environment, file descriptors, /proc, ptrace; devices are kernel-resident; service contracts rest on convention. This ambient authority makes isolation, recovery, and audit hard — and it is exactly the wrong default when the program you are running is an untrusted autonomous agent that will change repositories, CI, deployments, and external services on your behalf.

The mainstream response is a user-space sandbox (gVisor, Firecracker, WASI, seccomp+Landlock). These confine resources well, but they run on top of an ambient-authority host, so an effect log sits beside the resource and there is generally a path to the resource that skips it. You can kill an agent's process; you cannot cleanly attribute and reverse the whole set of effects it produced under one intent.

2. Thesis and system model

Dezh tests the opposite default:

No operation is authorized unless the caller holds an unforgeable capability for that exact operation and target — and that capability could only have been derived from a declared intent that structurally bounds it.

The model has five layered claims, each enforced by a concrete mechanism:

  1. No ambient authority. Zero access by default; enforced at the syscall boundary and by Sv39 paging (a U-mode task faults on ungranted memory/MMIO).
  2. Intent is the only path to authority (Ahd). A capability is derived as derived = requested ∩ intent_ceiling — a structural subset, not a purpose annotation. Anything beyond the intent is dropped and reported; the kernel denies the host call if it is attempted anyway.
  3. Every effect is a ledger record (Sand). An effect is a Cairn commit, enriched to carry actor → intent → derived cap → reversibility class → status → generation. The record is on the authorization/persistence path, not a side-log, so on a no-ambient-authority kernel it cannot be bypassed.
  4. A mission is reversible honestly (Sfar). The effects under one intent form a mission. A rollback forecast is computed before touching anything; the rollback retracts reversible effects by moving a ref, undoes compensatable effects by running and recording a registered compensating action (a saga step), and refuses irreversible/unknown effects with an explanation. Mission authority spans every namespace the mission touched.
  5. Denial and provenance are explainable (why-denied, Tbar). A refusal names the boundary that produced it; the actor → intent → effect provenance graph is queryable and unforgeable, because the intent id and derived capability are stamped kernel→daemon on the commit path.

The precise authority rules (derivation, attenuation, the effect-record schema, and the invariants they must satisfy) are stated in SECURITY_MODEL.md#enforcement-model.

3. Architecture

  • Kernel (dezh-boot, RISC-V). Boots in S-mode via OpenSBI, validates a boot contract, installs Sv39 paging and a trap/syscall boundary, attests each IPC sender's capabilities, enforces the intent-derivation rule, and preempts non-yielding tasks with a timer. Deliberately small — it is the TCB.
  • Drivers and storage out of kernel. virtio-block is a U-mode daemon holding only an explicit MMIO page grant + a DMA window + IPC/block authority. Clients reach it over typed IPC; there is no hidden kernel block path.
  • Cairn / Sand / Sfar / Tbar live inside that daemon: an on-disk commit-log store whose records double as the effect ledger, with mission rollback and the provenance query as read/rewrite operations over the same records.
  • Programs are typed IR (Dezh-IR) or capability-gated foreign ELF (Pol). The same byte-identical .dzp package runs on the RISC-V and x86_64 kernels (D016), and an unmodified static Linux/RISC-V ELF runs capability-gated under the Linux personality — the same bytes also run on real riscv64 Linux (D014).

4. Evaluation

Everything below is asserted by tools/ci/qemu_smoke.py and runs on every push; transcripts live in docs/.

  • Containment against an adversary. redteam turns a malicious agent loose against five escapes — cross-namespace read, raw MMIO write, capability forgery/amplification, out-of-intent action, CPU monopoly — and each is stopped at a named boundary (storage capability check / hardware paging / kernel syscall check / intent-derivation ceiling / preemptive scheduler); the console survives every one. Value is only legible with a villain in the room.
  • Honest whole-mission rollback. sfar-demo, comp-demo, and sfar-cross-demo show forecast → retract reversible → compensate compensatable → refuse irreversible, across one and multiple namespaces, with the refused effect and its provenance surviving a reboot.
  • The flagship. overnight collapses the whole story — an agent loose under one intent, a morning of forecast + provenance + honest rollback, and a contained escape — into one command (docs/transcripts/overnight.md).
  • Measurement (D015). Performance claims are architecture-backed and measured, never bare superlatives. The one real-silicon, same-CPU figure is the capability-check cost (~1 ns) vs the Linux syscall floor (~49 ns); everything measured inside the kernel is QEMU-emulated and labelled as such. The per-effect ledger overhead is analysed in dezh-boot/BENCH.md: the enrichment is bytes in a commit sector already being written — zero extra I/O per effect, a property of the record layout independent of timing.

Full treatment in RELATED_WORK.md. In one paragraph: Dezh reuses capabilities (Dennis & Van Horn; ocap/Miller; KeyKOS/EROS; seL4), the DIFC/provenance insight that such properties must be built in, not retrofitted (HiStar; Flume; PASS), and compensation-based recovery (sagas; Nix-style immutable versioned state). It does not claim any of these as its identity (D021). What it claims as new is the recombination — intent as the sole structurally-enforced authority path, an effect ledger on the authorization path carrying a reversibility class, honest forecastable saga rollback of a whole mission — made unbypassable by a from-scratch no-ambient-authority kernel, and aimed at autonomous agents as first-class principals.

6. Limitations (see STATUS.md, SECURITY_MODEL.md#threat-model)

QEMU-only; not formally verified (seL4 is the bar); most external effects are still modeled rather than wired to connectors, the exception being marz-effect, which drives a real external system through a host gateway that is outside the TCB — so Dezh proves authorization, egress, ledgering and compensation, and not the gateway's honesty; ledger integrity trusts the storage daemon (records are hashed/chained for corruption + rollback, not signed against a malicious writer); the commit log is a fixed 255 slots with no GC; intents are runtime sessions that do not survive a reboot, and in-flight capability clawback does not exist, though leases and intent-revoke do; no IOMMU; a small Pol syscall subset. Each is named rather than elided.

7. Future work

In-flight capability clawback (leases and intent-revoke shipped; taking back a capability already running inside another task did not); a multi-dimensional, formally-specified intent algebra (operation × resource × namespace × time × quota × destination × data-class × delegation-depth) with property tests that no dimension widens on derivation; Gateways beyond the first one — the git connector exists, HTTP/DB/secrets/CI/deploy do not, and none of them yet carry enforced effect schemas; unifying the host-crate and bare-metal authority implementations to a single source of truth; and, longer term, hardware-enforced capabilities (CHERI) and verification of the smallest kernel authority rules.

8. Review request

We specifically invite scrutiny of: the intent-derivation rule and its invariants; whether the effect ledger is genuinely unbypassable given the TCB; the honesty of the reversibility classification and mission rollback; the threat-model non-goals; and the novelty claim in §5 against the prior art in RELATED_WORK.md.

Architecture

The layers and their authority boundaries, the diagrams that show them, and where each piece lives in the tree.


Design

Dezh is a bare-metal OS research prototype focused on explicit authority, service-mediated effects, and recoverable lifecycle operations.

For visual diagrams, see Diagrams.

Design Center

The current prototype is built around four rules:

  1. No ambient authority by default.
  2. Device and storage access are service-mediated.
  3. Persistent lifecycle changes are transactional and recoverable.
  4. Runtime state should be inspectable by reviewers from the console and tests.

The strategic direction is to make intent and effect first-class OS concepts. The current implementation is not fully intent-native yet, but the package, service, IPC, and storage work is deliberately moving toward that shape.

Boot Flow

  1. OpenSBI starts the RISC-V kernel in S-mode on QEMU virt. The boot hart is whichever one firmware chose — it is never assumed to be hart 0.
  2. The kernel validates the boot contract from dezh-kernel.
  3. The kernel installs trap handling, timer support, and Sv39 paging.
  4. The PLIC is programmed to route virtio and UART0 interrupts to the boot hart's S-mode context, so device I/O can block instead of spin and console input does not depend on the console being the thing that looks for it.
  5. Secondary harts are started over the SBI HSM protocol, each with its own stack, trap stack, and per-hart ApCtx reached through sscratch.
  6. A capability-scoped console starts over UART on the boot hart.
  7. Services are declared from the boot plan and materialized in the service registry.
  8. Long-lived services such as virtio-block and marz are started explicitly or lazily from the registry.

Kernel Responsibilities

The kernel owns the confinement boundary:

  • address-space construction
  • trap and syscall handling
  • task scheduling, on the boot hart and symmetrically across secondary harts
  • device interrupt routing (PLIC) and blocking I/O, so a waiting driver costs no CPU
  • multi-hart bring-up (SBI HSM) and the mutual exclusion that shared state needs
  • IPC queues and typed receive timeout support
  • information-flow gates on both axes: secrecy on export, integrity on ingress
  • service registry state
  • explicit process launch grants
  • frame ownership and reclaim
  • fault containment for U-mode tasks, including on a secondary hart

The kernel does not implement the block or network I/O path directly — both are U-mode daemons holding nothing but the one device page and DMA window each was granted.

Process Model

Each ELF process receives:

  • its own address space
  • entry point and initial arguments
  • task capabilities
  • optional device mappings
  • optional DMA mappings
  • tracked frame ownership for reclamation

Foreground clients are reclaimed after exit or fault. Daemons remain alive until they stop, fault, or are explicitly restarted.

Capability Model

Task capability bits currently cover:

  • print
  • time
  • IPC
  • virtio-block device
  • block read
  • block write
  • Cairn namespaces 0..7 (bits 8..15): one bit per named storage namespace
  • egress destinations (bits 16+): one bit per named destination, not one bit for "the network" — revoking vault-sync leaves ops intact

Device authority is separately live-checked (dev-grant / dev-revoke), so a daemon that already holds a mapped device page can still be refused at the gate.

Information-flow labels (secrecy taint, integrity endorsements) are not capability bits: a task can hold every bit it needs and still be denied because the flow itself is illegal. See Information Flow.

The important property is attenuation: a task can only transfer capabilities it already holds. Manifest-declared package capabilities are separately translated into runtime grants; a manifest cairn-read/cairn-write grant maps to the app's own namespace bit only (matched by app name) — a manifest can never name another app's namespace.

IPC

The base IPC syscall sends a small payload, a scalar word, and an attenuated capability grant. Service paths pack a typed v0 envelope into the scalar word:

proto | service_id | op | request_id | status | arg

Storage, installer, app, and package paths use typed replies. Legacy demos can still use raw scalar messages.

Kernel-attested sender capabilities: on every send, the kernel records the sender's capability set in the message; on receive, the service gets that set alongside the payload. A service therefore checks the sender's authority against values a client cannot forge from user space. This is how the storage daemon enforces per-namespace access, and why its denials can name the exact missing capability (why-denied direction from the strategic plan).

User-Space Block Driver

The virtio-block daemon is a separate U-mode ELF. It alone receives:

  • the virtio MMIO page grant
  • the DMA window grant
  • IPC authority
  • block read/write authority

Foreground clients do not receive MMIO authority. A no-grant process touching the MMIO address faults and is killed without killing the console.

The daemon handles:

  • disk probe
  • block write/read
  • root install marker and metadata
  • Cairn v0 current/previous value operations (legacy demo path)
  • Cairn v1 commit-log store with per-namespace capability checks
  • embedded app registry operations
  • package registry, journal, and blob sectors
  • note/lab/calc/vault private storage
  • stop and controlled fault demo

User-Space Network Daemon (Marz)

The network edge is a second U-mode ELF, and deliberately not the same one. Marz receives its own virtio-net MMIO page and its own DMA window: two devices, two grants, so neither daemon can reach the other's hardware or corrupt the other's virtqueue.

Authority to send is not "network access". It is a capability for a named destination (address, port, and the secrecy label that destination is cleared to receive), so egress can be revoked one destination at a time.

Both directions exist:

  • Egress — the gate runs before a packet exists: device authority live, then destination capability held, then the secrecy check against that destination's label. Only then does a frame leave, and the transmission is recorded on the ledger as irreversible.
  • Ingress — Marz offers the NIC receive buffers, blocks on the device interrupt, resolves the destination by ARP, and completes an ICMP echo exchange, matching the reply by id and sequence. What comes back is attacker-chosen, so consuming it lowers integrity.

Interrupts And Blocking I/O

Device I/O is interrupt-driven. A driver submits a request, calls sys_irq_wait with the interrupt count it last saw, and is parked if nothing new arrived — its SEPC rewound so the ecall re-runs on wake. When no task is Ready but one is waiting on a device, the scheduler idles on wfi instead of returning.

The kernel services the PLIC by hand in that idle path deliberately: hardware clears sstatus.SIE on trap entry, so a pending interrupt would wake wfi and never be taken, stranding the sleeping driver. Counters are visible from irq-stat.

SMP

Secondary harts come up over SBI HSM and pull U-mode tasks off one shared run queue protected by a fair ticket spinlock. Tasks land wherever a hart is free and several run in U-mode at the same instant.

Parallelism does not cost isolation: each task carries its own address space (only its own stack region is U-mapped), so a task reaching into a concurrent neighbour's memory page-faults and dies on its own hart while the neighbour runs on. Per-hart trap state is reached through sscratch, never tp — a U-mode task owns every integer register and will have clobbered tp by the time it traps.

Honest scope: a secondary hart arms its own timer while a U-mode task runs there, so such a task is interrupted and resumed rather than owning the hart until it exits (smp-preempt). What that interrupt does not do is pick a different task — there is no migration and no scheduling decision on a secondary, because choosing means reading a task table that is still the boot hart's. The console's own scheduler is still single-hart.

Information Flow: Secrecy And Integrity

Capabilities answer "may this actor touch this object?". Information flow answers the separate question "may these bits go there?", and it has two axes (dezh_core::difc):

  • Secrecy — reading a labelled namespace raises the actor's taint, and taint only ever rises. A tainted actor cannot write down into a less-secret sink or export to a destination not cleared for that label.
  • Integrity — a sink may require endorsements; a value flows in only if it carries them (no write-up). Consuming unvalidated input can only ever lower an actor's integrity — the exact dual of taint only ever rising.

The escapes are explicit, privileged, and recorded: declassify for secrecy, endorse for integrity. They stay separate on purpose — declassify does not return lost integrity and endorse does not clear secrecy, so one privileged act never grants two. The lattice rules are proven exhaustively over the 8-bit label space, including that the two axes are independent so one gate cannot mask the other.

Live paths: ns=note and ns=vault require an endorsement, ns=lab (scratch) requires none; completing a network exchange lowers the operator's integrity, so a commit into a demanding namespace is refused with an explainable denial until a recorded endorsement (taintflow-demo, ingress-demo).

Honest scope: the ingress taint is at operator granularity — consuming any network reply lowers integrity wholesale rather than tracking individual bytes — and neither axis is enforced across the client→daemon IPC hop yet.

Cairn v1 (Commit-Log Store)

Cairn v1 lives inside the storage daemon on sectors 1600..1855:

  • a superblock holding the namespace table (note, lab, calc, vault, agent) with each namespace's head ref and commit count;
  • append-only commit records, each carrying: parent ref, FNV-1a hash of the value object, actor task id, a reversibility flag, and the inline value.

Semantics:

  • Commit appends a record and moves the namespace head ref.
  • Rollback N walks the parent chain and moves the ref back; history is never erased, and the state survives reboot.
  • Verify re-hashes the head object against its commit record.
  • Access requires the namespace's capability bit, checked against the kernel-attested sender capability set; denials name the missing capability.

The commit record fields (actor, reversibility class, provenance chain) are the seed of the effect ledger described in Strategic direction (decision D020).

Dezh-IR apps reach the store through the kernel's IR host, which routes cairn_put/cairn_get host calls over typed IPC to the daemon with the app's own namespace capability — there is no kernel-side block I/O shortcut.

Service Registry

The service registry tracks:

  • service name
  • service kind
  • state
  • task id
  • caps
  • grants
  • restart count
  • last exit
  • last started tick
  • fault reason

Manual stop and controlled fault are not hidden by automatic restart. Review commands use explicit svc-restart so service recovery remains visible and deterministic.

Package Store

The SDK builds .dzp packages. The OS stores them through the user-space block service, not through a kernel block path.

Current package features:

  • persistent registry on disk
  • transaction journal
  • active, previous, and stage blob areas
  • install/remove/update/rollback
  • recovery and quarantine
  • pin/unpin
  • cap-escalation review
  • explicit physical cleanup through pkg-gc run

Only Active packages are runnable. Removed, Corrupt, Pending*, and Quarantined packages do not run.

Embedded Apps

The current embedded app set is intentionally mixed:

  • note: persistent text app
  • lab: UI-like multi-task app with cooperating workers
  • calc: calculator app with stored last result
  • vault: private-value app used to exercise storage and device-denial paths

These are review demos, not a production app ecosystem.

Storage Path

The storage path is:

console command -> foreground client -> typed IPC -> virtio-block daemon
               -> granted MMIO/DMA -> disk image

This path is central to the project. It proves that storage does not silently fall back to a kernel block driver.

Review Surface

Useful review commands:

  • services
  • tasks
  • ipcstat
  • ipc-typed-demo
  • pkg-store
  • pkg-journal
  • pkg-review <name>
  • pkg-versions <name>
  • pkg-gc
  • cairn-demo / cairn-log <ns> / cairn-rollback <ns> [n] / cairn-verify <ns>
  • agent
  • overnight (the W8 intent → effect flagship, end to end)
  • irq-stat (interrupt counts and sleeping drivers)
  • smp-sched / smp-isolate (symmetric scheduling, isolation under parallelism)
  • marz-demo / marz-ping (guarded egress, ARP + ICMP receive)
  • taintflow-demo / ingress-demo / taint / declassify / endorse
  • why-denied / tbar
  • bench-all

Useful review tools:

  • tools/ci/qemu_smoke.py
  • tools/ci/sdk_test.py
  • tools/review/scan_public.py
  • tools/demo/run_review_demo.py
  • tools/demo/run_agent_demo.py (F1 agent-containment transcript)

Diagrams

These diagrams are part of the review surface. They show the current prototype, not a production promise.

System Overview

flowchart TB
    subgraph Kernel["Kernel boundary"]
        Trap["Trap + syscall handling"]
        VM["Address-space builder"]
        Sched["Task scheduler (boot hart)"]
        SMP["SMP: ticket lock + shared run queue"]
        IRQ["PLIC: device interrupts -> S-mode"]
        IPC["IPC queues + typed timeout"]
        DIFC["Information flow: secrecy + integrity"]
        Services["Service registry"]
        Frames["Frame ownership + reclaim"]
    end

    Console["Console task"] --> Trap
    Console --> Services

    subgraph User["U-mode processes"]
        VBlk["virtio-block daemon"]
        Marz["Marz egress daemon"]
        Client["Foreground clients"]
        Apps["Installed apps"]
        Bench["Benchmark app"]
    end

    Trap --> User
    IPC --> VBlk
    Client -->|typed IPC| VBlk
    Apps -->|declared caps only| IPC
    SMP -->|dispatch onto secondary harts| User

    VBlk -->|explicit MMIO grant| MMIO["virtio-mmio page"]
    VBlk -->|explicit DMA window| DMA["DMA bounce window"]
    DMA --> Disk["QEMU raw disk image"]

    Marz -->|its OWN NIC page + DMA| NIC["virtio-net page"]
    NIC --> Wire["the wire"]
    Wire -.->|reply lowers integrity| DIFC

    IRQ -.->|wakes a sleeping driver| VBlk
    IRQ -.->|wakes a sleeping driver| Marz

Every arrow into a device is an explicit grant, and the two daemons hold different ones: neither can reach the other's hardware or DMA window.

Blocking I/O Sequence

Device I/O is interrupt-driven, not polled. A driver that is waiting occupies no CPU, and the kernel has somewhere to idle when nothing is runnable — without which blocking on I/O is impossible, since the scheduler would simply return.

The console was the exception until recently, and it is worth saying how it was wrong, because the shape recurs. getc spun on the UART's line-status register and read the receive register directly. That keeps up with a person typing and loses bytes to anything faster: the FIFO is sixteen deep with no flow control, and the console spends most of its time not in getc — echoing a character, then running a whole command and printing as it goes. UART0 is IRQ 10 on the virt board and had never been enabled at the PLIC, which only routed the virtio slots. It is routed now, and both the interrupt handler and getc drain the FIFO into a ring the console reads, so input no longer depends on the console happening to be looking. irq-stat reports bytes received and the two places a byte could be dropped, so "characters went missing" is an arithmetic question rather than an argument.

sequenceDiagram
    participant D as Driver (U-mode)
    participant K as Kernel
    participant P as PLIC
    participant Dev as virtio device

    D->>Dev: submit request (queue notify)
    D->>K: sys_irq_wait(last_seen)
    Note over K: count unchanged -> park the task,<br/>rewind SEPC so the ecall re-runs
    K->>K: nothing Ready, but a task waits on a DEVICE
    K->>K: wfi + service the PLIC by hand
    Dev-->>P: raises its interrupt line
    P-->>K: claim
    K->>Dev: ACK (InterruptStatus / InterruptACK)
    K->>P: complete
    K->>D: mark Ready
    D->>K: ecall re-runs, returns the new count

The kernel services the PLIC by hand in its idle path deliberately: the hardware clears sstatus.SIE on trap entry, so a pending interrupt would wake wfi and never be taken, stranding the sleeping driver.

Mutual Exclusion, And Why The Lock Masks Interrupts

One ticket lock serves the whole kernel (sync::TicketLock): fair, so no hart starves under contention, and it masks the acquiring hart's interrupts for the length of the critical section. That second part is not a performance choice, it is what makes the lock usable at all for state an interrupt handler touches:

  1. a hart takes the lock,
  2. a device interrupt lands on that same hart,
  3. the handler waits for a lock whose holder cannot run until the handler returns.

Nothing about a fair queue helps; the hart is simply stuck. plic_handle writes scheduler state from interrupt context, so this is reachable rather than theoretical — and the console receive path hit the same shape for real, where a try-lock used to dodge it livelocked instead, the handler losing every race and the UART re-raising its line immediately.

Acquiring returns a guard, so releasing and restoring the interrupt state cannot be forgotten or ordered wrongly.

SMP: Symmetric Scheduling

The boot hart runs the console; secondary harts pull U-mode tasks off a shared queue and run them in parallel. Each task carries its own address space, so parallelism does not cost isolation. A secondary also arms its own timer while a U-mode task runs there, so the task is interrupted and resumed rather than owning the hart until it exits — and when a secondary has nothing to do it sleeps rather than spinning, because on an emulated host a spinning hart takes budget from the one running the console.

flowchart LR
    Boot["Boot hart<br/>(console, service registry)"] -->|fills| Q[("Shared run queue<br/>(ticket lock)")]
    Q --> H1["Secondary hart 1"]
    Q --> H2["Secondary hart 2"]
    Q --> H3["Secondary hart 3"]

    H1 --> T1["Task A<br/>own satp, own stack"]
    H2 --> T2["Task B<br/>own satp, own stack"]
    H3 --> T3["Task C<br/>own satp, own stack"]

    T1 -. "cross-task write<br/>page-faults" .-> T2

    H1 --> AP1["per-hart ApCtx<br/>frame + trap stack + kctx"]
    H2 --> AP2["per-hart ApCtx"]
    H3 --> AP3["per-hart ApCtx"]

Per-hart state is reached through sscratch (whose value is that hart's ApCtx, frame first), never through tp — a U-mode task owns every integer register and will have clobbered tp by the time it traps.

The Network Edge, Both Directions

Egress names a destination, not "the network", and is checked against secrecy before a packet exists. Ingress is the mirror: what arrives is unvalidated, so consuming it lowers integrity until an explicit endorsement.

flowchart TB
    Op["Operator / agent"]

    Op -->|"send to <dest>"| G1{"device capability live?"}
    G1 -->|no| D1["DENIED"]
    G1 -->|yes| G2{"destination capability held?"}
    G2 -->|no| D2["DENIED"]
    G2 -->|yes| G3{"secrecy: taint fits<br/>the destination?"}
    G3 -->|no| D3["DENIED: would exfiltrate"]
    G3 -->|yes| TX["Marz transmits"]
    TX --> L["irreversible effect on the ledger"]

    Op -->|"probe <dest>"| RX["Marz: ARP + ICMP echo,<br/>parses the reply"]
    RX --> I["integrity LOWERED<br/>(input is unvalidated)"]
    I --> G4{"write into a namespace<br/>requiring endorsement?"}
    G4 -->|not endorsed| D4["DENIED: would become trusted state"]
    G4 -->|after endorse| W["write permitted"]

    classDef deny stroke:#e5534b,stroke-width:2.5px;
    classDef ledger stroke:#8250df,stroke-width:2.5px;
    class D1,D2,D3,D4 deny
    class L ledger

Across every diagram here a red border marks a refusal and a purple one marks something the ledger now carries. Only the stroke is set, so both borders keep their meaning whichever theme GitHub renders the page in.

Boot And Service Graph

flowchart LR
    OpenSBI["OpenSBI"] --> Boot["dezh-boot"]
    Boot --> Contract["Validate boot contract"]
    Contract --> Paging["Install traps + Sv39"]
    Paging --> Registry["Build service registry"]
    Registry --> Console["Start console"]

    Console -->|lazy start| VBlk["virtio-block service"]
    VBlk --> Running["Running"]
    Running -->|svc-stop| Stopped["Stopped"]
    Running -->|svc-fault-demo| Faulted["Faulted"]
    Stopped -->|svc-restart| Running
    Faulted -->|svc-restart| Running

Storage Authority Path

sequenceDiagram
    participant C as Console command
    participant K as Kernel launch gate
    participant F as Foreground client
    participant D as virtio-block daemon
    participant Disk as Raw disk image

    C->>K: request storage operation
    K->>F: launch with IPC + DMA, no MMIO
    F->>D: typed IPC request
    D->>Disk: block I/O through granted MMIO/DMA
    Disk-->>D: status/data
    D-->>F: typed status
    F-->>C: command result

Important property: clients do not receive device MMIO authority. The daemon is the only process with the virtio MMIO page grant.

Package Lifecycle

stateDiagram-v2
    [*] --> Empty
    Empty --> PendingInstall: pkg-recv
    PendingInstall --> Active: commit verified blob
    PendingInstall --> Quarantined: suspicious recovery
    Active --> PendingRemove: pkg-remove
    PendingRemove --> Removed: commit remove
    Removed --> Empty: pkg-gc run
    Active --> Active: pkg-update commit
    Active --> Active: pkg-rollback
    Active --> Corrupt: blob/registry verify failure
    Corrupt --> Quarantined: explicit recovery
    Quarantined --> [*]

Lifecycle rules:

  • Only Active packages are runnable.
  • New capabilities during update require explicit --allow-new-caps.
  • Pins block update and rollback until explicit review.
  • GC never touches Active, Corrupt, or Quarantined slots.

Disk Layout

flowchart TB
    S0["sector 0<br/>install marker"] --> S2["sector 2<br/>Cairn v0 current"]
    S2 --> S3["sector 3<br/>Cairn v0 previous"]
    S3 --> S4["sector 4<br/>root metadata"]
    S4 --> S5["sectors 5..7<br/>app registry v0"]
    S5 --> S24["sector 24<br/>package marker"]
    S24 --> S25["sectors 25..31<br/>package registry"]
    S25 --> S32["sectors 32..39<br/>package journal"]
    S32 --> S64["sectors 64..575<br/>active package blobs"]
    S64 --> P["sectors 576..1087<br/>previous blobs"]
    P --> ST["sectors 1088..1599<br/>stage blobs"]
    ST --> C1["sector 1600<br/>Cairn v1 superblock"]
    C1 --> C2["sectors 1601..1855<br/>Cairn v1 commit log"]

The package store is intentionally small and inspectable in v0:

  • 8 package slots
  • 32 KiB per slot
  • active, previous, and stage blob areas
  • journaled recovery before package execution

Cairn v1 Commit Log

Each namespace is a ref into an append-only chain of commit records. Rollback moves the ref; nothing is erased.

flowchart RL
    subgraph Super["Superblock (sector 1600)"]
        NSnote["ns=note head"]
        NSvault["ns=vault head"]
        Next["next free slot"]
    end

    C2["commit slot 2<br/>value: bad-write<br/>parent: 1<br/>hash + actor"] --> C1["commit slot 1<br/>value: note-v2<br/>parent: 0<br/>hash + actor"]
    C1 --> C0["commit slot 0<br/>value: note-v1<br/>parent: none<br/>hash + actor"]

    NSnote -. before rollback .-> C2
    NSnote == after rollback 1 ==> C1

Commit record fields — parent ref, object hash (FNV-1a), actor task id, and a reversibility flag — are the on-disk seed of the effect ledger direction in Strategic direction (D020).

Namespace Capability Attestation (F1/F2 core mechanic)

The storage daemon never trusts what a client says; it checks what the kernel attests the sender holds.

sequenceDiagram
    participant A as Agent app (holds ns=agent bit)
    participant K as Kernel (SYS_SEND / SYS_RECV)
    participant D as Storage daemon

    A->>K: send commit request (ns=note)
    Note over K: kernel records sender's<br/>capability set in the message
    K->>D: deliver request + attested sender caps
    Note over D: check bit for ns=note<br/>in attested caps
    D-->>A: DENIED: ns=note requires CAIRN_NS_0,<br/>sender holds caps=0x...

    A->>K: send commit request (ns=agent)
    K->>D: deliver request + attested sender caps
    D-->>A: OK: commit slot N, parent P, hash H

Multi-ISA Execution (F3 direction)

The same Dezh-IR bytecode runs on every Dezh kernel; only the thin host bindings differ per ISA.

flowchart TB
    Source[".dzs source (SDK assembler)"] --> IR["Dezh-IR bytecode<br/>(verified, capability-gated)"]
    IR --> Engine["dezh-core engine<br/>(one shared no_std crate)"]
    Engine --> RV["RISC-V kernel host<br/>print → UART, cairn → storage daemon"]
    Engine --> X86["x86_64 kernel host<br/>print → COM1"]

Authority And Denial

flowchart TB
    Request["Operation request"] --> Intent["Declared operation / intent"]
    Intent --> CapCheck["Capability check"]
    CapCheck -->|allowed| Route["Service route / namespace"]
    Route --> Effect["Effect record or command result"]
    CapCheck -->|denied| Denial["Structured denial"]
    Denial --> Explain["why-denied direction"]

    classDef deny stroke:#e5534b,stroke-width:2.5px;
    classDef ledger stroke:#8250df,stroke-width:2.5px;
    class Denial deny
    class Effect ledger

The current implementation has capability-gated operations and audit events. The strategic direction is to make intent and effect records first-class OS objects.


Repository layout

This repository mixes a bare-metal OS prototype, host-side research crates, SDK tooling, QEMU test harnesses, and public review documentation. This file is the map for reviewers.

Bare-Metal Targets

PathRole
dezh-boot/Main RISC-V QEMU virt boot target. Contains kernel entry, console, task model, service registry, package store, package lifecycle, embedded apps, and user-space process launch.
dezh-boot/virtio-blk/User-space virtio-block daemon. It receives explicit MMIO and DMA grants and performs the prototype disk I/O path.
dezh-boot/marz/User-space network daemon. It receives its own virtio-net MMIO page and its own DMA window — separate from the block daemon's — and performs guarded egress plus the ARP/ICMP receive path.
dezh-boot/linux-guest/Static Linux/RISC-V ELF used by Pol (linux-elf); the same bytes run on real riscv64 Linux.
dezh-boot/userprog/Small user program used by legacy demos and process-launch smoke paths.
dezh-boot/bench-app/U-mode benchmark app used by bench-all.
dezh-boot/note-app/Embedded note demo app.
dezh-boot/lab-app/Embedded multi-task lab demo app.
dezh-boot/calc-app/Embedded calculator demo app.
dezh-boot/vault-app/Embedded private-value demo app.
dezh-boot-x86/Smaller x86_64 boot/smoke target for multi-ISA validation.

Shared Crates

PathRole
dezh-core/Shared .dzp, base64, and Dezh-IR support used by the boot target and SDK-adjacent code.
dezh-kernel/Boot contract, kernel plan, install manifest, and plan validation logic.
spikes/The Step 1..9 host-side prototypes, superseded and not shipping — nothing on the bare-metal path depends on them. Kept as the record of which design question each one settled; see spikes/README.md.

Tools

PathRole
tools/ci/qemu_smoke.pyBoots RISC-V or x86_64 QEMU targets and asserts expected console behavior.
tools/ci/sdk_test.pyEnd-to-end SDK/package lifecycle acceptance test across multiple QEMU reboots.
tools/sdk/build_pkg.pyBuilds .dzp packages from app directories.
tools/sdk/install_pkg.pyBoots Dezh in QEMU and streams packages through the console upload protocol.
tools/sdk/dzas.pyTiny Dezh-IR assembler for SDK apps.
tools/demo/run_review_demo.pyRuns the review demo and captures a transcript.
tools/demo/run_agent_demo.pyRuns an agent-containment demo transcript.
tools/review/scan_public.pyPublic hygiene scan for review-package readiness.
tools/review/make_review_package.pyBuilds a clean review package snapshot.

Documentation

PathRole
README.mdPublic landing page and quick review path.
docs/ARCHITECTURE.mdArchitecture explanation.
docs/ARCHITECTURE.md#diagramsMermaid diagrams for the current prototype.
docs/SECURITY_MODEL.md#enforcement-modelThreat model and enforced/not-yet-enforced boundaries.
docs/ROADMAP.md#strategic-directionIntent-native/effect-accountable direction and open review questions.
docs/SDK_GUIDE.mdHow to build, install, update, and run .dzp packages.
docs/REVIEWER_GUIDE.mdShort path for external technical review.
docs/ROADMAP.mdRoadmap and current milestone direction.
docs/DECISIONS.mdArchitecture decision notes.
docs/REVIEWER_GUIDE.md#running-the-demosManual demo script.
docs/WHITEPAPER.mdTechnical whitepaper draft.
docs/OUTREACH.mdDraft outreach templates.

Generated/Local Artifacts

These should not be committed:

  • target/
  • dist/
  • graphify-out/
  • raw QEMU disk images (*.img)
  • Python bytecode caches

The repository intentionally keeps reproducible tools and transcripts, but not local generated build output.

Related Work and Novelty

This document places Dezh in the scientific lineage it draws on, and states — honestly, mechanism by mechanism — what is prior art we deliberately reuse and what is the genuinely new recombination we claim. It follows the project's D015/D021 rule: we do not claim as novel anything that already has strong prior art, and we name that prior art precisely.

The short version: every ingredient in Dezh exists in the literature. The contribution is a specific recombination — intent as the sole, structurally enforced authority-derivation path + an effect ledger that is the authorization record itself, carrying a reversibility class + whole-mission saga rollback — made unbypassable by being built on a kernel with no ambient authority underneath, and aimed at a target the OS literature has not yet claimed: autonomous AI agents as first-class, effect-accountable principals.


1. Capability security (the authority model)

WorkWhat it establishedWhat Dezh reuses
Dennis & Van Horn, Programming Semantics for Multiprogrammed Computations, CACM 1966The capability: an unforgeable token naming an object + permitted operations.The core primitive — every authority in Dezh is a capability.
Saltzer & Schroeder, The Protection of Information in Computer Systems, 1975Least privilege, fail-safe defaults, complete mediation, economy of mechanism.The design principles: zero authority by default, mediate every effect.
KeyKOS (Hardy, 1985); EROS (Shapiro et al., SOSP 1999); CoyotosPersistent capability microkernels; orthogonal persistence; confinement.The microkernel-of-capabilities shape; persistence of authority-bearing state.
Miller, Yee & Shapiro, Capability Myths Demolished, 2003; Miller, Robust Composition (PhD), 2006The object-capability (ocap) model; POLA; why capabilities avoid the confused-deputy problem; attenuation (you can only delegate what you hold, and may narrow it).Attenuated delegation over IPC (granted = requested ∩ sender_caps).
seL4 (Klein et al., SOSP 2009)The first formally verified OS kernel; a capability microkernel with machine-checked proofs.The proof that a small capability kernel is a sound TCB. Dezh is not verified (honest gap); seL4 is the bar.
Barrelfish (Baumann et al., SOSP 2009); Genode; Fuchsia/ZirconCapabilities across cores (multikernel); a capability component framework; object handles as capabilities in a shipping OS.Evidence the model scales to real system structure.
CHERI (Woodruff et al., ISCA 2014; Watson et al., IEEE S&P 2015); Arm MorelloHardware-enforced capabilities at the pointer level.A future substrate: Dezh enforces at the syscall + paging boundary today; CHERI is the hardware end-state (D017-adjacent).

Dezh's delta here is not "capabilities." It is that authority may only be derived through a declared intent, and the derivation is a structural subset operation (derived = requested ∩ intent_ceiling), not a purpose string or a policy annotation. Attenuation is classic ocap; making the attenuation ceiling a first-class "intent" (Ahd) that is the only path any authority can enter through is the sharpening — see SECURITY_MODEL.md#enforcement-model.

2. Information flow, provenance, and audit

WorkWhat it establishedRelation to Dezh
Asbestos (SOSP 2005); HiStar (Zeldovich et al., OSDI 2006); Flume (Krohn et al., SOSP 2007)Decentralized information-flow control (DIFC) at the OS level: labels track and constrain how data propagates.Closest in spirit. Crucially, HiStar built a new OS because retrofitting IFC onto a conventional kernel leaks through ambient channels — the same architectural bet Dezh makes for effect accountability rather than information flow.
PASS — Provenance-Aware Storage System (Muniswamy-Reddy et al., USENIX ATC 2006)OS-level provenance: record where data came from.Dezh records actor → intent → effect provenance too — but not as a side-log. In PASS/DIFC the provenance is collected alongside the operation; in Dezh the effect record is the authorization-and-persistence record the effect flows through.
Biba (1977) integrity lattice; Clark–Wilson (1987)Integrity as the dual of secrecy: trusted data must not be contaminated by untrusted input, and the crossing point must be an explicit, controlled certification step.Dezh enforces both axes on the live storage path. Reading a secret raises secrecy (no write-down); consuming network input lowers integrity (no write-up), so bytes off the wire cannot become trusted state until an explicit endorse — the exact dual of declassify. Both are exhaustively proven over the label space in dezh_core::difc and driven by taintflow-demo / ingress-demo.
SELinux / AppArmor; Linux audit + audit2whyMandatory access control + explainable denial ("why was this denied").Dezh's why-denied names the boundary that produced a refusal — the same reviewer need, but attributed to a capability mechanism rather than a policy rule.

Dezh's delta here is that provenance is on the authorization path, not beside it: because there is no ambient authority under the ledger, an effect cannot reach a resource without going through the record that authorizes and logs it. A retrofit provenance/audit layer on an ambient-authority OS can be bypassed by any path to the resource that skips the logger; Dezh removes those paths by construction.

3. Recovery, transactions, and compensation (honest rollback)

WorkWhat it establishedRelation to Dezh
Garcia-Molina & Salem, Sagas, SIGMOD 1987; the distributed saga patternLong-lived transactions that cannot hold locks are undone by running compensating actions, not by rolling back a log.Dezh's Sfar rollback is a saga at the OS effect layer: reversible effects are retracted by a ref move, compensatable effects are undone by running and recording a registered compensating action, and effects with no inverse are refused with a reason.
Copy-on-write / log-structured stores; NixOS / Nix (Dolstra, PhD 2006)Versioned, immutable, reproducible state; roll back by moving a pointer, not by mutation; no ambient mutable global state.Cairn is a commit-log store: rollback moves a ref, history is never erased, state survives reboot. Nix's "no ambient mutable state" is the storage analogue of Dezh's "no ambient authority".

Dezh's delta here is the reversibility class as a first-class property of every effect (reversible / compensatable / irreversible / unknown) that drives an honest rollback: the system computes a forecast of what a rollback can and cannot undo before touching anything, and it never claims to undo what it cannot. An effect whose connector does not declare its semantics is unknown and is never optimistically treated as reversible. We are not aware of an OS-level effect ledger that classifies reversibility and refuses to over-promise.

4. Legacy compatibility and untrusted-code isolation (the real competitor)

WorkWhat it isRelation to Dezh
Mach/L4 personalities; User-Mode Linux; gVisorLegacy ABIs served by user-space personality servers / a user-space kernel.Pol runs unmodified static Linux ELFs capability-gated — compatibility as a security downgrade-free bridge, not the authority baseline (D014).
gVisor, Firecracker (microVMs), WebAssembly/WASI (wasmtime), seccomp-bpf + Landlock, containersStrong, shipping confinement of untrusted code.This is Dezh's real point of comparison, not other OSes (D021). They confine resources well. What they structurally cannot do — because they sit on an ambient-authority host — is attribute every effect to its authorizing intent and reverse a whole agent mission with no ambient path to route around the ledger.

4b. The closest capability systems, answered directly

The first question a serious reviewer asks is: what does this do that seL4, Genode, Fuchsia/Zircon, Capsicum, or EROS/Coyotos does not? These systems are mature and, in several cases, do things Dezh does not. The honest answer is not "we isolate better" — it is that none of them make the intent→effect chain the system's primitive.

  • seL4 — a formally verified capability microkernel. It is the gold standard for a trustworthy TCB, and Dezh is not verified. seL4 gives you verified access control; it does not give you an effect ledger, a reversibility taxonomy, or whole-mission rollback — those are policy you would build above it. Dezh's bet is that effect accountability belongs in the substrate.
  • Genode — has had user-space drivers and a capability architecture for years; component trees delegate authority explicitly. Dezh does not claim novelty on "user-space drivers + capabilities" (Genode owns that, D021). What Genode does not provide is an on-the-authorization-path effect ledger with per-effect reversibility and a forecastable, saga-style mission rollback. Dezh adds the effect dimension on top of the access dimension Genode already nails.
  • Fuchsia / Zircon — handles are unforgeable capabilities and packages are hermetic; a shipping, industrial capability OS. Again Dezh claims no novelty on handle-as-capability or hermetic packaging. Zircon does not attribute every effect to an authorizing intent, classify reversibility, or reverse a mission; its model is object access, not effect accounting.
  • Capsicum (FreeBSD) — a capability mode retrofitted into a mainstream ambient-authority kernel: a process opts into capability mode and loses ambient rights. It is pragmatic and shipping, but it is exactly the retrofit case — ambient authority still exists in the kernel and around opted-in processes, so a system-wide unbypassable effect ledger is not achievable the way it is on a no-ambient-authority substrate. Dezh's whole reason to be from-scratch is to avoid the Capsicum-style residual ambient surface.
  • EROS / KeyKOS / Coyotos — persistent capability microkernels; the direct ancestors of Dezh's shape. They pioneered orthogonal persistence of authority-bearing state. They did not target autonomous agents, an effect ledger with reversibility classes, or mission compensation — the recombination in §6.

In one line: these systems make access safe; Dezh's contribution is making effect accountable and reversible on a substrate where the ledger cannot be bypassed — and none of them claims that. If a reviewer shows a system that does, that is exactly the feedback we want.

5. The unclaimed ground: agents as effect-accountable OS principals

Autonomous-agent frameworks (tool-use runtimes, agent orchestrators) enforce permissions and log actions in user space, on top of an ambient-authority OS. That is exactly the retrofit HiStar showed leaks. The operating-systems literature has deep results on capabilities, IFC, provenance, and sagas — but not a from-scratch substrate that makes an AI agent a first-class principal whose every effect is intent-derived, ledgered on the authorization path, reversibility-classified, and reversible as a mission. That is the ground Dezh claims (D013, D021), and it is where the recombination above becomes a thesis rather than a feature.

6. Precise novelty claim (what we do and do not claim)

We claim as new the combination, enforced end-to-end on a no-ambient- authority kernel:

  1. Intent-as-mechanism: authority exists only as derived = requested ∩ intent_ceiling, structurally ⊆ a declared intent — the sole derivation path.
  2. Ledger-on-the-authorization-path: the effect record is the thing the effect flows through, not a side-log, carrying actor → intent → derived cap → reversibility class → status.
  3. Honest, forecastable, saga-style mission rollback driven by a per-effect reversibility class, with compensation and explicit refusal.
  4. Unbypassable because from-scratch: the above is only sound with no ambient authority underneath — the reason the OS form factor exists.
  5. Agent-first: the target principal is an autonomous agent, an OS-level position the literature has not taken.

We do not claim as new: capabilities, microkernels, attenuated delegation, formal-verification potential, DIFC, OS provenance, sagas, multi-ISA IR, or personality-based compatibility. Each has strong prior art named above, and Dezh's identity rests on the recombination and its substrate, not on any one of them (D021).

7. Honest scope versus this prior art

  • seL4 is formally verified; Dezh is not (a stated gap — see SECURITY_MODEL.md#threat-model).
  • gVisor/Firecracker/WASI are mature, portable, and battle-tested at confinement; Dezh is a QEMU-only research prototype.
  • CHERI enforces capabilities in hardware; Dezh enforces at the syscall and paging boundary in software.
  • HiStar/Flume have a worked-out DIFC label calculus; Dezh's authority model is today a capability-ceiling algebra, not yet a multi-dimensional, formally-specified one (future work — a stated risk).

The point of this document is not to claim Dezh is better than any of these systems. It is to show we know exactly where Dezh sits, what is borrowed, and what is genuinely new — which is the minimum a serious operating-systems reader should demand before taking a new OS seriously.

Security

What Dezh defends against, what it does not, and how the enforcement is actually built. The threat model comes first because every claim below is scoped by it.


Threat model

This document states, honestly and specifically, what Dezh defends, what it does not, and what you must trust for those defenses to hold. It follows the project's D015 honesty rule: no defense is claimed that is not enforced by a real mechanism in the tree, and every explicit non-goal is named rather than elided.

Dezh is a from-scratch, capability-secure OS substrate whose one non-negotiable thesis is no ambient authority: every principal — including an AI agent — starts with zero access and can only act through an explicit, unforgeable, attenuable capability for a specific resource and operation. W8 builds on that to make the intent → derived authority → effect → provenance → reversibility chain the only path to an effect, and to make a whole agent mission attributable and reversible.

Status: research prototype, QEMU-only. There is no real-silicon port, no production boot chain, and no formal verification. Read every claim below in that light.


1. Assets

What an attacker wants, and what Dezh is trying to protect:

  • Confidentiality of an app's state — one app's Cairn namespace must not be readable or writable by another principal that was not granted it.
  • Integrity of the effect ledger — the recorded chain actor → intent → derived cap → effect → reversibility class must not be forgeable or rewritable after the fact by the principal that produced the effect.
  • Containment of an untrusted agent — an agent must not exceed the authority derived from its intent, reach devices it was not granted, read another task's memory, or monopolize the CPU.
  • Reversibility guarantees being honest — a rollback must never claim to have undone something it cannot undo.

2. Principals

  • The operator / console. The human (or their tooling) driving the machine. Trusted to open intents and authorize missions; acts as the mission owner.
  • Installed apps / agents (Dezh-IR or Linux-ELF). Untrusted. Get exactly the capabilities their verified manifest declares, derived down through the intent (Ahd) they run under — never more.
  • User-space services (e.g. the virtio-block daemon that owns the disk and the Cairn/Sand/Sfar/Tbar store). Partially trusted — see the TCB below.

3. Trusted Computing Base (TCB)

For the defenses in §4 to hold, you must trust:

  1. The kernel (dezh-boot): the trap/syscall boundary, the Sv39 page tables, capability attestation on IPC, the intent-derivation rule (derived cap ⊆ Ahd), and the preemptive scheduler. A bug here can defeat everything.
  2. The boot chain (OpenSBI / firmware → S-mode entry). Not measured, not attested.
  3. The hardware / emulator (today: QEMU virt). Assumed to implement privilege levels, paging, and the timer honestly. No defense against a malicious or buggy CPU/emulator.
  4. The storage daemon for ledger integrity. The daemon owns the block device and is the sole writer of the Cairn/Sand records. It is a user-space process with no ambient authority of its own (it holds only the device MMIO + DMA capabilities it was granted, and it attests every caller's capabilities), but a compromised daemon could forge or corrupt ledger records. Moving more of its integrity into the kernel/records (e.g. signed or chained-hash records) is future work.

Everything outside this list is untrusted, including all installed apps/agents.

4. What Dezh defends — and the mechanism that enforces it

Each of these is exercised by the redteam console command (an adversary that tries each escape) and asserted in CI. The point of the differentiator is only legible with a villain in the room.

AttackStopped at (named boundary)Mechanism
Read another app's Cairn namespacestorage-service capability checkKernel attests the sender's caps on every IPC recv; the daemon checks the requested namespace's bit and denies with an explanation.
Write a device MMIO register directlyhardware memory boundarySv39 paging maps MMIO U=0; a U-mode store faults, the kernel kills only that task, the console survives.
Forge / amplify a capabilitykernel syscall capability checkA zero-authority task calling a privileged syscall is denied; granted = requested & sender_caps on delegation means you cannot pass authority you do not hold.
Act beyond the granted intentintent-derivation ceilingderived cap = requested & Ahd_ceiling; anything beyond the intent is dropped, and the kernel denies the host call if attempted anyway.
Monopolize the CPUpreemptive schedulerA timer interrupt forces a context switch; a non-yielding task cannot starve others.

Beyond containment, W8 defends honest reversibility:

  • Mission authority spans every namespace a mission touched. A whole-mission rollback (sfar-rollback) or provenance query (tbar) is refused unless the caller holds the capability for every namespace the mission wrote to — a partial rollback would be dishonest, so it is refused all-or-nothing with the missing namespace named.
  • Rollback never over-promises. Reversible effects are retracted by moving a ref; compensatable effects are undone by running and recording a registered compensating action (a saga step, itself an accountable effect on the ledger); irreversible/unknown effects are refused with an explanation, never silently "undone". A connector that does not declare its semantics is classified unknown and is never optimistically treated as reversible.
  • Effects are attributable. The intent id and derived cap are stamped kernel → daemon on the commit path, so the actor → intent → effect provenance (tbar) is not something the actor asserts about itself.

5. What Dezh does not defend (explicit non-goals)

Naming these is part of the honesty rule.

  • Confidentiality beyond read-access control — the exfiltration gap. This is the most important one for the agent-containment thesis, so it leads. Dezh confines read access by capability: an agent cannot read a Cairn namespace it was not granted (the redteam cross-namespace read is denied). The W8 effect ledger and mission rollback are integrity mechanisms — they attribute and undo what an agent did; they cannot un-leak what it read and sent. A commit log does not help against exfiltration. Information-flow control (DIFC) now exists and is enforced on the storage path. dezh_core::difc provides the primitive (a secrecy label per object, a taint per actor, taint ⊆ sink for a write — no write-down, HiStar/Flume, RELATED_WORK.md §2), and it is enforced on the live Cairn console path (taintflow-demo): reading ns=vault (labelled secret) taints the operator, after which a commit to a lower-secrecy namespace is refused until an explicit, privileged declassify. It is enforced at the network edge too: a secret-tainted operator cannot export to a destination not cleared for that secret (exfil-demo, Marz).

    The integrity axis — the dual, and the one ingress needs — is enforced as well (ingress-demo). Secrecy asks "may this leave?"; it says nothing about bytes arriving from outside, which are attacker-chosen and must not silently become trusted state (Biba; the endorsement half of HiStar/Flume). A namespace can require an endorsement; consuming network input lowers the operator's integrity, so a write into such a namespace is refused until a privileged endorse. The two escapes are deliberately separate: declassify does not hand back integrity, and endorse does not clear secrecy, so one privileged act never grants two.

    What is not yet enforced: either taint across the U-mode client→daemon hop or IPC generally, and the ingress taint is at operator granularity — consuming any network reply lowers integrity wholesale rather than tracking the individual bytes. So information-flow control is real on the storage path and at the network edge in both directions, but not yet pervasive per-value.

  • Side channels and covert channels. No defense against timing, cache, Spectre/Meltdown-class, or power side channels; no mitigation of covert channels between principals.

  • A malicious or buggy kernel. The kernel is fully trusted (§3). There is no formal verification (unlike seL4) and no runtime self-protection against a kernel-level bug.

  • Hardware and firmware faults. Rowhammer, malicious DMA from a device Dezh did not sandbox, firmware implants, a lying emulator — all out of scope.

  • DMA-capable devices without an IOMMU. A device with a DMA capability can, absent an IOMMU, reach memory outside its grant. Dezh has the device-as- process + device capability model but no IOMMU yet (D017 is a hypothesis). A driver process is trusted with the memory its DMA can reach.

  • Denial of service beyond CPU monopoly. CPU starvation is handled by preemption. Storage exhaustion (the 255-slot commit log filling; GC is future work), memory exhaustion, and IPC flooding are not bounded yet.

  • Ledger integrity against a compromised storage daemon (§3). Records are parent-linked and hashed for corruption detection and rollback, not signed against a malicious writer.

  • External / irreversible effects in the real world. Dezh models external effects (e.g. email.send) and is honest that they cannot be un-happened. It does not (yet) integrate real network/DB/secret connectors with enforced effect schemas — that is the Gateways line of future work.

  • Supply-chain integrity of packages. .dzp packages are CRC-checked and manifest-verified, not cryptographically signed. Signed manifests are future work.

  • Real hardware. QEMU-only today. VMware/VirtualBox is proven for the x86 port's boot path only.

  • Multi-agent sub-delegation, leases/revocation for long-lived agents, and a formal authority algebra are designed-for but not yet built; treat long-lived-agent authority as coarse today.

6. Why not just a user-space sandbox? (head-to-head)

The real competitor is not another OS; it is user-space agent isolation — gVisor, Firecracker, wasmtime/WASI, seccomp+landlock. Those are strong at confinement. Dezh's claim is narrower and different: it makes the effect ledger unbypassable and a whole mission attributable and reversible, which is structurally hard for a sandbox layered over an ambient-authority host.

  • Unbypassable ledger. On a host with ambient authority (inherited fds, /proc, ptrace, environment, shared mounts), any effect log sits beside the resource, and there is generally a path to the resource that skips the log. On Dezh there is no ambient authority under the ledger: the effect path goes through the record that authorizes it. This is the reason the from-scratch kernel exists — it is the only substrate where the ledger cannot be gone around.
  • Whole-mission accountability and rollback. A sandbox can kill a process; it cannot cleanly attribute and reverse the set of effects an agent produced across resources under one intent. Dezh can: sfar-plan forecasts what a rollback can and cannot undo before touching anything, sfar-rollback retracts the reversible effects, runs registered compensations, and refuses the irreversible with an explanation, and tbar renders the provenance graph. The Dezh side of this comparison is reproducible in CI (sfar-demo, comp-demo, sfar-cross-demo, tbar, redteam).

The honest scope: a sandbox is more mature, portable, and battle-tested at raw confinement today. Dezh trades that maturity for a property they cannot easily offer — an effect ledger that cannot be bypassed and a mission that can be accounted for and undone.

7. Reproduce the defended cases

Boot the RISC-V kernel (see docs/GETTING_STARTED.md#build-and-run) and run:

redteam          # five escapes, five named boundaries, system survives
why-denied       # explains the most recent denial and names its boundary
sfar-demo        # a mission with mixed effect classes: forecast, then honest rollback
comp-demo        # a compensatable effect undone by a recorded compensating action
sfar-cross-demo  # a mission across two namespaces; rollback needs authority over both
tbar <ahd>       # the actor -> intent -> effect provenance graph for an intent

All of the above are also asserted by tools/ci/qemu_smoke.py.


Enforcement model

Core Rule

No task receives authority by default. A task can only perform an effect if the kernel, boot plan, service registry, or caller has explicitly granted the required authority.

Prototype scope

The authoritative threat model is above; this is the narrower list of cases the prototype's enforcement was built and tested against:

  • untrusted U-mode tasks
  • apps with limited declared capabilities
  • service clients that should not touch devices directly
  • faulty or stopped services
  • malformed IPC requests
  • no-grant MMIO access attempts

Enforced Today

  • Syscalls are gated by task capabilities.
  • U-mode page tables deny access outside the task grant.
  • MMIO is mapped only for tasks with explicit device grants.
  • IPC send requires IPC capability.
  • Transferred capabilities are attenuated to the sender's own authority.
  • Foreground task faults kill only the faulting task.
  • User-space block driver failure does not kill the console.
  • Stopped or faulted block service causes clean command failure.

Not Enforced Yet

  • Real IOMMU-backed DMA isolation.
  • Production package signatures.
  • Multi-client block queues with per-client data windows.
  • Full revocation model for long-lived delegated capabilities.
  • Production installer and bootloader flow.
  • Side-channel resistance.
  • Formal verification.

Revocation (honest answer)

Reviewers ask this first, so here is the current stance plainly.

What exists today. Authority is attenuable and its effects are reversible, which covers the common cases without a general revocation mechanism:

  • A delegated capability can never exceed the sender's own (granted = requested & sender_caps), so authority only ever narrows as it spreads.
  • A capability is bound to a task; when the task exits or is killed on a fault, its authority is gone with it.
  • Damage done through a granted capability is undone structurally: Cairn's commit log lets an operator roll a namespace back to a prior state (the F1/F2 demos show exactly this — an agent's bad write is reverted after the fact).

What now exists (intent level). An intent (Ahd) can be opened with a lease (a bounded run count that auto-revokes on exhaustion) or revoked explicitly; a revoked or exhausted intent authorizes nothing further, while the effects it already produced keep their provenance (tbar/sfar still resolve). This is the first realization of the generation/lease scheme, at the intent layer — lease-demo proves it.

What still does not exist (capability level). There is no runtime lease/revoke for a single, long-lived task capability bit already delegated to a still-running task — you cannot reach into a live task and rescind one bit mid-execution. The honest reason is the point below: task capabilities are bitmask bits, not per-object revocable references.

What kind of capability is this? (bitmask vs object-capability)

Being precise, because it was the most important honest caveat. Dezh started with authority as a bit in a per-task bitmask (print, IPC, a Cairn namespace, device, block) rather than an unforgeable reference to one object as in seL4 or CHERI. That is no longer the whole story: the authorities that name real objects — namespaces, devices, egress destinations — are now generation-stamped handles with per-object revocation and attenuated delegation (see the migration below). What remains a plain bit is the process-level authority that names no object (print, time, ipc), and the per-message attestation the storage daemon uses.

Two things keep this from being "just Linux capabilities," though:

  • Not ambient, not inherited. Linux capabilities are ambient process privileges that a child inherits by default. A Dezh task starts with zero authority; it holds only bits explicitly granted, and a spawned process inherits none.
  • Kernel-attested and attenuable per message. The kernel stamps the sender's capabilities on every IPC message, and delegation is granted = requested ∩ sender_caps — you can pass a narrower subset of what you hold, checked by the kernel, and never more. Linux capabilities are not attenuable this way.

So Dezh sits between Linux capabilities and seL4/CHERI object-capabilities: far stronger than the former (no ambient authority, attenuable, kernel-attested, and now per-object revocable for every authority that names an object), and still short of the latter, whose object references are the only form authority takes and are enforced by the kernel (or hardware) on every use rather than at kernel-side chokepoints.

The path (the one big change), now prototyped. Turn a capability into a first-class object — a generation-stamped handle to a specific resource — so that (a) revocation of a single capability falls out (bump the generation; every outstanding handle is invalidated at next use), and (b) delegation forms a real provenance graph. This primitive is now built and proven in dezh_core::ocap (Cap = object + rights + generation; CapTable holds the live generation per object; derive attenuates rights along a delegation graph; revoke bumps a generation to invalidate every outstanding handle to that object). It is host-tested exhaustively and driven in the kernel by cap-demo: mint a handle, derive an attenuated child, use both, then revoke the object and watch the whole delegation subtree go stale at next use while a handle to a different object keeps working — per-object revocation a bitmask cannot express. A forged handle (guessed generation) is rejected.

Migration has started on the live plumbing, not just the primitive. The Cairn namespace capability is now ocap-backed at the kernel chokepoint: the console holds a generation-stamped handle per namespace, and the ocap gate is enforced on both the operator console path (cairn-commit/-get/... via ns_authority_live) and the untrusted agent path (KHost::cairn_put/ cairn_get). ns-revoke bumps a namespace's generation, and from that point a commit or an agent's write to that namespace is refused until ns-grant (nsrevoke-demo, agentrevoke-demo). So runtime revocation of a live namespace capability is real for every kernel-side path today.

Revocation is now also enforced by the object owner and survives reboot: the storage daemon records a per-namespace revoked flag in the Cairn superblock, so ns-revoke persists on disk and the daemon refuses every operation on a revoked namespace until ns-grant — independent of the in-memory kernel gate. A CI reboot leg proves it: revoke a namespace, power-cycle, and the daemon still refuses it from its superblock even though the kernel's in-memory handle is fresh. So the Cairn namespace capability has full ocap revocation at three layers: the console gate, the untrusted-agent (KHost) gate, and the persisted object-owner check.

Breadth: the object-like authorities are now all ocap-backed. Beyond namespaces, the two other authorities that name real objects have been migrated:

  • Devices. Each device is an object with a generation-stamped handle (dev-revoke / dev-grant). Revoking one stops every use of that device regardless of finer authority — a kill-switch above the per-destination gate (dev-demo). The grants themselves are now per-device: the kernel finds the block device and the NIC and maps only their own pages, so neither daemon can reach the other's hardware. (The block grant previously mapped the whole virtio-mmio window.)
  • Egress destinations. Authority names a destination, not "the network", and destinations are revoked individually (marz-revoke <dest>).

What deliberately stays a simple bit is the process-level authority that does not name an object: print, time, ipc. These are ambient-style permissions of a task, not references to a resource, so a generation-stamped handle would add ceremony without adding a revocable object. If they ever name objects (a specific console, a specific channel), they should migrate too.

Reviewer Notes

The current security value is architectural discipline, not production hardening. The relevant question is whether the authority boundaries are in the right places and whether the demo proves those boundaries under fault and denial scenarios.

Subsystem designs

Design notes for the subsystems that carry their own trust argument. Each is self-contained.


Marz: guarded egress

Marz (border) is the boundary an effect crosses to leave the machine. Crossing it is irreversible: once bytes are on the wire, no ledger can call them back. So Marz is where Dezh's whole stack has to hold at once — capability, intent, information flow, and effect accountability.

This document follows the project method: study what existing systems do and where they fail, state the precise delta, then design. Status: design + phased implementation; §6 marks what is built.


1. What the field already does

SystemNetwork access modelSource
seL4 / Genode / FuchsiaThe protocol stack runs in user space (lwIP/PicoTCP). An application has no direct channel to the NIC driver; it reaches the network only by capability-protected IPC to the stack.seL4 whitepaper
HiStar / Flume (DIFC)Data carries labels; exporting data out of the system is a declassification that only a privileged principal may perform. Flume gives each tag two capabilities (t+/t−) for declassify/endorse.Flume, SOSP'07
Linux / WindowsAmbient authority: a process names a destination and connects. Authorization is a global property of the process, not of the destination.Ambient authority

Read together, the field has solved two different halves:

  • capability systems confine access to the device/stack, and
  • DIFC systems constrain which data may flow out,

while mainstream systems do neither per-destination — which is exactly the exfiltration channel: a compromised process connects anywhere, and nothing records which destination on whose authority.

2. The mistakes we design against

  1. Ambient egress. "Any process may connect anywhere." → In Marz the destination is part of the capability, not a parameter the caller picks freely.
  2. Access control without flow control. Confining who may use the NIC does not stop a permitted principal from shipping a secret. → Marz applies the DIFC rule on export: a tainted actor may not send to a lower-secrecy destination without an explicit declassification (the Flume lesson).
  3. Flow control without accountability. A label check leaves no record of what left, under whose intent. → Every send is a Sand effect: actor → intent → derived cap → destination → irreversible.
  4. Pretending egress is reversible. Rollback machinery that "undoes" a send is a lie. → Marz effects are classified irreversible; sfar-rollback refuses them with an explanation, exactly as it already does for the modeled external effects.
  5. A side-channel audit log. A log beside the socket can be bypassed. → The Marz record is on the authorization path: no ambient route to the NIC exists, so an effect cannot reach the wire without going through the record.

3. Design

Principals. The NIC is owned by a user-space Marz daemon holding only an explicit MMIO + DMA grant for the virtio-net device — the same shape as the existing virtio-block daemon. No task, and no agent, ever touches the NIC.

The egress capability names a destination. Authority to send is not "network access"; it is a capability for a specific destination (address + port class). It is derived from an intent (Ahd) exactly like every other authority:

derived_destinations = requested_destinations ∩ intent_ceiling

so an agent can only reach destinations its intent already allowed, and anything beyond is dropped and reported.

Export requires declassification (the DIFC gate). Before a send, the actor's secrecy taint must flow to the destination's label:

send permitted  ⟺  taint(actor) ⊆ label(destination)

A secret-tainted actor sending to a public destination is refused — the exfiltration case — unless a privileged principal explicitly declassifies. This is Flume's rule applied at the wire.

Every send is a ledgered, irreversible effect. On success Marz appends a Sand record: actor, intent, derived capability, destination, reversibility = irreversible, so tbar attributes it and sfar-plan forecasts honestly that it cannot be undone.

4. The precise delta (what is ours)

We claim no novelty on user-space network stacks (seL4/Genode/Fuchsia) or on DIFC labels and declassification (HiStar/Flume). The recombination is:

egress as a per-destination, intent-derived capability whose every use is a declassification-checked, irreversibly-classified record on the same effect ledger — on a substrate with no ambient authority to route around it.

PropertyLinux/WinseL4/Genode/FuchsiaHiStar/FlumeMarz
No ambient path to the NIC~
Capability names the destination~
Authority derived from an intent
Flow control on export (declassify)
Send is a ledgered effect
Classified irreversible, rollback refuses
Attributed to a mission

5. Why this matters beyond a feature

Until now every external effect in Dezh has been modeled (email.send, prod.deploy) and the docs say so. Marz makes one real. It also makes the confidentiality work load-bearing: today an agent is bound to a single Cairn namespace, so there is no channel to exfiltrate through. A network gives it one — and the DIFC gate is what stands in the way.

6. Phases (each CI-green, in the W8 style)

  • M1 — device. DONE. The marz daemon is a separate U-mode ELF holding exactly two grants: the single virtio-net MMIO page the kernel discovered (capability TASK_DEVICE_VIRTIO_NET — not the whole window the block grant maps) and a DMA window. It never scans for hardware. It negotiates no features, arms the transmit queue, builds a real Ethernet + IPv4 + UDP frame and sends it. marz-send drives it; CI asserts the frame in QEMU's packet capture, so the claim is verified on the wire, not from a print.
  • M2 — the gate. DONE. Egress authority names a destination, not "the network": each destination carries an address and a secrecy label, and the gate requires (a) the capability for that destination and (b) a flow the destination may legally receive (taint(actor) subset of label(destination)). Revoking one destination leaves the others intact. marz-demo proves both on the wire, and CI counts frames in the capture: exactly the authorized sends appear, and a refused send leaves nothing behind. (Deriving the destination set from an intent ceiling is the remaining slice.)
  • M3 — the effect. DONE. Every authorized send is recorded as an irreversible Sand effect carrying its actor, intent and destination, so tbar attributes what left the machine and sfar-plan forecasts it honestly. sfar-rollback refuses it - the wire cannot be undone and Dezh does not pretend otherwise. marz-effect-demo shows the whole loop.
  • M4 — the receive path. DONE. Transmitting proves little on its own: a stack that cannot receive cannot be checked against reality. The daemon now offers the NIC receive buffers, blocks on the device interrupt, and parses what comes back: it resolves the destination with ARP and completes a real ICMP echo exchange, matching the reply by id and sequence (marz-ping <dest>, reported as NET-RX-OK). Ingress carries the same authority as egress — a revoked device or destination refuses the probe — because reaching the wire is reaching the wire.
  • M5 — the integrity axis. DONE. A receive path opens a hole secrecy does not close: bytes off the wire are not secret, they are attacker-chosen, and the danger is that they quietly become trusted state. Completing a network exchange therefore lowers the operator's integrity, and a sink that requires endorsement (ns=note, ns=vault; ns=lab is scratch and requires none) refuses the write with an explainable denial until a privileged, recorded endorse. endorse is the dual of declassify and the two stay separate — neither privileged act grants the other. ingress-demo walks it end to end (INGRESS-OK).
  • Verification. QEMU's packet capture (-object filter-dump) lets CI assert the permitted frame actually left and that the refused one did not — a real test, not a printed claim. CI decodes the capture as packets rather than scanning it for bytes, which matters once the host starts answering: its ICMP errors quote our datagram back, and a substring count would score those quotes as extra egress. The assertions are now structural — exactly four guest-sourced UDP datagrams carry the marker, and the echo request and its reply both appear.

Honest non-goals (v0)

No TCP, no DNS, no inbound listening (nothing accepts a connection), no routing, no DHCP — the address is static. ARP and ICMP echo exist because they are what a reachability probe needs. Ingress is DIFC-labelled on the integrity axis (M5), but only at operator granularity — completing any exchange lowers integrity wholesale rather than tracking individual bytes — and a received packet is still not a ledgered effect of its own. No cryptographic transport. This is the authority + accountability mechanism at the network edge, plus enough of a stack to prove the edge is real — not a general network stack.


Package signing

This document specifies how Dezh signs and verifies .dzp packages. It is written the way the rest of the project is: we first study, precisely, the mistakes real package-signing systems have made, then design so we do not repeat them — and we add the one thing that is only possible on a capability substrate, which is signing the authority a package requests, not merely its bytes.

Status: design + phased implementation. What is built vs designed is marked in §7. It follows D015 (no claim beyond what is enforced) and the no-ambient- authority thesis.


1. Why the current story is a real gap

Today a .dzp package is CRC32-checked and manifest-verified — this catches accidental corruption but not forgery: anyone who alters a package can recompute its CRC. For a system whose thesis is "no authority without explicit provenance," an unsigned package is a structural contradiction: an app requests capabilities in its manifest, but the request's author is unattributable. This is the gap docs/STATUS.md names, and it is the one we close here.

2. Mistakes we studied, and how Dezh avoids each

Mistake in the wildConsequenceHow Dezh avoids it
Sign the artifact bytes, not the metadata (early apt/yum, npm)Rollback, freeze, and mix-and-match attacks — the version/dependency/permission metadata is unprotected (TUF).The signature covers a canonical serialization of the whole manifest — name, version, a monotonic counter, payload kind, and the requested capability set — plus the payload hash. Changing any of them invalidates the signature.
A trusted signer is trusted with unbounded authority (xz / CVE-2024-3094: a maintainer social-engineered for two years, then shipped a backdoor in a validly-built release) (OpenSSF)A signed package still receives full ambient authority; one malicious/compromised signer = total compromise.Signing is provenance, not safety. A signed package still receives only the capabilities its manifest requests, those are bounded by the signer's own capability ceiling (§4), and every effect it makes is ledgered and reversible (W8). Dezh's defense is layered; the signature is one layer, not the wall.
A single, long-lived, online signing key (code-signing guidance) (Keyfactor)One key compromise signs everything, forever.Role separation: an offline root key authorizes publisher keys; each publisher key is scoped (a capability ceiling), rotatable, and independently revocable.
No, or slow, revocation — a compromised cert keeps being honored (AppViewX)Users keep trusting malicious software after compromise is known.A signer key is a trust-store entry; revoking it is an explicit, ledgered effect, and a revoked key's future installs are rejected. This is the same lease/revocation principle Dezh already applies to intents.
Signing is opaque and unauditable — you cannot tell what was signed, by whom, when (Sigstore's motivation for the Rekor transparency log) (Sigstore)No accountability; silent key abuse.An install is a Sand effect on the ledger: installer → signer identity → package → granted caps. The provenance graph (tbar) answers "who authorized this app's authority." The ledger is the transparency log, native to the system.
Roll-your-own cryptoSubtle, catastrophic bugs.We use an audited Ed25519 implementation (RustCrypto ed25519-dalek), never a hand-rolled one, isolated in one module shared by the SDK (signing) and the kernel (verification only — deterministic, no RNG in the kernel).
TOCTOU: verify one copy, execute anotherThe verified bytes are not the run bytes.The signature is verified over the exact staged blob at install time, and the registry independently re-hashes the blob on every load (existing behavior).

3. What is signed (bind authority, not just bytes)

The signed message is a canonical, length-prefixed serialization:

SIG_MSG = "DZSIG1" ||
          payload_hash (FNV/SHA of the payload bytes) ||
          len(name)    || name ||
          len(version) || version ||
          counter (u64, monotonic per name) ||
          kind (u16) ||
          caps (u32 manifest capability bitmask)

The capabilities are inside the signed message. No other package format does this, because no other package format treats requested authority as a first-class, install-time value. A signature therefore attests a precise claim:

Signer S authorizes name@version (sequence counter) to request exactly capability set caps.

Tampering with the requested capabilities — the most security-relevant field — breaks the signature.

4. The novel part: publisher capability attenuation

This is the W8 authority rule (derived ⊆ intent) applied to the supply chain. Every publisher key in the trust store carries a capability ceiling — the maximum authority that key is trusted to authorize. Install enforces:

granted_caps  =  requested_caps ∩ signer_ceiling         (structural subset)

Exactly as an intent bounds a running agent, a publisher key bounds what authority it may ever put into the world. A key trusted only for print + cairn cannot sign a package that receives device, MMIO, or DMA authority — the excess is dropped and reported, the same way intent-run drops beyond-intent capability. The confused-deputy and over-privileged-publisher problems dissolve: a publisher can never escalate a package beyond the ceiling the root granted the publisher's key.

This is the same algebra proved exhaustively in dezh-kernel::authority; package signing is that algebra at a new layer, so the invariant "authority can only ever be a subset of what authorized it" now holds from root → publisher → package → running app → effect, unbroken.

5. Trust model and roles

  • Root key (offline). The anchor. Signs the trust store: the set of trusted publisher keys, each with its capability ceiling and status (live/revoked). The root is never online; it only re-signs the trust store when publishers change. Compromise of a publisher key cannot forge a new trusted publisher.
  • Publisher keys. Sign packages. Scoped by a ceiling, revocable, rotatable.
  • Verifier (the Dezh kernel). Holds the root public key (measured/pinned). On install it: verifies the trust store against the root; looks up the signing publisher; verifies the package signature over SIG_MSG; enforces granted = requested ∩ signer_ceiling; and records the install as a Sand effect. The kernel only verifies — it never holds a private key.

6. Install becomes a ledgered, attributable effect

When a signed package installs, Dezh writes a Sand effect that binds the granted authority to the signer:

actor = installer
intent/authority-source = signer key id
effect = "installed name@version, granted caps = C (⊆ signer ceiling)"
reversibility = reversible (an install can be rolled back)

So tbar and the audit surface answer, unforgeably, who authorized the authority this app holds — the property Sigstore approximates with an external transparency log, here intrinsic to the OS because the OS already has an unbypassable effect ledger.

7. Defense in depth — the honest, layered claim

The xz backdoor is the cautionary tale: a validly signed artifact from a trusted maintainer was still malicious. Signing did not, and cannot, prevent that. What Dezh adds is that even a validly-signed malicious package:

  1. receives only the capabilities its (signed) manifest requested,
  2. bounded by its publisher's ceiling (a cairn-only publisher cannot ship a package that touches devices),
  3. runs with no ambient authority to escalate from,
  4. has every effect ledgered and attributable, and
  5. is reversible as a mission (retract / compensate / refuse).

Package signing on npm/PyPI/apt gives a signed package the host's full ambient authority — so an xz-style compromise is game over. On Dezh, signing is the provenance layer of a stack whose confinement and accountability layers do not depend on the signer being honest. That layering — not the signature alone — is the actual security claim, and it is only possible because the substrate has no ambient authority.

8. Implementation phases

  • P1 — crypto core. DONE. Ed25519 verify via the reputable, zero-dependency, no_std ed25519-compact crate, wrapped in dezh-core::sig; host-tested; builds for both bare-metal targets. attenuate/beyond_ceiling are the publisher-ceiling algebra, proved a subset exhaustively over the 8-bit space. No hand-rolled crypto.
  • P2 — signed .dzp. DONE. The DZSP envelope wraps an unsigned inner .dzp (so the core format and F3 byte-pinning are untouched); the signed message is inner || "DZSIG1" || counter; parse_envelope/pack_envelope with a full sign→pack→parse→verify round-trip test.
  • P3 — kernel enforcement. DONE. A build-time signer (build.rs, fixed seed, deterministic) embeds a signed demo package + its publisher key; the kernel trust store holds root-anchored publisher keys with ceilings + revocation; sig-demo verifies the signature, requires a trusted non-revoked signer, attenuates granted = requested ∩ ceiling (the demo's ipc is dropped), records the install as a ledgered Sand effect, and refuses a tampered package and a revoked key. A CI leg asserts all of it.

Each phase is a separate, CI-green commit, in the disciplined style of W8.

Still open (honest): a stand-alone developer signing CLI (today only the build-time signer exists); a root-signed trust store loaded from disk with key rotation (today the store is kernel-embedded); and wiring signature enforcement into the live pkg-recv install path so uploaded packages are verified too (today sig-demo proves the mechanism end to end on an embedded package). These are additive; the mechanism and the capability-native attenuation are built.

Explicit non-goals (honest scope)

No online PKI, no certificate transparency service, no threshold signatures (single root key for the prototype), no hardware key storage, no timestamping authority. These are the production hardening beyond a reviewable prototype; the mechanism and the capability-native attenuation are the contribution.

Reviewer guide

For someone judging whether the claims hold: what to check and in what order, how to run each demo yourself, and the questions reviewers ask first.

Captured output from real runs is in transcripts/ - that output is produced by the kernel, not written by hand.


What to check

This guide is organized around the four flagship demos — one per differentiator. Each is reproducible from a fresh clone and asserted in CI. For the honest scope of what is and is not true, read STATUS.md first; for the security argument, Enforcement model.

Setup

cargo test --locked --workspace
(cd dezh-core && cargo test --locked)       # shared IR engine + .dzp format
(cd dezh-boot && cargo build --locked)      # RISC-V kernel
(cd dezh-boot-x86 && cargo build --locked)  # x86_64 kernel

The fastest single check is the RISC-V smoke test, which drives every RISC-V demo end to end and fails loudly if any capability, isolation, or storage signal is missing:

python tools/ci/qemu_smoke.py riscv64 \
  --kernel dezh-boot/target/riscv64gc-unknown-none-elf/debug/dezh-boot \
  --qemu qemu-system-riscv64

The four flagship demos

F1 — Agent containment (D001/D013)

An agent app works inside its grant, is DENIED by the kernel beyond it, delegates an attenuated capability over IPC, and its damage is rolled back.

python tools/demo/run_agent_demo.py \
  --kernel dezh-boot/target/riscv64gc-unknown-none-elf/debug/dezh-boot \
  --qemu-riscv qemu-system-riscv64

Or interactively at the dezh> prompt: agent, then spy (no-cap app is denied by the kernel), then cairn-rollback.

Claim: authority is explicit, unforgeable, and attenuable — enforced by hardware privilege + paging, not a sandbox policy file.

F2 — Cairn storage (D004/D005)

Versioned state: commit, corrupt, roll back → restored, and restored across a reboot. A second app is denied the first app's namespace.

Interactive: cairn-demo, cairn-log note, cairn-rollback note 1, cairn-verify note. The smoke test also power-cycles the disk and re-checks the rolled-back value.

Claim: state recovery is structural (versioned objects + refs), not fsck; per-app namespaces are capability-gated by kernel-attested sender caps.

F3 — Multi-ISA apps (D003/D016)

The same byte-identical Dezh-IR payload runs on both kernels.

# x86_64 kernel runs the .dzp agent package (pack -> parse -> verify -> run):
python tools/ci/qemu_smoke.py x86_64 \
  --kernel dezh-boot-x86/target/x86_64-unknown-none/debug/dezh-boot-x86 \
  --qemu qemu-system-x86_64

The byte-identity is pinned by dezh-core's demo_sum_bytes_are_pinned test (len + CRC-32). The RISC-V agent demo runs the same bytes.

Claim: apps are ISA-portable by construction; proven today on 2 ISAs.

F4 — Pol compatibility (D007/D011/D014)

A real, unmodified static Linux/RISC-V ELF (built for riscv64gc-unknown-linux-musl, no Dezh code) runs under the Linux personality, capability-gated.

Interactive: linux-elf (serviced with the PRINT cap, DENIED without, unsupported syscall → clean -ENOSYS), and bench-pol for the measured translation overhead. The same ELF also runs on real riscv64 Linux (qemu-riscv64-static dezh-boot/linux-guest/target/.../linux-guest).

Claim: near-native compute for same-ISA binaries (no emulation); syscall translation overhead measured and honestly scoped in BENCH.md. Coverage is a small subset today.

Boot it like a real OS

tools/x86/build-iso.sh builds a GRUB Multiboot2 ISO that boots the x86 kernel in QEMU -cdrom and in VirtualBox/VMware. See Running in a VM.

Strong review questions

  • Are capabilities checked at the right enforcement points, on both the syscall and the memory boundary?
  • Does the driver grant model avoid hidden device authority?
  • Is attenuation-plus-rollback an adequate substitute for runtime revocation for the agent use case? Where does it break?
  • Are the benchmark caveats (emulated vs native) stated honestly enough?
  • Which assumptions need formalization before any production claim?

Public hygiene scan

python tools/review/scan_public.py

Running the demos

This script assumes the RISC-V kernel has been built and QEMU is available.

Run The Automated Demo

python tools/demo/run_review_demo.py \
  --qemu-riscv qemu-system-riscv64 \
  --transcript docs/transcripts/riscv64.md

On Windows, pass the full QEMU path if it is not on PATH.

Manual Command Sequence

At the dezh> prompt, run:

version
about
ipc-typed-demo
ipcstat
services
install --dry-run
install run
apps installed
app-permissions lab
app-run lab
calc 7 + 5
calc-history
vault-put demo-secret
vault-get
app-deny vault
svc-stop virtio-block
read
svc-restart virtio-block
write recovered
read
svc-fault-demo virtio-block
read
svc-restart virtio-block
bench-all
halt

Expected Signals

The transcript should include:

  • boot contract VALIDATED
  • [typed-ipc] PASS
  • VirtioBlock state=Running
  • dry-run complete; disk not modified
  • Install Report: Dezh Root v1
  • [installed] lab
  • [installed] calc
  • [installed] vault
  • Dezh Lab :: installable app system probe
  • PASS: scheduler, IPC, installer launch, and UI path cooperated
  • [calc] 7 + 5 = 12
  • calc last = "7 + 5 = 12
  • vault value = "demo-secret
  • vault device/block direct access denied; console survived
  • svc-stop virtio-block status=0 state=Stopped
  • virtio-block unavailable; command failed cleanly
  • svc-restart virtio-block state=Running
  • svc-fault-demo virtio-block request_status=0 state=Faulted
  • [bench-all] PASS

Short Review Path

For a shorter run, use:

python tools/demo/run_review_demo.py --mode short --qemu-riscv qemu-system-riscv64

The short run exercises boot, typed IPC, service startup, app install/run, service stop/restart, service fault/restart, and halt.


FAQ

Is Dezh production-ready?

No. Dezh is a working research prototype intended for architectural review. It boots, runs isolated tasks, uses a user-space block driver, validates typed IPC, and exercises a transactional package lifecycle in QEMU, but it is not a production OS.

Why publish it now?

The project is at the point where its core thesis can be inspected through code, QEMU transcripts, and repeatable tests. Public review is useful before the design becomes too large to change.

What is the main technical thesis?

Dezh explores intent-scoped authority and effect accountability. A program should receive the narrow authority needed for a specific effect, and important state changes should be visible, recoverable, and tied to an explicit service route or transaction.

Is Dezh a Unix clone?

No. Dezh intentionally avoids starting from ambient files, ambient devices, ambient process inheritance, or a global package registry. Compatibility layers may exist later, but they should not define the core authority model.

Is Dezh a microkernel?

It shares some microkernel instincts, especially user-space drivers and service boundaries, but the current goal is not to fit a label. The important boundary is explicit authority: kernel code should enforce isolation and routing, while device and storage effects should be delegated through granted user-space services.

Why user-space virtio-block?

The block device is a useful proof point: persistent storage should not require a hidden kernel I/O path. The current virtio-block daemon runs in U-mode and receives explicit MMIO and DMA grants.

How are apps installed?

The SDK builds .dzp packages. The package store writes registry, journal, and blob sectors through the registered user-space block service. Only Active packages are runnable.

What prevents half-installed apps?

Package install/remove uses a journaled state machine. Interrupted installs are rolled back, committed only when checks match, quarantined if suspicious, or blocked when the journal is corrupt.

What should reviewers focus on first?

The highest-value review areas are:

  • capability boundaries
  • user-space driver grants
  • typed IPC status handling
  • service stop/fault/restart semantics
  • package journal recovery
  • package capability escalation review
  • denial proofs and failure behavior

Why not build this on seL4 (or Genode)?

The most important question, answered honestly. seL4 is a formally verified capability microkernel; Genode is a mature capability component OS with user-space drivers and typed IPC. For a product, building the Dezh model on top of one of them would be the right call — you would inherit verification, real object-capabilities, and IOMMU support instead of re-deriving them.

So the from-scratch kernel is not the contribution, and we do not claim it is (see DECISIONS.md D021). The contribution is the model: intent as the sole authority-derivation path, an effect ledger on the authorization path with a reversibility class, and honest whole-mission rollback, aimed at autonomous agents. We wrote a small kernel to prototype that model end to end with nothing hidden underneath and full control of the substrate while the ideas were still moving — the pedagogical and iteration reasons, not a claim that the world needs another microkernel.

The honest consequence: several things seL4/Genode already do well (verification, per-object capabilities, IOMMU) are gaps here, named in STATUS.md and Threat model. A credible productization path is to port the intent→effect model onto seL4 or Genode and keep the model, not the kernel. If a reviewer's takeaway is "the ideas are interesting but belong on a verified base," that is a conclusion we agree with.

What is intentionally out of scope right now?

  • production bootloader and installer media
  • production networking (and with it, information-flow / exfiltration control)
  • dependency solving
  • real IOMMU integration
  • graphics stack
  • real hardware bring-up
  • formal verification of the whole system
  • online PKI / certificate-transparency for package signing (the signing mechanism now exists — see Package signing — but the key-distribution layer does not)

Status and honest limitations

One page, no spin. Dezh is a research prototype that demonstrates an architecture; it is not a production OS. This is exactly what is and is not true today, so a reviewer never has to guess.

What genuinely works (in CI, reproducible)

AreaState
No-ambient-authority thesisEnforced at the syscall boundary and by hardware paging (U-mode faults on ungranted memory/MMIO).
F1 — agent containmentAgent app runs in-grant, is DENIED by the kernel beyond it, delegates an attenuated cap over IPC, and its writes are rolled back.
F2 — Cairn v1 storageCommit-log store: commit, snapshot, roll back, verify; survives reboot; cross-namespace access denied by kernel-attested caps.
F3 — multi-ISAThe same Dezh-IR bytecode runs on the RISC-V and x86_64 kernels; the bytes are pinned byte-identical by a test; x86 runs it as a real .dzp package.
F4 — Pol (Linux personality)A real, unmodified static Linux/RISC-V ELF runs under a capability-gated Linux syscall shim; the same bytes also run on real riscv64 Linux.
x86_64 bootBoots via QEMU -kernel (PVH) and from a GRUB Multiboot2 ISO in QEMU and VirtualBox; a 32-vector exception IDT reports faults instead of triple-faulting.
x86_64 returnable interruptsA 256-vector IDT: exceptions still end in a reported halt, while vectors 32..255 save every general-purpose register, dispatch, restore, and iretq. A Local APIC timer is armed at 100 Hz from a rate measured against PIT channel 2 (~999 MHz APIC bus under QEMU, printed as counted). Proof is a work loop that keeps summing 1..=1000 across the ticks with no round corrupted, and a tick count that freezes when the timer is masked while that loop runs on. No device IRQs on x86 yet.
x86_64 preemptionThe same entry path can decline to resume what it interrupted: the dispatcher is handed the interrupted rsp and its return value is loaded back into rsp, so a saved 22-qword frame is a task. Three kernel tasks containing no yield of any kind are round-robined with the boot task, one tick per turn, each still checking its arithmetic; asserted in CI by turns granted (9/8/8 in both debug and release) rather than work completed, since only the interrupt handler can grant a turn.
x86_64 isolationPer-task address spaces (own cr3, kernel entries shared and USER-free) and real ring 3: a GDT with user descriptors, a TSS whose rsp0 follows the running task, and exactly one DPL3 IDT gate (0x80) as the way in. Two CPL3 tasks run programs copied into pages of their own — a kernel Rust function is unreachable from ring 3 by construction, since .text is mapped but never USER. One of them reads an address it was not given: it is killed alone, with cr2/rip/error reported, while its neighbour keeps making syscalls and exits normally. Asserted in CI, including the cs the CPU saved (0x23), which a task cannot forge. Still missing: nothing frees a dead task's pages, one CPU.
x86_64 derived authorityAn x86 task's syscalls are capability-checked, and the capability is derived from an intent ceiling, not held by default: granted = requested ∩ ceiling, computed by dezh_core::mcap — the same function the RISC-V kernel calls and the one its exhaustive test pins, not a second implementation. Two CPL3 tasks run byte-identical code from a byte-identical manifest under different ceilings; the narrow one is refused print that its own manifest requested, by name (DENIED: task 7 holds no PRINT capability), and reports back which of the two calls it was allowed. A refusal is a return value, not a fault. Honest scope: there is no Ahd token, no Sand ledger and no mission on x86 — the ceiling is a number the boot code passes in, so this is the derivation rule and the denial, not the accounting built on them.
Drivers out of kernelvirtio-block is a U-mode daemon holding an explicit MMIO + DMA grant; clients reach it only over typed IPC. Caveat (not buried): without an IOMMU this gives fault isolation + least privilege of the driver process, not memory safety against a malicious driver that programs the device to DMA anywhere. The IOMMU is core to this story, not future polish.
W8 — intent → effect runtimeAn agent runs under one intent (Ahd); its derived capability is provably ⊆ the intent. Every effect is a ledger record (Sand) carrying actor → intent → derived cap → reversibility. A whole mission (Sfar) is rolled back honestly: reversible effects retracted, compensatable effects undone by a recorded compensating action, irreversible effects refused with a reason — and rollback needs authority over every namespace the mission touched. A five-escape adversary (redteam) is stopped at five named boundaries; why-denied names the boundary of the last denial; Tbar renders the actor → intent → effect provenance graph. The overnight flagship runs the whole story.
W12 — an effect that really leavesUntil W12 every effect Dezh could attribute lived inside Dezh's own storage, so the ledger was checked against itself. marz-effect now drives a real external system: the request is authorized (NIC capability live, egress authority held for that named destination, DIFC export rule allows it), ARP-resolved, sent on the wire, and the outcome comes back and is ledgered — as compensatable, with the undo recorded on the effect, so sfar-plan can name the compensating action instead of promising one. The reply lowers operator integrity, because bytes off the wire are attacker-chosen. tools/ci/effect_test.py is the acceptance and it does not believe Dezh's transcript: all twelve checks read the external system's own state, including that a revoked NIC capability leaves it untouched. Boundary: the host gateway is not in Dezh's TCB — a compromised gateway can lie about what it did, and Dezh proves only the parts it owns.
Device interruptsThe kernel is interrupt-driven, not polled: a PLIC routes virtio IRQs to the boot hart's S-mode context, drivers sleep on sys_irq_wait and are woken by the device, and the scheduler idles (wfi) for a device when nothing else is runnable (irq-stat).
SMP bring-upSecondary harts are started through the real SBI HSM protocol, each with its own stack and identity (tp = hart id); a parallel round proves >1 hart executes concurrently on coherent shared memory (smp-demo, and asserted at boot under -smp 4). The boot hart is chosen by firmware and is not assumed to be hart 0.
SMP mutual exclusionThe kernel has a fair ticket spinlock. All harts hammer a non-atomic counter under it and the total is exact (MUTEX-OK) — proof the lock works, which atomics cannot show. This is the primitive symmetric scheduling is built on. Host-tested since W15 — eight threads on a deliberately non-atomic counter, and the u32 wraparound that no amount of booting reaches, where the release side used to do a plain + 1 against a fetch_add that wraps and would have aborted inside a Drop while holding the lock.
SMP shared run queueThe core of a symmetric scheduler: 48 jobs on ONE queue, drained concurrently by every hart under the lock, each item running exactly once (QUEUE-OK) — none lost, none double-run.
U-mode task on a secondary hartA real U-mode task is dispatched onto a secondary hart, drops to U-mode via a per-hart trap path, has its syscalls serviced on that hart, and runs to completion while the boot hart stays on the console (smp-task, U-MODE-ON-AP).
Symmetric schedulingOne task queue, every hart pulling from it: tasks land wherever a hart is free and several run in U-mode at the same instant (smp-sched — 4 tasks across 3 harts, each exactly once, peak 3 live → SCHED-OK). Per-hart trap state is reached via sscratch, so harts can trap simultaneously.
Isolation under parallelismEach task gets its own address space (only its stack region is U-mapped), so concurrent tasks on different harts cannot reach each other's memory: an intruder page-faults and dies on its own hart while its neighbour runs on (smp-isolate, ISOLATION-OK).
Information flow, both directionsSecrecy and integrity are enforced on the live storage path. Reading a labelled namespace raises secrecy so a secret cannot be written down or exported (taintflow-demo); consuming network input lowers integrity so unvalidated bytes cannot become trusted state (ingress-demo, INGRESS-OK). The escapes are explicit, privileged and recorded: declassify for secrecy, endorse for integrity — and neither grants the other.
Bidirectional networkingThe Marz daemon receives, not just transmits: it offers the NIC receive buffers, blocks on the device interrupt, resolves the destination by ARP, and completes a real ICMP echo exchange, matching the reply by id and sequence (marz-ping, NET-RX-OK). CI decodes the packet capture structurally and asserts the echo left and the reply came back.
Engineering baseline (W10)Every tree lints with -D warnings on a pinned toolchain, so a regression cannot land quietly and "green locally" cannot disagree with CI. No reference to a static mut survives anywhere in the kernel — the four clusters that had them (device authority, namespace authority, information flow, the event ring) now go through a pointer, which matters because a &mut to a static two harts can reach is UB and secondary harts run real tasks. The superseded Step 1..9 prototypes moved to spikes/, off the shipping path. Manifest capability derivation — the narrowest security decision in the system — is unit-tested exhaustively in dezh_core::mcap rather than only observed in a transcript.
Reviewability (W11)The kernel was one file of 8,776 lines. It is now 724 lines of boot sequence plus 26 modules, moved across 23 commits that each had to stay green. This is listed here because it is the difference between a reviewer being able to audit a subsystem and being told to trust a summary of it — the audience this repository asks for critique from reads code, not diagrams. Three gaps are stated rather than rounded away in the roadmap's W11 acceptance table: pkg.rs is over the size cap, smp still holds four static mut, and the command table is separate from its handlers.

What is measured, and how honestly

  • All performance numbers live in dezh-boot/BENCH.md and follow D015: a named architectural lever plus a measurement, never a bare "faster than X".
  • The only real-silicon, same-CPU comparison is the capability-check cost (~1 ns) vs the Linux syscall floor (~49 ns). Everything else measured inside the kernel (ecall round trip, Pol translation overhead) is QEMU-emulated and labelled as such; those absolute numbers are not comparable to hardware. The Pol overhead is reported as a delta precisely because the emulated trap cost cancels in the subtraction.

Known limitations (the parts reviewers should push on)

  • VM targets only. No real-hardware port; no real device drivers beyond virtio under QEMU/VirtualBox.
  • x86 kernel is thin, but no longer trusting. It has a returnable interrupt path, preemption, per-task address spaces, ring 3, a fault that kills only the task that caused it, and capability-checked syscalls whose grants are derived from an intent ceiling — all asserted in CI on both boot paths. What it does not have: device IRQs, storage, an SDK or install path, and — the honest gap — no Ahd token, no Sand effect ledger and no mission on x86, so authority is derived there but effects are not yet accounted for. Nothing frees a dead task's pages. The rich interactive surface (console, IPC, Cairn, Pol) and the whole intent-to-effect ledger remain RISC-V only.
  • Pol is a small syscall subset. write, exit/exit_group are serviced; everything else returns a clean -ENOSYS. No threads, no dynamic linking, no file system. It proves the mechanism, not broad Linux compatibility.
  • Intent-level leases + revocation exist; in-flight capability clawback does not. An intent (Ahd) can be opened with a lease (a bounded run count that auto-revokes on exhaustion) or revoked explicitly (intent-revoke); a revoked or exhausted intent authorizes nothing further, while the effects it already produced keep their provenance (tbar/sfar still resolve). This gives coarse, honest revocation for long-lived agents (lease-demo). What is still not done is clawing back a capability already handed to and running inside another task mid-execution; attenuation, task-death, and rollback cover the common cases. See Enforcement model.
  • No IOMMU. DMA isolation for the block daemon is a bounce-window convention, not hardware-enforced. Accelerator/DMA isolation (D017) is a hypothesis, not implemented.
  • Package signing — the mechanism is built; the distribution layer is not. .dzp packages can be wrapped in a signed DZSP envelope whose Ed25519 signature binds the authority the package requests, and the kernel verifies it against a root-anchored trust store, attenuating the grant to the publisher's ceiling (granted = requested ∩ ceiling) and refusing tampered or revoked-key packages — proven end to end by sig-demo (see Package signing). What is not done yet: a stand- alone developer signing CLI, a root-signed trust store loaded from disk with key rotation (today it is kernel-embedded), and verifying packages on the live pkg-recv upload path. No online PKI / certificate-transparency service.
  • SMP: symmetric scheduling works for queued tasks; the console's own scheduler is still single-hart. Secondary harts come up via SBI HSM, the kernel has a fair spinlock, several harts drain one shared queue, and real U-mode tasks are scheduled symmetrically across harts with per-task address spaces keeping them isolated (smp-sched, smp-isolate). A secondary hart now also arms its own timer while a U-mode task runs there, so such a task is interrupted and resumed instead of owning the hart until it exits — smp-preempt reports the tick count for the specific hart that ran the task, and refuses to claim success if the task landed on the boot hart, which has preempted since W9. What is not done: that interrupt only resumes the task, it does not yet pick a different one, so there is still no migration and no scheduling decision on a secondary. The console's scheduler with its task table, IPC mailboxes and frame allocator is still single-threaded on the boot hart and not under the lock, so daemons and console tasks are not yet dispatchable on any hart. Merging the two into one lock-protected scheduler is the rest of W13 (see ROADMAP.md).
  • No production installer, no side-channel hardening, no formal verification.
  • The live capabilities are a per-task bitmask; the object-capability primitive is built but not yet the substrate. Today's task authority is a bit per class/namespace (kernel-attested on every IPC message and attenuable on delegation — so not Linux-style ambient caps), not an unforgeable per-object reference like seL4/CHERI. The first-class alternative now exists and is proven (dezh_core::ocap + the cap-demo: generation-stamped object handles with per-object revocation and an attenuated delegation graph), but the live IPC / Cairn plumbing has not been migrated onto it yet — that migration is the single largest planned change. See Enforcement model.
  • Confidentiality: DIFC is enforced on the storage path; other channels are not yet. The information-flow-control primitive is built (dezh_core::difc) and enforced on the live Cairn path (taintflow-demo): reading ns=vault (labelled secret) taints the operator, then a commit to a lower namespace is refused (no write-down) until a privileged declassify. The integrity axis is enforced too (ingress-demo): talking to the network lowers the operator's integrity, so unvalidated input cannot be written into a namespace that demands an endorsement until a privileged endorse. What is not done is enforcing either taint across the U-mode client→daemon hop and IPC, and the ingress taint is at operator granularity — consuming any network reply lowers integrity wholesale rather than tracking the individual bytes through the system. See Threat model §5.
  • Networking is a probe, not a stack. Marz does Ethernet, ARP, IPv4, UDP egress and ICMP echo — enough to prove the edge is real and reachable in both directions. There is no TCP, no DNS, no DHCP (the address is static), no inbound listening and no routing. Only marz-effect ledgers what comes back (see the effect-gateway row above); marz-ping's ICMP replies are not effect records. See Marz.
  • Effect-runtime honesty (W8 + W12). Most modeled effects (email.send, prod.deploy, a compensatable api-key) are still models — they prove the mechanism, not an integration. One is not: marz-effect drives a real external system through the host gateway. The limit there is stated in the row above and is worth repeating, because it is the kind of thing a reader should not have to find twice — the gateway is outside Dezh's TCB. Dezh proves the effect was authorized, left the machine, was ledgered under an intent, and that its compensation ran. It cannot prove the gateway was honest about what it did on the other side. Ledger integrity trusts the storage daemon (records are parent-linked and hashed for corruption detection + rollback, not signed against a malicious writer). The commit log is a fixed 255 slots with no GC yet. Intents (Ahd) are runtime sessions and are not persisted across a reboot; for their lease and revocation status, which this bullet used to deny and the list above grants, see that entry — leases and intent-revoke are real, in-flight clawback is not. See Threat model.
  • Console input is reliable but not perfect. UART0 is routed through the PLIC and both the interrupt handler and getc drain the FIFO into a ring, so a pasted line no longer depends on the console happening to be inside getc. Pasting 64-character lines: 9/10 at -smp 1, 8/9 at -smp 2, 10/10 at -smp 4, 8/9 at -smp 8. What is gone is the collapse with hart count — the same test was 2/8 at -smp 4 and 0/3 at -smp 8, where most lines never arrived at all. That was never a race: idle secondary harts spun, and on an emulated host every vCPU shares one budget, so they took it from the hart draining the UART. They now sleep. What remains is not a Dezh defect, and irq-stat is now the instrument that says so: it reports bytes received alongside both drop counts. Six runs sending 204 bytes gave 204/202/200/188 received with zero drops at every layer, and the shortfall matched the echoed line's shortfall exactly each time (64/62/60/48). The guest is never handed those bytes, always on the first line after boot, so no change inside Dezh can recover them. Treat single-run paste results on a host pipe as noise — the same configuration measured 0/10 and 10/10 minutes apart. See issue #19.
  • In-kernel U-mode task caveat (RISC-V). Some baked demo tasks share the kernel binary and must avoid non-inlined calls; real apps use the separate-ELF and .dzp loader paths, which do not have this constraint.

How to check these claims yourself

See REVIEWER_GUIDE.md for the exact commands, or Running in a VM to boot a release in a VM. Everything in the first two tables above is asserted by tools/ci/qemu_smoke.py and runs on every push.

Dezh Architecture Decision Register

This register records the architectural decisions behind the current prototype. Each decision is either validated by running code, accepted as direction, hypothesis, deferred, or rejected.

Status Key

  • validated: proven by code, tests, or QEMU demo output.
  • accepted: chosen direction, not fully proven yet.
  • hypothesis: important enough to test next.
  • deferred: intentionally out of scope for the current prototype.
  • rejected: not a target for this architecture.

Decisions

IDStatusDecisionRationaleValidation
D001validatedNo ambient authority via explicit capabilities.Programs, services, drivers, and apps should start with no default access.Host capability tests, runtime integration, and bare-metal syscall checks.
D002acceptedRust is the trusted-core implementation language.Memory safety without a garbage collector fits the kernel and service goals.Core crates and bare-metal kernels are Rust.
D003validatedA typed, verifiable execution contract is the first portable app/agent substrate.A small typed surface supports validation and multi-ISA direction.dezh-ir and dezh-runtime validate imports, memory, and entry contracts.
D004validatedCairn-style storage is content-addressed and rollback-oriented.Immutable objects plus refs make recovery and rollback structural.dezh-cairn tests and bare-metal current/previous sector flow.
D005validatedImportant app and agent state changes should be rollbackable.Recovery must be a first-class state property, not an afterthought.Cairn tests and bare-metal rollback command.
D006validatedProvenance metadata is first-class.Reviewable systems need to know actor, authority, action, and output.Identity and runtime tests record delegation and invocation metadata.
D007validatedCompatibility should be a bridge, not the security baseline.Legacy-style interfaces should map into capability-checked services.Linux personality spike and bare-metal unsupported-syscall denial.
D008validatedThe OS shape is microkernel-based.Drivers and stateful services should be isolated and restartable.User-space IPC spike and bare-metal user-space virtio-block daemon.
D009validatedScheduling includes isolation and placement direction.Runtime policy needs both task switching and future placement decisions.Scheduler policy crate plus bare-metal preemptive round-robin demo.
D010acceptedGUI access will be mediated by compositor capabilities.Apps should not receive global input, clipboard, screenshot, or surface access by default.Deferred until GUI work starts.
D011validatedLinux-style compatibility is the first legacy personality path.It is a practical first bridge with a clear syscall surface.dezh-linux and bare-metal Linux ABI demo.
D012validatedKernel boot is QEMU-first with explicit service capability seeds.A narrow hardware surface keeps authority boundaries reviewable.RISC-V QEMU boot contract and service registry.
D013acceptedAgent execution is a primary target.Autonomous software needs capability-bound, rollbackable, provenance-aware effects.Identity, runtime, Cairn, and IR layers partially validate the direction.
D014acceptedLegacy compatibility is delivered through capability-mediated personality services.Compatibility should not reintroduce ambient authority.Linux personality prototype; additional personalities are deferred.
D015acceptedPerformance claims must be evidence-backed.Architectural claims need measured support and clear baselines.Existing microbenchmarks and QEMU benchmark suite; broader comparisons deferred.
D016acceptedOne program should eventually run across ISAs through the typed contract.Portability and sandboxing should share one validation path.IR contract validated; bare-metal x86_64 smoke validates a second path.
D017hypothesisAccelerator and DMA access require IOMMU-backed grants.DMA can bypass CPU page tables unless device-side translation enforces grants.Current DMA discipline is modeled; real IOMMU work is deferred.
D018acceptedCross-domain data sharing should move toward zero-copy object capabilities.Immutable object capabilities can reduce copy cost without broadening authority.Cairn provides immutable objects; bare-metal zero-copy path is future work.
D019acceptedMVP scope is: installable, programmable, and four demonstrable differentiators.External review requires independence (bootable VM images), an SDK (out-of-tree apps with capability manifests), and reproducible flagship demos for agent containment, Cairn rollback storage, multi-ISA app portability, and Pol foreign-binary execution — each with D015-compliant claim wording. Outreach waits until all four demos are green in CI.Roadmap workstreams W1–W7; not yet validated.
D020acceptedDezh is framed as intent-native and effect-accountable, bound to the MVP.Intent must be the only authority-derivation path (derived capability narrower than or equal to the declared intent, enforced not annotated); effects are ledger records carrying authority provenance and a reversibility class; ledger/denial state lives in user-space services on Cairn, never the kernel. This narrative extends the D019 demos (F1/W2/W3) rather than forking the roadmap; namespace migration is post-MVP.docs/ROADMAP.md#strategic-direction; not yet validated.
D021acceptedDezh does not compete with existing OSes or on ISA breadth; the ground it owns is being the only substrate where the intent-to-effect ledger cannot be bypassed.Microkernel design, capability security, and multi-ISA execution all have strong prior art and none is a defensible identity. The real competition for containing an untrusted agent is user-space isolation (gVisor, Firecracker/microVMs, wasmtime/WASI, seccomp+landlock, containers), which confines resources well but cannot attribute every effect to its authorizing intent or reverse a whole agent mission on a substrate with no ambient authority underneath to route around. ISA is an implementation backend, not identity. Value is only visible against an adversary, so the proving demo carries a villain and one honestly-irreversible external effect.docs/ROADMAP.md#strategic-direction "The Ground We Own"; demos in roadmap W8; not yet validated.

Current Bare-Metal State

The RISC-V kernel boots in QEMU, validates its boot contract, runs a capability-gated console, launches isolated U-mode ELF processes, and uses Sv39 to deny access outside each process grant.

The current storage path runs through a user-space virtio-block daemon. The daemon receives explicit MMIO and DMA grants; clients communicate with it over typed IPC. The service registry supports start, stop, controlled fault, and explicit restart.

Cairn v1 runs inside that daemon: an on-disk commit-log store (superblock + append-only commit records with parent refs and object hashes) with named namespaces gated by kernel-attested per-namespace capability bits. Rollback moves a namespace's head ref back along the parent chain without erasing history, survives reboot, and denials name the missing capability.

The app registry v0 supports embedded app bundles, install/remove state, private app storage sectors, and no-grant denial demos. This is sufficient for reviewing the authority model, not a production package ecosystem.

Names (ours)

NameSubsystemStatus
DezhThe OS.shipped
CairnThe persistent effect layer — a versioned, rollbackable object store (a filesystem is one use of it).shipped (v1)
PolLegacy-compatibility personality servers (Linux first); foreign binaries run capability-gated.shipped (Linux)
AhdAn intent token: the declared capability ceiling and the only path to authority (derived capability ⊆ Ahd).W8, planned
SandThe effect ledger: records actor → intent → effect on Cairn (a deed/record; also stone/waypoint, Cairn-adjacent).W8, planned
SfarA mission: the set of effects produced under one Ahd; reversible as a single unit.W8, planned
TbarThe provenance graph: a queryable actor → intent → effect lineage.shipped (W8)
MarzThe guarded egress boundary (border): the network edge where an effect leaves the machine and becomes irreversible — a per-destination, intent-derived capability, DIFC-checked on export, recorded as an irreversible effect. See Marz.designed, M1 in progress

Naming Policy

Public documentation uses the name Dezh OS only as a project label. Public review material does not include origin stories, location claims, or personal identity details. The review package is intentionally neutral and technical.

Getting started

From a clone to a running kernel: what Dezh is, the shortest path to a boot, the full build matrix, and running it outside QEMU.

To judge the claims rather than run them, start at REVIEWER_GUIDE.md.


Overview

Dezh OS is a capability-secure operating-system research prototype. It tests a microkernel-shaped design where programs, apps, services, and drivers receive no default authority. Authority is granted explicitly through capabilities, address-space mappings, IPC permissions, device grants, and DMA windows.

Why This Exists

Modern systems still carry many broad authority paths: inherited process authority, global filesystem assumptions, kernel-resident drivers, and service interfaces that blur ownership. Dezh explores a stricter baseline:

No authority exists unless the boot plan, service registry, or caller grants it.

This rule is enforced in the current prototype at several layers:

  • syscall capability checks
  • U-mode page-table isolation
  • explicit device and DMA mappings
  • capability-gated IPC
  • service-mediated storage
  • app registry validation

Current Demonstration

The RISC-V QEMU build demonstrates:

  • boot contract validation
  • capability-scoped console
  • isolated U-mode ELF processes
  • user-space virtio-block daemon
  • typed IPC status and timeout behavior
  • install/root marker on a real disk image
  • app install, run, remove, and deny flows
  • service stop, restart, and controlled fault recovery
  • benchmark and denial suites

The x86_64 build demonstrates the shared Dezh IR path on a second ISA.

What Makes The Prototype Interesting

  • No ambient authority: there is no default device, filesystem, block, IPC, or time access for tasks.
  • Drivers outside the kernel: the block device is serviced by a U-mode daemon that alone receives the MMIO and DMA grants.
  • Typed service contracts: important storage and installer paths return structured statuses instead of raw ad hoc values.
  • Service supervision: the console survives service stop and controlled service fault, then restarts the driver explicitly.
  • Install path discipline: app install and app private storage go through the registered service path.
  • Reviewable evidence: the smoke test and review demo exercise the path end to end under QEMU.

Prototype Boundaries

Dezh is not production-ready. The current work is a research artifact with a small kernel, embedded app bundles, a v0 registry format, and QEMU-centered device support. The point of the current repository state is to make the architecture concrete enough for serious review.


Quickstart

This guide is the shortest path to validating Dezh locally.

Prerequisites

Install:

  • Rust stable
  • Python 3.10 or newer
  • QEMU:
    • qemu-system-riscv64
    • qemu-system-x86_64

Install Rust targets:

rustup target add wasm32-unknown-unknown
rustup target add riscv64gc-unknown-none-elf
rustup target add x86_64-unknown-none

Clone And Test

git clone https://github.com/alisalimi77/Dezh.git
cd Dezh
cargo test --locked --workspace

Build The Bare-Metal Kernels

cd dezh-boot
cargo build --locked
cd ../dezh-boot-x86
cargo build --locked
cd ..

Run The RISC-V Smoke Test

python tools/ci/qemu_smoke.py riscv64 \
  --kernel dezh-boot/target/riscv64gc-unknown-none-elf/debug/dezh-boot \
  --qemu qemu-system-riscv64

This boots the RISC-V kernel in QEMU with a real temporary disk image and checks the console, service registry, typed IPC, storage path, package path, denial proofs, and benchmark command.

Run The SDK Package Acceptance Test

python tools/ci/sdk_test.py \
  --kernel dezh-boot/target/riscv64gc-unknown-none-elf/debug/dezh-boot \
  --qemu qemu-system-riscv64

This validates that a .dzp package can be built, installed, run, denied, removed, recovered, updated, rolled back, pinned, unpinned, and garbage collected through the service-mediated package store.

Run The Public Hygiene Scan

python tools/review/scan_public.py

The scan checks public-facing files for private paths, secret-like tokens, and non-neutral identity/geography markers.

One-Command Review Runner

For a consolidated pass:

python tools/review/run_full_review.py --quick --qemu-riscv qemu-system-riscv64 --qemu-x86 qemu-system-x86_64

Use --full to include the longer SDK package lifecycle acceptance test.


Build and run

This document describes repeatable local validation for Dezh OS.

Toolchain

Required:

  • Rust stable
  • Python 3.10 or newer
  • QEMU RISC-V and x86_64 system emulators

Rust targets:

rustup target add wasm32-unknown-unknown
rustup target add riscv64gc-unknown-none-elf
rustup target add x86_64-unknown-none

Windows PowerShell

If QEMU is installed in the default Windows path:

$QemuRiscv = "C:/Program Files/qemu/qemu-system-riscv64.exe"
$QemuX86 = "C:/Program Files/qemu/qemu-system-x86_64.exe"

Build:

cargo test --locked --workspace
Push-Location dezh-boot
cargo build --locked
Pop-Location
Push-Location dezh-boot-x86
cargo build --locked
Pop-Location

RISC-V smoke:

python tools\ci\qemu_smoke.py riscv64 `
  --kernel dezh-boot\target\riscv64gc-unknown-none-elf\debug\dezh-boot `
  --qemu $QemuRiscv

Interactive RISC-V boot with a local disk image:

fsutil file createnew dezh-local.img 2097152
& $QemuRiscv `
  -machine virt `
  -nographic `
  -bios default `
  -kernel dezh-boot\target\riscv64gc-unknown-none-elf\debug\dezh-boot `
  -drive file=dezh-local.img,format=raw,if=none,id=dezhdisk `
  -device virtio-blk-device,drive=dezhdisk

At the prompt, try:

help
status
services
ipc-typed-demo
install run
pkg-store
bench-all
halt

Linux

Install QEMU using the distribution package manager. On Debian or Ubuntu:

sudo apt-get update
sudo apt-get install -y qemu-system-misc qemu-system-x86

Build:

cargo test --locked --workspace
(cd dezh-boot && cargo build --locked)
(cd dezh-boot-x86 && cargo build --locked)

Run:

python tools/ci/qemu_smoke.py riscv64 \
  --kernel dezh-boot/target/riscv64gc-unknown-none-elf/debug/dezh-boot \
  --qemu qemu-system-riscv64

macOS

Install QEMU with Homebrew:

brew install qemu

Build and smoke commands are the same as Linux.

Review Validation

Run the consolidated quick review:

python tools/review/run_full_review.py --quick

Run the longer review path:

python tools/review/run_full_review.py --full

The full path runs public hygiene checks, host tests, RISC-V and x86_64 builds, RISC-V QEMU smoke, review demo transcript generation, and SDK package lifecycle acceptance.

Troubleshooting

If QEMU is not found, pass the full path using --qemu, --qemu-riscv, or --qemu-x86, depending on the script.

If the RISC-V console appears but the Enter key does not work in a terminal, use the scripted smoke runner. The console accepts carriage return and newline, but some terminal pipelines buffer input differently.

If package commands fail with virtio-block unavailable, confirm that QEMU was started with:

-drive file=...,format=raw,if=none,id=dezhdisk
-device virtio-blk-device,drive=dezhdisk

Running in a VM

Two ways to see Dezh boot, one per architecture. Neither needs the source tree — just a released artifact and a VM. Both show the same thesis in action: a program (here, an agent package) can only do what it was granted.

x86_64 in VirtualBox or VMware (bootable ISO)

This is the "install it like a real OS" path.

  1. Download dezh-<tag>-x86_64.iso from the release.
  2. Create a new VM: type Other / Unknown (64-bit), 128 MB RAM, no hard disk.
  3. Attach the ISO as the VM's optical (CD/DVD) drive.
  4. Start the VM.

The kernel boots through GRUB into 64-bit long mode and runs a real .dzp agent package on screen: it verifies the package (kind=dezh-ir, name=agent-sum), runs the capability-gated agent (prints 15), then runs it again without the print capability and the kernel denies it. Output goes to the VGA screen (shown below) and to COM1 serial.

Dezh x86_64 booting in VirtualBox

The boot also installs a 256-vector IDT and exercises both kinds of trap. The first 32 vectors are CPU exceptions, and the boot deliberately raises a breakpoint at the end to prove faults are caught and reported (not a silent triple-fault reset) before halting. The rest are interrupts, which must not halt: a Local APIC timer is armed at 100 Hz from a rate measured against the PIT, and the kernel keeps a work loop running through the ticks to show the interrupted work resumes intact. The boot then preempts three kernel tasks that never yield; runs two ring-3 tasks in separate address spaces, one of which touches memory it was not given and is killed while the other finishes; and finally runs two more whose code and manifest are identical but whose intents are not, so one of them is refused a capability by name. Device IRQs, storage and an effect ledger on x86 are still future work — see ROADMAP.md.

x86_64 in QEMU (same ISO)

qemu-system-x86_64 -cdrom dezh-<tag>-x86_64.iso -serial stdio

RISC-V in QEMU (one-liner)

The RISC-V kernel is an interactive capability console — the richest demo surface (agent containment, Cairn rollback, the Linux personality, benchmarks).

# optional: a disk enables reboot-persistent Cairn state
qemu-img create -f raw dezh-disk.img 4M

qemu-system-riscv64 -machine virt -nographic -bios default \
  -kernel dezh-<tag>-riscv64-qemu-kernel.elf \
  -drive file=dezh-disk.img,format=raw,if=none,id=hd0 \
  -device virtio-blk-device,drive=hd0

At the dezh> prompt, try:

CommandShows
capsthe console's own capabilities
linux-elfa real unmodified Linux/RISC-V ELF run under the Pol personality (F4)
cairn-demoversioned storage: commit, roll back, cross-namespace denial (F2)
agenta .dzp agent: works in-grant, denied beyond it (F1/F3)
bench-polmeasured Pol syscall-translation overhead (F4/D015)
helpthe full command list

Exit with halt.

Write Your First Dezh App in 10 Minutes

Dezh apps ship as .dzp packages: a manifest that declares every capability the app wants, plus a payload. The kernel records those grants at install time and checks them on every use at run time. Nothing is ambient: an app that didn't declare print cannot print — the kernel denies the host call.

Prerequisites

  • Rust toolchain + riscv64gc-unknown-none-elf target (to build the kernel)
  • qemu-system-riscv64
  • Python 3.10+

Build the kernel once:

cd dezh-boot && cargo build

1. Copy the template (1 minute)

cp -r tools/sdk/templates/hello my-app

Two files:

  • app.toml — name, version, and the capability list:

    name = "hello"
    version = "0.1.0"
    kind = "dezh-ir"
    entry = "hello.dzs"
    caps = ["print"]
    
  • hello.dzs — the program, in Dezh-IR assembly (a tiny, verifiable stack machine; the same bytecode runs on every ISA Dezh is ported to):

        string 0 "hello from a .dzp package!"
        prints 0 26
        push 6
        push 7
        mul
        hostcall print_num
        halt
    

Edit the string, do some arithmetic — dzas.py documents the full instruction set in its header.

2. Build the package (1 minute)

python tools/sdk/build_pkg.py my-app
# -> my-app/hello-0.1.0.dzp

3. Install and run it (2 minutes)

python tools/sdk/install_pkg.py my-app/hello-0.1.0.dzp --run hello

This boots Dezh in QEMU, streams the package over the UART through the capability-gated console (pkg-recv), and runs it:

[pkg] installed 'hello' 0.1.0 kind=dezh-ir payload=536 bytes persistent_slot=0 state=Active
[pkg] grants recorded at install time: print (kernel-enforced at run time; persisted on disk)
--- pkg-run hello ---
  [ir] hello from a .dzp package!
  [ir] print -> 42

At install the kernel: checks a CRC-32, statically verifies the bytecode (malformed programs never become runnable), and records the manifest grants. The package is committed transactionally to the disk-backed package store through the user-space virtio-block service, so pkg-list and pkg-run still work after reboot when the same disk image is used.

4. See the denial (2 minutes)

Remove "print" from caps in app.toml, rebuild, reinstall, rerun:

[pkg-run] DENIED by kernel: missing required capability for this host call

Same bytes, different grant — the authority lives in the installed grant, not in the program. That is the Dezh thesis in one demo.

5. Poke around (4 minutes)

Boot interactively (pwsh dezh-boot/scripts/console-test.ps1 or the QEMU one-liner in dezh-boot/README.md) and try:

  • pkg-list — package slots, state, checksums, and grants
  • pkg-info hello — state, GRANTED vs DENIED, blob range, runnable reason
  • pkg-store — registry checksum, journal status, slot counts, blob range
  • pkg-journal — active package transaction, if any
  • pkg-recover — explicit recovery/quarantine for interrupted transactions
  • pkg-verify hello — verify registry entry and persisted blob
  • pkg-update hello — upload a new .dzp for an Active package; new caps are denied unless --allow-new-caps is explicit
  • pkg-rollback hello — restore the verified previous checkpoint, if present
  • pkg-versions hello — show active and previous checkpoint metadata
  • pkg-review hello — inspect caps, pin state, previous delta, and policy
  • pkg-pin hello / pkg-unpin hello — block or allow surprise lifecycle changes
  • pkg-remove hello — grants are revoked with the package
  • pkg-gc / pkg-gc run — plan or execute explicit physical cleanup for logically removed package blobs
  • audit — install/run/deny events are recorded

Capability vocabulary (v1)

cap in app.tomlgrantspayload kinds
printwrite to the consoledezh-ir, elf-riscv64
ipcsend/receive typed IPCelf-riscv64
uptimeread the system clockelf-riscv64
cairn-readread the app's Cairn namespacedezh-ir (service lands in W2)
cairn-writewrite the app's Cairn namespacedezh-ir (service lands in W2)

Unknown capability names make the install fail — an undeclared string never silently grants anything. Device/DMA/MMIO authority is never grantable from a manifest.

Native (ELF) packages

kind = "elf-riscv64" with entry = <static ELF> packages a native program; the kernel loads it into its own address space with exactly the manifest grants. The Rust app crates under dezh-boot/*-app/ show the target setup. The end-to-end ELF story (including running unmodified Linux binaries) is the W4 workstream.

Honest limits (v1)

  • Package registry persists on the QEMU disk image; use --persistent-disk with tools/sdk/install_pkg.py when you want to keep it across tool runs.
  • Install/remove is journaled in sectors 32..39. Interrupted installs are rolled back or quarantined; interrupted removes complete as logical remove.
  • Updates are explicit and checkpointed: each slot has an Active blob, a Previous blob for one verified rollback, and a Stage blob for promotion.
  • New capabilities during update require pkg-update <name> --allow-new-caps; silent permission expansion is denied.
  • Pins block update/rollback until pkg-unpin or an explicit rollback force.
  • The v0 store has 8 package slots, each capped at 32 KiB of raw .dzp data.
  • pkg-remove is logical: grants are revoked immediately, but bytes are not physically wiped until an explicit pkg-gc run.
  • pkg-gc run is the explicit physical cleanup path for Removed slots. It refuses to run while a transaction journal is active/corrupt and never touches Active, Corrupt, or Quarantined slots.
  • pkg-fault exists for deterministic QEMU recovery tests; it is not an app API and does not grant extra authority.
  • Runtime payload cache is rebuilt lazily from disk after boot.
  • Dezh-IR linear memory is 256 bytes, programs ≤ 4 KiB — demo-scale on purpose; it keeps the verifier and engine small enough to review.
  • The reproducible test for everything on this page: python tools/ci/sdk_test.py

Roadmap and direction

Where the work is going. The roadmap is the near-term plan; the strategic direction is the reasoning that produced it and is the slower-moving document.


Roadmap

MVP — The Reviewable OS (current focus)

One sentence: install Dezh, write an app for it, hand it an untrusted program or agent — it can only do what you granted, and its effects are rollbackable.

MVP is done when a stranger, with no help from us, can:

  1. Boot a downloadable Dezh image in a VM (QEMU one-liner; VirtualBox for x86).
  2. Write and install their own app in about 10 minutes using the SDK.
  3. Reproduce four flagship demos, one per differentiator (below).

Every claim follows D015: measured, honestly scoped, no bare superlatives.

Flagship demos (one per differentiator)

#DifferentiatorDemo a reviewer runsHonest claim wording
F1Agent containment (D001/D013)Install an "agent" app with narrow caps: it works inside its grant, is DENIED by the kernel beyond it, delegates an attenuated cap to a sub-task over IPC, and its damage is undone by rollback."Authority is explicit, unforgeable, attenuable — enforced by hardware privilege + paging, not by a sandbox policy file."
F2Cairn storage (D004/D005)App state is versioned: write, snapshot, corrupt, roll back → restored across reboot. A second app is DENIED access to the first app's namespace."State recovery is structural (versioned objects + refs), not fsck. Per-app namespaces are capability-gated."
F3Multi-ISA apps (D003/D016)The same byte-identical .dzp package (Dezh-IR payload) installs and runs on the RISC-V kernel and the x86_64 kernel."Apps are ISA-portable by construction; proven today on 2 ISAs (RISC-V, x86_64), designed for all."
F4Pol compatibility (D007/D011/D014)An unmodified static Linux riscv64 ELF (built on stock Ubuntu) runs under the Linux personality, capability-gated; syscall-translation overhead is measured and published."Near-native compute for same-ISA binaries (no emulation); syscall translation overhead measured at N ns vs native Linux on the same substrate. Coverage is a small syscall subset today."

Workstreams

W1 — SDK, packages, install flow (foundation; everything rides on it)
  • app.toml manifest: name, version, entry, payload type (elf-riscv64 | dezh-ir), requested capabilities (print, uptime, cairn namespace, ...).
  • .dzp package format: header + manifest + payload, built by tools/sdk/build-pkg.py from an out-of-tree app directory.
  • App template + "write your first Dezh app in 10 minutes" guide (becomes the heart of REVIEWER_GUIDE).
  • Package ingestion into a live system: UART upload command (install-pkg, chunked/base64) first; disk-image staging as fallback.
  • Grants happen at install time from the manifest (mobile-permission feel, but kernel-enforced and unforgeable); recorded in the app registry; visible via app-permissions.
  • Dogfood: port calc, vault, lab from embedded bundles to .dzp.
  • Acceptance: an out-of-tree hello app builds on the host, installs into a running Dezh, runs; an undeclared cap use is DENIED.
W2 — Cairn v1 (differentiator F2)
  • On-disk object store with a ref/commit log: rollback N steps, not just current/previous sectors; survives reboot.
  • Per-app namespaces (/app/<name>/...) mediated by the storage service over typed IPC; namespace access is a manifest capability.
  • cairn-demo console flow proving F2 end to end.
  • Acceptance: F2 transcript reproducible by the demo runner.

Status (2026-07-04): DONE. Commit-log store on sectors 1600..1855 (superblock + append-only commit records carrying parent ref, FNV-1a object hash, actor task id, and a reversibility flag — the D020 effect-ledger seed). Namespace access is enforced by kernel task-capability bits 8..15: the kernel attests the sender's caps on every IPC recv and the storage daemon checks the requested namespace's bit, with an explainable denial message. Console: cairn-commit/get/log/rollback/verify/status + cairn-demo; rollback moves the head ref and keeps history. Manifest wiring: a cairn-read/cairn-write grant maps to the app's OWN namespace only (matched by app name); IR apps reach the store through the kernel Host routed over IPC to the user-space daemon (no kernel block I/O path). Covered by CI smoke (including a second-boot persistence phase) and the review demo runner.

W3 — Agent containment demo (differentiator F1; ties W1+W2 together)
  • Agent app (Dezh-IR payload) with a narrow cairn namespace grant.
  • Shows: in-grant work, kernel DENIED beyond grant, attenuated delegation over IPC (granted = requested & sender_caps), rollback of its writes.
  • Publish alongside the capability-vs-syscall mediation benchmark.
  • Acceptance: F1 transcript reproducible by the demo runner.

Status (2026-07-04): first full pass DONE via tools/demo/run_agent_demo.py (in CI): SDK-built out-of-tree agent app uploaded over the UART, installed with manifest-scoped grants (own namespace only), does durable in-grant commits then a bad write; the operator undoes the damage with a one-step rollback (hash-verified, history kept); a no-capability spy app is DENIED by the kernel; attenuated delegation shown over IPC; state re-checked after reboot. Transcript: docs/transcripts/agent-f1.md. Found and fixed a latent W1 bug on the way: the storage daemon truncated every sector write to 511 bytes, corrupting any package larger than two sectors. Remaining polish: fold the mediation benchmark numbers into the published F1 material.

W4 — Pol: run a real foreign binary (differentiator F4)
  • Extend the process ELF loader to load an unmodified static Linux riscv64 ELF (musl hello-world class), personality = Linux.
  • Syscall subset: write, exit/exit_group, brk, set_tid_address (sane stubs); everything else → clean ENOSYS. No threads, no dynamic linking.
  • Measure translation overhead vs native Linux on the same substrate; publish the number and method (D015).
  • Acceptance: a binary compiled on stock Ubuntu runs on Dezh, capability-gated (no PRINT cap → denied).

Status (2026-07-06): DONE. dezh-boot/linux-guest is a genuine static riscv64 musl ELF (no Dezh code) issuing the raw Linux syscall ABI via ecall; the console linux-elf command loads it under the Linux personality — write serviced by Pol with the PRINT cap, denied -EACCES without it, unsupported getpid returns a clean -ENOSYS. The very same bytes also run unmodified on real riscv64 Linux (verified under qemu-riscv64-static). Translation overhead is measured by the bench-pol command (native vs Pol path, kernel-timed): ~0–80 ns/call, within noise of the ~780 ns emulated round trip — a fixed, near-noise dispatch (BENCH.md, F4). Both legs are in CI smoke.

W5 — x86_64 to parity for F3 (largest chunk)
  • M2: IDT/exceptions + timer on the x86 kernel.
  • Package runner on x86: execute the same .dzp Dezh-IR payload (print/arith hostcalls; cairn on x86 deferred until it has a disk).
  • M3: real bootable ISO (Limine) → boots in VirtualBox/VMware, which also delivers the "install it like a real OS" feel.
  • Acceptance: F3 — byte-identical package runs on both kernels; x86 ISO boots in VirtualBox.

Status (2026-07-06): F3 and the bootable ISO are DONE; M2 is partial. The x86_64 kernel installs and runs a real .dzp agent package (pack → parse → verify → run) — the same architecture-independent format the SDK builds and the RISC-V kernel installs. The agent bytecode is pinned byte-identical by dezh-core's demo_sum_bytes_are_pinned test (in CI), so both ISAs provably execute the same bytes. A Multiboot2 header + tools/x86/build-iso.sh (GRUB grub-mkrescue) produce a BIOS ISO that boots in QEMU -cdrom and in VirtualBox (screenshot: docs/assets/dezh-x86-virtualbox.png); output is mirrored to the VGA text buffer so it is visible on the VM screen. The QEMU -kernel PVH path still works for CI. M2 (DONE): the x86 kernel installs a 256-vector IDT. The first 32 route every CPU fault to a handler that reports vector/error/RIP and halts — the boot deliberately raises a breakpoint to prove faults are caught, not silent triple-faults. Vectors 32..255 are the returnable path (W16.1): they save every general-purpose register, dispatch, restore and iretq, and a Local APIC timer armed at 100 Hz from a PIT-measured rate proves it by leaving an interrupted work loop intact across the ticks. W16.2 adds the other half: the dispatcher may hand back a different saved frame, which is the whole context switch, and three kernel tasks with no yield in them are round-robined by the tick. W16.3 makes that containment: per-task address spaces, a GDT and TSS that can describe an untrusted task, ring 3, one DPL3 syscall gate, and a fault that kills the faulting task instead of the machine. W16.4 makes the authority derived rather than ambient: syscalls are capability-checked and the grant is requested ∩ ceiling through the shared dezh_core::mcap, so two tasks with identical code and manifest hold different authority because their intents differ. Still future work: device IRQs, storage, and an effect ledger on x86.

W6 — Independence and release packaging
  • Prebuilt release artifacts: dezh-riscv.img + one-line QEMU script, dezh-x86.iso for VirtualBox.
  • Install/app state persists across reboot (app registry on disk).
  • CI builds the images and runs the full demo transcript from a fresh clone.
W7 — Presentation hygiene (before any outreach)
  • LICENSE (Apache-2.0 proposed).
  • Honesty pass over all docs: QEMU-only status, emulated-vs-native benchmark caveats, syscall coverage, no IOMMU yet, revocation status.
  • Revocation: at minimum a documented honest answer; implement cheap lease/revoke if it falls out of the registry work.
  • Refresh REVIEWER_GUIDE / DEMO_SCRIPT around the four flagship demos.

Suggested order: W1 → W2 → W3 → W4 → W5 → W6 → W7 (W7 items can land alongside any workstream; outreach only after all four flagship demos are green in CI).

W8 — Intent + Effect Runtime (the differentiator made visible; D020/D021)

The MVP (W1–W7) proves the mechanism — no ambient authority, capability-gated storage, rollback, multi-ISA, Pol. W8 turns that mechanism into the thing the project is actually about: an unbypassable intent-to-effect ledger, and it is scoped so the value is legible to a skeptical practitioner audience (not another happy-path demo). It is the final form of the F1 demo, not a new differentiator.

Real competitor to beat: not another OS, but user-space agent isolation (gVisor, Firecracker, wasmtime/WASI, seccomp+landlock). W8 must show something they structurally cannot — attributing and reversing a whole agent mission.

  • Intent as mechanism (Ahd). — DONE (P1). intent-open <kind> mints an Ahd (a capability ceiling), intent-run <ahd> <app> runs an app whose derived capability is proven ⊆ the Ahd — the only path to authority — and intent-list enumerates open Ahds. intent-demo is the self-contained proof (same agent under two Ahds). A request for authority beyond the Ahd is DENIED in a CI smoke leg.
  • Effect ledger on Cairn (Sand). — DONE (P2). Sand is the same Cairn v1 commit log (user-space, never kernel), enriched so every commit is an effect record: the commit header now carries intent (Ahd id) → derived cap → reversibility class → status → generation alongside the existing actor → parent → hash. It is not a parallel store. The intent id and derived cap are threaded kernel→daemon on the commit IPC (request-id + status byte) and recorded by the daemon that owns the disk. Commands sand-log <ns>, sand-info <ns>, and the self-contained sand-demo (open a writer intent → run the built-in agent under it → read the effect back off the ledger). CI proves effects are recorded, carry their intent, and survive a reboot with the provenance intact.
  • Mission (Sfar) + whole-mission rollback + honest external effect. — DONE (P3, first slice). A Sfar = the effects under one Ahd (found by the intent id stamped on each Sand commit). sfar-plan <ahd> is the rollback forecast — it walks the live per-namespace chains and reports how many of the mission's effects are reversible / compensatable / irreversible / unknown, with an honest confidence (never "full" if anything cannot be undone). sfar-rollback <ahd> retracts the contiguous reversible head-run per namespace with a single atomic superblock write and refuses the rest with an explanation. A fourth reversibility class unknown exists so a connector that does not declare semantics is never optimistically treated as reversible. sfar-demo is the self-contained proof: a mission with one MODELED irreversible external send + two reversible writes → forecast "partial" → rollback undoes the two writes and refuses the send ("already happened in the outside world"). CI proves the outcome and that the refused effect + its provenance survive a reboot. Slice 2 — DONE. comp-demo proves a compensatable effect with a registered compensating action is undone by running and recording that action (status=compensation on the ledger) rather than refused; sfar-cross-demo proves mission authority spans every namespace a mission touched (a rollback holding authority over only one of two namespaces is refused, naming the missing one).
  • The adversary (redteam). — DONE (P4). A malicious agent tries to escape five ways — cross-namespace read, raw MMIO write, capability forgery/ amplification, out-of-intent action, CPU monopoly — each stopped at a named boundary (storage capability check / hardware paging / kernel syscall check / intent-derivation ceiling / preemptive scheduler); the system survives every one. CI asserts all five named boundaries.
  • Explainable denial + provenance. — DONE (P5). why-denied walks the event ring and names the boundary that produced the last denial; Tbar (tbar <ahd>) renders the queryable actor → intent → effect provenance graph, unforgeable because the intent id + derived cap are stamped kernel→daemon.
  • Credibility layer. — DONE (P6). Per-effect ledger overhead documented in BENCH.md (D015: the enrichment is +12 header bytes in the same commit sector, zero extra I/O); docs/SECURITY_MODEL.md#threat-model states the trusted base, what is defended (with the mechanism for each), and the explicit non-goals (side channels, malicious kernel, hardware, no-IOMMU DMA), plus the head-to-head where a user-space sandbox cannot cleanly undo a whole mission but Dezh can (Dezh side reproducible in CI).
  • One flagship narrative. — DONE (P7). overnight collapses P1–P5 into a single story — "leave a coding agent loose on your machine overnight" — with a captured transcript (docs/transcripts/overnight.md) and a CI smoke leg.

W8 is complete: every part above is green in tools/ci/qemu_smoke.py.

W9 — Hardware maturity: interrupts and SMP

The bottleneck that most kept Dezh from reading as a real OS was that it drove no hardware asynchronously: all device I/O was polled and only one hart ever ran. Both are now addressed on RISC-V, in order:

  • Interrupt-driven I/O. — DONE. A PLIC routes virtio device interrupts to the boot hart's S-mode context; drivers block on sys_irq_wait (a restartable blocking syscall) and are woken by the device rather than by spinning; the scheduler idles with wfi for a device when nothing else is runnable and services the PLIC by hand (the hardware clears sstatus.SIE on trap entry, so a pending interrupt must be taken explicitly). irq-stat reports interrupts serviced and driver waits woken by hardware; CI asserts both.
  • SMP bring-up + parallel proof. — DONE. Secondary harts are started through the standard SBI Hart State Management call, each given its own stack and tp = hart id; a parallel round has every secondary hammer one shared atomic counter and the coherent total proves genuine concurrent execution on shared memory (smp-demo; asserted at boot under -smp 4). The boot hart is read from the firmware, not assumed to be hart 0 — which surfaced and fixed a latent PLIC bug (interrupts were hardcoded to hart 0's context).
  • Mutual-exclusion lock. — DONE. Symmetric scheduling needs a run queue shared by more than one hart, which is impossible without a lock — and the kernel had none (single-hart discipline covered everything until now). A fair ticket spinlock (TicketLock, FIFO order so no hart starves) is now in the kernel and proven: all four harts hammer a non-atomic counter under it and the total lands exactly on contributors x work (smp-demo reports MUTEX-OK; CI asserts it at boot and interactively). Atomics alone cannot prove this — the hardware serialises them regardless — so the non-atomic counter is the point.
  • Shared run queue. — DONE. The structural core of a symmetric scheduler: ONE queue of work, every hart popping the next item under the lock and running it in parallel. 48 jobs are enqueued and drained concurrently by all harts, and the correctness property a run queue must have is checked — every item runs exactly once (none lost to a torn dequeue, none run twice by two harts). smp-demo reports QUEUE-OK; CI asserts it at boot and interactively.
  • A U-mode task on a secondary hart. — DONE. A real U-mode task is now dispatched onto a secondary hart: it switches into the task's address space, drops to U-mode through a separate AP trap path (its own trap stack + saved kernel context, kept isolated from the boot hart's utrap/KCTX so the console scheduler is untouched), services the task's syscalls on that hart, and longjmps back to the hart's loop when the task exits — all while the boot hart keeps running the console. smp-task reports U-MODE-ON-AP; CI asserts the task's own output appears and it runs to completion on a hart other than the boot hart. Landing this surfaced a real bug worth recording: the AP trap path must not read tp to find its stack/context, because a U-mode task owns every integer register and clobbers tp before it traps.
  • Symmetric scheduling, with isolation intact. — DONE. The previous step ran one task pinned to a hart the boot hart chose. Now the boot hart fills a single task queue and every secondary hart pulls from it, so tasks land wherever a hart is free and several execute in U-mode at the same instant (smp-sched: 4 tasks placed across 3 harts, each run exactly once, peak 3 live concurrently → SCHED-OK).
    • Per-hart state is found through sscratch, not tp: each hart's ApCtx begins with its trap frame, so the trap entry lands on it and reads that hart's trap stack and saved kernel context at fixed offsets. Several harts can be in a trap simultaneously.
    • Parallelism did not cost isolation. Each task gets its own address space — a private copy of the page tables in which only that task's stack region carries the U bit — so two tasks running concurrently on two harts cannot touch each other's memory. smp-isolate proves it: a task that reaches into a neighbour's stack page-faults and is killed on its own hart while the neighbour runs on undisturbed (ISOLATION-OK).
  • A receive path on the network. — DONE. A transmit-only stack cannot be checked against reality — nothing answers it. The Marz daemon now arms the NIC's receive queue, blocks on the device interrupt, resolves its destination with ARP, and completes a real ICMP echo exchange, matching the reply by id and sequence (marz-ping <dest>NET-RX-OK). Ingress is gated by the same authority as egress: a revoked device or destination refuses the probe. CI now decodes the packet capture instead of scanning it — necessary because the host answers with ICMP errors that quote our datagram, which a substring count would misread as extra egress. Landing this also fixed a real bug: the internet checksum dropped the final byte of an odd-length body, so our echo request was silently discarded by the host and no reply ever came.
  • Information flow on ingress. — DONE. Having a receive path creates a new hole, and it is not the one secrecy solves. Bytes off the wire are not secret; they are unvalidated, and the danger is that they quietly become trusted state. dezh_core::difc gained the integrity axis (Biba's dual: a sink may require endorsements, and reading untrusted input can only lower an actor's integrity, never raise it), proven exhaustively over the label space. In the kernel, ns=note and ns=vault require an endorsement, talking to the network lowers the operator's integrity, and a write into a demanding namespace is refused until a privileged, recorded endorse (ingress-demoINGRESS-OK). The two escapes stay separate on purpose: declassify does not restore integrity and endorse does not clear secrecy, so one privileged act cannot grant two.
  • Remaining SMP work. — NOT started. Tasks run to completion on the hart that picked them: there is no preemption or migration on a secondary hart (no timer armed there yet), and the console's own scheduler — task table, IPC mailboxes, frame allocator — is still single-threaded on the boot hart and not yet under the lock. Merging the two schedulers into one lock-protected structure, so every task in the system (daemons included) is dispatchable on any hart, is the next step.
W10 — A foundation that can be developed on

W1–W9 proved the mechanisms. W10 does not add one: it removes the four things that made the next ten commits cost more than the last ten did.

The problem was measurable. The kernel that ships had no unit tests — all 110 host tests belonged to crates that ran nowhere. dezh-boot/src/main.rs was 8,722 lines in one file with 44 static mut, 184 unsafe blocks and a ~200-arm console. The root workspace carried eight superseded prototypes and 194 crates of wasmtime that nothing shipping needed. And nothing linted the kernel at all, so its 103 warnings could only grow.

Guardrail for the whole workstream: no behaviour change. Every step is verified by the existing QEMU legs passing unchanged. A step that needs a smoke-test edit is a step that got something wrong.

  • Lint ratchet. — DONE. cargo clippy -- -D warnings runs over all five trees (host workspace --all-targets, dezh-core, spikes, dezh-boot, dezh-boot-x86), and the 103 existing warnings are gone. Most were mechanical (45 function-item-to-integer casts now go through *const ()). Four were judgement calls kept with a reason at the site rather than silently "fixed": AP_OFF_KCTX and DEV_OBJ_BLOCK look dead to the compiler but record the ApCtx layout and the device enumeration; COM1 + 0 on x86 is the UART register map written out in order; three record-encoding signatures are wide because they are the record's fields.
  • static mut → pointer access. — DONE. Taking &/&mut to a static a second hart can reach is undefined behaviour, not a style preference, and secondary harts have run real U-mode tasks since W9. All 37 reference-creating sites sat in four clusters — device authority, namespace authority, information flow, and the event ring — each now a Global<T> whose only accessor returns *mut T, with the hart that may touch it stated at the declaration. Clusters were converted whole; converting one static of a three-static ring buffer would have been worse than either end state.
  • Superseded spikes moved out. — DONE. spikes/ is its own workspace, off the default CI path but still built, with a README recording what each of the eight proved and which subsystem superseded it. The root lockfile went from 194 crates to 1. Deleting them was the other option and the wrong one: this repository treats the record of why a design is what it is as part of the work. What it must not do is sit in the shipping tree pretending to be live.
  • First unit tests for kernel logic. — DONE (first slice). Manifest capability derivation — the narrowest, most load-bearing decision in the system — moved to dezh_core::mcap and the kernel now calls it rather than keeping a copy. Nine tests, two exhaustive over the whole manifest bit space and every app name: granted authority never exceeds the manifest, an app reaches its own Cairn namespace and no other, an unknown app name yields no namespace rather than a default, and cap_delta reports escalation exactly. Plus four crc32 tests against the published IEEE 802.3 vectors — it had only ever been exercised in a loop closed on itself. dezh-core: 39 → 52 tests.
  • Pinned toolchain. — DONE. The lint gate went red on its first real CI run, on code nobody had touched: the contributor's machine had Rust 1.94, CI resolved stable to 1.97, and 1.97 ships lints 1.94 does not know. Three sites were genuinely better fixed than allowed (two checked_div, one sort_by_key), but the fix is rust-toolchain.toml: new lints now arrive when the version is deliberately bumped, and local matches CI by construction. -D warnings on a floating stable turns Rust's release calendar into a source of red builds, and a gate that fails for reasons unrelated to the change under review is a gate people learn to route around.
  • Split main.rs into modules. — NOT started. See W11; it is P1.
  • Edition 2024. — PARTIAL. Two blockers cleared early because they are valid in 2021 (gen is now a reserved keyword; five extern "C" blocks are unsafe extern). The rest is measured, not guessed: 81 #[unsafe(no_mangle)] conversions and 65 unsafe_op_in_unsafe_fn sites where an unsafe fn body is no longer implicitly an unsafe block. See W15.

Cross-ISA status, and where the two kernels actually diverge

W5 and W16 are the only workstreams with x86 in the title, and reading the rest of this file it would be easy to conclude that everything else is ISA-neutral. It is not. Every workstream from W8 onward has landed on RISC-V alone, and this section exists so that fact is stated once, in a table, rather than inferred from silence.

What is genuinely shared. dezh-core — 2,228 lines of mcap, dzp, ir, sig, ocap, difc, b64. Both kernels execute the same pinned .dzp bytes (demo_sum_bytes_are_pinned, in CI), and both derive authority through mcap's requested ∩ ceiling. That is the whole of the shared surface: x86 reaches dezh_core::{mcap, dzp, ir} and nothing else, and it does not depend on dezh-kernel at all — even though dezh-kernel already models BootTarget::QemuVirtioX86_64.

CapabilityRISC-Vx86_64Why the gap
Boots, long/S-mode, own page tablesyesyes
Runs the pinned .dzp / Dezh-IR packageyesyesthe F3 claim; the point of dezh-core
Authority as requested ∩ ceilingyesyesshared mcap
Timer, returnable IRQ pathyesyesW9 / W16.1
Preemptive scheduler, ring 3, per-task address spaceyesyesW9 / W16.2–3
Bootable ISO (GRUB, VirtualBox)noyesthe one place x86 is ahead; RISC-V boots via -kernel
Console (941 lines vs 56)yesnonever built on x86
Device IRQs (PLIC), blocking, irq_waityesnoW16 remainder
Disk, virtio-block driveryesnoW16 remainder; blocks Cairn and the ledger
IPC (typed, timeouts, mailboxes)yesnono counterpart in x86's task model
Cairn (commit log, namespaces, rollback)yesnoneeds a disk first
Effect ledger (Sand), mission (Sfar)yesnoneeds Cairn first
DIFC taint, Marz egress, ocap tablesyesnonever built on x86
Pol (foreign Linux binary)yesnoRISC-V-specific by nature (Linux syscall ABI per ISA)
Package install lifecycle (pkg.rs, 3,059 lines)yesnoneeds a disk first
SMP: several harts, one scheduler (W13)in progressnono x86 AP bring-up at all

The divergence that actually costs money is not the missing features — it is the data model underneath them. These two schedulers were derived twice, independently, and they do not agree on what a task is:

dezh-boot/src/sched.rsdezh-boot-x86/src/sched.rs
size1,203 lines437 lines
task stateUnused / Ready / Blocked / DoneIdle / Runnable
the running task[usize; MAX_HARTS], NO_TASK doubling as the run claimone AtomicUsize
saved frame33 slots (32 registers + the dispatching hart)22 qwords
blocking, IPC, resource accountingyesnone

So a step like W13 cannot be ported to x86; it would have to be re-derived, because there is no Blocked state to teach about harts and no claim to make per-hart. Every deep change from here is paid for twice unless something changes.

What we are choosing, deliberately. Not parity. D021's claim is that the ISA is an implementation backend, and what that claim needs is for x86 to have a runtime — which W16.1–W16.4 delivered — not for x86 to have Cairn. So x86 is a second-class backend until W16 completes, and this file says so rather than implying otherwise by omission.

The rule going forward. Every workstream below carries a *Cross-ISA:* line saying which of three it is: shared (lands once, in dezh-core or dezh-kernel), RISC-V first (will need re-deriving on x86, and W16 owns that debt), or RISC-V only by design (nothing to port). An entry with no such line is a gap in this ledger, not an ISA-neutral workstream.

The extraction question, and its answer for now. The obvious fix to the double-payment is to lift the arch-independent half of the scheduler — task table, state machine, IPC, the run claim, the capability checks — into a crate behind a trait, leaving frame layout, trap assembly, satp/cr3, PLIC/APIC and SBI/ACPI on the arch side. That is the right end state and it is not the right next move: W13 is mid-surgery on exactly the interface such a trait would have to name, and an abstraction extracted from code that is still changing freezes the wrong shape. Order: finish W13, then extract, then make both kernels prove the same contract. Doing it in the other order costs the extraction twice.

The order after W10, and why

W11–W17 are ranked by one criterion: how much other work each unblocks per unit of cost, with a second look at where a serious reviewer actually pushes. Each entry states what it costs and what it is blocked on, because a roadmap that hides those is a wish list.

One honest caveat about the ranking itself. W11 (the split) adds no capability and supports no new claim. It is first because every deep change after it lands in the code it cleans. If the near-term goal is a funding pitch rather than a codebase, swap W11 and W12: W12 is the only item that closes a gap in the thesis, and W11 can wait a cycle. That is a real fork, not a hedge — pick one deliberately.

W11 — Split the kernel into modules (P1)

dezh-boot/src/main.rs is 8,776 lines and 236 functions. The next three workstreams are each deep surgery on the task table and the trap path, and attempting them here is how a prototype of this quality stalls.

The file already carries 32 section banners; they are the seams. Current sizes:

ModuleLinesModuleLines
console/ (the ~200-arm dispatcher)1,920net/marz.rs300
sched.rs (tasks, IPC, mailboxes)1,645proc/loader.rs295
cairn/console.rs1,340mm/ (paging, frames)235
smp/ (HSM, per-hart trap, run queue)1,155difc.rs210
arch/entry.rs (boot, trap, switch)380ocap/device.rs190
demos/370cairn/service.rs145
syscall.rs (ABI + task caps)200ocap/ns.rs120
dev/plic.rs, dev/uart.rs, time.rs, mm/bump.rs215

Order: leaves first (uart, plic, time, frames), then single-inbound-edge (marz, loader, difc, ocap/*), then cairn, smp, sched, and console last — it depends on everything, so it falls out once the rest have real interfaces.

Status: done, with three gaps named below. main.rs went from 8,776 lines to 724 across 23 commits; the kernel is now 26 modules. Every step kept the clippy gate and all three QEMU legs green, with the smoke transcript's 26 PASS lines byte-identical throughout.

Against the acceptance criteria:

CriterionOutcome
main.rs is the boot sequence and nothing elsemet — assembly, trap path, syscall ABI, capability bits, kmain
No file over 1,200 linesmet except pkg.rs (3,059)
All QEMU legs byte-identicalmet
Zero bare static mut in the moved modulesmet except smp (4)
Console dispatcher becomes a tablepartial — the table carries name, capability, group and help; the handler is still a match arm

The three gaps are real and none of them is bookkeeping. pkg.rs is the virtio-block daemon; it was already its own module before W11 and was never on the split list, so the cap catches it by accident. smp's four static mut carry a comment arguing they are single-threaded — that argument is precisely what W13 has to revisit, so converting them now would settle by fiat a question W13 needs to ask. And putting handlers in the command table needs one uniform handler signature where the arms currently take four shapes; that is a rewrite, not a move.

What the work turned up, beyond the line count:

  • plic is not a leaf. It reaches into TSTATE and MAX_TASKS to wake drivers blocked on sys_irq_wait, so it had to come out after sched. Recorded at step 3 and acted on at step 23.
  • Moving a demo does not make state private. pub(crate) is crate-wide, so relocating a caller changes nothing. What closes a module is giving it narrow accessors so demos stop reaching into state — a logic change, and its own commit. An earlier version of this note claimed otherwise and was wrong.
  • A const used as a match pattern is a silent trap. If it is not in scope it becomes an irrefutable binding that swallows every arm below it, and the build succeeds. This bit the syscall dispatch twice — sched (step 16) and the AP trap path (step 21) — and only -D warnings caught it either time. cargo fix proposed renaming the constant to _sys_exit, which would have made it permanent. This is the single strongest argument for the W10 clippy gate.
  • Banners are not subjects. The "cooperative multitasking scheduler" heading held an event ledger, an IPC/block ABI, a service registry and a block-daemon client. The "Cairn v1 console front-end" heading was 1,335 lines of which 130 were Cairn. Sections had been growing by chronology.
  • An explicit import list is a measurement when it names coupling, and noise when it names vocabulary. proc::loader opened at 31 crate-root imports with a falsifiable prediction that it should shrink twice; it went 31 → 19 → 7, and what remains is the loader's own job. abi and the console dispatcher got globs, because enumerating a shared vocabulary measures nothing.

Cross-ISA: RISC-V only by design — x86 is 2,598 lines across 21 files and was never the monolith this splits. Cost: large but mechanical; one module per commit. Actual: 23 commits. Blocked on: nothing. Acceptance: no file in dezh-boot/src/ over 1,200 lines; main.rs is the boot sequence and nothing else; all QEMU legs byte-identical; zero remaining bare static mut in the moved modules.

W12 — A real external effect (P2)

The whole W8 argument is that Dezh attributes and reverses effects. Today every effect it can attribute lives inside Dezh's own storage. email.send, prod.deploy and the compensatable api-key are modeled. The repository says so honestly in three places — and that honesty does not remove the gap.

This is the one item that closes a hole in the thesis rather than a limitation beside it, and it is what a funder or a programme reviewer attacks: "a beautiful accounting system for effects that only exist inside your toy."

Scope correction, recorded so it is not underestimated again. An in-OS connector (git, HTTP) needs TCP, DNS and probably TLS. Dezh has ARP, ICMP and UDP egress. That is a workstream, not a step.

The tractable design is a host-side gateway: a small daemon outside Dezh that Dezh reaches over existing UDP egress. It performs the real effect — a git commit, an HTTP call — and reports the outcome. The effect genuinely leaves the machine, carries a declared schema and a registered compensation, and lands on the Sand ledger like any other. The honesty boundary is stated up front: the connector is outside the TCB, and a compromised gateway can lie about what it did. That is a smaller and much more defensible claim than pretending the OS speaks git.

Status: done. marz-effect <dest> <verb> <arg> performs a real git commit on a real repository outside Dezh, over the UDP egress that already existed, and git.revert undoes it. Three commits: the gateway and its standalone proof, the daemon's request/response path, and the registered compensation.

Against the acceptance criteria:

CriterionOutcome
An effect that changes state on a real external systemmet — a git commit, verified by git, not by Dezh's transcript
Recorded with its intentmet — the console opens a mission; the commit message carries Ahd#n so the external system holds the attribution too
Forecast by sfar-planmet — and the forecast now names the registered compensating action rather than only counting it
Undone by a compensating action that really runsmet — the file is gone and history is kept
Reproducible in CI against a local gatewaymet — tools/ci/effect_test.py, twelve checks

The honesty boundary is in the gateway's own header, not a footnote: the gateway is outside the TCB and can lie about what it did. Dezh proves the request was authorized for a named destination, left on the wire, was answered, and was recorded; and that the compensation ran. It does not prove the gateway was honest. That is a smaller claim than "the OS speaks git" and it is the true one.

Two things the work turned up:

  • "Not recorded" and "did not happen" are different. The first end-to-end run had an off-by-ten in the reply parser (rx_wait already steps past the virtio header; the new parser added it again). The gateway committed and Dezh saw nothing. Refusing to record an unobserved effect is right, but the external system had still changed — which is the exact failure this workstream exists to make visible, arrived at by accident.
  • An effect record needs the undo, not just the class. sfar-plan reported compensatable=1 while naming no compensation, which is a promise rather than a plan. The daemon already persisted a registered compensation; the forecast simply never printed it. It does now, and says so explicitly when one is missing.

Still modeled, and still labelled as such: email.send and prod.deploy. What changed is that the ledger now holds at least one effect that is not.

Cross-ISA: RISC-V first. The connector is arch-neutral but the ledger it records into is Cairn, which x86 has no disk for. Cost: medium. Effect schema, one connector, compensation registration, and the marz request/response path (which already receives). Blocked on: nothing — UDP egress and the ICMP receive path exist. Acceptance: an effect that changes state on a real external system, recorded with its intent, forecast by sfar-plan, and undone by a registered compensating action that also really runs — reproducible in CI against a local gateway process.

W13 — One scheduler across all harts (P3)

W9's own closing note. Tasks on secondary harts run to completion — no preemption, no migration, no timer armed there — and the console's scheduler, task table, IPC mailboxes and frame allocator are still single-hart and not under the lock. Merging them into one lock-protected structure, so every task in the system (daemons included) is dispatchable on any hart, is what moves Dezh from "several convincing demos" to "an operating system".

Cross-ISA: RISC-V first, and the most expensive entry in that column — x86 has no AP bring-up and no Blocked state, so this is a re-derivation, not a port. See the extraction note above. Cost: large, and genuinely hard — this is real concurrency work. Blocked on: W11 in practice; the tables must be modules with owners first. Acceptance: a daemon migrates between harts under load; a task on a secondary is preempted by that hart's own timer; smp-* and every existing demo unchanged.

Step 1 — done: a secondary hart's own timer. ap_execute arms the timer and sets sie.STIE around the U-mode window, and the per-hart trap path services the tick and resumes. smp-preempt is the evidence and is deliberately narrow: it counts ticks for the specific hart the task ran on, and prints INCONCLUSIVE rather than success if that hart was the boot hart. The negative control was run — with the arming line removed the same demo reports zero ticks and FAILED. Asserted in tools/ci/qemu_smoke.py.

This buys the second half of the acceptance and none of the first. The tick resumes the interrupted task; it does not choose another, because choosing means reading a task table that is still Global<T> on the boot hart with no lock.

Step 2 — done: the tables are private, and the reachable surface is locked. sync::TicketLock is one lock for the kernel and masks the acquiring hart's interrupts, because plic_handle writes scheduler state from interrupt context. The task table is private to sched; the five accessors other modules call and wake_irq_waiters take the lock.

Step 3a — done: the scheduler entry is lock-safe. schedule_or_return and idle_until_device take the lock in scopes rather than across the sleep, since the sleep services the PLIC and reaches the same lock.

Step 3b — next, and it needs a decision before it needs code. What is left is utrap_handler: 280 lines, ~35 table accesses, 29 return points, 8 of which call schedule_or_return — which now takes the lock itself, so a guard held across the handler would deadlock on them.

Two shapes, and the obvious one is wrong:

  • One lock across the syscall dispatch. Mechanical to write, and it puts SYS_PRINT — a byte-at-a-time UART write — inside a critical section with this hart's interrupts masked. A long line would hold off every device interrupt on the hart for the length of the print. Correct and unusable.
  • Fine-grained, one critical section per table touch. Keeps the sections short, but a syscall stops being atomic against another hart: read TSTATE, release, act on a value that has changed. Which of those reads actually need to be atomic together is the design question, and it is answerable — the syscall paths that matter are IPC send/receive and the capability checks.

Step 3b — done. The atomic unit is one syscall's table work. SYS_SEND, SYS_RECV/_TIMEOUT and SYS_IRQ_WAIT each take one section; the rest are short reads. SYS_PRINT turned out to touch no table at all, so the argument against a coarse lock was aimed at the wrong line. Guard placement is checked by a script that walks brace depth, because the failure mode is a hang.

Step 3c — the trap-path merge, and the crux. Two routes, both real:

  • The boot hart becomes per-hart. ktrap_stack and KCTX are singletons and a second hart entering utrap clobbers both. Fixing it means widening the saved frame past its 32 slots (index 31 is sepc, all are used) so each dispatch can record the running hart's stack and context, then changing utrap, run_first, enter_user and restore_kernel_ctx. High risk: it edits the proven trap path.
  • The AP adopts the real handler. smp already has per-hart trap state (ApCtx via sscratch) and its own kernel context, but ap_trap_handler is 74 lines serving two syscalls against utrap_handler's 348. Lower risk, because the boot path is untouched.

Both meet the same wall: restore_kernel_ctx is wired to one saved context, so no hart but the boot hart can return from schedule_or_return. Choosing the right context per hart starts with a hart being able to ask which it is — and until now the boot hart was the one that could not, because _start never set tp. It does now (smp::current_hart), verified by a boot-time check against the id SBI passes, with a negative control: remove the register write and tp reads as garbage and the kernel refuses to continue.

current_hart is kernel-context only — was. Inside a U-mode trap the task owns every register including tp, so the handler read whatever the task left behind. That is now closed from the other side: the saved frame carries a slot 33 (F_HART) that the dispatching hart stamps with its own id, and utrap loads it into tp on the way in. Both first-dispatch paths stamp it too, and because it is written every time a task is chosen, it survives migration by construction.

Every trap now checks the restored identity against the hart that dispatches tasks and halts on a mismatch, since a wrong answer would send a hart at another hart's per-hart state — a corruption rather than a crash. Negative control: remove the load and the handler reports hart 0 while the boot hart is 2.

KCTX and ktrap_stack are per-hart now, indexed by tp in all four places that touch them. Verified at non-zero indices — runs landing on boot harts 1, 2 and 3 exercise KCTX[1..3] — but not in the case the split exists for: a control pointing every hart back at hart 0's stack still passes, because nothing puts two harts in a trap at once yet.

What is left, and the constraint that shapes it. A secondary hart cannot run just any task. set_active_task_mem flips PTE_U in the shared kernel page table so exactly one task's stack is reachable from U-mode — one global view. Two harts running two such tasks would race, the last writer would win, and the loser's task would fault on its own stack: corruption, not a crash. Only tasks with a private satp are free of it, which is why smp already builds one per AP slot.

That is now enforced in schedule_or_return rather than left as a comment: a hart other than the boot hart picking a task that shares the kernel address space halts the kernel. The guard costs nothing today, because only the boot hart dispatches — it is there so the piece that changes that cannot land quietly wrong.

CURRENT is per-hart now too, and it was the last singleton in the dispatch path. It carries two jobs — whose syscall utrap_handler is serving, and where pick_next resumes its round-robin — and one cell for both across two harts would charge a syscall to the wrong task's capability set. That is an authority bug, not a lost tick, which is why it moves before anything starts dispatching. Reads and writes go through one accessor pair so the hart index cannot be dropped at one of the seven sites.

Indexing by tp is now bounded, once, where the boot hart's identity is already checked: three tables (KCTX, ktrap_stack, CURRENT) are indexed by it with no check at the use site, and two of those indexings are in assembly where a check is not available. Negative control: with MAX_HARTS temporarily at 2, the runs QEMU lands on harts 2 and 3 print the FATAL and halt while harts 0 and 1 boot normally — the guard fires exactly at the boundary and nowhere else.

And two harts can no longer pick the same task. Ready means runnable, not idle — a task stays Ready for the whole time it runs — so pick_next would have handed the same slot to both, and the second hart would have resumed from a register frame the first was still saving into. The claim is the CURRENT entry itself rather than a Running state or a second table: one cell cannot disagree with itself, and a Running state would have to be got right by all eleven places that write TaskState, none of which are about this. NO_TASK is the other half — a hart on the console holds no claim, and the claim is dropped inside the same locked section that reads claims, on the way out through restore_kernel_ctx, because that path never returns.

Negative control, and this one does exercise the case the mechanism exists for: a phantom claim on task 1, planted at run entry as if a second hart held it, makes task 1 undispatchable — ipc-typed-demo reaches the console, prints its banner and then never prints PING -> 0, because the server task can no longer be chosen. Every leg before it is unaffected. Remove the claim and the same run is green.

The run entries are closed too, which was the gap named here a commit ago. They built the table and took the first claim unlocked, and step 2's note said why: the ticket lock is not reentrant and reclaim_task_resources is called both from outside this module and from within it. So it splits — a public wrapper that locks, a _locked inner for callers that already hold it — and the four entries now hold the lock across their table setup and their first claim, and drop it before run_first, which never returns to them.

build_address_space stays outside on purpose. It loads an ELF and walks page tables, and this lock masks the hart's interrupts, so a section that long would hold off every device interrupt for the length of a program load. It touches the frame allocator, not the table, so the unit stays one task's row at a time — the same unit step 3b chose for syscalls.

Getting guard placement wrong hangs the hart instead of crashing it, and CI would show only a QEMU timeout. So it is checked rather than reviewed: tools/ci/check_sched_lock.py walks brace depth, computes which functions take the lock transitively, and fails if any call inside a guard reaches one. Negative control: a task_state() call planted inside the run_tasks guard is reported by file and line, naming both the callee and the guard it sits in.

The last shared write is gone too. set_active_task_mem was called on every pick, and it writes PTE_U into the one L1 that the kernel root and every process root point at. That is the state a second hart in schedule_or_return would have raced on — last writer wins, and the losing hart's baked task faults on its own stack. It is now called only when the picked task shares the kernel address space, which is the only case that needs it.

The same edit closes something that was already true: calling it for a loaded process wrote PTE_U onto baked stack region i inside that process's address space, exposing 2 MiB of kernel RAM for as long as the process ran. No run mixes baked tasks with processes — run_tasks wipes every slot to baked, run_processes wipes every slot to loaded, and the daemon at slot 0 is a loaded process — so the region held no task's data and nothing was leaked. It was slack, and it is closed.

Negative control: invert the condition, so the call is made for processes and skipped for baked tasks, and ipc-typed-demo never reaches PING -> 0 — the baked tasks fault on stacks that are no longer mapped for U-mode. The call is load-bearing exactly where it was kept.

The trap guard now names the invariant, not the boot hart. It read current_hart() != BOOT_HART, which was true only because the boot hart was the only dispatcher — a secondary joining would have had to weaken the check that exists to catch exactly that hart being wrong. The property that has to hold is that the trapping hart holds a claim: a trap from U-mode means it is running a task, so CURRENT[hart] must name that task. A restored tp pointing at another hart fails that for free, since that hart's claim is either NO_TASK or some other task, and the check keeps holding once a second hart dispatches — with no list of permitted harts to maintain beside the claim it would duplicate. tp is bounded against MAX_HARTS first, because it is the subscript.

Negative control, the same one that proved the stamp: delete the ld tp, 256(sp) that restores kernel identity in utrap, and the runs QEMU lands on harts 1 and 3 print FATAL: trap on hart 0 which holds no task and halt, while runs on hart 0 pass — there the wrong answer and the right one coincide. The guard fires exactly when the identity is actually wrong.

The address-space rule became a filter instead of a halt. It had been a guard in schedule_or_return that stops the kernel when a secondary picks a task sharing the kernel address space — right for a rule nothing was meant to reach, useless for a secondary that has to keep going. pick_next now skips such a task and looks at the next one, and the halt stays as a backstop for a task arriving at dispatch by some path that did not come through the filter. Negative control: invert the predicate so the boot hart is the one refused a baked task, and ipc-typed-demo never reaches PING -> 0 — the filter is what chooses, not decoration next to the choice.

The merge was attempted, and it has a defect. Here is exactly what is known. A secondary_serve was written — pick under the lock honouring claims and the address-space filter, install stvec = utrap, SUM, the task's satp and this hart's own timer, run_first, and undo all of it on the way back — plus a CONSOLE_SMP_ON switch (off by default, because W13's acceptance requires every existing demo to be unchanged) and an smp-console demo. It is not in the tree, because it hangs. What was measured before backing it out:

  • With no prior demo, it works, five runs out of five: three loaded processes, three different harts, clean exit, MERGED-OK. A console task really does run on a secondary through utrap — all 348 lines of it — not the AP path's 74-line handler.
  • After any demo that has run a U-mode task on a secondary via the AP path (smp-task, smp-sched, sometimes smp-preempt), the next smp-console wedges. smp-demo, which runs no U-mode task, does not poison it.
  • The hang is in the boot hart's run_processes, between installing the trap vector and returning — the first task never prints.
  • Replacing the U-mode entry with a pick-and-release, so a secondary claims a task and never srets, is clean in every case. The defect is in a secondary entering U-mode on the console trap path, not in the pick.
  • Not the secondary's timer: the hang survives with sie.STIE left clear.
  • Not the UART lock: the hang survives with the macros not taking it.
  • Not run_processes itself: after smp-task, the existing procs command — the same entry with the switch off — is fine.

The leading suspicion is per-hart CSR state the AP path leaves behind — sscratch, which both trap paths use for different structures. Refuted, and replaced by a measurement.

tools/debug/hart_pcs.py was written for this and answered it on the first catch. At the wedge, all four harts are in Ticket::acquire — the spin loop. It is a lock, not a CSR. And the counters name which one:

SCHED_LOCK      next=0x2e  serving=0x25     <- 9 outstanding
RX_LOCK         next=serving
TX_LOCK         next=serving
SMP_RUNQ_LOCK   next=serving
SMP_LOCK        next=serving
AP_Q_LOCK       next=serving

Every other ticket lock in the kernel is balanced. SCHED_LOCK is not, and the gap grows with the run — a later catch read next=0x44 serving=0x26, thirty outstanding, with only four harts alive to hold them. Four harts cannot hold thirty tickets, so this is not one holder that vanished: serving is failing to keep up with next.

What has been ruled out since:

  • Not a static escape. A scan for a guard still live across restore_kernel_ctx, run_first or shutdown finds one hit, and it is the shutdown in the address-space guard — which halts the machine, so holding the lock into it costs nothing.
  • Not another lock's holder. Per-lock owner recording (temporary, not committed) reads the holder's return address as 0 at the wedge, meaning the last Drop ran. A holder that released is not a holder that disappeared.

It is also intermittent — roughly one run in two with the same binary — which the old note's CSR theory could not explain, because stale sscratch would be deterministic.

secondary_serve, CONSOLE_SMP_ON and smp-console are in the tree now, behind a switch that is off, so the reproduction is one console command away rather than a patch to re-apply. Every existing demo is unchanged with the switch closed, and the whole suite is green.

Narrowed again, and the mechanism is named. Ticket::release is not at fault: a global acquire/release count taken at the wedge reads acq - rel = 4, exactly the four blocked harts. Nothing is leaked and no release is lost.

Reading which lock each hart waits on, and the ticket its last successful acquire was served at:

SCHED_LOCK   next=0x33 (51)   serving=0x2c (44)
hart 0  ->  SCHED_LOCK   last served ticket 4    from utrap_handler+0x98
hart 1  ->  SCHED_LOCK   last served ticket 39   from utrap_handler+0x98
hart 2  ->  SCHED_LOCK   last served ticket 44   from utrap_handler+0x98
hart 3  ->  AP_Q_LOCK    (balanced; it gets in)

Hart 2 was served ticket 44, and serving is 44 — so it holds the lock — and it is waiting for SCHED_LOCK again, from the same source offset. A hart blocked on a lock it already holds. Harts 0 and 1 are behind it, which is why the gap looks like a leak from the outside; it is one self-deadlock with a queue.

utrap_handler+0x98 is the first acquisition in the trap handler:

#![allow(unused)]
fn main() {
let cur = {
    let _held = SCHED_LOCK.lock();
    current_task()
};
}

That scope is correct, and tools/ci/check_sched_lock.py agrees — it reports 33 guard scopes, none re-entrant, and it is right about the source. So the second acquisition is not a call the source makes from inside the first. Something re-enters utrap_handler while the lock is held, and the lock masks sstatus.SIE, which gates S-mode interrupts only.

Answered, and it is worse and simpler than a re-entrancy. Reading each hart's privilege, trap cause, satp and claim at the wedge:

CURRENT[hart] = [0, 1, NO_TASK, 2]
TSATP[task]   = [ffe, fea, fd5, 0]
live satp     = [ffe, fea, ffe, fd5]
                  ^         ^
                hart 0    hart 2

All four harts have SPP=0 — every one arrived from U-mode, so there is no S-mode exception and no masked-interrupt puzzle. Hart 2 holds no claim at all and is executing in task 0's address space, the one hart 0 claims. Two harts, one task. That is the exact failure the run claim exists to prevent, and the deadlock is downstream of it rather than the thing itself.

And the guard written for this could never fire. utrap_handler opened by reading the claim from behind SCHED_LOCK, so the cur == NO_TASK check depended on acquiring a lock in precisely the situation where the scheduler is what has gone wrong. Measured, not supposed: at the wedge the handler is stopped in Ticket::acquire on that very line, on the hart whose claim is NO_TASK.

CURRENT[hart] has exactly one writer — that hart — so the read needs no section, and it no longer takes one. With the guard reachable, two runs in eight now name the state instead of stopping silently:

FATAL: trap on hart 0 which holds no task (satp=0x8000000000087fd5, scause=0x8000000000000005)
FATAL: trap on hart 0 which holds no task (satp=0x8000000000087ffe, scause=0x8)

scause=0x8000...05 is a supervisor timer interrupt; 0x8 is an ecall. So a hart is in U-mode running a task it does not claim, and either the timer or the task's own syscall brings it in. Three runs in eight still stop without a word, because a hart can reach one of the handler's later lock sites first — those still read under the lock, and legitimately so.

Which leaves one question, and it is about the claim rather than the lock: how does a hart come to be executing a task it never claimed? The candidates are restore_kernel_ctx returning a secondary somewhere other than secondary_serve, and a stale KCTX[hart] — both testable with the same tool.

So the last piece is: let a secondary pull from the console task table, limited to tasks with their own address space, and then make a daemon migrate under load. Everything under it is in place — identity in both contexts, per-hart context, stack and current task, the table private and locked on every path including entry, the run claim enforced, syscalls atomic per call — and the entry itself is written and known to work from a cold console. What is left is one defect with a bounded search space, not a design question.

Migration needs one more thing the demo made obvious: a hart keeps its claim across preemption, and with as many harts as runnable tasks it re-picks its own. A task moves hart only after it blocks and is woken, so the daemon — which blocks on sys_irq_wait — is the case that shows it, and the loop tasks never will.

W14 — Object-capabilities as the live substrate (P4)

docs/STATUS.md calls this "the single largest planned change", and it is still ahead of us. Two steps landed before W10 — the Cairn namespace gate and the device gate are real dezh_core::ocap tables with generation-stamped revocation, proven at runtime by nsrevoke-demo, agentrevoke-demo and dev-demo. W10 only changed how those statics are stored; it moved the migration forward by nothing, and it would be easy to misread the diff as progress here.

What remains is the substrate itself: the per-task capability bitmask. It is kernel-attested on every IPC message and attenuable on delegation — so not Linux-style ambient authority — but it is a bit per class, not an unforgeable per-object reference in the seL4/CHERI sense. The ocap tables today are a gate layered above it, not the thing authority is made of.

Cross-ISA: shared, and this is the strongest candidate for it — ocap already lives in dezh-core, and x86 already derives authority through mcap. Cost: large; it touches every syscall check and the IPC attestation path. Blocked on: W11 and W13 (the task table is the thing being changed). Acceptance: a task holds object handles, not a bitmask; delegation is a graph edge; redteam's forgery escape still fails, now against a generation check.

W15 — Edition 2024 and the rest of the kernel's tests (P5) — done

The edition half is done. Every live Cargo.toml is on edition 2024 — the two shared crates, both kernels, the nine embedded user programs, the three wasm guests and the eight superseded spikes — with no #[allow] added to get there. The only 2021 left in the tree is inside dist/, a published release snapshot rather than source.

It was done by reading rather than by cargo fix --edition, for the reason recorded at W11: the last time that tool was pointed at this repository it proposed renaming a constant to _sys_exit, which would have made a match-arm bug permanent and silent.

Three things it turned up that were not bookkeeping:

  • unsafe_op_in_unsafe_fn, denied an edition early, named all 99 sites while both kernels still built either way. Wrapping the bodies produced exactly one unnecessary unsafe in return, and that one is a finding: BumpHeap::alloc performs no unsafe operation at all — UnsafeCell::get is safe and so is every atomic under it. The unsafe on that signature is GlobalAlloc's contract with its caller, not the body's with the hardware.
  • gen is a reserved word now, and both dezh_core::ocap and the virtio-blk daemon used it. The rename is where the only real hazard was: a whole-word pass also rewrote sys_print(b" gen=") into b" generation=" — a string literal, in output CI asserts against. Caught and reverted. Renaming an identifier and renaming everything spelled like one are different operations.
  • A workspace edition bump can break a crate the workspace does not list. The guests/ wasm crates are built by dezh-host's build script, so their errors arrived as that script exiting 101 rather than as a compile failure anyone could read.

What remains under W15 is the second half: the tests — Cairn commit-record encode/decode, the 255-slot boundary with no GC, the ticket lock's arithmetic, run-queue push/pop under simulated interleaving, and the Marz checksum.

And the reason they have not moved is not the one recorded here before. The note used to say all five were blocked on making dezh-boot host-testable. That is true and it is not the binding constraint. Two of the five are not even in that crate — the Cairn record format lives in the virtio-blk daemon (33 CAIRN1_OFF_* sites) and the checksum lives in the marz daemon, both separate no_std RISC-V binaries.

The binding constraint is narrower and more fixable: these functions take their input from a fixed address rather than as an argument. ip_checksum(off, len) volatile-reads out of the DMA window at DMA_VA; the Cairn codec reads through d_read_u8 from the daemon's data pointer. A host test cannot call either one, and making the crate host-testable would not change that — there is no input to supply.

So the work is not "port a kernel to the host". It is to give five pieces of logic real parameters, and it has a shape that keeps the volatile reads where they belong: split the arithmetic from the reading, so the caller passes an accessor and a length while a test passes an array. Doing it any other way — building a &[u8] over a DMA window — creates exactly the aliasing that Global<T> exists to prevent.

The checksum is done, and it is the worked example for the other four. The arithmetic is dezh_core::net::internet_checksum, taking a length and an accessor; the marz daemon keeps the volatile loads and passes a closure over its DMA window. Eight tests, and a negative control that matters: reintroducing the W9 bug — dropping the odd tail instead of padding it — fails exactly an_odd_tail_byte_is_padded_not_dropped and the_odd_tail_is_the_high_half_of_its_word, and leaves the other six silent. The second exists because the near-miss (padding on the wrong side) passes the first.

The known-vector test is only worth something because of the one beside it: inserting_the_checksum_makes_a_fresh_sum_zero is the receiver's own check and does not depend on knowing the right constant, and 0xb861 was verified against an independent implementation rather than against this one. A constant and an implementation that are wrong in the same way agree perfectly.

Cost of the dependency: 8 bytes. marz builds release with lto = true, so dezh-core's signature verifier and everything else unused is dead-stripped — 15,920 to 15,928.

The ticket lock is done too, and it is the one that paid. The checksum test pinned a bug that was already known. This one found a bug, which is the case the whole exercise was for.

next is handed out with fetch_add, defined to wrap. The release side did serving.store(serving.load() + 1) — a plain add. On the dev profile, which is what CI builds and what every smoke run boots, overflow checks are on, so at u32::MAX that is an abort rather than a wrap: inside a Drop, while holding the lock, in the kernel's only mutual-exclusion primitive. Four billion acquisitions away, and unreachable by booting — which is precisely why nothing had found it, and precisely the shape of thing a test reaches and a demo cannot.

The queue is dezh_core::sync::Ticket; masking sstatus.SIE stays in dezh-boot. Same split as the checksum, same reason. Both kernels can now share one queue instead of deriving it twice, which is one small piece off the cross-ISA debt.

Six tests. Two fail on the old arithmetic with attempt to add with overflow and the other four stay green, so the control names the defect rather than just going red. The other four are the first real test this lock has ever had: eight threads doing two thousand non-atomic read-modify-writes each — the property is that the lock serialises the unserialised, so an atomic would prove nothing — and every ticket handed out exactly once across eight racing threads. Before this, the only evidence was one QEMU demo counting to 200000. It still says MUTEX-OK.

W15 is done. All five, and three of them found bugs rather than pinning known ones. dezh-core went from 52 tests to 81.

  • The run queue could silently lose a job. push had no full check: the 65th push into 64 slots overwrote the oldest unpopped entry. The demo asserts exactly the property that breaks — QUEUE-OK, "each job ran exactly once, none lost, none double-run" — and passed anyway, because it pushes 48 jobs into 64 slots. The claim was true of the workload, not of the queue. There were two identical copies; now there is one dezh_core::runq::RunQueue<N> and push reports a refusal. Control: with the check removed, six consumer threads against a ring smaller than the workload return 2672 of 4000 items — 1328 lost or double-popped — while three other tests stay green. QUEUE-OK in QEMU has never reached that state.
  • The Cairn record had two sides and no referee. Encode and decode were both open-coded in the daemon against a fixed data pointer, so the only thing checking the format was that one file agreed with itself. dezh_core::cairn now holds the header, the offsets, the classes and the slot bound, and the daemon's constants are aliases of them. Three of the nine tests earn their place beyond round-tripping: the generation is little-endian across two hand-split bytes (any value under 256 round-trips either way, which is where the slip hides); a pre-Sand record must still decode as a direct commit, which is the compatibility claim the format makes in its own comment and had never been checked; and slot 255 must be refused rather than wrapping onto slot 0 and overwriting the first effect ever recorded.

What the five have in common is worth keeping: every one of them was untestable because it took its input from a fixed address rather than as an argument, and two of the three bugs were unreachable by any amount of booting — a u32 wraparound four billion acquisitions away, and a full ring the demo is sized never to reach. That is the argument for having tests at all, and it is now an observation rather than a position.

The measured remainder: 81 attribute conversions and 65 unsafe fn bodies to wrap. Both are mechanical and both are much easier to review once W11 has split the trap and boot paths into their own files. The second half of W10.4 rides along here: Cairn commit-record encode/decode, the 255-slot boundary with no GC (currently untested), the ticket lock's arithmetic, run-queue push/pop under simulated interleaving, and the Marz checksum — whose odd-length-body bug was found by hand in W9 and is exactly what a three-line test catches.

Cross-ISA: both, separately, and x86 is the cheaper half — it is on edition 2021 like the rest of the workspace, but it carries zero static mut against RISC-V's 16, so only the attribute conversions apply to it. Cost: medium, entirely mechanical. Blocked on: W11. Acceptance: every Cargo.toml on edition 2024 with no new #[allow]; cargo test inside dezh-boot runs in CI.

W16 — x86_64 to system parity (P6)

Roughly 900 lines against 12,359. F3 proves the program format is portable; it does not prove the system is. Until x86 has a runtime, "ISA is an implementation backend, not the identity" (D021) is a RISC-V thesis.

W16.1 through W16.4 are done: a 256-vector IDT, a Local APIC timer measured against the PIT and armed at 100 Hz, an interrupt entry path that saves and restores every general-purpose register, a round-robin scheduler over kernel tasks that never yield, per-task address spaces, ring 3 — a CPL3 task that touches memory it was not given is killed alone while its neighbour finishes — and capability-checked syscalls whose grants are requested ∩ ceiling through the shared dezh_core::mcap. All asserted in CI on both x86 boot paths. Still missing: no disk, no drivers, and no effect ledger, so an x86 task's authority is derived but its effects are not yet accounted for.

This is also the only practical route to an IOMMU — see W17.

Cross-ISA: this workstream is the cross-ISA debt. Everything in the "RISC-V first" column above is owed here. Cost: very large. Timer and returnable IRQ path (done, W16.1), scheduler (done, W16.2), paging and ring-3 containment (done, W16.3), intent-derived capability checks (done, W16.4), then a virtio-pci disk driver, then Cairn and the effect ledger. Blocked on: nothing technically; competes with everything for time. Acceptance: the x86 kernel runs the console, the scheduler and Cairn; the same .dzp installs and persists on both ISAs.

W17 — IOMMU-enforced DMA isolation (P7)

The most-attacked gap, and deliberately last, because it is blocked rather than hard. The investigation, recorded so it is not repeated:

  • RISC-V: re-measured 2026-08-21, and one of the two recorded premises was wrong. The claim that "every IOMMU in QEMU sits on the PCI root complex" is false on a current QEMU. On 11.0.50, -machine virt,iommu-sys=on instantiates a platform IOMMU, and the device tree it produces is the same shape as every device Dezh already drives:

    iommu@3010000
      compatible = "riscv,iommu"
      reg        = 0x3010000, size 0x1000
      interrupts = 0x24 0x25 0x26 0x27
    

    MMIO, at a fixed address, with its own interrupts. No PCI root complex, and no MSI-X.

    But nothing is behind it. Dumping the device tree with the smoke run's own devices — virtio-blk-device and virtio-net-device on virtio-mmio — finds zero nodes carrying an iommus property. The virt machine can instantiate the IOMMU and does not route platform-device DMA through it. So the conclusion holds and the reason has changed: the blocker is not that the device does not exist, it is that QEMU does not put Dezh's devices behind the one it now has.

    riscv-iommu-pci does exist, so translated DMA is reachable through PCI — and that is where the virtio-pci migration is genuinely required, rather than being required for want of any IOMMU at all.

    The second premise still holds, and moves: CI runs on ubuntu-latest and Dockerfile.review is ubuntu:24.04, both QEMU 8.2, which models no riscv-iommu. That is now a toolchain blocker rather than an architectural one — a container bump, not a driver rewrite — and it is the cheaper half to fix first.

  • x86: the hardware is there, the system is not. intel-iommu (VT-d) and amd-iommu are available and mature even on QEMU 8.2. But with no scheduler, no disk and no drivers, there is no DMA to protect. An IOMMU on x86 today means writing translation tables for a device that does not exist.

So it is a leaf of the dependency tree, and the route is through W16, where VT-d is waiting with no QEMU upgrade required.

A note on how this gap is weighted. It is the first thing a systems audience attacks, and the repository already concedes it by name in STATUS.md, the threat model, and the comparison matrix — which a serious reviewer accepts. What they do not accept is a claim that was never true. W12 closes a gap of that second kind, which is why it ranks above this one despite being less famous.

Cross-ISA: x86 first, uniquely — VT-d is mature on QEMU 8.2 while the RISC-V IOMMU needs QEMU 9.1 and a virtio-pci migration. Cost: large, after a larger prerequisite. Blocked on: W16 (x86) or a virtio-pci migration plus QEMU 9.1+ (RISC-V). Acceptance: the block daemon's DMA is confined by hardware, and a deliberately malicious driver programming the device to write outside its window is stopped by the IOMMU rather than by convention.

Post-MVP horizon (recorded, deliberately not started in W8): explicit system generations / time-travel, multi-agent attenuated sub-delegation with provenance chains, full saga/compensation for external effects, human-approval gates for sensitive intents, cross-ISA effect-semantics identity, and non-storage typed effects (network/service/install). See docs/ROADMAP.md#strategic-direction.

Beyond W17

Everything with a named workstream above has an owner, a cost and an acceptance test. What is listed here does not yet, and saying so is the point - these are directions, not commitments.

Three items that used to live here have graduated and should not be re-listed: intent leases and revocation shipped in W8 (lease-demo), signed package manifests shipped as the DZSP envelope (sig-demo), and IOMMU-backed DMA isolation is now W17 with its blockers written down.

  • Convert the remaining embedded demo apps into separate ELF services.
  • A richer app lifecycle: staged rollout, audit queries over the ledger.
  • Per-client block queues and real storage concurrency (today one daemon serialises every request).
  • Reusable typed service interface definitions, so a service contract is declared once rather than hand-matched on both sides.
  • ARM bring-up as a third ISA - but only after W16, and only if a third backend would teach something the second did not.
  • Production boot media and an installer flow.
  • A capability-aware GUI / compositor boundary.
  • Measured boot, and a root-signed trust store loaded from disk with key rotation (today it is kernel-embedded - see the signing limitation in STATUS).
  • Formal verification of the smallest kernel authority rules. The authority rule is already machine-checked by exhaustive enumeration in dezh-kernel; this would be the real thing, and it is honestly a research project.
  • Ledger integrity against a malicious storage daemon. Records are parent-linked and hashed for corruption detection, not signed; today the daemon that owns the disk is trusted. The commit log is also a fixed 255 slots with no GC.

Non-Goals For MVP

  • Claiming production readiness.
  • Replacing an existing general-purpose OS.
  • Full POSIX compatibility (small measured subset only).
  • Full package ecosystem.
  • Real-hardware driver support (VM targets only).
  • Production cryptographic supply-chain infrastructure.

Strategic direction

Position

Dezh should not be framed as a cleaner copy of existing operating-system ideas. The long-term thesis is stronger:

Dezh is an intent-native, effect-accountable operating-system prototype.

The goal is not just to combine a microkernel, capability security, user-space drivers, package rollback, and service supervision. Those are necessary building blocks, but they are not the differentiator by themselves.

The differentiator should be that Dezh treats intent and effect as first-class OS concepts.

The Ground We Own (D021)

Dezh is not trying to be a better microkernel, a cleaner capability system, or a kernel that compiles to more ISAs. Each of those has strong prior art (seL4, KeyKOS, EROS, Barrelfish) and none is a defensible identity. Running on both x86 and RISC-V is a portability property, not the point — ISA is an implementation backend, not identity: the same mission bytes should produce the same effect semantics on every backend, and if a new ISA appears in ten years, Dezh's identity does not change.

The real competitor is not another OS. For the concrete job "contain an untrusted agent and let it be productive," the incumbents are user-space isolation layers: gVisor, Firecracker / microVMs, wasmtime / WASI, seccomp+landlock, containers. They confine syscalls and resources well and they ship today. Any honest positioning compares against them, not against a research microkernel.

What none of them do:

  • Tie every effect to the intent that authorized it as part of the execution model (not a bolt-on audit log an app can route around).
  • Reverse a whole agent mission atomically — undo everything one intent caused, in one operation.
  • Do both on a substrate where the ledger cannot be bypassed. On a conventional OS the ledger is a library sitting on top of ambient authority; a program can always reach the resource underneath. On Dezh there is no authority underneath to reach — the intent-derived path is the only path, so the ledger is not optional instrumentation, it is the execution itself.

One-line differentiator (the reviewer challenge): Unlike seL4, Barrelfish, Fuchsia, or Redox — which make access safe — Dezh makes effect accountable: every action an agent takes is bound to its intent, attributable, and reversible where possible, and because the kernel has no ambient authority by construction, that ledger cannot be bypassed.

Value Is Only Visible Against An Adversary

A secure system that is never attacked is just an assertion. The proving demo must carry a villain: an agent that actively tries to escape its intent — read another namespace, write raw device MMIO, forge or amplify a capability, act outside its declared intent, monopolize the CPU — and is stopped at a named boundary each time, with why-denied explaining it. Happy-path demos (an app acting inside its grant) do not make the value visible; the escape that fails does.

The Mission Is The Reversible Unit

The unit that makes effect-accountability compelling is not a single write but a mission: the set of effects produced under one intent. Whole-mission atomic rollback ("undo everything this agent's task did") is precisely what the user-space sandboxes above cannot offer, because they have no structured notion of which effects belonged to which authorized purpose. The ledger groups effects by intent so a mission is a first-class, reversible object.

Honesty boundary: a mission may contain an irreversible external effect (a network send, a physical output). Whole-mission rollback undoes the internal and compensatable effects and refuses the irreversible ones with an explanation — it never pretends an external effect was recalled. A separate docs/SECURITY_MODEL.md#threat-model states what Dezh's trusted base is, what it defends, and what it explicitly does not defend (side channels, a malicious kernel, hardware, no-IOMMU DMA).

Why This Matters

Traditional operating systems usually grant authority around processes, users, files, devices, paths, package managers, or broad service APIs. That creates common failure modes:

  • Ambient authority that silently spreads through the system.
  • Filesystem or registry state that accumulates unclear ownership.
  • Package updates that change code, data, and permissions without enough reviewability.
  • Service failures that turn into hangs, vague errors, or hidden recovery.
  • Logs that describe what happened after the fact, but are not part of the OS authority model.

Dezh should avoid repeating these patterns.

Core Thesis

Instead of asking only:

  • Which process is running?
  • Which file or device can it access?
  • Which package is installed?
  • Which service is reachable?

Dezh should also ask:

  • What is the declared intent?
  • Which authority was derived for that specific intent?
  • Which namespace or service route was used?
  • What effect did the operation create?
  • Can the effect be verified, explained, rolled back, or quarantined?

Competitive Advantages To Build Toward

Intent-Scoped Authority

Authority should be issued for a declared purpose, not as a broad ambient grant.

Example:

  • Avoid: "this app can write storage."
  • Prefer: "this app can commit note update transaction #42 in its own namespace."

Hard rule: intent is a mechanism, not metadata. A narrow capability by itself is not new — capability attenuation is decades old. Intent only becomes a real OS concept if:

  • deriving authority from a declared intent is the only way to obtain it,
  • the kernel/runtime guarantees the derived capability is narrower than or equal to the declared intent,
  • the intent, the derivation, and the resulting effects are linked in the ledger.

If intent is just a purpose string attached to a grant, it degenerates into permission theater (the failure mode of macOS TCC purpose strings and loosely checked OAuth scopes). Dezh must not ship that version.

Effect Ledger

Important OS effects should be structured records, not loose logs:

  • actor/component
  • declared intent
  • derived capability
  • target namespace/service
  • status
  • reversibility class: reversible | compensatable | irreversible
  • rollback or compensation handle (when the class allows one)
  • generation/checkpoint metadata

Not every effect can be undone (a network send, a physical output). Claiming universal rollback would violate D015 honesty; instead every ledger entry declares its class up front, and effect-rollback refuses irreversible entries with an explanation rather than pretending.

This should support commands such as:

  • effect-log
  • effect-info <id>
  • effect-rollback <id>
  • why-denied <last|id>

Placement rule: the ledger and denial-context store are user-space services backed by Cairn, not kernel code. The kernel only emits minimal structured events at authority boundaries; anything stateful lives outside it. This keeps the microkernel minimal (D008) and makes the ledger itself rollback-aware for free (D004).

Reversible OS Boundary

Install, update, storage writes, service lifecycle changes, and namespace migrations should be transaction-aware and preferably reversible or compensatable.

Package lifecycle work already moves in this direction:

  • transactional install/remove
  • journaled recovery
  • quarantine
  • explicit GC
  • update checkpoints
  • rollback
  • pin/unpin
  • cap escalation review

The next step is to extend this model beyond packages into app data, namespaces, services, and system generations.

No Ambient Continuity

State should not silently carry forward forever.

Dezh should make generations explicit:

  • boot generation
  • service graph generation
  • package generation
  • namespace generation
  • intent/effect generation

Rollback and audit should be generation-aware.

Explainable Denial

"Permission denied" is not enough.

Dezh should explain:

  • which intent was denied
  • which capability was missing or too broad
  • which component requested it
  • which safer route is available
  • whether review, migration, or explicit override is required

Agent-Ready Without Blind Trust

Future systems will run more agents and automation.

Dezh should be designed so agents can operate productively without receiving ambient authority:

  • intent-scoped capability grants
  • bounded namespaces
  • structured effect ledger
  • review gates for sensitive changes
  • rollback/compensation where possible
  • denial explanations that can guide safer retries

Honest Novelty Accounting (D015)

Serious reviewers (seL4, Genode, CHERI communities) will immediately map each piece to prior art. Dezh's public claims must do that mapping first:

Existing ideas Dezh builds on (never claim these as new):

  • Capability security: KeyKOS, EROS, seL4, Capsicum.
  • User-space drivers and minimal kernel: every serious microkernel.
  • Generations and transactional packages: NixOS, ostree.
  • Snapshot/rollback storage: ZFS, btrfs.
  • Denial explanation: SELinux audit2why (bolted-on; ours is first-class, which is a UX differentiator, not a research one).

What is genuinely new in combination:

  1. Intent as the sole authority-derivation path, enforced (derived capability ⊆ declared intent), not annotated.
  2. An effect ledger that ties each effect to its authority provenance (actor → intent → derived capability → effect → rollback class/handle) as part of the OS authority model, not an audit afterthought.
  3. Agent-first framing: the above two designed so untrusted agents are productive without ambient authority (D013).

Public wording pattern: "Dezh combines known building blocks X and Y; what is new is 1–3 above." Anything stronger must be measured or demonstrated first.

Architectural Guardrails

These should remain hard rules:

  • No intent-as-metadata: authority is only derivable from a declared intent, and the derived capability must be provably narrower or equal.
  • No ledger or denial-context state inside the kernel; those are user-space services on Cairn.
  • No hidden kernel block I/O path.
  • No global registry as an app-facing configuration dump.
  • No Unix-style ambient filesystem authority as the default app model.
  • No silent package update.
  • No silent permission expansion.
  • No automatic physical cleanup without explicit command and audit.
  • No recovery path that widens authority.
  • No service failure that causes indefinite hangs.
  • No device/MMIO/DMA access without explicit grant.

Relationship To The MVP (D019)

This document is the narrative over the already-defined MVP (D019, docs/ROADMAP.md W1–W7), not a parallel roadmap. Rule: any work item here must map onto an existing workstream or be explicitly marked post-MVP. Two competing "what's next" documents would be strategic drift.

Mapping:

  • Effect ledger → extends W2 (Cairn v1 commit log is the ledger substrate) and the existing package journal.
  • effect-log / effect-info / effect-rollback / why-denied → fold into F1/W3 (agent containment demo) and W2.
  • Intent-derivation rule → hardens W1 (manifest cap grants become intent-derived grants).
  • Capability attestation (cap-audit, cap-tree, component-info) → supports F1 demo credibility; small enough to ride along W3.
  • App storage namespace + migration (ns-*) → genuinely new scope; explicitly post-MVP. Recorded here so it is not lost, deliberately not started before the four flagship demos are green.

Near-Term Milestones

These are now consolidated as roadmap W8 (Intent + Effect Runtime), the one workstream that turns D020/D021 from prose into a demonstrated differentiator. W8 is not "add a feature"; it is the feature plus the three things that make its value legible to a skeptical practitioner audience — an adversary, a whole-mission rollback with an honest irreversible effect, and an owned cost.

1. Intent as mechanism (Ahd)

  • intent-open <kind> issues an Ahd (an intent token: a ceiling of capabilities for a target namespace), intent-run <ahd> <app> runs an app whose derived capability is proven ⊆ the Ahd, intent-list enumerates open Ahds.
  • Manifest grants (W1) become Ahd-derived; a request for authority beyond the Ahd is denied. This rides the existing IPC attenuation and per-task capability bits.

2. Effect ledger on Cairn (Sand) — built (W8 P2)

  • Sand is the same Cairn v1 commit log, enriched — not a parallel store. The user-space storage daemon (which alone holds the disk capability) records each effect on the very commit that produces it: actor → intent (Ahd) → derived capability → target namespace → status → reversibility class → generation, alongside the pre-existing parent → hash. The intent id and derived cap are supplied by the kernel on the commit IPC; the daemon only records them.
  • Commands: sand-log <ns>, sand-info <ns>, and sand-demo (open an intent → run an agent under it → read the effect back off the ledger). Provenance survives a reboot because it lives on the durable commit.

3. Mission (Sfar) + whole-mission rollback + honest external effect

  • A Sfar groups the effects under one Ahd; effect-rollback <sfar> undoes them atomically; effect-rollback <id> undoes one.
  • At least one irreversible external effect (simulated network/print) that rollback refuses with an explanation, and one compensatable effect with a registered compensation action.

4. The adversary

  • A redteam scenario: a malicious agent that attempts cross-namespace reads, raw MMIO writes, capability forgery/amplification, out-of-intent actions, and CPU monopoly — each stopped at a named boundary (page fault / capability check / intent bound / preemption) with why-denied.

5. Explainable denial + provenance

  • why-denied <last|id>, cap-tree / cap-audit / component-info, and Tbar, a queryable actor → intent → effect provenance graph ("everything this agent touched and why").

6. Credibility layer

  • Cost: the per-effect ledger overhead measured and folded into BENCH.md (D015).
  • Head-to-head: a documented scenario where gVisor / Firecracker / wasmtime cannot cleanly undo a whole mission but Dezh can (Dezh's side reproducible in CI even if the competitor is only described).
  • docs/SECURITY_MODEL.md#threat-model: trusted base, what is defended, and what is explicitly not defended.

7. One flagship narrative

All of the above collapse into a single story — "leave a coding agent loose on your machine overnight" — with a transcript and a CI smoke leg. This is the final form of the F1 (D020) agent-containment demo, not a separate demo.

The first implementation maps a small set of intents onto existing package, storage, and service operations, with the ledger stored in Cairn.

2. Capability Attestation v1 (rides along W3)

Make authority explainable at runtime.

Candidate commands:

  • cap-audit
  • cap-tree
  • why-denied
  • component-info <id>

3. App Storage Namespace + Migration v0 (post-MVP)

Package update is now stronger than data lifecycle. The next major gap after the MVP demos is app data.

Build:

  • per-app namespace identity
  • namespace metadata
  • migration-required flag
  • migration transaction
  • rollback-aware data contract
  • namespace verification

Candidate commands:

  • ns-list
  • ns-info <app>
  • ns-migrate <app>
  • ns-verify <app>

4. Dezh Tooling MCPs

MCP should be used around Dezh, not inside the OS kernel/runtime.

Highest-value MCP candidates:

  1. dezh-qemu-mcp

    • boot QEMU
    • send commands
    • preserve disk image across reboots
    • collect transcript
    • assert expected OS behavior
  2. dezh-image-mcp

    • inspect raw disk image
    • decode install marker
    • decode package registry
    • decode journal
    • show package blobs, quarantine, GC state
  3. dezh-guard-mcp

    • enforce architecture guardrails
    • detect kernel-side block I/O regressions
    • detect ambient capability paths
    • scan public docs/package for unsafe claims, secrets, local paths, or non-public identity markers
  4. GitHub MCP

    • CI status
    • PR/release/review package workflow
  5. Browser/Playwright MCP

    • docs/review kit/demo rendering checks

Review Outcome (2026-07-04)

The direction was reviewed critically and accepted with three corrections, now folded into the text above:

  1. Intent must be a mechanism, not metadata — otherwise it is renamed audit logging. Added as a hard guardrail.
  2. This document binds to the MVP (D019) instead of forking the roadmap; namespace migration is explicitly post-MVP.
  3. Novelty claims follow D015 honesty — prior art is named; the genuinely new parts are the intent-derivation rule, the provenance-linked effect ledger, and the agent-first combination.

Answers to the open review questions:

  • The strongest single differentiator is the effect ledger tied to authority provenance, not intent alone.
  • The proving demo is F1 extended: give an untrusted agent an intent → show the derived narrow capability → agent acts → effect-log shows the record → effect-rollback undoes it → agent attempts something outside the intent → kernel denial → why-denied explains. One demo covers intent, ledger, rollback, explainable denial, and agent containment.
  • The main drift risks are convenience pressure (granting the shell broad capabilities) and letting ledger/denial state creep into the kernel; both are now guardrails.

Registered as D020 in DECISIONS.md.

Release Notes

v0.5-review Candidate

The release where the second ISA stops being a demo target, and where a secondary hart stops running a task uninterruptibly.

v0.4 shipped with STATUS.md saying the x86 kernel had "no returnable interrupt path yet — no timer, no device IRQs, no scheduler on x86", and that tasks on secondary RISC-V harts "run to completion — no preemption or migration there". Both sentences are why this release exists.

x86_64: from a boot smoke to a kernel that does not trust its tasks

Sixteen commits, each green on both boot paths (QEMU -kernel PVH and the GRUB Multiboot2 ISO):

  • A returnable interrupt path. The IDT grows from 32 exception vectors to 256, and vectors 32..255 save every register, dispatch, restore and iretq — an interrupt now interrupts work and hands control back, rather than ending it.
  • Preemption. Three tasks that never yield, and none of them keeps the CPU.
  • Per-task address spaces. Every task gets its own cr3 and reads a different page from the same address.
  • Ring 3. A task runs at CPL3 with one door back into the kernel, and a faulting task dies without taking the machine with it.
  • Authority derived from intent, not asserted by the caller. x86 had no capability check at all — its syscalls served anyone. A task now carries a capability word and the syscall path consults the task table, never a register the caller set. The derivation is granted = requested ∩ ceiling, computed by the same dezh_core::mcap function the RISC-V kernel calls, so a second implementation cannot drift from the exhaustive test that pins it. Two CPL3 tasks run byte-identical code from a byte-identical manifest and end up with different authority.

RISC-V: W13 steps 1 and 2

  • A secondary hart arms its own timer, so a U-mode task there is interrupted and resumed instead of owning the hart until it exits (smp-preempt). The demo counts ticks per hart and refuses to claim success if the task landed on the boot hart, which has preempted since W9.
  • Idle secondary harts sleep. They used to spin, and on an emulated host every vCPU shares one budget — so they were taking it from the hart draining the console.
  • One ticket lock for the whole kernel, which masks the acquiring hart's interrupts. Not a performance choice: plic_handle writes scheduler state from interrupt context, so without masking a hart can take the lock, take an interrupt, and wait for itself.
  • The task table is private to the scheduler and its reachable surface is under that lock. Six modules used to read and write it directly.

The console stops losing pasted input

UART0 is IRQ 10 on the virt board and had never been enabled at the PLIC, which routed only the virtio slots — so getc spun on the line-status register and read the receive register directly. That keeps up with a person typing and loses bytes to anything faster. It is routed now, with a receive ring both the handler and getc drain into, and irq-stat reports bytes received plus the two places a byte could be dropped.

Honest scope

  • No migration. The timer interrupt on a secondary resumes the task it interrupted; it does not pick a different one, because choosing means reading a task table that is still the boot hart's. That is the rest of W13.
  • x86 derives authority but does not account for effects. No Ahd token, no Sand ledger, no mission there. It has no device IRQs, no storage, no install path, and nothing frees a dead task's pages.
  • Console input can still be lost, and it is not ours. Six runs sending 204 bytes measured 204/202/200/188 received with zero drops at every layer, the shortfall matching the echoed line exactly each time. The guest is never handed those bytes. Single-run paste measurements on a host pipe are noise — the same configuration gave 0/10 and 10/10 minutes apart.
  • The release workflow no longer publishes a container image. It never succeeded in publishing one; see RELEASING.

v0.4-review Candidate

The release where the effect ledger stops being checked against itself, and where the kernel becomes something a stranger can actually read.

v0.3 could attribute and undo an agent's night — but every effect it attributed lived inside Dezh's own storage. The ledger was the only witness to its own claims. That is the gap this release closes.

What a reviewer can now do

  • Run marz-effect <dest> <verb> <arg> and watch an effect leave the machine: authorized against a live NIC capability, egress authority for that named destination and the DIFC export rule, ARP-resolved, sent on the wire — and then the outcome comes back and is ledgered. It records compensatable and carries the undo itself, not just the class, so sfar-plan names the compensating action rather than promising one exists. The reply lowers operator integrity, because bytes off the wire are attacker-chosen.
  • Check that claim without trusting us. tools/ci/effect_test.py runs twelve checks and none of them read Dezh's transcript — every assertion about external state is made against the external system itself, including that a revoked NIC capability leaves it untouched.
  • Read the kernel. main.rs went from 8,776 lines to 724 plus 26 modules, across 23 commits that each stayed green.
  • Type help and see all 151 commands. In v0.3 it silently listed 111: the Intent and Effects groups were missing from a hand-written list, so intent-open, sand-log, tbar, why-denied, overnight and redteam were absent from the first screen a reviewer reads. The list is now checked against the command table when the kernel is built.

The boundary, stated up front

The host gateway that performs the external effect is not in Dezh's TCB. A compromised gateway can lie about what it did. Dezh proves the parts it owns — authorized, left the machine, ledgered under an intent, compensation ran — and not the gateway's honesty. That is a smaller claim than "the OS speaks git", and it is the true one.

Honest scope

Everything named in the v0.3 notes that is still open stays open: no IOMMU, the x86 kernel has no scheduler or drivers, Pol is a small syscall subset, the console's own scheduler is single-hart, and in-flight capability clawback does not exist. See STATUS.md, which now also lists the three W11 gaps rather than rounding them away.

One correction to the v0.3 notes: they described intents as having no lease or revocation. That had already stopped being true when lease-demo shipped, and STATUS.md said so in one place while denying it in another. The contradiction is fixed in favour of the accurate half.

v0.3-review Candidate

The milestone where the no-ambient-authority rule stops being a single-core, single-threaded claim. Two bodies of work land here: an intent-to-effect runtime that can undo an agent's night honestly, and the hardware work — real device interrupts, symmetric multiprocessing, and a bidirectional network edge — that tests whether the rule holds when the machine gets harder.

What a reviewer can now do

  • Run overnight — leave a coding agent loose under one intent, then in the morning forecast the rollback, retract what is reversible, run and record a compensating action for what is compensatable, and watch the system refuse the irreversible rather than pretend. The agent's attempt to act outside its intent is denied by the kernel, and why-denied names the boundary.
  • Boot under -smp 4 and watch U-mode tasks run on several harts at the same instant, each in its own address space — then watch an intruder page-fault and die on its own hart while its neighbour keeps running.
  • Send a real ICMP echo to a destination the caller holds a capability for, and see the reply come back through ARP resolution — then watch consuming that reply lower integrity, so unvalidated network bytes cannot quietly become trusted state.
  • Verify a signed .dzp: the Ed25519 envelope binds the authority the package asks for, and the kernel checks it.

Flagship demos

Everything from v0.2-review (F1 containment, F2 Cairn, F3 multi-ISA, F4 Pol) still runs, joined by overnight, smp-sched, smp-isolate, marz-ping, ingress-demo, taintflow-demo, redteam, sig-demo and lease-demo — all green in tools/ci/qemu_smoke.py.

Honest scope

Three limitations named in the v0.2 notes are closed: runtime revocation, package signing, and SMP. Still open, and stated plainly rather than buried:

  • No IOMMU. User-space drivers buy fault isolation and least privilege of the driver process, not memory safety against a malicious driver that programs the device to DMA anywhere. This is core to the story, not polish.
  • No formal verification, and no in-flight capability clawback — revocation is at the intent-lease and object-generation level, which is coarse but honest.
  • Package signing has no distribution layer yet: no standalone signing CLI, no on-disk root-signed trust store.
  • QEMU and VirtualBox targets only. Emulated benchmarks are labelled as such.

Full detail, including what reviewers should push on, in docs/STATUS.md.

Artifacts

RISC-V and x86_64 kernels, the bootable dezh-<tag>-x86_64.iso, a .dzp sample package, a RUN.txt, the docs bundle, a manifest, and SHA256SUMS.

v0.2-review Candidate

The milestone where all four flagship demos are green in CI and a reviewer can boot Dezh in a VM with no source tree.

What a reviewer can now do

  • Boot the x86_64 kernel from a real bootable ISO in VirtualBox / VMware (or QEMU -cdrom); it reaches 64-bit long mode, installs and runs a .dzp agent package, enforces the print capability, and catches a deliberately-raised CPU exception instead of triple-faulting. See GETTING_STARTED.md#running-in-a-vm.
  • Run the RISC-V capability console — agent containment (F1), Cairn versioned storage with rollback across reboot (F2), the same byte-identical Dezh-IR app on both ISAs (F3), and a real unmodified Linux ELF under Pol (F4).

Flagship demos

  • F1 agent containment — narrow caps, kernel-DENIED beyond grant, attenuated IPC delegation, rollback (tools/demo/run_agent_demo.py).
  • F2 Cairn v1 — commit log, rollback, reboot-persistent, capability-gated namespaces (cairn-demo).
  • F3 multi-ISA — byte-identical .dzp runs on RISC-V and x86_64; bytes pinned by a test.
  • F4 Pol — a stock static Linux/RISC-V ELF runs capability-gated; the same bytes run on real Linux; translation overhead measured (bench-pol).

Honest scope

QEMU/VirtualBox targets only; benchmarks that are emulated are labelled as such; Pol is a small syscall subset; no runtime revocation, IOMMU, package signing, or SMP yet. Full detail in docs/STATUS.md.

Artifacts

RISC-V and x86_64 kernels, the bootable dezh-<tag>-x86_64.iso, a .dzp sample package, a RUN.txt, the docs bundle, a manifest, and SHA256SUMS.

v0.1-review Candidate

v0.1-review is the first public review candidate for Dezh OS.

It is intended for architecture, security-model, package-lifecycle, and prototype-execution review. It is not a production release.

Highlights

  • Bare-metal RISC-V QEMU boot through OpenSBI.
  • x86_64 smoke target for the shared runtime path.
  • U-mode task isolation with contained page faults.
  • Explicit capability gates for syscall effects.
  • Long-lived user-space virtio-block daemon.
  • Typed IPC with status-aware replies and timeout accounting.
  • Service registry with stop, restart, and controlled fault demos.
  • Reboot-safe package store for SDK-built .dzp packages.
  • Transactional install/remove/update/rollback path.
  • Journal recovery, quarantine, pin/unpin, explicit GC, and capability escalation review.
  • Embedded apps for note, lab, calculator, and vault workflows.
  • Public demo transcripts and review tooling.

Validation

Recommended validation:

python tools/review/run_full_review.py --quick

Full validation:

python tools/review/run_full_review.py --full

Expected release artifacts are described in Release Process. GitHub Packages usage is described in Packages And Releases.

Known Limitations

  • QEMU is the primary validation environment.
  • The installer initializes a prototype disk layout, not production boot media.
  • Package checksums are deterministic v0 checks, not production cryptographic signatures.
  • DMA isolation is modeled through page-table discipline and grants; real IOMMU work is future scope.
  • Store sizes and package limits are intentionally small for reviewability.
  • Networking, graphics, formal verification, and real hardware bring-up are future work.

Review Questions

  • Is the no-ambient-authority model visible in the code and tests?
  • Is the user-space block driver boundary placed correctly?
  • Are package lifecycle states and recovery rules sufficiently explicit?
  • Are service failure modes clean enough for long-running operation?
  • Which parts should be reduced, split, or formalized before the next review candidate?

Releasing

Cutting a release, how packages relate to releases, and the branch and profile conventions around them.

The notes for shipped releases stay in their own file, RELEASE_NOTES.md, because the release workflow feeds that file to GitHub verbatim.


Release process

Dezh uses review releases rather than production releases at this stage.

Release Goals

A review release should give an external reviewer:

  • a fixed source revision
  • repeatable CI evidence
  • bootable QEMU kernel artifacts
  • a review demo transcript
  • SDK .dzp sample packages
  • checksums and an artifact manifest
  • a review environment they can build locally from Dockerfile.review

Version Names

Use tags in this shape:

v0.1-review
v0.2-review

The suffix makes the release status explicit. These are not production OS releases.

Before Tagging

Run:

python tools/review/run_full_review.py --full

The full review suite validates:

  • public hygiene
  • host workspace tests
  • RISC-V kernel build
  • x86_64 kernel build
  • RISC-V QEMU smoke
  • x86_64 QEMU smoke
  • review demo transcript
  • SDK package lifecycle acceptance
  • release artifact generation

Create A Release

From main, after it has fast-forwarded from develop:

git tag -a v0.1-review -m "Dezh OS v0.1-review"
git push origin v0.1-review

Pushing the tag starts .github/workflows/release.yml.

Release Artifacts

The release workflow attaches:

  • dezh-<tag>-riscv64-qemu-kernel.elf
  • dezh-<tag>-x86_64-qemu-kernel.elf
  • transcripts/riscv64.md
  • dezh-<tag>-hello.dzp
  • dezh-<tag>-review-docs.zip
  • release-manifest.json
  • SHA256SUMS

Container Package

The release workflow publishes no container image. It used to try; see the review environment for what happened and how to build it locally from Dockerfile.review, which carries Rust, Python, QEMU and the Rust targets review needs.

Release Discipline

  • Do not tag from a dirty tree.
  • Do not create a release without passing the full review suite.
  • Do not attach local disk images or ad-hoc binaries.
  • Do not publish production claims in review release notes.
  • Do not use GitHub Packages for app storage semantics; Dezh packages are .dzp artifacts and OS-managed package-store entries.

Packages and releases

GitHub shows two related surfaces: Releases and Packages. Dezh uses both, but for different purposes.

Releases

Releases are the public review checkpoints.

Each release should contain:

  • QEMU kernel artifacts
  • a generated review transcript
  • a sample SDK .dzp package
  • documentation archive
  • artifact manifest
  • checksums

This lets a reviewer inspect a fixed point in the project without guessing which commit, transcript, or binary was used.

The review environment

Build it locally:

docker build -f Dockerfile.review -t dezh-review-env .

The image is not the OS. It is the build-and-review environment: Rust targets, Python, and QEMU.

The release publishes it to GHCR, and it is public:

docker pull ghcr.io/alisalimi77/dezh-review-env:latest

The record of getting this wrong is kept below, because this page told reviewers the opposite for two releases and it was checkable in one command.

The publish was refused on v0.3-review and v0.4-review with denied: permission_denied: write_package, and after the second refusal the steps were removed rather than left turning the release red — a gate that breaks for reasons unrelated to the change under review is one people learn to ignore, and this one would have taught that to the reviewers the project is asking for critique from.

What was written here at the same time was wrong, and wrong in the direction that flatters nobody: "There is no published image", "no tag of the image has ever existed", "docker pull was never going to work". All three were false when written. v0.1-review, v0.2-review and latest were in the registry the whole time and anonymously pullable; a docker pull would have said so. README repeated the claim.

The timeline nobody looked at:

ReleaseDateGHCR
v0.1-review2026-07-04published
v0.2-review2026-07-07published
(repository made public)2026-08-03
v0.3-review2026-08-03denied: permission_denied: write_package
v0.4-review2026-08-15same denial
v0.5-review2026-08-18publish step had been removed

The first denial and the day the repository went public are the same day. What broke was the package's own Actions access — web-UI state, not anything in this repository — which is why packages: write in the workflow and a write default workflow-token permission both checked out and neither helped.

Two other explanations were written down before that one. The first was a visibility problem, which was never it. The second was that the push was refused because the package did not exist yet — also wrong, and it is the assumption that made the false claim above feel safe: nobody pulls an image they have concluded cannot exist.

Granting this repository write on the package fixed it. It was confirmed before restoring anything, by a probe using GITHUB_TOKEN — the credential that was refused, since a personal access token would have succeeded and proved nothing — opening a blob upload session against the package: HTTP 202, and no tag left behind.

Dezh .dzp Packages

Dezh application packages are .dzp artifacts. They are installed into the OS through the console and the service-mediated package store.

They are intentionally separate from GitHub Packages:

  • GitHub Packages distributes host-side review tooling.
  • .dzp packages exercise Dezh's own app installation model.
  • OS package state remains capability-scoped, transactional, and auditable.

Why Not Publish Every App To GitHub Packages?

The package-store design is part of the OS thesis. Treating every Dezh app as a generic host package would hide the lifecycle that Dezh is trying to make explicit: capability requests, install journal, registry state, rollback, quarantine, and garbage collection.

For public review, release assets are enough. Later, Dezh can add a dedicated package index with signatures, reproducible builds, and capability review.


Git workflow

Dezh uses a two-branch integration flow:

  • develop is the active integration branch.
  • main is the stable branch for coherent, tested milestones.

Feature Work

Create focused branches from develop:

git switch develop
git pull
git switch -c feature/<short-name>

Use these prefixes:

  • feature/<name> for product or kernel functionality.
  • fix/<name> for bug fixes.
  • docs/<name> for documentation-only work.
  • spike/<name> for exploratory work.

Required Validation

Before merging to develop:

cargo test --locked --workspace
cd dezh-boot && cargo build --locked && cd ..
cd dezh-boot-x86 && cargo build --locked && cd ..

For bare-metal changes, also run:

python tools/ci/qemu_smoke.py riscv64 \
  --kernel dezh-boot/target/riscv64gc-unknown-none-elf/debug/dezh-boot \
  --qemu qemu-system-riscv64

python tools/ci/qemu_smoke.py x86_64 \
  --kernel dezh-boot-x86/target/x86_64-unknown-none/debug/dezh-boot-x86 \
  --qemu qemu-system-x86_64

For external-review states, also run:

python tools/demo/run_review_demo.py --qemu-riscv qemu-system-riscv64
python tools/review/scan_public.py

External Review Snapshot

External review material should be exported from a clean snapshot, not from a branch with internal work-in-progress history. Use the review package tool:

python tools/review/make_review_package.py

The exported package should pass the public hygiene scan before distribution.

Main Branch

Fast-forward main only after the milestone is coherent and the validation commands above are green.


GitHub profile

Use this text for the public GitHub repository profile.

Description

Intent-native, capability-secure OS research prototype with user-space drivers, typed IPC, transactional package lifecycle, and reboot-safe QEMU demos.

Shorter variant:

Capability-secure OS research prototype with user-space drivers, typed IPC, and transactional package lifecycle.

Suggested Topics

  • operating-system
  • research-os
  • capability-security
  • microkernel
  • riscv
  • qemu
  • user-space-drivers
  • typed-ipc
  • package-management
  • rust
  • systems-programming
  • sandboxing
  • agent-sandbox
  • ai-agents
  • rollback

Website

Leave empty for now unless a dedicated documentation site is published.

Social Preview

Recommended preview concept:

  • dark technical diagram background
  • title: Dezh OS
  • subtitle: Intent-native. Capability-secure. Effect-accountable.
  • small visual motif: kernel boundary, U-mode services, package lifecycle

Do not use screenshots that expose local paths, private terminals, or development-only artifacts.

Outreach

Targeted technical review requests. Do not mass-send. Verify the appropriate public channel for each community or organization at send time, and post as a person asking for critique — not as an announcement.

Every claim below must stay true to STATUS.md and Threat model. If a reviewer finds a gap we did not name ourselves, the post was wrong.


1. Technical post — OS and systems people

(r/osdev, and the seL4 / Genode / CHERI / capability-systems crowd. Goal: serious critique, not applause.)

Title: Dezh — making an agent's effects accountable, on a kernel with no ambient authority

I've been building a from-scratch capability OS substrate and I'd like it torn apart by people who know this space.

What I am not claiming. Capability security (Dennis & Van Horn; KeyKOS/EROS; Miller's object-capability model), a verified microkernel (seL4), user-space drivers and capability components (Genode), hardware capabilities (CHERI), decentralized information-flow control (HiStar/Flume), and compensation-based recovery (sagas) are all prior art. None of them is my contribution, and the from-scratch kernel is not the point — for a product, building this on seL4 or Genode would be the right call, and the FAQ says so.

What I think is new is a recombination that needs a substrate with no ambient authority underneath it:

  • Intent is the only path to authority. A capability is derived as requested ∩ intent_ceiling — a structural subset, not a purpose string. Anything beyond the intent is dropped and reported.
  • The effect ledger is on the authorization path, not beside it. Every effect is the record that authorized and persisted it, carrying actor → intent → derived capability → reversibility class. On an ambient-authority host you can usually reach the resource around the logger; here there is no such path.
  • Rollback that refuses to lie. Effects are classified reversible / compensatable / irreversible / unknown. A whole mission is forecast before anything is touched; then reversible effects are retracted, compensatable ones are undone by running and recording a compensating action, and irreversible ones are refused with a reason. A connector that declares nothing is unknown and is never optimistically "undone".
  • Egress is a first-class effect. Network authority names a destination, not "the network"; export is checked against an information-flow taint (the Flume rule that leaving the system is a declassification); and a raw send is recorded as an irreversible effect that rollback refuses.
  • The effect leaves the machine and the outcome comes back. This is the part I most expect to be attacked, so it is stated narrowly. marz-effect drives a real external system through a host gateway: authorized against a live device capability, egress authority for that named destination and the export rule, ARP-resolved, sent on the wire — and the reply is ledgered, as compensatable, carrying the undo itself rather than only its class, so the rollback forecast names the compensating action instead of promising one exists. The reply also lowers integrity, because bytes off the wire are attacker-chosen.

Evidence, not slides. Everything is exercised in CI on a RISC-V kernel under QEMU. The one I'd point a skeptic at first: the external-effect test never reads Dezh's own transcript. Every assertion about what happened out there is made against the external system's own state, including the negative case — a revoked device capability must leave it byte-for-byte untouched. The egress test is the same idea one layer down: QEMU captures the packets and it fails unless exactly the authorized frames are on the wire, so a refused send that leaked or an authorized one that never left both turn CI red. The capability algebra (derived ⊆ intent; delegation only attenuates) is proved by exhaustive enumeration in a host test rather than asserted in prose.

Where it is weak, in my own words. Not formally verified — seL4 is the bar and I am not near it. QEMU only. No IOMMU, so a user-space driver gives fault isolation and least privilege of the driver process, not memory safety against a malicious driver; that is core to the story, not future polish. The gateway that performs the external effect is outside the TCB — a compromised one can lie about what it did, so what is proved is authorization, egress, ledgering and that the compensation ran, not the gateway's honesty; it is one connector, and the rest of the modeled effects are still models. Information flow is enforced on the storage path and at egress, not across every channel. Packages are signed, but there is no key distribution or transparency service. print/time/ipc are still plain permission bits on purpose (they name no object); namespaces, devices and destinations are generation-stamped handles with per-object revocation.

What I would most value critique on: whether the ledger is genuinely unbypassable given the trusted base I describe; whether the reversibility classification is honest; and whether the novelty claim survives contact with prior art you know better than I do.

Repo: https://github.com/alisalimi77/Dezh · Design + prior-art comparison: docs/RELATED_WORK.md, docs/SUBSYSTEMS.md#marz-guarded-egress · Honest limits: docs/STATUS.md, docs/SECURITY_MODEL.md#threat-model


2. Short intro — agent-runtime and coding-agent teams

(Teams shipping agents that touch repos, CI, deploys, or secrets. Goal: a design partner and a real use case.)

Subject: containing coding agents at the OS level — worth a look?

You already sandbox the agents you run. Sandboxes are good at confinement and bad at the question that actually comes up after an incident: what exactly did it do, on whose authority, and how much of it can I undo?

Dezh is a research OS substrate built around that question. An agent runs under a single declared intent; every effect it produces is recorded on the path that authorized it, carrying who did it, under which intent, and whether it can be undone. In the morning you get a forecast of what a rollback can and cannot reverse — then reversible work is retracted, compensatable work is undone by a recorded compensating action, and anything genuinely irreversible is refused with an explanation instead of being silently "rolled back".

Three things that usually surprise people:

  • Exfiltration is refused at the wire, not audited afterwards. If the agent reads something secret it becomes tainted, and a send to a destination not cleared for that secret is blocked before a packet exists. Network authority names a destination, so "it had network access" is not a thing here.
  • The undo is recorded with the effect, not guessed later. One connector is real rather than modeled: an effect that leaves the machine, changes an external system, and comes back to be ledgered with the specific action that reverses it. So the morning forecast names the compensating command instead of claiming a class and hoping. Honest limit: that gateway is outside the trusted base, so it can lie about what it did on the far side.
  • It is one command. overnight runs the whole story: an agent loose under one intent, a morning of forecast and provenance, an honest rollback, and a contained escape attempt.

Honest framing: this is a research prototype on QEMU, not a product. No formal verification, no IOMMU yet, a small syscall surface. I am looking for one or two teams whose agents change real repos/CI/deploys to tell me where the effect model breaks against their workflow — especially which effects would need typed connectors, and what "undo" has to mean for them.

Repo: https://github.com/alisalimi77/Dezh · Start here: docs/transcripts/overnight.md


Sending checklist

  • Verify every link resolves before sending.
  • Re-read STATUS.md: if a limitation changed, fix the post first.
  • Ask for critique on something specific; a post with no question gets no review.
  • One channel at a time. Answer the hard replies before posting anywhere else.

Dezh OS RISC-V Review Demo Transcript

Mode: short

This transcript is generated by tools/demo/run_review_demo.py.

OpenSBI v1.7
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name               : riscv-virtio,qemu
Platform Features           : medeleg
Platform HART Count         : 1
Platform IPI Device         : aclint-mswi
Platform Timer Device       : aclint-mtimer @ 10000000Hz
Platform Console Device     : uart8250
Platform HSM Device         : ---
Platform PMU Device         : ---
Platform Reboot Device      : syscon-reboot
Platform Shutdown Device    : syscon-poweroff
Platform Suspend Device     : ---
Platform CPPC Device        : ---
Firmware Base               : 0x80000000
Firmware Size               : 317 KB
Firmware RW Offset          : 0x40000
Firmware RW Size            : 61 KB
Firmware Heap Offset        : 0x46000
Firmware Heap Size          : 37 KB (total), 2 KB (reserved), 11 KB (used), 23 KB (free)
Firmware Scratch Size       : 4096 B (total), 1400 B (used), 2696 B (free)
Runtime SBI Version         : 3.0
Standard SBI Extensions     : time,rfnc,ipi,base,hsm,srst,pmu,dbcn,fwft,legacy,dbtr,sse
Experimental SBI Extensions : none

Domain0 Name                : root
Domain0 Boot HART           : 0
Domain0 HARTs               : 0*
Domain0 Region00            : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01            : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02            : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region03            : 0x0000000080040000-0x000000008004ffff M: (R,W) S/U: ()
Domain0 Region04            : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region05            : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06            : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07            : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address        : 0x0000000080200000
Domain0 Next Arg1           : 0x0000000087e00000
Domain0 Next Mode           : S-mode
Domain0 SysReset            : yes
Domain0 SysSuspend          : yes

Boot HART ID                : 0
Boot HART Domain            : root
Boot HART Priv Version      : v1.12
Boot HART Base ISA          : rv64imafdch
Boot HART ISA Extensions    : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count         : 16
Boot HART PMP Granularity   : 2 bits
Boot HART PMP Address Bits  : 54
Boot HART MHPM Info         : 16 (0x0007fff8)
Boot HART Debug Triggers    : 2 triggers
Boot HART MIDELEG           : 0x0000000000001666
Boot HART MEDELEG           : 0x0000000000f4b509

   ____            _
  |  _ \  ___  ___| |__
  | | | |/ _ \/_  / '_ \
  | |_| |  __/ / /| | | |
  |____/ \___//___|_| |_|
  Dezh OS - capability-secure - no ambient authority
  v0 - riscv64 - 126 MiB usable - 4 services

[dezh-boot] alive on bare metal (qemu virt, riscv64, S-mode)
[dezh-boot] boot contract VALIDATED
[dezh-boot] banner: dezh-kernel-boot-v0:qemu-virtio-riscv64:services=4:usable_bytes=132120576
[dezh-boot] no ambient authority: capability seeds bound to declared services only
[dezh-boot] installing trap vector + supervisor timer...
[dezh-boot] enabling Sv39 paging (U-mode confined to its own region)...
[dezh-boot] frame allocator: 28672 x 4 KiB frames (112 MiB free)
[dezh-boot] embedded user ELFs: userprog=8952 bytes, virtio-blk=82264 bytes, dezh-bench=15592 bytes, dezh-note=7080 bytes, dezh-lab=17208 bytes, dezh-calc=10440 bytes, dezh-vault=7824 bytes
[dezh-boot] install manifest v0: root=cairn block=virtio-block marker_sector=0
[dezh-boot] service registry built from boot plan (4 services)

Dezh console. Every command requires an explicit capability.
Type 'help'. The console holds: INSPECT TIME ECHO HALT SPAWN
dezh> ipc-typed-demo
[typed-ipc] demo: typed OK, BAD_REQUEST, TIMEOUT, and DENIED
    [typed-ipc] PING -> 0
  [kernel] task 0 exited (code 0)
    [typed-ipc] BADREQ -> 4
  [kernel] task 1 exited (code 0)
    [typed-ipc] RECV_TIMEOUT -> 3
  [kernel] task 0 exited (code 0)
  [kernel] DENIED send: task 0 holds no IPC capability
    [typed-ipc] no-IPC SEND -> 1
  [kernel] task 0 exited (code 0)
[typed-ipc] PASS: OK=OK, BAD_REQUEST=BAD_REQUEST, TIMEOUT=TIMEOUT, DENIED=DENIED
dezh> ipcstat
ipcstat: sends=4 receives=4 denied_sends=1 timeouts=1 queue_full=0 max_depth=1
dezh> services
[services] starting virtio-block from boot registry as task 0
  [virtio-blk-daemon] started as a long-lived U-mode driver service
  [virtio-blk-daemon] device + DMA capabilities accepted
[services] virtio-block Running (task 0)
runtime services (4 total):
  - init          Init state=Declared task=18446744073709551615 caps=0x5 grants=0x0 restarts=0 last_exit=0 started_tick=0 
  - cairn         Cairn state=Declared task=18446744073709551615 caps=0x35 grants=0x1 restarts=0 last_exit=0 started_tick=0 
  - wasm-runtime  WasmRuntime state=Declared task=18446744073709551615 caps=0x5 grants=0x0 restarts=0 last_exit=0 started_tick=0 
  - virtio-block  VirtioBlock state=Running task=0 caps=0x3d grants=0x3 restarts=0 last_exit=0 started_tick=0 
dezh> install --dry-run
Install Plan: Dezh Root v1
  [01] Probe block service        ready
  [02] Validate boot manifest     ready
  [03] Write root marker          pending
  [04] Initialize app registry    pending
  [05] Install base apps          note lab calc vault
  [06] Verify root/app state      pending
  [07] Commit install report      pending
[##------------------]  14%  probe block service          OK
[#####---------------]  28%  validate boot manifest       OK
[########------------]  42%  write root marker            dry-run
[###########---------]  57%  initialize app registry      dry-run
[##############------]  71%  install base apps            dry-run
[#################---]  85%  verify root/app state        dry-run
[####################] 100%  commit install report        dry-run
[install-v1] dry-run complete; disk not modified
dezh> install run
Install Plan: Dezh Root v1
  [01] Probe block service        ready
  [02] Validate boot manifest     ready
  [03] Write root marker          pending
  [04] Initialize app registry    pending
  [05] Install base apps          note lab calc vault
  [06] Verify root/app state      pending
  [07] Commit install report      pending
[##------------------]  14%  probe block service          OK
[#####---------------]  28%  validate boot manifest       OK
[########------------]  42%  write root marker            running
[services] resolved service virtio-block task=0; launching foreground client
  [virtio-blk-daemon] install-init: wrote marker/root metadata status=0
  [vblk-client] install-init status=0
  [kernel] task 1 exited (code 0)
[###########---------]  57%  initialize app registry      running
[services] resolved service virtio-block task=0; launching foreground client
  [installer] installed note version=0.1.0 state=Active caps=PRINT,IPC root=sector:16
  [vblk-client] app-install note status=0
  [kernel] task 1 exited (code 0)
[##############------]  71%  install base apps            running
[services] resolved service virtio-block task=0; launching foreground client
  [installer] installed lab version=0.1.0 state=Active caps=PRINT,IPC root=sector:17 ui=terminal workers=2
  [vblk-client] app-install lab status=0
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [installer] installed calc version=0.1.0 state=Active caps=PRINT,IPC root=sector:18 compute=integer
  [vblk-client] app-install calc status=0
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [installer] installed vault version=0.1.0 state=Active caps=PRINT,IPC root=sector:19 storage=PrivateValue
  [vblk-client] app-install vault status=0
  [kernel] task 1 exited (code 0)
[#################---]  85%  verify root/app state        running
[install-v1] verifying root marker, metadata, and base app registry
[services] resolved service virtio-block task=0; launching foreground client
  [virtio-blk-daemon] install-check: installed root marker found
  [vblk-client] install-check status=0
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [virtio-blk-daemon] root-status: metadata read status=0
  [vblk-client] root-status status=0
  [vblk-client] root metadata = "DEZHROOT v0 cairn_current=2 cairn_previous=3 metadata_sector=4
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [installed] note version=0.1.0 state=Active caps=PRINT,IPC root=sector:16
  [installed] lab version=0.1.0 state=Active caps=PRINT,IPC root=sector:17 ui=terminal
  [installed] calc version=0.1.0 state=Active caps=PRINT,IPC root=sector:18 compute=integer
  [installed] vault version=0.1.0 state=Active caps=PRINT,IPC root=sector:19 storage=PrivateValue
  [vblk-client] apps installed status=0
  [kernel] task 1 exited (code 0)
[####################] 100%  commit install report        OK
Install Report: Dezh Root v1
  root marker      sector 0
  root metadata    sector 4
  app registry     sectors 5..10
  private data     sectors 16..19
  required service virtio-block
  policy           no ambient authority
events:
  TICK   ACTOR      ACTION          TARGET          RESULT
  0      console    install.run     root-v1         start
  0      installer  install.dryrun  root-v1         OK
  0      console    install.run     root-v1         start
  1      console    app.install     note            start
  1      installer  app.install     note            done
  1      console    app.install     lab             start
  1      installer  app.install     lab             done
  1      console    app.install     calc            start
  1      installer  app.install     calc            done
  1      console    app.install     vault           start
  1      installer  app.install     vault           done
  1      installer  install.verify  root-v1         done
dezh> app-permissions lab
app permissions: lab
  REQUESTED  PRINT IPC
  GRANTED    PRINT IPC
  DENIED     DEVICE_VIRTIO_BLK DMA BLOCK_DIRECT MMIO
  STORAGE    service-mediated via virtio-block daemon
dezh> app-run lab
[services] resolved service virtio-block task=0; launching foreground client
  [installer] lab is installed state=Active
  [vblk-client] app-require lab status=0
  [kernel] task 1 exited (code 0)
[app-run] preparing lab private storage through virtio-block service
[services] resolved service virtio-block task=0; launching foreground client
  [lab-storage] lab-set status=0
  [vblk-client] lab-set status=0
  [kernel] task 1 exited (code 0)
[app-run] launching lab UI + workers with caps=PRINT,IPC only

  +--------------------------------------------------+
  | Dezh Lab :: installable app system probe         |
  +--------------------------------------------------+
  | UI        terminal dashboard                     |
  | Runtime   3 foreground U-mode tasks              |
  | IPC       worker -> dashboard scalar messages    |
  | Storage   private app sector via virtio service  |
  | Caps      PRINT,IPC only; no device/DMA grant    |
  +--------------------------------------------------+
  [lab-ui] waiting for worker signals
    [lab-worker] start id=1
    [lab-worker] start id=2
    [lab-worker] sent signal id=1
  [kernel] task 2 exited (code 0)
    [lab-worker] sent signal id=2
  [kernel] task 3 exited (code 0)
  [lab-ui] signal from task=2
  [lab-ui] payload=701
  [lab-ui] signal from task=3
  [lab-ui] payload=702
  [lab-ui] worker signals received=2
  [lab-ui] PASS: scheduler, IPC, installer launch, and UI path cooperated
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [lab-storage] lab-set status=0
  [vblk-client] lab-set status=0
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [lab-storage] lab-get status=0
  [vblk-client] lab-get status=0
  [vblk-client] lab value = "lab-run-complete
  [kernel] task 1 exited (code 0)
[app-run] lab exited; console returned
dezh> calc 7 + 5
[services] resolved service virtio-block task=0; launching foreground client
  [installer] calc is installed state=Active
  [vblk-client] app-require calc status=0
  [kernel] task 1 exited (code 0)
    [calc] 7 + 5 = 12
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [calc-storage] calc-set status=0
  [vblk-client] calc-set status=0
  [kernel] task 1 exited (code 0)
dezh> calc-history
[services] resolved service virtio-block task=0; launching foreground client
  [calc-storage] calc-get status=0
  [vblk-client] calc-history status=0
  [vblk-client] calc last = "7 + 5 = 12
  [kernel] task 1 exited (code 0)
dezh> vault-put demo-secret
[services] resolved service virtio-block task=0; launching foreground client
  [installer] vault is installed state=Active
  [vblk-client] app-require vault status=0
  [kernel] task 1 exited (code 0)
[services] resolved service virtio-block task=0; launching foreground client
  [vault-storage] vault-put status=0
  [vblk-client] vault-put status=0
  [kernel] task 1 exited (code 0)
dezh> vault-get
[services] resolved service virtio-block task=0; launching foreground client
  [vault-storage] vault-get status=0
  [vblk-client] vault-get status=0
  [vblk-client] vault value = "demo-secret
  [kernel] task 1 exited (code 0)
dezh> app-deny vault
[app-deny] vault has no direct block grant when launched without IPC
    [vault-deny] attempting block IPC without IPC cap
  [kernel] DENIED send: task 1 holds no IPC capability
    [vault-deny] direct block IPC denied
  [kernel] task 1 exited (code 0)
[app-deny] vault has no MMIO/device grant
    [vault-deny] attempting MMIO without device grant
  [kernel] task 1 DENIED: faulted on 0x50000000 (outside its grant) -- killing
[app-deny] vault device/block direct access denied; console survived
dezh> svc-stop virtio-block
[services] stopping virtio-block task=0 with typed STOP
  [virtio-blk-daemon] STOP received; exiting cleanly
  [kernel] task 0 exited (code 0)
  [kernel] task 1 exited (code 0)
[services] svc-stop virtio-block status=0 state=Stopped
dezh> read
[services] virtio-block unavailable: service is Stopped; use `svc-restart virtio-block`
[services] virtio-block unavailable; command failed cleanly
dezh> svc-restart virtio-block
[services] starting virtio-block from boot registry as task 0
  [virtio-blk-daemon] started as a long-lived U-mode driver service
  [virtio-blk-daemon] device + DMA capabilities accepted
[services] virtio-block Running (task 0)
[services] svc-restart virtio-block state=Running restart_count=1
dezh> write recovered
[services] resolved service virtio-block task=0; launching foreground client
  [virtio-blk-daemon] CAIRN SET via IPC status=0
  [vblk-client] cairn set via registered daemon status=0
  [kernel] task 1 exited (code 0)
dezh> read
[services] resolved service virtio-block task=0; launching foreground client
  [virtio-blk-daemon] CAIRN GET via IPC status=0
  [vblk-client] cairn get via registered daemon status=0
  [vblk-client] cairn current = "recovered
  [kernel] task 1 exited (code 0)
dezh> svc-fault-demo virtio-block
[services] resolved service virtio-block task=0; launching foreground client
  [virtio-blk-daemon] FAULT-DEMO received; exiting with fault code
  [kernel] task 0 exited (code 99)
  [kernel] task 1 exited (code 0)
[services] svc-fault-demo virtio-block request_status=0 state=Faulted last_exit=99
dezh> read
[services] virtio-block unavailable: service is Faulted; use `svc-restart virtio-block`
[services] virtio-block unavailable; command failed cleanly
dezh> svc-restart virtio-block
[services] starting virtio-block from boot registry as task 0
  [virtio-blk-daemon] started as a long-lived U-mode driver service
  [virtio-blk-daemon] device + DMA capabilities accepted
[services] virtio-block Running (task 0)
[services] svc-restart virtio-block state=Running restart_count=2
dezh> cairn-demo
[cairn-demo] F2: versioned app state, capability-gated namespaces, rollback
[cairn-demo] 1/6 two commits into ns=note (each is an object + ref move)
  [cairn] v1 store formatted: superblock sector 1600, 255 commit slots
  [cairn] commit ns=note slot=0 parent=none len=7 hash=0xa7d46f4c1e286c79 actor=task1
  [vblk-client] cairn-commit status=0
  [kernel] task 1 exited (code 0)
  [cairn] commit ns=note slot=1 parent=0 len=7 hash=0xa7d46c4c1e286760 actor=task1
  [vblk-client] cairn-commit status=0
  [kernel] task 1 exited (code 0)
[cairn-demo] 2/6 commit log for ns=note (newest first)
  [cairn] log ns=note (newest first):
    slot=1 parent=0 len=7 hash=0xa7d46c4c1e286760 actor=task1 reversible=yes
    slot=0 parent=none len=7 hash=0xa7d46f4c1e286c79 actor=task1 reversible=yes
  [vblk-client] cairn-log status=0
  [kernel] task 1 exited (code 0)
[cairn-demo] 3/6 a bad write lands
  [cairn] commit ns=note slot=2 parent=1 len=15 hash=0xc8e7037cf5e1b495 actor=task1
  [vblk-client] cairn-commit status=0
  [kernel] task 1 exited (code 0)
  [cairn] get ns=note head=2 len=15
  [vblk-client] cairn-get status=0
  [vblk-client] cairn value = "corrupted-write
  [kernel] task 1 exited (code 0)
[cairn-demo] 4/6 rollback one step restores the previous commit
  [cairn] rollback ns=note steps=1 head 2 -> 1
  [cairn] history preserved: rollback moves the ref, commits stay on disk
  [vblk-client] cairn-rollback status=0
  [kernel] task 1 exited (code 0)
  [cairn] get ns=note head=1 len=7
  [vblk-client] cairn-get status=0
  [vblk-client] cairn value = "note-v2
  [kernel] task 1 exited (code 0)
  [cairn] verify ns=note slot=1 hash MATCH 0xa7d46c4c1e286760
  [vblk-client] cairn-verify status=0
  [kernel] task 1 exited (code 0)
[cairn-demo] 5/6 cross-namespace access must be DENIED
[cairn-demo]     client holds CAIRN_NS_vault only and requests ns=note
  [cairn] DENIED: ns=note requires capability CAIRN_NS_0 sender task=1 holds caps=0x0000000000000835
  [cairn] hint: grant the namespace in the app manifest / launch caps
  [vblk-client] cairn-get status=1
  [vblk-client] DENIED by storage service (kernel-attested caps)
  [kernel] task 1 exited (code 1)
[cairn-demo] 6/6 store status
  [cairn] v1 store: commit slots used=3/255 region=sectors 1600..1855
    ns=note cap=CAIRN_NS_0 head=1 commits=2
    ns=lab cap=CAIRN_NS_1 head=none commits=0
    ns=calc cap=CAIRN_NS_2 head=none commits=0
    ns=vault cap=CAIRN_NS_3 head=none commits=0
    ns=agent cap=CAIRN_NS_4 head=none commits=0
  [vblk-client] cairn-status status=0
  [kernel] task 1 exited (code 0)
[cairn-demo] PASS: commit/log/rollback/verify OK and cross-namespace DENIED
[cairn-demo] state is on disk: after reboot, `cairn-get note` still answers
dezh> cairn-log note
  [cairn] log ns=note (newest first):
    slot=1 parent=0 len=7 hash=0xa7d46c4c1e286760 actor=task1 reversible=yes
    slot=0 parent=none len=7 hash=0xa7d46f4c1e286c79 actor=task1 reversible=yes
  [vblk-client] cairn-log status=0
  [kernel] task 1 exited (code 0)
dezh> cairn-status
  [cairn] v1 store: commit slots used=3/255 region=sectors 1600..1855
    ns=note cap=CAIRN_NS_0 head=1 commits=2
    ns=lab cap=CAIRN_NS_1 head=none commits=0
    ns=calc cap=CAIRN_NS_2 head=none commits=0
    ns=vault cap=CAIRN_NS_3 head=none commits=0
    ns=agent cap=CAIRN_NS_4 head=none commits=0
  [vblk-client] cairn-status status=0
  [kernel] task 1 exited (code 0)
dezh> halt
halting.

Dezh OS F1 Agent-Containment Demo Transcript

Generated by tools/demo/run_agent_demo.py.

Flow: attenuated delegation over IPC; SDK-built agent app installed with manifest-scoped grants (own Cairn namespace only); in-grant durable commits; a bad write undone by a one-step rollback (history kept, hash-verified); a no-capability spy app DENIED by the kernel; state checked again after a reboot.

OpenSBI v1.7
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name               : riscv-virtio,qemu
Platform Features           : medeleg
Platform HART Count         : 1
Platform IPI Device         : aclint-mswi
Platform Timer Device       : aclint-mtimer @ 10000000Hz
Platform Console Device     : uart8250
Platform HSM Device         : ---
Platform PMU Device         : ---
Platform Reboot Device      : syscon-reboot
Platform Shutdown Device    : syscon-poweroff
Platform Suspend Device     : ---
Platform CPPC Device        : ---
Firmware Base               : 0x80000000
Firmware Size               : 317 KB
Firmware RW Offset          : 0x40000
Firmware RW Size            : 61 KB
Firmware Heap Offset        : 0x46000
Firmware Heap Size          : 37 KB (total), 2 KB (reserved), 11 KB (used), 23 KB (free)
Firmware Scratch Size       : 4096 B (total), 1400 B (used), 2696 B (free)
Runtime SBI Version         : 3.0
Standard SBI Extensions     : time,rfnc,ipi,base,hsm,srst,pmu,dbcn,fwft,legacy,dbtr,sse
Experimental SBI Extensions : none

Domain0 Name                : root
Domain0 Boot HART           : 0
Domain0 HARTs               : 0*
Domain0 Region00            : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01            : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02            : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region03            : 0x0000000080040000-0x000000008004ffff M: (R,W) S/U: ()
Domain0 Region04            : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region05            : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06            : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07            : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address        : 0x0000000080200000
Domain0 Next Arg1           : 0x0000000087e00000
Domain0 Next Mode           : S-mode
Domain0 SysReset            : yes
Domain0 SysSuspend          : yes

Boot HART ID                : 0
Boot HART Domain            : root
Boot HART Priv Version      : v1.12
Boot HART Base ISA          : rv64imafdch
Boot HART ISA Extensions    : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count         : 16
Boot HART PMP Granularity   : 2 bits
Boot HART PMP Address Bits  : 54
Boot HART MHPM Info         : 16 (0x0007fff8)
Boot HART Debug Triggers    : 2 triggers
Boot HART MIDELEG           : 0x0000000000001666
Boot HART MEDELEG           : 0x0000000000f4b509

   ____            _
  |  _ \  ___  ___| |__
  | | | |/ _ \/_  / '_ \
  | |_| |  __/ / /| | | |
  |____/ \___//___|_| |_|
  Dezh OS - capability-secure - no ambient authority
  v0 - riscv64 - 126 MiB usable - 4 services

[dezh-boot] alive on bare metal (qemu virt, riscv64, S-mode)
[dezh-boot] boot contract VALIDATED
[dezh-boot] banner: dezh-kernel-boot-v0:qemu-virtio-riscv64:services=4:usable_bytes=132120576
[dezh-boot] no ambient authority: capability seeds bound to declared services only
[dezh-boot] installing trap vector + supervisor timer...
[dezh-boot] enabling Sv39 paging (U-mode confined to its own region)...
[dezh-boot] frame allocator: 28672 x 4 KiB frames (112 MiB free)
[dezh-boot] embedded user ELFs: userprog=8952 bytes, virtio-blk=82280 bytes, dezh-bench=15592 bytes, dezh-note=7080 bytes, dezh-lab=17208 bytes, dezh-calc=10440 bytes, dezh-vault=7824 bytes
[dezh-boot] install manifest v0: root=cairn block=virtio-block marker_sector=0
[dezh-boot] service registry built from boot plan (4 services)

Dezh console. Every command requires an explicit capability.
Type 'help'. The console holds: INSPECT TIME ECHO HALT SPAWN
dezh> ipc
[kernel] IPC: a no-authority service + an agent that delegates PRINT to it
  [kernel] DENIED print: task 0 holds no PRINT capability
    [agent] delegating my PRINT capability to the service over IPC
  [kernel] task 1 exited (code 0)
    [service] received a delegated PRINT capability via IPC; now I can print:
    [service] <payload delivered with a delegated PRINT cap>
  [kernel] task 0 exited (code 0)
[kernel] IPC demo done; back in the console
dezh> pkg-recv
[services] starting virtio-block from boot registry as task 0
  [virtio-blk-daemon] started as a long-lived U-mode driver service
  [virtio-blk-daemon] device + DMA capabilities accepted
[services] virtio-block Running (task 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
[pkg-recv] ready: send base64 lines; end with '.', abort with '!'
+ok 30
+ok 60
+ok 90
+ok 120
+ok 150
+ok 180
+ok 210
+ok 240
+ok 270
+ok 300
+ok 330
+ok 360
+ok 390
+ok 420
+ok 450
+ok 480
+ok 510
+ok 540
+ok 570
+ok 600
+ok 630
+ok 660
+ok 690
+ok 720
+ok 750
+ok 780
+ok 810
+ok 840
+ok 870
+ok 900
+ok 930
+ok 960
+ok 990
+ok 1020
+ok 1050
+ok 1080
+ok 1110
+ok 1140
+ok 1170
+ok 1200
+ok 1230
+ok 1260
+ok 1290
+ok 1320
+ok 1350
+ok 1380
+ok 1410
+ok 1440
+ok 1470
+ok 1500
+ok 1530
+ok 1560
+ok 1590
+ok 1620
+ok 1650
+ok 1680
+ok 1710
+ok 1740
+ok 1753
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [virtio-blk-daemon] pkg-store-init status=0
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
[pkg] installed 'agent' 0.1.0 kind=dezh-ir payload=1328 bytes persistent_slot=0 state=Active
[pkg] grants recorded at install time: print cairn-read cairn-write (kernel-enforced at run time; persisted on disk)
dezh> pkg-recv
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
[pkg-recv] ready: send base64 lines; end with '.', abort with '!'
+ok 30
+ok 60
+ok 90
+ok 108
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [virtio-blk-daemon] pkg-store-init status=0
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
[pkg] installed 'spy' 0.1.0 kind=dezh-ir payload=12 bytes persistent_slot=1 state=Active
[pkg] grants recorded at install time: (none) (kernel-enforced at run time; persisted on disk)
dezh> pkg-info agent
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
  [kernel] task 1 exited (code 0)
package: agent 0.1.0
  state    Active runnable=yes
  kind     dezh-ir
  raw      1753 bytes crc=0x5bc04a5
  store    slot=0 blob_sector=64 sectors=64
  GRANTED  print cairn-read cairn-write
  DENIED   ipc uptime + device/DMA/MMIO (never grantable from a manifest)
  model    grants fixed by verified manifest; no inheritance from console/installer
dezh> pkg-run agent
[pkg-run] 'agent' 0.1.0 kind=dezh-ir caps=print cairn-read cairn-write
  [ir] agent online, caps checked by kernel
  [cairn] v1 store formatted: superblock sector 1600, 255 commit slots
  [cairn] commit ns=agent slot=0 parent=none len=15 hash=0xeaa3bce638240191 actor=task1
  [vblk-client] cairn-commit status=0
  [kernel] task 1 exited (code 0)
  [cairn] commit ns=agent slot=1 parent=0 len=14 hash=0xdf6c9b3bfe5385cf actor=task1
  [vblk-client] cairn-commit status=0
  [kernel] task 1 exited (code 0)
  [cairn] get ns=agent head=1 len=14
  [vblk-client] cairn-get status=0
  [vblk-client] cairn value = "agent-note-BAD
  [kernel] task 1 exited (code 0)
  [ir] agent-note-BAD
[pkg-run] 'agent' finished
dezh> cairn-log agent
  [cairn] log ns=agent (newest first):
    slot=1 parent=0 len=14 hash=0xdf6c9b3bfe5385cf actor=task1 reversible=yes
    slot=0 parent=none len=15 hash=0xeaa3bce638240191 actor=task1 reversible=yes
  [vblk-client] cairn-log status=0
  [kernel] task 1 exited (code 0)
dezh> cairn-rollback agent 1
  [cairn] rollback ns=agent steps=1 head 1 -> 0
  [cairn] history preserved: rollback moves the ref, commits stay on disk
  [vblk-client] cairn-rollback status=0
  [kernel] task 1 exited (code 0)
dezh> cairn-get agent
  [cairn] get ns=agent head=0 len=15
  [vblk-client] cairn-get status=0
  [vblk-client] cairn value = "agent-note-good
  [kernel] task 1 exited (code 0)
dezh> cairn-verify agent
  [cairn] verify ns=agent slot=0 hash MATCH 0xeaa3bce638240191
  [vblk-client] cairn-verify status=0
  [kernel] task 1 exited (code 0)
dezh> pkg-run spy
[pkg-run] 'spy' 0.1.0 kind=dezh-ir caps=(none)
[pkg-run] DENIED by kernel: missing required capability for this host call (grant it in app.toml caps=[...])
dezh> events
events:
  TICK   ACTOR      ACTION          TARGET          RESULT
  0      installer  pkg.tx.start    package         OK
  0      installer  pkg.blob.verify package         OK
  0      installer  pkg.registry.pending package         OK
  0      installer  pkg.tx.commit   package         OK
  0      installer  pkg.tx.start    package         OK
  0      installer  pkg.blob.verify package         OK
  0      installer  pkg.registry.pending package         OK
  0      installer  pkg.tx.commit   package         OK
  0      installer  pkg.run         package         start
  0      installer  pkg.run         package         OK
  0      console    cairn.rollback  agent           ok
  0      installer  pkg.run         package         start
  0      kernel     pkg.run         package         DENIED
dezh> halt
halting.


--- reboot ---

OpenSBI v1.7
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name               : riscv-virtio,qemu
Platform Features           : medeleg
Platform HART Count         : 1
Platform IPI Device         : aclint-mswi
Platform Timer Device       : aclint-mtimer @ 10000000Hz
Platform Console Device     : uart8250
Platform HSM Device         : ---
Platform PMU Device         : ---
Platform Reboot Device      : syscon-reboot
Platform Shutdown Device    : syscon-poweroff
Platform Suspend Device     : ---
Platform CPPC Device        : ---
Firmware Base               : 0x80000000
Firmware Size               : 317 KB
Firmware RW Offset          : 0x40000
Firmware RW Size            : 61 KB
Firmware Heap Offset        : 0x46000
Firmware Heap Size          : 37 KB (total), 2 KB (reserved), 11 KB (used), 23 KB (free)
Firmware Scratch Size       : 4096 B (total), 1400 B (used), 2696 B (free)
Runtime SBI Version         : 3.0
Standard SBI Extensions     : time,rfnc,ipi,base,hsm,srst,pmu,dbcn,fwft,legacy,dbtr,sse
Experimental SBI Extensions : none

Domain0 Name                : root
Domain0 Boot HART           : 0
Domain0 HARTs               : 0*
Domain0 Region00            : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01            : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02            : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region03            : 0x0000000080040000-0x000000008004ffff M: (R,W) S/U: ()
Domain0 Region04            : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region05            : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06            : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07            : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address        : 0x0000000080200000
Domain0 Next Arg1           : 0x0000000087e00000
Domain0 Next Mode           : S-mode
Domain0 SysReset            : yes
Domain0 SysSuspend          : yes

Boot HART ID                : 0
Boot HART Domain            : root
Boot HART Priv Version      : v1.12
Boot HART Base ISA          : rv64imafdch
Boot HART ISA Extensions    : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count         : 16
Boot HART PMP Granularity   : 2 bits
Boot HART PMP Address Bits  : 54
Boot HART MHPM Info         : 16 (0x0007fff8)
Boot HART Debug Triggers    : 2 triggers
Boot HART MIDELEG           : 0x0000000000001666
Boot HART MEDELEG           : 0x0000000000f4b509

   ____            _
  |  _ \  ___  ___| |__
  | | | |/ _ \/_  / '_ \
  | |_| |  __/ / /| | | |
  |____/ \___//___|_| |_|
  Dezh OS - capability-secure - no ambient authority
  v0 - riscv64 - 126 MiB usable - 4 services

[dezh-boot] alive on bare metal (qemu virt, riscv64, S-mode)
[dezh-boot] boot contract VALIDATED
[dezh-boot] banner: dezh-kernel-boot-v0:qemu-virtio-riscv64:services=4:usable_bytes=132120576
[dezh-boot] no ambient authority: capability seeds bound to declared services only
[dezh-boot] installing trap vector + supervisor timer...
[dezh-boot] enabling Sv39 paging (U-mode confined to its own region)...
[dezh-boot] frame allocator: 28672 x 4 KiB frames (112 MiB free)
[dezh-boot] embedded user ELFs: userprog=8952 bytes, virtio-blk=82280 bytes, dezh-bench=15592 bytes, dezh-note=7080 bytes, dezh-lab=17208 bytes, dezh-calc=10440 bytes, dezh-vault=7824 bytes
[dezh-boot] install manifest v0: root=cairn block=virtio-block marker_sector=0
[dezh-boot] service registry built from boot plan (4 services)

Dezh console. Every command requires an explicit capability.
Type 'help'. The console holds: INSPECT TIME ECHO HALT SPAWN
dezh> cairn-get agent
[services] starting virtio-block from boot registry as task 0
  [virtio-blk-daemon] started as a long-lived U-mode driver service
  [virtio-blk-daemon] device + DMA capabilities accepted
[services] virtio-block Running (task 0)
  [cairn] get ns=agent head=0 len=15
  [vblk-client] cairn-get status=0
  [vblk-client] cairn value = "agent-note-good
  [kernel] task 1 exited (code 0)
dezh> cairn-verify agent
  [cairn] verify ns=agent slot=0 hash MATCH 0xeaa3bce638240191
  [vblk-client] cairn-verify status=0
  [kernel] task 1 exited (code 0)
dezh> halt
halting.

Flagship demo: leave a coding agent loose overnight

This is the one story the whole W8 intent/effect runtime exists to tell. You give an AI agent a single intent and turn it loose overnight. It does real work, touches the outside world, and even tries to escape its intent. In the morning you account for and undo its night — honestly, with no over-promising.

One console command, overnight, runs the whole thing, and it is asserted end to end in CI (tools/ci/qemu_smoke.py). It collapses every W8 part into a single narrative:

  • Intent (Ahd, P1) — the agent runs under one declared authority ceiling; its derived capability is provably a subset of that intent.
  • Effect ledger (Sand, P2) — every effect is a Cairn commit enriched with actor → intent → derived cap → reversibility class. No second write, no bypass.
  • Mission + honest rollback (Sfar, P3) — the effects under one intent are a mission. Reversible effects are retracted by a ref move; a compensatable effect is undone by running and recording its registered compensating action; an irreversible effect is refused with a reason, never silently undone. Mission authority spans every namespace the mission touched.
  • Adversary (redteam, P4) — the agent also tries to act beyond its intent; the kernel denies it at a named boundary.
  • Explainable denial + provenance (why-denied / Tbar, P5) — the refusal is attributable to a specific mechanism, and the provenance graph attributes every effect to its actor and intent, unforgeably.

What the agent's night looks like

The agent, under intent Ahd#8, produces a mission of mixed effects:

EffectNamespaceReversibilityHow a rollback treats it
prod.deploy:web@v9 (modeled external send)labirreversiblerefused with a reason — it already happened
wrote build cachelabreversibleretracted by moving the ref
updated changeloglabreversibleretracted by moving the ref
created api-key:tmp/42 (modeled)calccompensatablecompensatedrevoke api-key:tmp/42 is run and recorded

It also tries to write to Cairn beyond the compute ceiling it derived — an out-of-intent escape — and is denied by the kernel.

Transcript (captured from the RISC-V kernel under QEMU)

dezh> overnight
[overnight] you leave a coding agent loose overnight under ONE intent; in the morning you account for and undo its night
[overnight] 1/6 opened the agent's intent Ahd#8 (a writer ceiling) and turned it loose
[overnight] 2/6 the agent's night: an irreversible deploy + two reversible writes (ns=lab), one compensatable external action (ns=calc)
  [cairn] commit ns=lab slot=19 parent=none len=55 hash=0x7378c6ecf8dd4b1f actor=task1 intent=Ahd#8
  [cairn] commit ns=lab slot=20 parent=19 len=17 hash=0x13a7541a8ace112c actor=task1 intent=Ahd#8
  [cairn] commit ns=lab slot=21 parent=20 len=17 hash=0x910d3425d3954e9a actor=task1 intent=Ahd#8
  [cairn] commit ns=calc slot=22 parent=18 len=68 hash=0xb86876c08745737e actor=task1 intent=Ahd#8
[overnight] 3/6 morning: FORECAST the rollback before touching anything, and read the provenance
  [sfar] rollback forecast for mission Ahd#8 (live effects, newest first):
    slot=21 gen=3 actor=task1 intent=Ahd#8 derived=print,cairn-read,cairn-write reversibility=reversible status=committed hash=0x910d3425d3954e9a ns=lab
    slot=20 gen=2 actor=task1 intent=Ahd#8 derived=print,cairn-read,cairn-write reversibility=reversible status=committed hash=0x13a7541a8ace112c ns=lab
    slot=19 gen=1 actor=task1 intent=Ahd#8 derived=print,cairn-read,cairn-write reversibility=irreversible status=committed hash=0x7378c6ecf8dd4b1f ns=lab
    slot=22 gen=3 actor=task1 intent=Ahd#8 derived=print,cairn-read,cairn-write reversibility=compensatable status=committed hash=0xb86876c08745737e ns=calc
  [sfar] plan: reversible=2 compensatable=1 irreversible=1 unknown=0 confidence=partial (some effects cannot be undone)
  [tbar] provenance graph for intent Ahd#8 (actor -> intent -> effect, unforgeable):
    actor task1 -> intent Ahd#8 (derived print,cairn-read,cairn-write) -> effect ns=lab slot=21 class=reversible status=committed hash=0x910d3425d3954e9a
    actor task1 -> intent Ahd#8 (derived print,cairn-read,cairn-write) -> effect ns=lab slot=20 class=reversible status=committed hash=0x13a7541a8ace112c
    actor task1 -> intent Ahd#8 (derived print,cairn-read,cairn-write) -> effect ns=lab slot=19 class=irreversible status=committed hash=0x7378c6ecf8dd4b1f
    actor task1 -> intent Ahd#8 (derived print,cairn-read,cairn-write) -> effect ns=calc slot=22 class=compensatable status=committed hash=0xb86876c08745737e
  [tbar] 4 effect(s) attributed to intent Ahd#8
[overnight] 4/6 undo the night honestly: retract the reversible writes, run the compensation, REFUSE the irreversible deploy with a reason
    [sfar] REFUSED at ns=lab slot=19: irreversible effect already happened in the outside world; cannot be undone
    [sfar] COMPENSATED at ns=calc slot=22: ran compensating action "revoke api-key:tmp/42" recorded as effect slot=23
  [sfar] mission Ahd#8 rolled back: reversible effects retracted=2 compensations performed=1 refused_irreversible=1 refused_compensatable=0
  [sfar] history preserved: reversible effects retracted by ref, compensatable effects undone by a recorded compensating action, irreversible effects explained not erased
[overnight] 5/6 the agent also TRIED to escape its intent (a write beyond the ceiling); the kernel denied it
[redteam] agent under Ahd#9 kind=compute requests=print cairn-read cairn-write
[redteam] beyond-intent dropped by the derivation ceiling: cairn-read cairn-write (derived cap proven <= Ahd)
[redteam] kernel DENIED the out-of-intent Cairn write: missing required capability for this host call
[overnight] 6/6 why was the escape denied? name the boundary:
[why-denied] last denial: actor=overnight action=intent.derive target=cairn-write result=DENIED (tick 168)
[why-denied] boundary: intent-derivation ceiling (derived cap <= Ahd), enforced in the kernel
[why-denied] policy: authority is explicit and unforgeable; nothing runs on ambient permission
[overnight] PASS: the whole night is accounted for - reversibles undone, the compensatable action compensated, the irreversible deploy refused with a reason, and the escape contained

Why a user-space sandbox cannot tell this story

A sandbox (gVisor, Firecracker, wasmtime/WASI, seccomp+landlock) can confine the agent, and it can kill the process. What it cannot cleanly do is attribute and reverse the whole set of effects the agent produced under one intent — the effect log sits beside the resource on an ambient-authority host, and there is generally a path to the resource that skips it. On Dezh there is no ambient authority under the ledger: the effect path goes through the record that authorizes it, which is why the from-scratch kernel exists. See SECURITY_MODEL.md#threat-model.

Reproduce

Boot the RISC-V kernel (see GETTING_STARTED.md#build-and-run) and type overnight. The individual acts are also available on their own: intent-open / intent-run, sand-log / sand-info, sfar-plan / sfar-rollback, comp-demo, sfar-cross-demo, tbar, redteam, why-denied. All are asserted in tools/ci/qemu_smoke.py.