feat(infra): NixOS VM config, CI type-check job, documentation

- flake.nix, infra/nixos/: NixOS VM with Mesa, virtio-gpu, Wayland,
  systemd user services for compositor and session supervisor
- infra/vm/: QEMU build and run scripts
- .github/workflows/ci.yml: add Linux job to type-check weft-servo-shell
  and weft-app-shell with --features servo-embed
- docs/architecture.md, docs/security.md, docs/building.md: replace
  stale pre-implementation design documents
- README.md: rewrite to reflect current codebase
- crates/weft-servo-shell/SERVO_PIN.md: update implementation status and add
  SpiderMonkey process boundary statement
This commit is contained in:
Marco Allegretti 2026-03-12 15:41:33 +01:00
parent 794f6c2225
commit e3504c324b
18 changed files with 666 additions and 1189 deletions

View file

@ -76,3 +76,35 @@ jobs:
-p weft-compositor
-p weft-servo-shell
-p weft-app-shell
# Type-check the servo-embed feature gate.
# Full Servo compilation (30-60 min) is not run in CI; this job checks
# that the feature-gated code at least type-checks against the declared deps.
servo-embed-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.93.0
- name: install Linux system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends \
libwayland-dev \
libxkbcommon-dev \
libegl-dev \
libgles2-mesa-dev \
libgbm-dev \
libdrm-dev \
pkg-config
- name: cargo check servo-embed (weft-servo-shell)
run: >
cargo check
-p weft-servo-shell
--features servo-embed
- name: cargo check servo-embed (weft-app-shell)
run: >
cargo check
-p weft-app-shell
--features servo-embed

281
README.md
View file

@ -1,223 +1,100 @@
# WEFT OS
WEFT OS is an operating system project that treats **authority boundaries** as the primary design problem.
WEFT OS is a Wayland compositor and application runtime where every app is a WebAssembly component rendered in an isolated Servo WebView. No capability is granted by default; all resource access is declared in a per-app manifest and enforced at runtime.
At a high level, the system is organized around three roles:
## What is implemented
- a **Wayland compositor** (Rust + Smithay) that owns surfaces, stacking, and input routing
- a **system shell renderer** that aims to render the desktop UI from HTML/CSS via Servo (as a Wayland client)
- an **application supervisor** that owns app lifecycle and brokers app sessions
**Compositor** — `weft-compositor` is a Smithay-based Wayland compositor with DRM/KMS and winit backends. It implements the `zweft-shell-unstable-v1` protocol extension, which typed shell slots (panel, application) register against.
The thesis is not “the desktop is a web page.”
**System shell** — `weft-servo-shell` embeds Servo (feature-gated, `--features servo-embed`) and renders `system-ui.html` as a Wayland panel. Without `servo-embed`, the binary builds as a no-op stub. Navigation gestures from the compositor are forwarded to `weft-appd` over WebSocket.
The thesis is:
**App shell** — `weft-app-shell` is a per-process Servo host for application WebViews. It resolves `weft-app://<id>/ui/index.html`, injects a `weftIpc` WebSocket bridge into the page, and registers with the compositor as an application surface. Also feature-gated behind `servo-embed`.
- a system UI can be rendered using web technologies
- without collapsing the OS into a single privileged UI process
- if the compositor and runtime boundaries remain explicit and enforced
**App daemon** — `weft-appd` supervises sessions: spawns `weft-runtime`, waits for READY, spawns `weft-app-shell`, manages the per-session IPC relay between the Wasm component and the WebView, and handles session teardown. Wraps processes in systemd scopes (`CPUQuota=200%`, `MemoryMax=512M`) when available.
This repository is intentionally rigorous about distinguishing:
**Runtime** — `weft-runtime` runs WASI Component Model binaries under Wasmtime 30 (`--features wasmtime-runtime`). Provides `weft:app/notify`, `weft:app/ipc`, `weft:app/fetch`, `weft:app/notifications`, and `weft:app/clipboard` host imports. Preopens filesystem paths according to declared capabilities.
- what is **implemented today**
- what is the **design direction**
**Package management** — `weft-pack` handles check, sign, verify, install, uninstall, list, build-image (EROFS dm-verity), and info. Validates capability strings at check time.
That distinction is critical for meaningful community contribution.
**File portal** — `weft-file-portal` is a per-session file proxy with a path allowlist and `..` blocking.
## Current status (verifiable from source)
**Mount helper** — `weft-mount-helper` is a setuid helper for EROFS dm-verity mount/umount via `veritysetup`.
This repo is a Rust workspace (edition **2024**) with `unsafe_code` **forbidden** at the workspace lint level.
**Demo apps** — `examples/org.weft.demo.counter` and `examples/org.weft.demo.notes` are pre-built Wasm Component binaries (`wasm32-wasip2`, wit-bindgen 0.53) with HTML UIs, signed with a committed demo keypair.
It contains working scaffolding and partial implementations for:
## Repository layout
- `weft-compositor`
- starts with a **winit** backend when `--winit` is passed or when a display environment is detected
- includes a Linux-only path for a **DRM/KMS** backend
- integrates a custom Wayland protocol XML under `protocol/` and generates server bindings
- `weft-servo-shell`
- connects to a Wayland compositor and locates `system-ui.html`
- **Servo embedding is not yet implemented** (the entry point currently errors intentionally)
- `weft-appd`
- listens on a Unix socket for IPC
- exposes a local WebSocket endpoint (127.0.0.1) for clients
- maintains a simple app session registry and supervises a runtime process
- `weft-runtime`
- resolves an app “package” directory and expects an `app.wasm` file
- prints `READY` and exits cleanly as a placeholder
- **Wasmtime integration is not yet implemented**
Public engineering documentation lives under `docs/architecture/`.
## What WEFT OS is trying to build (in-depth architecture)
WEFT OS is best understood as a set of promises. Those promises are implemented as explicit boundaries.
### 1) The compositor is the authority
In WEFT, the compositor is not “the thing that draws windows.”
It is the component that answers (consistently) the questions that become security and reliability properties:
- which surfaces exist
- which surfaces are visible, and where
- which surface receives input
- what happens when a client misbehaves or dies
Waylands model makes this boundary practical: clients do not get global visibility by default.
Smithay is used to build this compositor because it provides structured protocol handling and a place for centralized state, which makes it easier to express policy explicitly.
### 2) The shell is a document, but not a sovereign
WEFTs system shell is intended to be an HTML/CSS UI rendered via Servo, running as a Wayland client.
This choice is not about using “web UI” for novelty.
It is about:
- treating layout and animation as a disciplined constraint system
- leveraging a widely understood UI language
- while keeping system authority out of the UI runtime
The shell must be able to crash without automatically taking the whole session with it.
That requires the compositor to remain authoritative.
### 3) Apps are supervised sessions, not ambient processes
WEFT treats application launch as the moment where the system can be honest about authority.
The intended model is:
- the OS creates an app session
- the OS launches the apps runtime participant
- the OS launches the apps UI participant
- the OS brokers their relationship
In this repo today, `weft-appd` provides the session registry and launch/supervision skeleton.
### 4) WebAssembly is an OS runtime model (not a browser feature)
WebAssembly is used in WEFT as a way to make “no ambient authority” plausible.
The core idea is capability-shaped access:
- a path string is not permission
- holding a handle is permission
This repo does not yet integrate Wasmtime, but it includes a runtime placeholder (`weft-runtime`) designed to evolve into a host-controlled Wasmtime embedder.
### 5) The core/UI channel is brokered (and must stay that way)
WEFTs app model assumes the app has two isolated parts:
- a Wasm core
- an HTML UI
Those two still need to exchange events and state.
The direction documented in `docs/architecture/wasm-servo-channel.md` is:
- a brokered message channel owned by `weft-appd`
- structured, validated messages
- explicit backpressure and bounded payloads
- session teardown on process death
The channel is explicitly not treated as a capability transport.
### 6) Storage and packages are integrity and authority problems
WEFTs storage stance is shaped by two goals:
- the OS should be able to answer “what is running?”
- apps should not get filesystem traversal by default
The design direction emphasizes immutable payloads, separation of code and writable data, and mediated user-intent file access (portal-style) rather than ambient path access.
Not all of this is implemented in this repository today.
It is included here because it defines what “substantial contribution” means: work that preserves explicit boundaries.
## Collaboration: lets find the hard answers together
If you want to contribute, dont start by asking “what feature should we add.”
Start by asking:
- what promise are we trying to make?
- what would count as evidence that we can keep it?
### High-value open problems
These are the areas where contribution has the highest leverage:
- **Servo embedding for a shell-grade Wayland client**
- input correctness, event loop integration, stable embedding surface
- **Shell/compositor boundary**
- the smallest protocol surface that keeps authority correct
- object lifecycle and stale identifier rejection
- **Wasm ↔ UI channel implementation**
- brokered transport, validation, backpressure, observability
- **Wasmtime integration in `weft-runtime`**
- host-controlled execution, resource limits, and capability-shaped IO
- **Failure and recovery behavior**
- compositor restart behavior
- shell restart behavior
- app crash containment
If you disagree with the premises, thats also useful.
The most valuable criticism is the one that points at a boundary and says:
- “this is where the promise will break”
## Runtime knobs (verifiable from source)
This repo is early, but some runtime configuration already exists in code. These values are listed here so contributors can run components consistently and so future work doesnt accidentally break the contract.
### `weft-servo-shell`
- `WEFT_SYSTEM_UI_HTML`
- If set, `weft-servo-shell` will use this path as the `system-ui.html` entry point.
- `WAYLAND_DISPLAY`
- Must be set; `weft-servo-shell` uses it to connect to the running compositor.
### `weft-appd`
- `WEFT_APPD_SOCKET`
- If set, overrides the Unix socket path used for IPC.
- `WEFT_APPD_WS_PORT`
- If set, overrides the local WebSocket port (defaults to `7410`).
- `XDG_RUNTIME_DIR`
- Required; used to place runtime files such as the default Unix socket location.
### `weft-runtime`
- `WEFT_APP_STORE`
- If set, overrides the app package store root.
## Development and validation
### Validation
On Windows PowerShell:
```powershell
./infra/scripts/check.ps1
```
crates/ Rust workspace members
examples/ Demo app packages (wasm32-wasip2, not workspace members)
keys/ Demo Ed25519 keypair
protocol/ zweft-shell-unstable-v1 Wayland protocol XML
infra/
nixos/ NixOS VM configuration and package derivations
scripts/ check.ps1, check.sh
shell/ system-ui.html, weft-ui-kit.js
systemd/ service unit files
vm/ build.sh, run.sh (QEMU)
docs/
architecture.md Component map, IPC, capability table, env vars
security.md Capability model, process isolation, GAP-6 statement
building.md Build instructions for all targets
```
On Linux:
## Building
```bash
./infra/scripts/check.sh
Linux system packages required (Ubuntu/Debian):
```sh
sudo apt-get install -y \
libwayland-dev libxkbcommon-dev libegl-dev libgles2-mesa-dev \
libgbm-dev libdrm-dev libinput-dev libseat-dev libudev-dev \
libsystemd-dev pkg-config clang cmake python3
```
These scripts run formatting, clippy, and workspace tests (with `weft-compositor` excluded on non-Linux hosts).
Build non-Servo crates:
### Repository layout
```text
crates/ Rust workspace members
docs/ Public engineering documentation
infra/ Validation scripts and VM workflow material
protocol/ Wayland protocol XML definitions
```sh
cargo build --workspace --exclude weft-servo-shell --exclude weft-app-shell
```
Build Linux-only crates (no Servo):
```sh
cargo build -p weft-compositor -p weft-servo-shell -p weft-app-shell
```
Build with Servo embedding (3060 min, requires clang + python3):
```sh
cargo build -p weft-servo-shell --features servo-embed
cargo build -p weft-app-shell --features servo-embed
```
See `docs/building.md` for full instructions including Wasm component builds, NixOS VM, and signing.
## CI
Three jobs on every push and pull request:
- `cross-platform` — fmt, clippy, tests on Ubuntu and Windows
- `linux-only` — clippy and tests for compositor and shell crates
- `servo-embed-linux``cargo check --features servo-embed` for both servo crates
## Security
See `docs/security.md`. Key points:
- Capabilities declared in `wapp.toml`, validated at install, enforced at runtime
- Per-app OS processes with systemd cgroup limits
- WASI filesystem isolation via preopened directories
- Ed25519 package signing; optional EROFS dm-verity
- Optional seccomp BPF blocklist in `weft-runtime`
- SpiderMonkey is not sandbox-isolated beyond process-level isolation (GAP-6; see `docs/security.md`)
## Servo fork
- Repository: `https://github.com/marcoallegretti/servo`, branch `servo-weft`
- Base revision: `04ca254f`
- Patches: keyboard input (GAP-1), backdrop-filter stylo (GAP-4)
- See `crates/weft-servo-shell/SERVO_PIN.md` for full gap status

View file

@ -98,7 +98,13 @@ DMA-BUF buffer sharing with the compositor transparently.
WebRender stacking-context early-return on `backdrop_filter.0.is_empty()`; `display_list/mod.rs`
adds `BuilderForBoxFragment::build_backdrop_filter` calling `push_backdrop_filter` before
background paint.
- **GAP-5**: Per-app process isolation — requires Servo multi-process (constellation) architecture.
- **GAP-5**: ~~Per-app process isolation~~ **Resolved** — each app runs in a separate
`weft-app-shell` and `weft-runtime` OS process pair supervised by `weft-appd`. OS-level
isolation does not require Servo's multi-process constellation architecture.
- **GAP-6**: SpiderMonkey is not sandbox-isolated beyond process-level isolation. JIT-compiled JS
runs with the same memory permissions as the Servo process. WEFT relies on SpiderMonkey's own
security properties for the JavaScript execution boundary. See `docs/security.md` for the full
bounded statement. Not addressed.
## Update policy
@ -136,5 +142,5 @@ selectors = { git = "https://github.com/marcoallegretti/stylo", branch = "servo-
servo_arc = { git = "https://github.com/marcoallegretti/stylo", branch = "servo-weft" }
```
4. Run `cargo update` to resolve the new stylo deps.
5. Commit both the `Cargo.toml` and `Cargo.lock` changes to `servo-weft`.
1. Run `cargo update` to resolve the new stylo deps.
2. Commit both the `Cargo.toml` and `Cargo.lock` changes to `servo-weft`.

105
docs/architecture.md Normal file
View file

@ -0,0 +1,105 @@
# WEFT OS Architecture
## Overview
WEFT OS is a Wayland compositor and application runtime where every app is a WebAssembly component served via a Servo-rendered WebView. The system has no ambient authority: capabilities are declared in `wapp.toml`, verified at install time, and enforced at runtime.
## Components
### weft-compositor
Smithay-based Wayland compositor. Implements the `zweft-shell-unstable-v1` protocol extension, which allows shell clients (servo-shell, app-shell) to register their surfaces as typed shell slots. Supports DRM/KMS and winit (software/dev) backends.
### weft-servo-shell
System UI host. Renders one WebView pointing at `system-ui.html` using the embedded Servo engine (feature-gated behind `servo-embed`). Connects to the compositor as window type `panel`. Dispatches the `zweft_shell_manager_v1` event queue each frame. Forwards navigation gestures received from the compositor to `weft-appd` over WebSocket.
### weft-app-shell
Per-application Servo host. Spawned by `weft-appd` after the Wasm runtime signals READY. Takes `<app_id>` and `<session_id>` as arguments. Resolves `weft-app://<app_id>/ui/index.html` and injects the `weftIpc` WebSocket bridge into the page. Registers with the compositor as window type `application`. Exits when the appd session ends.
### weft-appd
Session supervisor. Listens on a Unix socket (MessagePack protocol) and a WebSocket port (JSON). For each session: spawns `weft-runtime`, waits for READY, spawns `weft-app-shell`, manages the per-session IPC relay, and supervises child processes. Handles graceful termination and cgroup resource limits (via systemd-run when available).
### weft-runtime
WASI Preview 2 + Component Model execution host (Wasmtime 30). Loads `app.wasm` from the installed package directory. Provides host imports for `weft:app/notify`, `weft:app/ipc`, `weft:app/fetch`, `weft:app/notifications`, and `weft:app/clipboard`. Preopens filesystem paths according to capabilities declared in `wapp.toml`.
### weft-pack
Package management CLI. Subcommands: `check` (validate wapp.toml + wasm module), `sign` (Ed25519 signature), `verify` (verify signature), `generate-key`, `install`, `uninstall`, `list`, `build-image` (EROFS dm-verity), `info`.
### weft-file-portal
Per-session file proxy. Runs as a separate process with a path allowlist derived from preopened directories. Accepts JSON-lines requests over a Unix socket. Blocks path traversal. Used by apps that require file access without direct WASI preopens.
### weft-mount-helper
Setuid helper binary. Calls `veritysetup` and mounts EROFS images for dm-verity-protected packages.
## Process Topology
```
systemd
├── weft-compositor (user)
├── weft-servo-shell (user, after compositor)
└── weft-appd (user, after compositor + servo-shell)
└── per-session:
├── weft-runtime <app_id> <session_id> [--preopen ...] [--ipc-socket ...]
├── weft-app-shell <app_id> <session_id>
└── weft-file-portal <socket> [--allow ...]
```
## IPC
| Channel | Protocol | Purpose |
|---|---|---|
| weft-appd Unix socket | MessagePack | appd ↔ other system daemons |
| weft-appd WebSocket (:7410) | JSON | system-ui.html ↔ appd (gesture events, app lifecycle) |
| per-session IPC socket | newline-delimited JSON | weft-runtime ↔ weft-app-shell (weft:app/ipc) |
| weft-file-portal socket | JSON-lines | weft-runtime ↔ file proxy |
## Capability Enforcement
Capabilities are declared in `wapp.toml` under `[package] capabilities`. `weft-pack check` validates that only known capabilities are listed. At runtime, `weft-appd` reads capabilities and maps them to WASI preopened directories and host function availability:
| Capability | Effect |
|---|---|
| `fs:rw:app-data` | Preopen `~/.local/share/weft/apps/<id>/data` as `/data` |
| `fs:read:app-data` | Same, read-only |
| `fs:rw:xdg-documents` | Preopen `~/Documents` as `/xdg/documents` |
| `net:fetch` | Enables `weft:app/fetch` host function |
| `sys:notifications` | Enables `weft:app/notifications` host function |
## Package Format
```
<app_id>/
wapp.toml — manifest (id, name, version, capabilities, runtime.module, ui.entry)
app.wasm — WASI Component Model binary
ui/
index.html — entry point served by weft-app-shell
signature.sig — Ed25519 signature over SHA-256 of (wapp.toml + app.wasm)
```
Package store roots (in priority order):
1. `$WEFT_APP_STORE` (if set)
2. `~/.local/share/weft/apps/`
3. `/usr/share/weft/apps/`
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `WEFT_RUNTIME_BIN` | — | Path to `weft-runtime` binary |
| `WEFT_APP_SHELL_BIN` | — | Path to `weft-app-shell` binary |
| `WEFT_FILE_PORTAL_BIN` | — | Path to `weft-file-portal` binary |
| `WEFT_MOUNT_HELPER` | — | Path to `weft-mount-helper` binary |
| `WEFT_APP_STORE` | — | Override package store root |
| `WEFT_APPD_SOCKET` | — | Unix socket path for weft-appd |
| `WEFT_APPD_WS_PORT` | `7410` | WebSocket port for weft-appd |
| `WEFT_EGL_RENDERING` | — | Set to `1` to use EGL rendering in Servo shell |
| `WEFT_DISABLE_CGROUP` | — | Set to disable systemd-run cgroup wrapping |
| `WEFT_FILE_PORTAL_SOCKET` | — | Path forwarded to app runtime for file portal |
| `XDG_RUNTIME_DIR` | — | Standard XDG runtime dir (sockets written here) |

View file

@ -1,177 +0,0 @@
# WEFT Application Package Format
**Status:** Design — not yet implemented.
---
## Purpose
This document specifies the on-disk format for WEFT application packages (`.wapp` files).
A `.wapp` file is the unit of distribution and installation for WEFT OS applications.
`weft-appd` uses this format to resolve an `app_id` to a launchable Wasm module and
associated UI assets.
---
## Package Identity
Each package is identified by a **reverse-domain app ID** — a dot-separated string using
DNS naming conventions:
```
com.example.notes
org.weft.calculator
io.github.username.app
```
The app ID uniquely identifies the application within a WEFT session. It is used:
- As the `app_id` field in IPC `LaunchApp` / `TerminateApp` messages.
- As the directory name under the package store root.
- In session registry lookups.
The app ID must match `^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*){2,}$`.
---
## Package Store
Packages are installed to:
```
$WEFT_APP_STORE / <app_id> /
```
Where `WEFT_APP_STORE` defaults to `/usr/share/weft/apps` (system) or
`$HOME/.local/share/weft/apps` (user). `weft-appd` searches user store first, then system
store.
The resolved package directory must contain a `wapp.toml` manifest.
---
## Directory Structure
```
<app_id>/
├── wapp.toml # Required. Package manifest.
├── app.wasm # Required. The WebAssembly module (Wasm 2.0).
└── ui/ # Required. UI assets served to Servo.
├── index.html # Required. Entry point loaded by servo-shell.
└── ... # Optional CSS, JS, images.
```
No other top-level files are specified. Tools must ignore unknown files.
---
## Manifest: wapp.toml
```toml
[package]
id = "com.example.notes"
name = "Notes"
version = "1.0.0"
description = "A simple note-taking application."
author = "Example Author"
[runtime]
module = "app.wasm"
[ui]
entry = "ui/index.html"
```
### [package] fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Reverse-domain app ID. Must match identity rules above. |
| `name` | string | yes | Human-readable display name. Max 64 characters. |
| `version` | string | yes | SemVer string. |
| `description` | string | no | One-line description. Max 256 characters. |
| `author` | string | no | Author name or email. |
### [runtime] fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `module` | string | yes | Path to the Wasm module, relative to package directory. |
### [ui] fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `entry` | string | yes | Path to the HTML entry point, relative to package directory. |
---
## Wasm Module Contract
The Wasm module is run by `weft-runtime` (a separate binary embedding Wasmtime).
### Startup signal
The module signals readiness by writing the literal string `READY\n` to its standard output.
`weft-appd` monitors the process stdout; on receiving `READY\n` it transitions the session
state from `Starting` to `Running` and broadcasts `APP_READY` to connected clients.
If the module exits before writing `READY\n`, the session transitions to `Stopped` and no
`APP_READY` is sent.
If the module does not write `READY\n` within 30 seconds, `weft-appd` sends `SIGTERM` and
transitions the session to `Stopped`.
### Exit codes
| Exit code | Meaning |
|-----------|---------|
| 0 | Clean shutdown. Session transitions to `Stopped`. |
| Non-zero | Abnormal exit. Session transitions to `Stopped`. `weft-appd` logs the code. |
No automatic restart is performed by `weft-appd`. Restart policy (if any) is the
responsibility of the calling client (servo-shell).
### Stdio
- **stdin**: closed.
- **stdout**: monitored for `READY\n`. All other output is discarded.
- **stderr**: captured and forwarded to `weft-appd`'s tracing log at `WARN` level.
---
## Capability Model
Capabilities are not yet specified. This section is reserved.
Initial implementation: no capabilities are declared or checked. The Wasm module runs with
the WASI permissions granted by `weft-runtime`'s command-line configuration.
---
## Launch Flow
```
servo-shell weft-appd weft-runtime (child)
| | |
|-- LAUNCH_APP(app_id) --> | |
| | resolve package store |
| | create session |
| | spawn weft-runtime --app |
| | -->|
|<-- LAUNCH_ACK(id) --- | |
| | (monitors stdout) |
| |<-- "READY\n" ------------|
| | broadcast APP_READY |
|<== WS push APP_READY === | |
```
---
## What This Format Does Not Cover
- Package signing or verification. Not designed.
- Dependency resolution. Not designed.
- Update channels. Not designed.
- Sandboxing beyond WASI. Not designed.
- Multi-arch fat packages. Not designed. Packages are x86-64 Wasm only.

View file

@ -1,66 +0,0 @@
# WEFT OS Architecture Baseline
This document records the implementation baseline derived from the authoritative blueprint in `docu_dev/WEFT-OS-COMPREHENSIVE-BLUEPRINT.md`.
## Authority
The comprehensive blueprint is the source of truth for technical direction in this repository.
The earlier concept and integrative blueprint documents are secondary references. They are useful only where they do not conflict with the comprehensive blueprint.
## Defined foundation
The current defined foundation is:
- Linux kernel
- systemd for the initial prototype
- Smithay for the Wayland compositor
- Servo as the system shell renderer running as a Wayland client
- Wasmtime for application execution
- Rust for all core system components
## System shape
The intended top-level structure is:
```text
Linux kernel
-> weft-compositor
-> servo-shell
-> weft-appd
```
`weft-compositor` owns Wayland surfaces, surface stacking, input routing, and compositing.
`servo-shell` renders the system shell UI from HTML and CSS as a Wayland client.
`weft-appd` is the process supervisor and capability broker for application launch and lifecycle management.
## Open blockers
The following items are not treated as solved in this repository:
- `weft-shell-protocol` specification
- WasmServo channel design
- Servo Wayland input audit through winit
- per-app Servo isolation model
- SpiderMonkey stability verification relevant to UI-side JavaScript
## Consequences for repository scope
Until the open blockers are closed at design level, this repository should avoid:
- broad multi-crate scaffolding for unresolved runtime boundaries
- developer SDK or packaging tooling built against unstable contracts
- application examples that imply the WasmServo contract is already decided
## Historical mismatches that are not implementation authority
The earlier concept document is not used as implementation truth where it conflicts with the comprehensive blueprint.
Examples of mismatches already identified:
- unsigned package examples versus the authoritative signed bundle requirement
- simplified permission names versus the authoritative capability taxonomy
- a historical top-level `shaders/` directory versus the authoritative package layout
- broader early-language discussion that does not define current core-system policy

View file

@ -1,115 +0,0 @@
# weft-runtime: Wasmtime Integration Plan
**Status:** Not yet implemented. weft-runtime currently stubs execution with `println!("READY")`.
---
## Current State
`crates/weft-runtime/src/main.rs` resolves the package directory, validates `app.wasm`
exists, then prints `READY\n` and exits. No Wasm code is executed.
The stub satisfies the supervisor contract (described in `docs/architecture/app-package-format.md`)
and allows the full session lifecycle to be tested end-to-end.
---
## Required Changes
### 1. Optional feature gate
Add a `wasmtime` cargo feature to `weft-runtime/Cargo.toml` so the default build remains
fast:
```toml
[features]
default = []
wasmtime-runtime = ["dep:wasmtime"]
[dependencies]
wasmtime = { version = "30", optional = true, features = ["wasi"] }
```
`cfg(feature = "wasmtime-runtime")` guards the real implementation.
`cfg(not(feature = "wasmtime-runtime"))` keeps the stub for tests and development.
The production service unit (`infra/systemd/weft-appd.service`) would set
`WEFT_RUNTIME_BIN` to a binary built with `--features wasmtime-runtime`.
### 2. Engine and module setup
```rust
use wasmtime::{Config, Engine, Module, Store};
use wasmtime_wasi::{WasiCtxBuilder, preview1};
let engine = Engine::new(Config::default())?;
let module = Module::from_file(&engine, &wasm_path)?;
```
### 3. WASI context
```rust
let wasi = WasiCtxBuilder::new()
.inherit_stdout()
.inherit_stderr()
.build();
let mut store = Store::new(&engine, wasi);
```
stdout is inherited so the Wasm module can write `READY\n` directly via WASI
`fd_write` (standard output fd=1).
### 4. READY signal timing
The READY signal to weft-appd must be sent after module initialization but before
the main event loop blocks. Two options:
**Option A (simpler):** weft-runtime prints `READY\n` before calling the Wasm entry
point. The Wasm module is responsible for being ready to handle requests when it
starts running.
**Option B (accurate):** The Wasm module exports a `weft_ready()` function that
weft-runtime calls explicitly before starting the main event loop. The ready signal
is sent after `weft_ready()` returns.
Initial implementation should use Option A. Option B requires a convention on the
Wasm module's exported interface.
### 5. Entry point call
```rust
let linker = wasmtime_wasi::preview1::add_to_linker_sync(&engine)?;
let instance = linker.instantiate(&mut store, &module)?;
println!("READY");
let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
start.call(&mut store, ())?;
```
`_start` is the standard WASI entry point generated by Rust's `wasm32-wasi` target.
### 6. Error handling
- `Module::from_file` failure: exit with non-zero code (weft-appd supervisor logs it).
- `_start` trap: log the trap message, exit non-zero.
- No `_start` export: log and exit non-zero. The package checker (`weft-pack`) should
eventually validate this at install time.
---
## What Is Not Designed
- **Host function imports**: Wasm modules that import host functions beyond WASI are not
supported in the initial implementation. Custom host bindings are deferred.
- **Memory limits**: no per-session memory cap is enforced. Deferred to capability model.
- **Wasm component model**: not used. Wasm 2.0 module format only.
- **Multi-threading (WASM threads)**: not enabled.
- **Fuel metering**: not enabled.
---
## Prerequisite
Before implementing: confirm the target Wasm module compilation pipeline. The WEFT
app SDK must produce a valid `wasm32-wasi` binary that exports `_start`. Until an
example app exists that can be tested, the Wasmtime integration cannot be verified
end-to-end.

View file

@ -1,127 +0,0 @@
# SpiderMonkey Bridge Assessment
**Status:** Assessment complete — blocker 3 resolved.
**Decision:** Custom SpiderMonkey bindings are not required. WebSocket satisfies the UI endpoint contract.
---
## Context
The weft-appd IPC channel design (see `docs/architecture/wasm-servo-channel.md`) requires a
UI endpoint: a mechanism by which the system UI HTML page running inside Servo can receive
notifications from `weft-appd` (e.g., `APP_READY`) and send requests (e.g., `LAUNCH_APP`).
The original concern ("blocker 3") was that satisfying this requirement might need custom
SpiderMonkey bindings injected into Servo — functionality that is not stable or exposed as a
public Servo API.
---
## What the UI Endpoint Requires
1. The system-ui.html page must be able to **send** structured messages to `weft-appd`
(LAUNCH_APP, TERMINATE_APP, QUERY_RUNNING, QUERY_APP_STATE).
2. The system-ui.html page must be able to **receive** asynchronous push notifications from
`weft-appd` (LAUNCH_ACK, APP_READY).
3. Messages must carry a session identifier and structured payload.
4. The transport must work over a local connection (same machine, session-local).
None of these requirements are specific to SpiderMonkey internals. They describe a generic
bidirectional message channel.
---
## Servo's Relevant Capabilities (as of 2025)
| Feature | Status | Notes |
|---------|--------|-------|
| WebSocket API (RFC 6455) | Implemented | `servo/components/script/dom/websocket.rs` |
| fetch() | Implemented | Standard Fetch API |
| JSON.parse / JSON.stringify | Implemented | Core JS, SpiderMonkey |
| `addEventListener` / `dispatchEvent` | Implemented | Full DOM events |
| ES modules | Partially implemented | Not required for this use case |
| Service Workers | Not implemented | Not required |
| Custom native APIs via embedder | Not stable / no public API | This was the original concern |
**Key finding:** WebSocket is implemented in Servo and is the standard API for persistent
bidirectional message channels between a web page and a local service.
---
## Proposed Implementation: WebSocket Endpoint in weft-appd
`weft-appd` already has `tokio` with the `net` feature. Adding a WebSocket server alongside
the existing Unix socket server requires only the `tokio-tungstenite` crate (tokio-native
WebSocket).
### Transport architecture
```
system-ui.html (Servo)
└─ WebSocket ws://127.0.0.1:<port>/appd
│ JSON frames (human-readable, same semantics as MessagePack IPC)
weft-appd WebSocket listener
└─ shared SessionRegistry (same Arc<Mutex<...>>)
```
The Unix socket remains the production IPC path for machine-to-machine communication
(servo-shell native path). The WebSocket listener is the UI endpoint for Servo.
### Message format
The same `Request` / `Response` enum used by the MessagePack Unix socket path is also
serialized as JSON for the WebSocket channel (using `serde_json`). The type tag remains
`SCREAMING_SNAKE_CASE`.
Example:
```json
{"type":"LAUNCH_APP","app_id":"com.example.notes","surface_id":0}
```
### Port selection
`weft-appd` binds on `127.0.0.1` only. The port is resolved in priority order:
1. `WEFT_APPD_WS_PORT` environment variable
2. Default: `7410`
The port is written to `$XDG_RUNTIME_DIR/weft/appd.wsport` at startup so that the
system-ui.html can discover it without hardcoding.
### Push notifications
The WebSocket connection stays open. `weft-appd` pushes `APP_READY` frames to all connected
clients when a session transitions to `Running`. This is implemented with a `tokio::sync::broadcast`
channel that the WebSocket handler subscribes to.
---
## What This Assessment Does Not Cover
- **Servo's Wayland EGL surface stability**: covered in `docs/architecture/winit-wayland-audit.md`.
- **Servo DPI / HiDPI rendering**: not assessed; deferred.
- **Content Security Policy interactions with ws://127.0.0.1**: `system-ui.html` is served
from the local filesystem (file://) or a bundled origin. CSP headers are controlled by
WEFT, so `ws://127.0.0.1` can be explicitly allowed.
- **Servo WebSocket reconnect on compositor restart**: not designed; out of scope for initial
implementation.
---
## Blocker 3 Resolution
The original concern was that the UI endpoint would require injecting custom SpiderMonkey
bindings into Servo, which is not a stable operation.
**This concern is resolved.** The UI endpoint is implemented entirely via the standard
WebSocket API, which Servo ships as a fully functional implementation. No custom SpiderMonkey
bindings are needed. The WEFT codebase does not modify Servo's internals for this feature.
**Implementation required:**
1. Add `tokio-tungstenite` + `serde_json` to `weft-appd`.
2. Add `broadcast::Sender<Response>` to `run()` for push notifications.
3. Start a WebSocket listener task alongside the existing Unix socket listener.
4. Update `system-ui.html` to connect via WebSocket and handle `APP_READY` events.
This work is scoped and ready to implement.

View file

@ -1,177 +0,0 @@
# WasmServo Channel Design
This document defines the initial WEFT direction for communication between an application's Wasm core and its HTML UI.
## Status
Defined as the WEFT repository direction for implementation planning.
Not implemented.
Requires further upstream-facing discussion where Servo integration points are affected.
## Problem statement
A WEFT application has two isolated parts:
- `core.wasm` running under Wasmtime
- `ui/index.html` rendered by Servo
They must exchange application events and state updates without collapsing process isolation or introducing ambient authority.
## Decision
The initial WEFT direction is a brokered message channel with these properties:
- `weft-appd` owns the session
- the Wasm core and HTML UI are peers, not parent and child
- all payloads are structured messages
- all messages cross an explicit broker boundary
- neither side receives direct ambient access to system resources through the channel
## Transport shape
The selected transport model is:
- one app session broker owned by `weft-appd`
- one message stream from Wasm core to broker
- one message stream from UI to broker
- broker validation before delivery in either direction
The transport must support:
- ordered delivery within a session
- bounded message size
- explicit backpressure
- session teardown on process death
This document does not lock the implementation to a specific byte framing library.
It does lock the architecture to brokered message passing rather than shared memory or in-process embedding.
## Why this direction was selected
This direction was selected because it preserves the design constraints already established in the authoritative blueprint:
- process isolation remains intact
- capability mediation stays outside the UI runtime
- the shell and app model do not depend on embedding Wasmtime into Servo
- the design does not depend on Worker support being complete in Servo
- the design avoids shared-memory synchronization complexity in the first implementation track
## Explicitly rejected directions for the initial WEFT track
### Shared memory ring buffer
Rejected for the initial track because it increases synchronization complexity, failure complexity, and debugging cost before the protocol contract is stable.
### In-process Wasmtime hosted directly inside Servo
Rejected for the initial track because it collapses the process boundary too early and would require a much larger Servo-side architectural commitment before WEFT has closed the surrounding interface contracts.
### Worker-based UI execution model as the primary plan
Rejected for the initial track because Worker support should not be treated as a closed dependency assumption for WEFT planning.
## Session model
Each launched app receives an app session identified by a session identifier created by `weft-appd`.
The session owns:
- the approved capability set
- the Wasm process handle
- the UI browsing-context handle
- the two channel endpoints
- the teardown state
A session ends when either side dies, the app is closed, or the broker invalidates the session.
## Message model
All messages are structured and versioned.
Every message includes:
- `session_id`
- `stream_id`
- `message_type`
- `sequence`
- `payload`
Message classes:
- UI event messages
- state update messages
- lifecycle messages
- error messages
The broker validates message class and payload shape before forwarding.
## Capability boundary
The channel is not a capability transport.
Capabilities are granted only through app launch and host-managed handles.
The UI cannot escalate privileges by sending broker messages.
The Wasm core cannot mint new authority by sending channel payloads.
## Failure model
### Wasm process crash
If the Wasm process dies:
- the broker closes the Wasm endpoint
- the UI receives a terminal session error event
- the app session is torn down unless explicit recovery is later designed
### UI crash
If the UI browsing context dies:
- the broker closes the UI endpoint
- the Wasm endpoint is terminated by `weft-appd`
- the app session ends
### Broker restart or broker failure
If the broker fails, the session is invalid.
Both sides must be treated as disconnected.
Session recovery is not implicit.
## Performance budget
The initial design target is correctness and isolation first.
Performance requirements for future implementation work:
- message transport must not permit unbounded queue growth
- large binary payloads are out of scope for the control channel
- high-frequency UI state churn must be coalesced before transport where possible
This document does not claim a measured latency target because no implementation exists yet.
## Observability requirements
Any implementation of this design must expose:
- session creation and teardown events
- message validation failures
- endpoint disconnect reasons
- queue pressure indicators
## Open implementation questions
These questions remain open for the implementation phase and any Servo-facing discussion:
- the exact UI-side binding surface presented to application JavaScript
- the exact framing and serialization format
- whether the UI endpoint is surfaced through a custom embedder API or another browser-facing transport mechanism
- whether a limited reconnect path is worth supporting
The open questions do not change the architectural decision that the channel is brokered and process-separated.

View file

@ -1,197 +0,0 @@
# WEFT Shell Protocol Design
This document defines the WEFT-specific protocol boundary between `weft-compositor` and `servo-shell`.
## Status
Defined for WEFT repository planning and future implementation.
Not implemented.
Not proposed as a standard Wayland extension.
## Participants
- `weft-compositor`
- `servo-shell`
`weft-compositor` is authoritative for:
- Wayland connection ownership
- surface lifecycle
- input focus at the surface level
- surface stacking between independent client surfaces
- output geometry and presentation timing
`servo-shell` is authoritative for:
- shell DOM structure
- shell chrome layout
- window metadata presentation inside the shell UI
- shell requests to create, move, resize, and destroy shell-managed windows
## Problem boundary
The shell needs to represent desktop windows as DOM elements while the compositor owns the actual client surfaces.
The protocol exists to let the shell request and manage compositor-backed window surfaces without pretending that app content lives inside Servo's DOM tree.
## Object model
The protocol defines a shell session and a set of shell-managed window objects.
### Shell session
A shell session is created when `servo-shell` binds the WEFT global.
The session has exactly one active owner.
If the shell disconnects, the compositor invalidates the session and destroys all shell-owned protocol objects.
### Shell window object
A shell window object represents a compositor-managed surface slot associated with one visual shell window.
Each shell window object has:
- `window_id`
- `app_id`
- `title`
- `role`
- `geometry`
- `state`
- `z_hint`
`window_id` is generated by the compositor and is stable only for the lifetime of the session.
## Core requests
### `create_window`
Sent by `servo-shell` to request creation of a shell-managed window slot.
Inputs:
- `app_id`
- `title`
- `role`
- initial `geometry`
- initial `state`
Result:
- success with assigned `window_id`
- failure with a reason code
The compositor creates the server-side object and returns a `window_id`.
### `update_window_metadata`
Sent by `servo-shell` when title, role, or state hints change.
Metadata updates are advisory except where explicitly marked authoritative in this document.
### `set_window_geometry`
Sent by `servo-shell` to request a geometry change.
The compositor validates the request against output bounds, policy, and current window state.
The compositor is not required to honor the requested geometry exactly.
### `destroy_window`
Sent by `servo-shell` when the shell no longer wants the window slot.
The compositor destroys the protocol object and any shell-owned chrome surfaces associated with it.
## Core compositor events
### `window_configured`
Sent by the compositor when the effective geometry or state changes.
`servo-shell` must treat this as authoritative and update its DOM layout to match.
### `focus_changed`
Sent by the compositor when surface-level focus changes.
The shell updates visual focus state but does not override compositor focus directly.
### `window_closed`
Sent by the compositor when a window object is no longer valid.
After this event, the `window_id` is dead and cannot be reused by the shell.
### `presentation_feedback`
Sent by the compositor to report frame presentation timing relevant to shell-managed surfaces.
This event exists to align shell animations and resize flows with compositor timing.
## Surface ownership rules
The compositor owns the actual application content surfaces.
The shell may own separate chrome surfaces where needed.
The shell does not embed application pixels into the Servo DOM.
The compositor remains the authority for final stacking and composition.
## Failure model
### Shell crash
If `servo-shell` disconnects:
- the compositor invalidates the shell session
- shell-owned protocol objects are destroyed
- application surfaces remain alive if supervised elsewhere
- a restarted shell receives a new session and must rebuild its model
### Compositor crash
If `weft-compositor` crashes:
- Wayland connections are lost
- the shell must reconnect after compositor restart
- all protocol object identifiers from the prior session are invalid
- the shell rebuilds state from the authoritative process manager once the compositor is ready
### Stale identifiers
A `window_id` is session-scoped.
A stale `window_id` must be rejected by the compositor.
## Security model
The shell can request metadata and geometry changes, but the compositor enforces:
- output bounds
- focus routing
- stacking policy
- lifecycle validity
The shell is not trusted to define final compositor state.
## Test requirements
Implementation work against this protocol must include:
- unit tests for object lifecycle and stale identifier rejection
- integration tests for shell reconnect behavior
- integration tests for compositor restart handling
- validation of focus and configure event ordering
## Deferred topics
This document does not define:
- client application protocol toward `weft-appd`
- package or launcher metadata schemas
- accessibility mappings
- persistence of shell layout state across sessions

View file

@ -1,125 +0,0 @@
# winit Wayland Input Audit
Audit of winit's Wayland backend against WEFT OS system shell input requirements.
Required by Wave 4 gate: Servo Wayland input audit result assessed.
Source: blueprint Section 11, GAP 1. Servo version audited: main branch (2025).
winit version audited: 0.30.x (smithay-client-toolkit backend).
---
## Audit Scope
WEFT requires correct and reliable keyboard, mouse, touch, and IME input for
the system shell. Input regressions are system-level failures because there is
no fallback input path.
Servo delegates all windowing and input to winit. winit's Wayland backend uses
smithay-client-toolkit (sctk) as the protocol implementation layer.
---
## Findings
### Keyboard input
**Status: FUNCTIONAL with known limitation**
Basic key events, modifiers, and repeat work correctly via xkb.
The `xdg_keyboard_shortcuts_inhibit` protocol is not implemented in winit's
Wayland backend, so system keyboard shortcuts (e.g. Alt+F4) cannot be
inhibited by client surfaces. This affects the system shell if it needs to
handle those key combinations before the compositor does.
Relevant winit issue: https://github.com/rust-windowing/winit/issues/2787 (open).
### Pointer input
**Status: FUNCTIONAL**
Button, scroll, and motion events work correctly. `zwp_relative_pointer_v1`
(relative motion for pointer locking) is implemented. `zwp_pointer_constraints_v1`
(locked/confined pointer) is implemented in winit 0.30+.
Frame-accurate pointer position via `wl_pointer.frame` is handled.
### Touch input
**Status: PARTIAL**
Single-touch is functional. Multi-touch slots are tracked via `wl_touch` protocol.
Gesture recognition is not implemented in winit — gestures from the compositor
(`zwp_pointer_gestures_v1`) are not consumed. This affects swipe/pinch gesture
handling in the system shell.
Relevant winit issue: not filed as of audit date.
### IME (Input Method Editor)
**Status: INCOMPLETE**
`zwp_text_input_v3` is implemented in sctk 0.18+ but winit's integration is
incomplete. Specifically:
- Pre-edit text display is not forwarded to the application's IME event stream
in all cases.
- `done` events with surrounding text are not always handled correctly.
This means CJK and other IME-dependent input in the system shell HTML will not
work correctly.
Relevant sctk issue: https://github.com/Smithay/client-toolkit/issues/605 (open).
### Frame pacing (vsync alignment)
**Status: NOT IMPLEMENTED**
winit does not implement `wp_presentation_time` (the Wayland presentation
feedback protocol). Frame timing is based on `wl_callback` only. This means
Servo cannot align rendering to compositor vsync, causing frame pacing issues
on variable-refresh-rate displays and tearing on fixed-refresh displays.
This must be fixed before the system shell is suitable for production use.
Relevant Servo issue: not filed as of audit date.
### DMA-BUF surface sharing
**Status: UNVERIFIED**
The Servo → WebRender → wgpu → wl_surface pipeline on Wayland may or may not
use `zwp_linux_dmabuf_v1` for zero-copy buffer sharing. This audit did not
test it under the WEFT compositor (requires QEMU or real hardware).
Must be verified when DRM backend testing is available.
---
## Assessment
| Input area | Status | Blocks Wave 4 skeleton? |
|---------------------|-------------|-------------------------|
| Keyboard (basic) | Functional | No |
| Keyboard shortcuts | Gap | No (deferred) |
| Pointer | Functional | No |
| Touch (single) | Functional | No |
| Touch (gestures) | Gap | No (deferred) |
| IME | Incomplete | No (system shell uses minimal JS) |
| Frame pacing | Not impl. | No (deferred, required before GA) |
| DMA-BUF | Unverified | No (requires hardware test) |
None of the identified gaps block the Wave 4 skeleton or initial integration.
They block production readiness, as documented in the blueprint.
**Gate decision**: Wave 4 may proceed. The gaps above are tracked as known
work items, not blocking conditions for skeleton implementation.
---
## Required Follow-up
Before WEFT OS reaches GA:
1. Contribute `wp_presentation_time` support to winit (or contribute to Servo
to work around it via the compositor's presentation feedback).
2. Contribute `zwp_text_input_v3` fix to sctk and winit for correct IME.
3. File and track a winit issue for `zwp_pointer_gestures_v1`.
4. Verify DMA-BUF path under the WEFT DRM compositor (requires hardware).
5. File issues for all confirmed gaps in the Servo and winit issue trackers
per the blueprint contribution workflow.

97
docs/building.md Normal file
View file

@ -0,0 +1,97 @@
# Building WEFT OS
## Prerequisites
Linux (x86_64 or aarch64). Building on Windows is supported for workspace validation only; runtime components require Linux kernel interfaces.
System packages (Ubuntu/Debian):
```sh
sudo apt-get install -y \
libwayland-dev libxkbcommon-dev \
libegl-dev libgles2-mesa-dev libgbm-dev libdrm-dev \
libinput-dev libseat-dev libudev-dev libsystemd-dev \
pkg-config clang cmake python3
```
Rust toolchain: pinned in `rust-toolchain.toml`. Run `rustup show` to confirm the active toolchain matches.
## Workspace crates (non-Servo)
```sh
cargo build --workspace \
--exclude weft-servo-shell \
--exclude weft-app-shell
```
These crates do not require Servo and build in under two minutes.
## weft-compositor, weft-servo-shell, weft-app-shell (Linux)
```sh
cargo build -p weft-compositor
cargo build -p weft-servo-shell
cargo build -p weft-app-shell
```
Without `--features servo-embed`, the servo-shell and app-shell stubs compile and print READY without running an actual WebView. This is the default and the CI baseline.
## Servo embedding (optional, slow)
```sh
cargo build -p weft-servo-shell --features servo-embed
cargo build -p weft-app-shell --features servo-embed
```
This fetches and compiles the Servo fork (`github.com/marcoallegretti/servo`, branch `servo-weft`). Expect 3060 minutes on a clean build. Servo's dependencies include SpiderMonkey (C++), which requires `clang` and `python3`.
## Demo apps (wasm32-wasip2)
Each demo is a standalone crate in `examples/`. Pre-built `app.wasm` binaries are committed. To rebuild:
```sh
rustup target add wasm32-wasip2
cd examples/org.weft.demo.counter
cargo build --release
# output: target/wasm32-wasip2/release/app.wasm
```
## weft-runtime with Wasmtime
```sh
cargo build -p weft-runtime --features wasmtime-runtime,net-fetch
```
Without `--features wasmtime-runtime`, the runtime prints READY and exits (stub mode, used in CI on platforms without Linux system dependencies).
## Signing packages
```sh
weft-pack generate-key ./keys
weft-pack sign ./examples/org.weft.demo.counter --key ./keys/weft-sign.key
weft-pack verify ./examples/org.weft.demo.counter --key ./keys/weft-sign.pub
```
## NixOS VM (requires Nix with flakes)
Build the VM image:
```sh
bash infra/vm/build.sh
```
Run with QEMU:
```sh
bash infra/vm/run.sh
```
See `infra/nixos/weft-packages.nix` for the package derivations. The `outputHashes` entry for the Servo git dependency must be filled in before the `servo-embed` packages will build under Nix.
## CI
Three jobs run on every push to `main` and on pull requests:
- `cross-platform` — fmt, clippy, tests on Ubuntu and Windows (excludes Wayland crates)
- `linux-only` — clippy and tests for `weft-compositor`, `weft-servo-shell`, `weft-app-shell`
- `servo-embed-linux``cargo check --features servo-embed` for servo-shell and app-shell

45
docs/security.md Normal file
View file

@ -0,0 +1,45 @@
# Security Model
## Principles
WEFT OS enforces a capability-based security model. No capability is granted by default; all capabilities are declared in `wapp.toml` and verified before an app runs.
## Capability Verification
`weft-pack check` validates capability strings against a known set (`KNOWN_CAPS`) before installation. Unknown capability strings are rejected. The validated capability list is read by `weft-appd` at session start to map capabilities to concrete resource grants.
## Process Isolation
Each app session runs as a separate OS process (`weft-runtime`). When systemd is available, the process is wrapped in a systemd scope (`weft-apps.slice`) with `CPUQuota=200%` and `MemoryMax=512M`.
## Filesystem Isolation
Apps access the filesystem only through WASI preopened directories. Each capability maps to a specific host path preopened at a fixed guest path. The `weft-file-portal` process enforces path allowlists and blocks `..` traversal for apps that use the portal protocol.
## Package Signing
Packages are signed with Ed25519 (`ed25519-dalek`). The signature covers the SHA-256 hash of `wapp.toml` and `app.wasm`. `weft-pack verify` checks the signature before installation.
For verified read-only package storage, `weft-pack build-image` produces an EROFS image protected with dm-verity. Mounting requires the setuid `weft-mount-helper` which calls `veritysetup`.
## Seccomp
`weft-runtime` supports an optional seccomp BPF filter (compiled in with `--features seccomp`). The filter blocks a set of dangerous syscalls: `ptrace`, `process_vm_readv`, `process_vm_writev`, `kexec_load`, `mount`, `umount2`, `setuid`, `setgid`, `chroot`, `pivot_root`, `init_module`, `finit_module`, `delete_module`, `bpf`, `perf_event_open`, `acct`. All other syscalls are allowed; the policy is permissive with a syscall blocklist, not a strict allowlist.
## Wayland Surface Isolation
Each app registers its surface with the compositor via `zweft_shell_manager_v1`. The compositor enforces that each surface belongs to the session that created it. The app cannot render outside its assigned surface slot.
## JavaScript Engine — GAP-6
The Servo embedding uses SpiderMonkey as its JavaScript engine. SpiderMonkey is a complex JIT compiler. The following are known limitations that are not mechanically addressed by WEFT OS at this time:
- SpiderMonkey is not sandboxed at the OS level beyond what the Wasmtime/Servo process isolation provides.
- JIT-compiled JavaScript runs with the same memory permissions as the rest of the Servo process.
- SpiderMonkey vulnerabilities (CVE-class bugs in the JIT or parser) would affect the isolation boundary.
**Current mitigation:** the `weft-app-shell` process runs as an unprivileged user with no ambient capabilities. The seccomp filter blocks the most dangerous privilege-escalation syscalls when enabled. WEFT does not claim stronger JavaScript engine isolation than Gecko/SpiderMonkey itself provides.
**Not addressed:** JIT spraying, speculative execution attacks on SpiderMonkey's JIT output, and parser-level memory corruption bugs. These require either a Wasm-sandboxed JS engine or hardware-enforced control-flow integrity, neither of which is implemented.
This gap (GAP-6) is tracked. The bounded statement is: *WEFT OS relies on SpiderMonkey's own security properties for the JavaScript execution boundary. Any SpiderMonkey CVE that allows code execution within the renderer process is in-scope for the WEFT OS threat model.*

61
flake.nix Normal file
View file

@ -0,0 +1,61 @@
{
description = "WEFT OS capability-secure Wayland compositor and app runtime";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
in
flake-utils.lib.eachSystem supportedSystems (system:
let
pkgs = nixpkgs.legacyPackages.${system};
weftPkgs = pkgs.callPackage ./infra/nixos/weft-packages.nix { };
in
{
packages = weftPkgs // {
default = weftPkgs.weft-appd;
};
devShells.default = pkgs.mkShell {
name = "weft-dev";
nativeBuildInputs = with pkgs; [
rustup
pkg-config
cmake
clang
python3
];
buildInputs = with pkgs; [
openssl
libdrm
mesa
wayland
wayland-protocols
libxkbcommon
libseat
udev
dbus
libGL
];
shellHook = ''
export LIBCLANG_PATH="${pkgs.libclang.lib}/lib"
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath [
pkgs.mesa pkgs.wayland pkgs.libxkbcommon pkgs.libdrm
]}:$LD_LIBRARY_PATH"
'';
};
}
) // {
nixosConfigurations.weft-vm = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit self; };
modules = [
./infra/nixos/configuration.nix
];
};
};
}

View file

@ -0,0 +1,113 @@
{ pkgs, lib, self, modulesPath, ... }:
{
imports = [
"${modulesPath}/profiles/qemu-guest.nix"
];
system.stateVersion = "24.11";
boot.loader.grub = {
enable = true;
device = "/dev/vda";
};
fileSystems."/" = {
device = "/dev/vda1";
fsType = "ext4";
};
virtualisation = {
qemu.options = [ "-vga virtio" "-display gtk,gl=on" ];
memorySize = 4096;
cores = 4;
diskSize = 20480;
};
hardware.opengl = {
enable = true;
driSupport = true;
extraPackages = with pkgs; [ mesa.drivers virglrenderer ];
};
networking = {
hostName = "weft-vm";
firewall.enable = false;
};
time.timeZone = "UTC";
users.users.weft = {
isNormalUser = true;
description = "WEFT OS session user";
extraGroups = [ "video" "render" "seat" "input" "audio" ];
password = "";
autoSubUidGidRange = false;
};
services.getty.autologinUser = "weft";
security.polkit.enable = true;
services.dbus.enable = true;
services.udev.packages = [ pkgs.libinput ];
environment.systemPackages = with pkgs; [
mesa
wayland-utils
libinput
bash
coreutils
curl
htop
];
nixpkgs.overlays = [
(final: prev: {
weft = final.callPackage ./weft-packages.nix { };
})
];
systemd.user.services = {
weft-compositor = {
description = "WEFT OS Wayland Compositor";
after = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
Type = "notify";
ExecStart = "${pkgs.weft.weft-compositor}/bin/weft-compositor";
Restart = "on-failure";
RestartSec = "1";
};
};
weft-appd = {
description = "WEFT Application Daemon";
requires = [ "weft-compositor.service" ];
after = [ "weft-compositor.service" "weft-servo-shell.service" ];
serviceConfig = {
Type = "notify";
ExecStart = "${pkgs.weft.weft-appd}/bin/weft-appd";
Restart = "on-failure";
RestartSec = "1s";
Environment = [
"WEFT_RUNTIME_BIN=${pkgs.weft.weft-runtime}/bin/weft-runtime"
"WEFT_FILE_PORTAL_BIN=${pkgs.weft.weft-file-portal}/bin/weft-file-portal"
"WEFT_MOUNT_HELPER=${pkgs.weft.weft-mount-helper}/bin/weft-mount-helper"
];
};
};
};
programs.bash.loginShellInit = ''
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
systemctl --user start graphical-session.target
fi
'';
nix.settings = {
experimental-features = [ "nix-command" "flakes" ];
trusted-users = [ "root" "weft" ];
};
}

View file

@ -0,0 +1,67 @@
{ pkgs, ... }:
let
src = ../..;
cargoLock = {
lockFile = ../../Cargo.lock;
outputHashes = {
"servo-0.0.1" = pkgs.lib.fakeSha256;
};
};
commonArgs = {
inherit src cargoLock;
version = "0.1.0";
nativeBuildInputs = with pkgs; [ pkg-config ];
};
mkWeftPkg = { pname, extraBuildInputs ? [], extraNativeBuildInputs ? [], cargoFlags ? [] }: pkgs.rustPlatform.buildRustPackage (commonArgs // {
inherit pname;
cargoBuildFlags = [ "--package" pname ] ++ cargoFlags;
cargoTestFlags = [ "--package" pname ];
buildInputs = extraBuildInputs;
nativeBuildInputs = commonArgs.nativeBuildInputs ++ extraNativeBuildInputs;
doCheck = true;
});
in {
weft-compositor = mkWeftPkg {
pname = "weft-compositor";
extraBuildInputs = with pkgs; [
libdrm
mesa
wayland
libxkbcommon
libseat
udev
dbus
libGL
];
extraNativeBuildInputs = with pkgs; [ wayland-scanner ];
};
weft-appd = mkWeftPkg {
pname = "weft-appd";
extraBuildInputs = with pkgs; [ openssl ];
};
weft-runtime = mkWeftPkg {
pname = "weft-runtime";
extraBuildInputs = with pkgs; [ openssl ];
cargoFlags = [ "--features" "wasmtime-runtime,net-fetch" ];
};
weft-pack = mkWeftPkg {
pname = "weft-pack";
};
weft-file-portal = mkWeftPkg {
pname = "weft-file-portal";
};
weft-mount-helper = mkWeftPkg {
pname = "weft-mount-helper";
extraBuildInputs = with pkgs; [ cryptsetup ];
};
}

31
infra/vm/build.sh Normal file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
OUT="${1:-"${REPO_ROOT}/infra/vm/weft-vm.qcow2"}"
if [ -f "$OUT" ]; then
echo "error: $OUT already exists; remove it before rebuilding" >&2
exit 1
fi
cd "$REPO_ROOT"
echo "building NixOS VM image..."
nix build .#nixosConfigurations.weft-vm.config.system.build.qcow2 \
--out-link /tmp/weft-vm-result \
--print-build-logs
SOURCE="$(readlink -f /tmp/weft-vm-result)"
if [ ! -f "$SOURCE" ]; then
SOURCE="$(find /tmp/weft-vm-result -name '*.qcow2' | head -1)"
fi
if [ -z "$SOURCE" ]; then
echo "error: could not locate .qcow2 in build output" >&2
exit 1
fi
cp "$SOURCE" "$OUT"
rm -f /tmp/weft-vm-result
echo "image: $OUT"

27
infra/vm/run.sh Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE="${1:-"${REPO_ROOT}/infra/vm/weft-vm.qcow2"}"
if [ ! -f "$IMAGE" ]; then
echo "error: $IMAGE not found; run infra/vm/build.sh first" >&2
exit 1
fi
MEM="${WEFT_VM_MEM:-4096}"
CPUS="${WEFT_VM_CPUS:-4}"
DISPLAY_OPT="${WEFT_VM_DISPLAY:-gtk,gl=on}"
exec qemu-system-x86_64 \
-enable-kvm \
-m "${MEM}M" \
-smp "${CPUS}" \
-drive "file=${IMAGE},format=qcow2,if=virtio" \
-vga virtio \
-display "${DISPLAY_OPT}" \
-device virtio-net-pci,netdev=net0 \
-netdev user,id=net0 \
-device virtio-rng-pci \
-serial mon:stdio \
"$@"