# Proposal: Virtio-9p Host Directory Passthrough

A QEMU-only userspace driver that mounts a host directory into a capOS guest
as `Directory`/`File` capabilities over the virtio-9p device, using the
9P2000.L protocol. Read-only first; a separate write-support slice follows.
Like every other QEMU fixture source, the grant fails closed in non-`qemu`
manifests: this is development infrastructure, never a production path.

## Motivation

Three concrete needs, in priority order:

1. **Dev-loop payload injection.** Today every guest-visible input — WASI
   payloads, Lua scripts, test corpora, fixture files — must be baked into the
   boot ISO or a disk image (`tools/mkstore-image`) before boot. Changing one
   test input costs a full ISO rebuild. A host-shared directory removes that
   rebuild from the inner loop.
2. **Artifact export from proofs (write slice).** QEMU proofs assert by
   grepping serial-log markers. With guest→host file export, a proof can write
   structured results (reports, dumps, benchmark tables) that host harnesses
   read directly — richer assertions than ordered-marker matching, and a
   natural output channel for a future declarative proof runner.
3. **Task-backend persistence shortcut.** The
   [Self-Hosted Task Backend](../backlog/self-hosted-task-backend.md) track
   aims to replace the host-side PostgreSQL task board with a capOS-served
   coordinator. Its durable serve loop is currently blocked on a spawn-grant
   authority gap for `BlockDevice` caps; `Directory` caps already have a
   landed spawn-grant path (`make run-spawn-grant-directory`). A 9p-backed
   `Directory` gives the coordinator durable, **host-inspectable** state files
   through the grant path that exists today.

## What Exists Today

`Directory`/`File`/`Store`/`Namespace` are established Cap'n Proto interfaces
with several backers: the RAM-backed kernel fixtures (`kernel/src/cap/
directory.rs`, `file.rs`, `store.rs`, `namespace.rs`), the block-device
filesystems (`readonly_fs`, `fat_fs`, `persistent_store`, `writable_fs`), and
the boot-ISO reader (`installable_image`). All disk-backed paths require an
image prepared host-side before boot. There is no host-directory passthrough
of any kind in the tree.

The transport groundwork exists: `capos-rt`'s userspace driver track has
landed polled virtio drivers over the device-agnostic modern-PCI surface
(`kernel/src/virtio_transport.rs`) using DDF grants
(`DeviceMmio`/`DMAPool`/`Interrupt`), proven by the virtio-net and NVMe
userspace driver proofs.

## Design

- **Device**: virtio-9p (`-fsdev local,path=<hostdir>,security_model=none,
  readonly=on -device virtio-9p-pci,mount_tag=...`). Fully emulated inside
  QEMU; no external daemon.
- **Protocol**: 9P2000.L, minimal client subset. Read path:
  `Tversion`/`Tattach`/`Twalk`/`Tlopen`/`Tread`/`Treaddir`/`Tgetattr`/
  `Tclunk`. Write slice adds `Tlcreate`/`Twrite`/`Tfsync`/`Trename`/
  `Tunlinkat`. Message codec is pure `no_std` logic in `capos-lib` style:
  host-tested, fuzzable, bounded, fail-closed on malformed replies.
- **Driver shape**: a userspace driver process (the established DDF pattern),
  **polled** — no MSI-X dependency, matching the landed polled virtio-net
  driver default. A dev fixture has no latency requirement that justifies
  interrupt plumbing.
- **Capability surface**: the driver serves the existing `Directory`/`File`
  interfaces over Endpoint IPC — consumers (shell `ls`/`cat`, `wasm-host`,
  the task coordinator) need no new interface. Read-only exports serve a
  `Directory` whose mutating methods fail closed.
- **Manifest gating**: the grant source is `qemu`-gated and fails closed in
  non-`qemu` manifests, exactly like the kernel socket and virtio-blk fixture
  sources. Write-enabled exports additionally require an explicit manifest
  flag so a read-only share cannot be silently upgraded.

### Why 9p and not virtiofs

virtiofs needs an external `virtiofsd` daemon, vhost-user shared-memory
plumbing, and a guest FUSE client — an order of magnitude more machinery whose
payoff is performance, which is irrelevant for a development fixture.
virtio-9p is built into QEMU, its transport is plain virtqueues over the
already-landed modern-PCI surface, and the 9P2000.L request/reply messages map
nearly 1:1 onto the `Directory`/`File` capability methods. Other rejected
alternatives: `fw_cfg` (single small blobs, no directory semantics) and
keeping the disk-image-only workflow (remains correct for production-shaped
storage proofs, but leaves the dev loop and the spawn-grant shortcut on the
table).

## Task-Backend Shortcut: Honest Trade-offs

The `CAPOSRS1` WAL record store over `BlockDevice` already provides durable,
crash-recovering coordinator persistence (`make run-task-coordinator-persist`)
— 9p adds no new durability capability. What it adds:

- **A grant path that works now.** `Directory` spawn-grants are landed; the
  `BlockDevice` spawn-grant gap blocks the durable Endpoint serve loop.
- **Host-inspectable state.** Record-per-file layout (one file per task,
  atomic `Trename` commit) makes the board state a plain host directory:
  greppable, diffable, backed up or versioned with ordinary tools, no image
  mounting.
- **Operational simplicity for self-hosting.** A capOS-in-QEMU task backend
  whose state survives VM restarts in a host directory is a realistic
  dogfooding deployment shape for the multi-agent workflow.

Limits to state plainly: durability depends on the host filesystem and QEMU's
`Tfsync` handling, weaker than the fenced `CAPOSRS1` frame guarantees — the
WAL-over-`BlockDevice` path remains the reference persistence design, and the
9p path is a pragmatic parallel track, not a replacement. Single writer only.
QEMU-only by construction; a production deployment still needs the NVMe/
`writable_fs` path or the future userspace storage service.

## Decomposition

Tracked as loopyard tasks; each behavior slice carries its QEMU proof and the
driver slices maintain a `docs/devices/virtio-9p.md` provenance map as part of
the same change:

1. [virtio-9p transport bring-up](https://tasks.cap-os.dev/p/capos/t/virtio-9p-transport-bringup) —
   modern-PCI probe, feature negotiation, virtqueue setup over
   `virtio_transport`, `Tversion`/`Tattach` handshake proof.
2. [9P2000.L client core](https://tasks.cap-os.dev/p/capos/t/virtio-9p-client-core) — host-tested `no_std`
   message codec (read subset), bounded fail-closed decode, fuzz target.
3. [Directory/File caps over 9p](https://tasks.cap-os.dev/p/capos/t/virtio-9p-directory-file-caps) —
   read-only `Directory`/`File` served over Endpoint IPC from a host fixture
   directory; shell `ls`/`cat` smoke; fail-closed non-`qemu` proof.
4. [Write support](https://tasks.cap-os.dev/p/capos/t/virtio-9p-write-support) — write-path messages,
   explicit manifest write flag, host-verified write/rename/fsync smoke.
5. [Task-coordinator 9p persistence](https://tasks.cap-os.dev/p/capos/t/task-backend-9p-persistence) —
   record-per-file durable serve loop over a spawn-granted 9p `Directory`,
   proven across VM restart with host-visible state files.
6. [WASI payload hot-reload](https://tasks.cap-os.dev/p/capos/t/wasm-payload-hot-reload-9p) — `wasm-host`
   loads its payload from the share; rebuild on host, re-run in guest, no ISO
   rebuild.

## Security Considerations

The guest→host boundary is the new surface. Mitigations: the share is scoped
to one dedicated host directory; QEMU-level `readonly=on` mirrors the
capability-level read-only export (defense in depth); `security_model=none`
maps all access to the QEMU process's own uid — the guest can never act as
another host principal; write-enabled shares are opt-in per manifest and
should point only at dedicated scratch/state directories. The 9p client treats
the QEMU server as untrusted input: all replies are bounded and fail closed,
and reply parsing is host-fuzzed like the other mount/wire parsers.
