# Programming Languages

capOS currently supports native Rust programs that are written for the capOS
userspace runtime. Other languages are design tracks, not implemented platform
support. The main rule is simple: a language runtime may expose familiar APIs,
but authority still comes from the process CapSet and typed capability calls.

## Current Support

| Language or runtime | Status | Path |
| --- | --- | --- |
| Rust, capOS-native | Implemented baseline | `#![no_std]`, `alloc`, `capos-rt`, static ELF, `x86_64-unknown-capos`. Phase D best-effort fair scheduling closed at commit `77caafc0` (`2026-05-10 19:39 UTC`): per-thread weighted vruntime, per-CPU WFQ run queues, bounded steal/migration, and `SchedulingPolicyCap` weight/latency-class authority. |
| Rust `std` | Not implemented | Future Rust standard-library or adapter work over capabilities |
| C | Phase 0 in tree (libcapos C-substrate v0 + libcapos-posix v0) | `libcapos.a` exposes capos-rt syscalls, ring CALL, CapSet lookup, a heap shim, typed `Console.writeLine`, `Timer.now`, `EntropySource.fill`, VirtualMemory wrappers, and native `ProcessSpawner.createPipe` / `Pipe` wrappers through `extern "C"`; `make run-c-hello` proves baseline C wrappers and `make run-c-pipe` proves a C binary can create a Pipe, write/read a marker, close the writer, and observe EOF without using the POSIX adapter. `libcapos_posix.a` adds the POSIX adapter v0 surface above libcapos: per-process static fd table (32 fds), TLS errno via `__errno_location()`, historical UDP `socket`/`sendto`/`recvfrom`/`close` wrappers over the retired qemu-only kernel `UdpSocket` cap, `clock_gettime(CLOCK_MONOTONIC, ...)`, `gettimeofday(&tv, NULL)`, `time`, `nanosleep`, and `sleep` over the kernel `Timer` cap, fail-closed signal stubs, `pipe`/`read`/`write`/`dup`/`dup2` over the kernel `Pipe` cap, and `fork`/`execve`/`waitpid`/`_exit`/`posix_inherit_stdio` plus a direct `posix_spawn` successor via the recording-shim Move-grant path through `ProcessSpawner.createPipe` / `ProcessSpawner.spawn`. See the POSIX adapter row for shipped smokes; the old DNS smoke is retired until resolver networking is rebuilt on the userspace stack. |
| C++ | Future experiment | Depends on C startup, ABI choices, allocator, exceptions/RTTI policy, and a useful freestanding subset |
| Go | Future design | Custom `GOOS=capos` per [Go Runtime proposal](proposals/go-runtime-proposal.md); a separate Phase W.8 path (`docs/proposals/wasi-host-adapter-proposal.md` Task 9) targets a TinyGo / upstream Go `GOOS=wasip1` CUE evaluator binary that runs inside the WASI host adapter against a future `ScriptPackage` cap |
| Python | Future design | Native CPython or MicroPython through a POSIX-style adapter; WASI/Emscripten for sandboxed or compute-only use |
| Lua | Phase 1 in tree (L.3 deterministic memory release) | `demos/lua-smoke/` runs a hand-written Lua-subset interpreter that exercises three capability-aware host bindings: `console:write_line`, `timer:now`, and L.3 `memory:{alloc,write,read,size,release}` over `capos-rt::VirtualMemoryClient` (kernel-mapped address never crosses to Lua; every byte access is bounds-checked host-side; release unmaps the exact rounded region and marks the userdata dead). PUC Lua dialect compatibility is deferred to the future C/libcapos port. See [Lua Scripting proposal](proposals/lua-scripting-proposal.md). |
| JavaScript / TypeScript | Future design | QuickJS-style native runner or WASI-hosted engine; not a browser JS shell |
| WASI / WebAssembly | Phase W.5 landed 2026-05-17 05:42 UTC (Phase W.4 closed 2026-05-07 20:09 UTC; Phase W.3 closed 2026-05-07 18:25 UTC; Phase W.2 closed 2026-05-07 10:53 UTC) | Host imports backed by capabilities; useful for sandboxed code and portable tools. W.1 vendored upstream wasmi (`v1.0.9`) at `vendor/wasmi-no_std/wasmi-1.0.9/` and shipped the `capos-wasm/` standalone crate that exposes a `Runtime` value (wasmi `Engine` + `Store<HostState>`). W.2 sub-slice 1 added the `wasm-host` userspace binary in `capos-wasm/src/bin/wasm-host.rs`, the `system-wasm-host.cue` focused-proof manifest, and `make run-wasm-host`, which still asserts the empty-instantiation regression. W.2 sub-slice 2 grew the same binary with the Preview 1 import resolver in `capos-wasm/src/wasi/preview1.rs`: 46 `wasi_snapshot_preview1` imports land on the wasmi linker; `clock_time_get(CLOCKID_MONOTONIC)` is backed by the manifest-granted `Timer` cap; `proc_exit` exits via `capos_rt::syscall::exit`; `fd_write(1, …)` / `fd_write(2, …)` route through the manifest-granted `Console` cap with a fixed 4 KiB iov-total scratch ceiling and a 1 KiB per-call chunk that matches the kernel `Console` cap's `MAX_SERIAL_CAP_WRITE_BYTES`; everything else (including `random_get`, which Phase W.4 promotes against `EntropySource`) returns `ERRNO_NOSYS`. A 114-byte hand-encoded probe module imports `random_get`, calls it once, stores the returned errno in an exported global, and the host refuses to print the `[wasm-host] preview1 imports linked: clock_time_get, fd_write, proc_exit, args/environ empty; nosys=52` proof line unless that errno equals `ERRNO_NOSYS`. W.2 sub-slice 3 added `demos/wasi-hello-rust/` (a one-liner `println!` Rust crate built for the upstream `wasm32-wasip1` target), `system-wasi-hello-rust.cue` (now grants `console`, `timer`, and the optional `boot` (`BootPackage`) cap), `tools/qemu-wasi-hello-rust-smoke.sh`, and `make run-wasi-hello-rust`. The wasm-host binary keeps running the sub-slice 1 + 2 regression first; when the manifest grants `boot`, it also reads the manifest blob through `BootPackage`, decodes `binaries[]` via raw capnp readers (new `capos_wasm::payload` module), instantiates the `wasi-payload` wasm, explicitly invokes the `_start` export (wasmi's `instantiate_and_start` runs the WebAssembly `start` section, NOT WASI's `_start`), and lets the payload's `println!` reach the kernel Console cap through Preview 1 `fd_write`. capos-rt grew narrow re-exports (`capos_capnp` and `default_reader_options`) so capos-wasm keeps a single direct path-dep on capos-rt and the vendored wasmi tree. The slice also kept the W.2 sub-slice 1 userspace-image budget bump (USER_STACK_BASE `0x100_0000`) for wasmi's ~3 MiB BSS. W.2 sub-slice 4 closed Phase W.2 by adding `demos/wasi-hello-c/` (a single `printf("Hello, wasi from capOS C\n")` C `main()` built directly with system clang-18 against the Ubuntu wasi-libc + `libclang-rt-18-dev-wasm32` apt packages: `clang --target=wasm32-wasi --sysroot=/usr -O2 -Wall -Wextra` produces a ~46 KiB `wasm32-wasi` module), `system-wasi-hello-c.cue`, `tools/qemu-wasi-hello-c-smoke.sh`, and `make wasi-hello-c-build` / `make run-wasi-hello-c`. C runs on capOS without any `libcapos`/POSIX work in tree because the wasm-host payload-load path landed in sub-slice 3 carries the C `.wasm` payload through the same wasm-host binary unchanged. Phase W.3 backed `args_get` / `args_sizes_get` with the manifest-supplied `initConfig.init.wasiArgs` text grant: the wasm-host walks the field through raw capnp readers in `capos_wasm::payload::read_wasi_args`, validates against `WASI_ARGS_MAX_COUNT = 32` / `WASI_ARGS_MAX_ARG_BYTES = 4096` / `WASI_ARGS_MAX_TOTAL_BYTES = 8192` (rejecting interior NUL bytes), packs the bytes into a per-instance `HostState` argv buffer, and reflects them through Preview 1 to the wasm guest. A 2026-05-13 bounded environment grant mirrors that path for `initConfig.init.wasiEnv`: the wasm-host walks `capos_wasm::payload::read_wasi_env`, validates against `WASI_ENV_MAX_COUNT = 32` / `WASI_ENV_MAX_ENTRY_BYTES = 4096` / `WASI_ENV_MAX_TOTAL_BYTES = 8192` (rejecting interior NUL bytes), packs `KEY=value` entries into a per-instance environment buffer, and reflects them through Preview 1 `environ_get` / `environ_sizes_get`; absent grants remain empty. The W.2 sub-slice 2 "args/environ empty" proof line stays byte-identical because the regression module passes empty argv and no environment. The new `demos/wasi-cli-args/` Rust smoke (`println!` of `argv[1]`), `system-wasi-cli-args.cue`, `tools/qemu-wasi-cli-args-smoke.sh`, and `make wasi-cli-args-build` / `make run-wasi-cli-args` close the per-instance argv plumbing; `demos/wasi-env/`, `system-wasi-env.cue`, `tools/qemu-wasi-env-smoke.sh`, and `make wasi-env-build` / `make run-wasi-env` prove one granted environment value reaches a Rust `wasm32-wasip1` payload. Schema/`schema/capos.capnp` is unchanged because `initConfig` is already a `CueValue` and unknown sub-fields under `initConfig.init` are ignored by the existing manifest decoder. Phase W.4 wires Preview 1 `random_get` through the kernel `EntropySource` cap. The wasm-host (`capos-wasm/src/bin/wasm-host.rs`) looks up an optional per-instance `EntropySource` cap from the CapSet under the well-known name `random` and installs the typed `EntropySourceClient` on `HostState` AFTER the W.2 sub-slice 2 probe regression has run, keeping the closed-fail `nosys=52` proof line byte-identical. Preview 1 `random_get` (`capos-wasm/src/wasi/preview1.rs`) drains arbitrary wasm-supplied byte ranges through `EntropySourceClient::fill_wait`, chunked at the kernel cap's `MAX_ENTROPY_FILL_BYTES = 64` ceiling and capped per Preview 1 invocation at `RANDOM_GET_MAX_BYTES = 65_536`. RDRAND-unavailable / truncated kernel responses surface as `ERRNO_IO`; oversized requests as `ERRNO_INVAL`; out-of-bounds wasm pointer writes as `ERRNO_FAULT`. Manifests without the grant keep returning `ERRNO_NOSYS` from the closed-fail refusal branch which never enters the kernel, so an instance without an `EntropySource` grant cannot leak entropy. Wall-clock support stays deferred until capOS has a typed `WallClock`/`RealTimeClock` cap; `clock_time_get(CLOCKID_REALTIME)` keeps the W.2 sentinel `ERRNO_NOSYS`. The new `demos/wasi-random/` Rust smoke (raw Preview 1 `random_get` binding reading N=64 bytes), `system-wasi-random.cue` (granted), `system-wasi-random-ungranted.cue` (ungranted), `tools/qemu-wasi-random-smoke.sh`, `tools/qemu-wasi-random-ungranted-smoke.sh`, `make wasi-random-build`, `make run-wasi-random`, and `make run-wasi-random-ungranted` close Phase W.4. A 2026-05-13 compatibility-import smoke adds `demos/wasi-stdio-fd/`, `system-wasi-stdio-fd.cue`, `tools/qemu-wasi-stdio-fd-smoke.sh`, and `make run-wasi-stdio-fd`; it directly imports `clock_res_get(MONOTONIC)`, `sched_yield`, `fd_fdstat_get(1/2)`, and `fd_seek(1/2)` and requires every promoted import to return a non-`ERRNO_NOSYS` result without granting filesystem, socket, or stdin authority. A 2026-05-13 harness-hardening smoke adds `demos/wasi-preview1-refusals/`, `system-wasi-preview1-refusals.cue`, `tools/qemu-wasi-preview1-refusals-smoke.sh`, and `make run-wasi-preview1-refusals`; it directly imports `path_open`, `fd_prestat_get`, `fd_read`, `sock_send`, and `sock_recv` and asserts the documented fail-closed errno when no Namespace/File/Store/socket authority exists. Phase W.5 (2026-05-17 05:42 UTC) wires the Preview 1 preopened-directory filesystem against the kernel `Directory` / `File` cap interface: the wasm-host looks up an optional per-instance `Directory` cap from the CapSet under the well-known name `root` and installs it as a single Preview 1 preopen at fd 3 named `/preopen-0`. `capos-wasm/src/wasi/fs.rs` implements `path_open`, `fd_read`, `fd_write`, `fd_seek`, `fd_close`, `fd_filestat_get`, `fd_prestat_get`, and `fd_prestat_dir_name` over `DirectoryClient` / `FileClient`; the resolver mirrors POSIX P1.4 Slice 4's `libcapos-posix/src/path.rs` -- intermediate segments walk `Directory.sub`, the leaf mints either an existing or freshly created `File` via `Directory.open(flags=CREATE|TRUNCATE)`. WASI `fd_close` only releases the local cap-table slot because the kernel-side `File.close()` would invalidate the `Arc<FileCap>` keyed by name in the parent directory and break re-open of the same path. The preopen sandbox refuses absolute paths and `..` segments with `ERRNO_NOTCAPABLE = 76`. The new `demos/wasi-fs/` Rust smoke (raw Preview 1 bindings), `system-wasi-fs.cue`, `tools/qemu-wasi-fs-smoke.sh`, `make wasi-fs-build`, and `make run-wasi-fs` complete a full `path_open(CREAT+TRUNC)` / `fd_write` / `fd_close` / re-open / `fd_filestat_get` / `fd_seek` / `fd_read` round trip and assert both sandbox refusals. `fd_readdir` over `DirectoryClient::list` landed 2026-05-24 08:44 UTC (no schema / generated-bindings change): `fs::fd_readdir_impl` enumerates the preopen, returning `ERRNO_NOTDIR = 54` for an open file fd and `ERRNO_BADF = 8` for an unknown fd; `preview1::fd_readdir` serializes the fixed 24-byte little-endian Preview 1 `dirent` records (`d_next`, zero `d_ino`, `d_namlen`, `d_type` from `DirEntry.is_dir`) plus name bytes with cookie-based resume and a short-buffer truncation contract that never writes past `buf_len`, and `make run-wasi-fs` now also asserts `[wasi-fs-smoke] readdir_found_smoke=true entries>=1`. `fd_tell` and `fd_filestat_set_size` landed 2026-05-24 09:34 UTC (no schema / generated-bindings change -- `File.truncate` already shipped), completing the File-cap method triad: `fs::fd_tell_impl` reads the host-side `FileEntry::position` (no kernel call, symmetric with `fd_seek`'s SET/CUR branches) and `fs::fd_filestat_set_size_impl` calls `FileClient::truncate_wait` leaving the file offset unchanged per the WASI contract; `preview1::fd_tell` returns `ERRNO_SPIPE = 70` on a stdio fd and writes the position as LE-u64, while `preview1::fd_filestat_set_size` rejects negative `size` with `ERRNO_INVAL = 28` and non-file fds with `ERRNO_BADF = 8`, and `make run-wasi-fs` now also asserts `[wasi-fs-smoke] tell_ok=true truncate_size=4`. `path_create_directory` and `path_remove_directory` landed 2026-05-24 10:09 UTC over `DirectoryClient::mkdir` / `remove` (no schema / generated-bindings change -- `Directory.mkdir`/`remove` already shipped): both reuse the `path_open` resolve path and the same preopen sandbox (absolute / `..` paths refused with `ERRNO_NOTCAPABLE = 76` before any kernel call), the `mkdir` result-cap is released immediately, and `make run-wasi-fs` now also asserts `[wasi-fs-smoke] mkdir_ok=true rmdir_ok=true dir_escape_refused=true`. `fd_pread` and `fd_pwrite` landed 2026-05-30 14:49 UTC as positional I/O over the host `File` cap (no schema / generated-bindings change -- `File.read`/`File.write` already carry an explicit offset): `fs::fd_pread_impl` / `fs::fd_pwrite_impl` mirror `fd_read_impl` / `fd_write_file_impl` but use the WASI-supplied `offset` and leave `FileEntry::position` untouched (the positional-I/O invariant), and `preview1::fd_pread` / `fd_pwrite` reuse the shared iovec gather/scatter helpers `fd_read` / `fd_write` were refactored onto, reject a negative `offset` with `ERRNO_INVAL = 28`, and return `ERRNO_SPIPE = 70` on a stdio fd; `make run-wasi-fs` now also asserts `[wasi-fs-smoke] pwrite_pread_ok=true pos_unchanged=true`, `pread_neg_offset_inval=true`, and `ppos_stdio_refused=true`. `path_filestat_get` and `path_unlink_file` landed 2026-05-30 as path-resolved metadata/removal over the host `File.stat` / `Directory.remove` caps (no schema / generated-bindings change): `fs::path_filestat_get_impl` resolves the leaf under the preopen, opens a transient read-only `File`, runs `File.stat`, and releases the transient cap, while `fs::path_unlink_file_impl` deletes the named entry through `Directory.remove` (the same op `path_remove_directory` uses); both enforce the absolute/`..` `ERRNO_NOTCAPABLE = 76` sandbox before any kernel call, and `make run-wasi-fs` now also asserts `[wasi-fs-smoke] pathstat_size=4 unlink_ok=true path_escape_refused=true`. The refusal smoke now expects `ERRNO_BADF = 8` from `path_open` / `fd_prestat_get` / `fd_read` / `path_create_directory` / `fd_pread` / `fd_pwrite` / `path_filestat_get` / `path_unlink_file` when no preopen is granted; only the socket imports stay at `ERRNO_NOSYS = 52` because they are not in the W.5/W.6 scope. `Store` / `Namespace` integration remains available but is deferred until a use case requires the content-addressed pseudo-fs shape. W.6 remains blocked on socket authority (or the `Fetch`/`HttpEndpoint` v0 shim). W.7 (wasmtime swap or WASI Preview 2 / Component Model promotion) and W.8 (TinyGo / `GOOS=wasip1` CUE evaluator) stay blocked on the std-userspace decision. See [WASI Host Adapter proposal](proposals/wasi-host-adapter-proposal.md) and [plan](proposals/wasi-host-adapter-proposal.md). |
| POSIX-shaped software | Partial implementation | Compatibility adapter over explicit file, directory, socket, stdio, timer, process, and namespace caps. See [POSIX Adapter proposal](proposals/posix-adapter-proposal.md) and [plan](proposals/posix-adapter-proposal.md). P1.1, P1.2, and P1.3 are closed; the former direct DNS smoke is retired with the qemu-only kernel `UdpSocket` owner, while `make run-posix-pipe-smoke`, `make run-posix-spawn-smoke`, and `make run-posix-stdio-smoke` cover pipe/fork-for-exec, direct `posix_spawn`, and Console-backed stdio surfaces. P1.4 file/directory fd work closed at commit `f97d9833` (`2026-05-23 06:23 UTC`): `make run-posix-file` proves `open()`, `write()`, `lseek()`, `read()`, `opendir()`, `readdir()`, and `closedir()` through a live C process over the RAM-backed root `Directory` cap. Closed P1.4 successors now include printf/string (`make run-posix-printf`), identity stubs (`make run-posix-identity`), and signal/time stubs (`make run-posix-signal-time`). Remaining P1.4 work is dash vendoring/patching, the multi-translation-unit C build, and `make run-posix-shell-smoke`; long-form decomposition lives in [`docs/backlog/posix-adapter-dash-port.md`](backlog/posix-adapter-dash-port.md). |

## Native Rust Today

The implemented path is Rust without the standard library. Programs use
`core` and may use `alloc` types such as `Vec`, `String`, `Box`, and
`BTreeMap` because `capos-rt` installs a userspace allocator. They do not get
`std::fs`, `std::net`, `std::thread`, `println!`, environment variables,
process arguments, or a libc syscall table.

`capos-rt` owns the repeated runtime machinery:

- the `_start` entry point and `capos_rt_main` handoff;
- fixed heap initialization;
- panic output through an emergency Console path when available;
- raw `exit` and `cap_enter` syscall wrappers;
- CapSet lookup and interface-id checks;
- a single-owner ring client;
- typed clients for implemented kernel and service capabilities;
- result-cap adoption and queued local release.

Native programs should keep ordinary Rust business logic in normal modules and
push OS interaction to typed capOS clients. That keeps pure logic host-testable
while making authority visible at capability lookup and child-spawn sites.

## Why `std` Is Different

Rust `std` is not just "more Rust." It is an operating-system binding. It
expects an implementation of filesystem, networking, threads, time, standard
I/O, process, environment, synchronization, and platform error APIs. On Linux
those calls are ambient: a process can ask the kernel to open a path or create
a socket and the kernel consults global process credentials.

capOS does not have that ambient authority model. A future Rust `std` path must
choose how each `std` feature gets authority:

| `std` area | capOS authority source |
| --- | --- |
| `std::io::{stdin, stdout, stderr}` | `StdIO`, `Console`, or `TerminalSession` caps |
| `std::fs` | scoped `Directory`, `File`, `Store`, or `Namespace` caps |
| `std::net` | socket or listener caps minted by a network service |
| `std::thread` | `ThreadSpawner`, `ThreadControl`, `ThreadHandle`, and ParkSpace support |
| `std::time` | `Timer` and future wall-clock caps |
| process spawn and wait | `ProcessSpawner`, `RestrictedLauncher`, and `ProcessHandle` caps |
| `std::env` and current directory | synthetic runtime state backed by manifest or namespace caps |

That mapping can be implemented as a capOS `std` backend, a Rust compatibility
crate, or a POSIX-style adapter. The project has not selected one shared ABI
for all language runtimes.

## Compatibility Terms

Use these terms instead of the vague phrase "compatibility layer":

- **Native runtime adapter**: language-specific runtime glue that talks to
  capOS capabilities directly. `capos-rt` is the implemented Rust example;
  `GOOS=capos` would be the Go example.
- **Capability-native bindings**: generated or handwritten bindings that expose
  Cap'n Proto interfaces as language-level APIs without POSIX names.
- **POSIX compatibility adapter**: a libc or library surface that translates
  `open`, `read`, `write`, `socket`, `poll`, `clock_gettime`, and similar APIs
  into operations on granted capabilities.
- **WASI host adapter**: a WebAssembly host implementation whose imports are
  backed by granted capOS capabilities.

The adapter may make code look familiar, but it cannot create authority. A
process without a namespace cap still cannot open a file. A process without a
network cap still cannot create a socket. A process without a launcher or
spawner cap still cannot create children.

## Language Tracks

### Rust

Rust is the only implemented userspace language. The current target is
`targets/x86_64-unknown-capos.json`, which exposes `target_os = "capos"` while
keeping the booted userspace baseline `no_std`, static, and `panic = "abort"`.
`init`, `demos`, `shell`, and the `capos-rt` smoke binary build through this
custom target.

Open work before broader Rust support:

- generated clients after the schema surface stabilizes;
- runtime ParkSpace clients and multi-threaded ring demultiplexing;
- a decision on Rust `std` over native capabilities versus a POSIX adapter;
- package/build conventions for out-of-tree capOS Rust programs.

### C and C++

C support is in tree as a Phase 0 substrate. The `libcapos/` crate compiles to
`libcapos.a`, a thin Rust staticlib that exposes the capos-rt syscall, ring
CALL, CapSet lookup, typed `Console.writeLine`, `Timer.now`,
`EntropySource.fill`, VirtualMemory wrappers, native
`ProcessSpawner.createPipe` / `Pipe` wrappers, and the global allocator under
an `extern "C"` ABI. C binaries link statically against the archive and run on
the same userspace ELF layout as Rust demos; `make run-c-hello` boots a C
`main()` that calls the baseline wrappers, and `make run-c-pipe` boots a C
`main()` that creates a Pipe, round-trips a marker, closes the writer, and
observes EOF. The substrate is intentionally narrow -- no
`errno`, no fd table, no POSIX surface -- so the separate
`libcapos-posix` layer can own those decisions without churning the
substrate. The same archive is what later runtimes such as CPython,
MicroPython, Lua, and QuickJS will link against.

`libcapos-posix/` builds `libcapos_posix.a` on top of `libcapos.a` and
ships the v0 POSIX surface: a 32-fd static table, `__errno_location()`
TLS, UDP `socket`/`sendto`/`recvfrom`/`close` over the kernel
`UdpSocket` cap, `clock_gettime(CLOCK_MONOTONIC, ...)` and
`gettimeofday(&tv, NULL)` over the kernel `Timer` cap,
`pipe`/`read`/`write`/`dup`/`dup2` over the kernel `Pipe` cap, file/directory
fd operations (`open`, `lseek`, `opendir`, `readdir`, `closedir`) over the
RAM-backed root `Directory` cap, and a recording-shim
`fork`/`execve`/`waitpid`/`_exit`/`posix_inherit_stdio` path plus direct
`posix_spawn` with `posix_spawn_file_actions` support, all routed through
`ProcessSpawner.createPipe` / `ProcessSpawner.spawn` when spawning is needed.
The shipped smokes are `make run-posix-pipe-smoke`,
`make run-posix-spawn-smoke`, `make run-posix-stdio-smoke`, and
`make run-posix-file`. The former `make run-posix-dns-smoke` target is retired
with the qemu-only kernel `UdpSocket` owner. The remaining v0
phase is the dash port (Phase P1.4) over the kernel RAM-backed
`File`/`Directory`/`Store`/`Namespace` caps from Storage Phase 3 slices 1-3.
See `docs/backlog/posix-adapter-dash-port.md` for the long-form
decomposition.

C++ should wait until the C substrate exists and the project decides its C++
ABI policy: exceptions, RTTI, TLS, allocation, unwind behavior, and standard
library scope. A freestanding container/arena subset is plausible earlier than
full hosted C++.

### Go

Go is a dedicated future design because its runtime is close to a userspace
operating system. A native `GOOS=capos` port needs virtual memory reservation
and commitment, TLS setup, OS-thread creation, park/wake, monotonic time,
debug output, process exit, and eventually network polling.

The current kernel/runtime substrate already proves useful pieces:
`VirtualMemory`, `Timer`, `ThreadControl`, `ThreadSpawner`, `ThreadHandle`,
and private ParkSpace wait/wake exist at the capOS level. The missing work is
the Go runtime port and the runtime-side integration contract, not a new
ambient syscall namespace.

Go through WASI may be sufficient for CPU-bound tools such as CUE evaluation;
that path is tracked as Phase W.8 in
[`docs/proposals/wasi-host-adapter-proposal.md`](proposals/wasi-host-adapter-proposal.md) (TinyGo
or upstream Go `GOOS=wasip1` against a future `ScriptPackage` cap). Native
`GOOS=capos` remains the path for Go network services and full runtime
behavior.

### Python

Python is not currently supported on booted capOS. The practical paths are:

- **Native CPython through a POSIX compatibility adapter.** This depends on the
  C/libc substrate plus file, stdio, timer, networking, and process adapters.
  It is the likely path for trusted system scripts, configuration tooling, and
  Python programs that need capOS networking or storage.
- **MicroPython through the same native C substrate.** This is a smaller early
  scripting option with less runtime surface than CPython.
- **WASI or Emscripten-hosted Python.** This is useful for sandboxed or
  compute-oriented Python. It still runs a Python interpreter; WebAssembly is
  the sandbox/host ABI, not a way to avoid Python runtime work.

Current upstream CPython support is relevant but not sufficient by itself:
[PEP 11](https://peps.python.org/pep-0011/) lists
`wasm32-unknown-wasip1` as a Tier 2 CPython platform and
`wasm32-unknown-emscripten` as Tier 3, while
[PEP 776](https://peps.python.org/pep-0776/) records Emscripten support for
Python 3.14. Those targets help the WASM path. They do not provide native capOS
file, socket, thread, or capability bindings.

### Lua

Lua is a capability-scoped scripting runner. The target is not a POSIX Lua
shell. A `capos-lua` process should receive an exact CapSet, load curated
standard libraries, expose capabilities as unforgeable host userdata, deny raw
CapIds, and flush owned handles at script exit.

Phase 0 lives in `demos/lua-smoke/` as a hand-written Lua-subset interpreter
written entirely on top of `capos-rt`. It exists to validate the long-term
capability-aware host API design (typed userdata, `obj:method(args)` dispatch
through a host registry, no raw SQE or method-id leak into Lua, errors
surfaced as Lua runtime errors) without committing capOS to a particular Lua
dialect. The interpreter accepts a strict subset (`local`, `if`/`elseif`/
`else`, numeric `for`, `while`, integer/float arithmetic, string concat,
comparison, `obj:method(args)` calls); tables, closures, coroutines,
metatables, and the Lua standard library are not implemented.

Upstream PUC Lua is a small C implementation, so the dialect-compatible path
waits on the C/libcapos substrate. The Phase 0 interpreter is not a promise
of PUC Lua compatibility and the smoke binary is explicitly labelled
`runtime = "capos-lua-subset"` rather than `lua-5.x`. When the C/libcapos
port lands, the embedded interpreter is replaced or kept as a research-grade
sandbox; the host binding shape stays.

### JavaScript and TypeScript

JavaScript support means running an engine as an ordinary capOS process. A
small QuickJS-style runner is the plausible early native path once C support
exists. V8 or SpiderMonkey are much larger C++ runtime ports and should be
treated as later experiments. TypeScript would normally compile before
execution; capOS should not make a TypeScript compiler part of the kernel or
base runtime.

### WASI and Browser WebAssembly

WASI support is a host-runtime track: the host imports become capability
calls. The full design is in the
[WASI Host Adapter proposal](proposals/wasi-host-adapter-proposal.md), and
the implementation decomposition is in
[`docs/proposals/wasi-host-adapter-proposal.md`](proposals/wasi-host-adapter-proposal.md). The
proposal selects wasmi for the v0 phases (`no_std + alloc` userspace
runtime, fuel metering, externref support) and frames wasmtime / WAMR as
the W.7+ migration targets. Each WASI import is backed by a typed capOS
capability the host adapter already holds; ungranted authority is refused,
not synthesised. WASI is a good fit for code that is already designed
around explicit imports and sandboxed execution. It is not a replacement
for native runtime ports when the language expects OS threads, signals,
sockets, memory mapping, or a large POSIX surface.

The browser/WebAssembly proposal is separate. It explores running capOS concepts
in a browser using worker-per-process isolation and SharedArrayBuffer-backed
rings. It is a teaching and demo target, not current native userspace language
support.

## Proposal Map

- [Userspace Runtime](architecture/userspace-runtime.md) documents the current
  `capos-rt` implementation.
- [Userspace Binaries](proposals/userspace-binaries-proposal.md) owns the
  native binary, language, POSIX-adapter, and WASI roadmap.
- [Go Runtime](proposals/go-runtime-proposal.md) owns the native Go plan.
- [Go VirtualMemory Contract](backlog/go-virtual-memory-contract.md) freezes
  the allocator-facing memory contract needed by Go-style runtimes.
- [Lua Scripting](proposals/lua-scripting-proposal.md) owns the Lua runner
  design.
- [WASI Host Adapter](proposals/wasi-host-adapter-proposal.md) owns the
  WebAssembly host adapter design; the implementation decomposition lives
  in [`docs/proposals/wasi-host-adapter-proposal.md`](proposals/wasi-host-adapter-proposal.md).
- [POSIX Adapter](proposals/posix-adapter-proposal.md) owns the
  POSIX-compatibility roadmap above the libcapos C-substrate; the
  implementation decomposition lives in
  [`docs/proposals/posix-adapter-proposal.md`](proposals/posix-adapter-proposal.md).
- [Shell](proposals/shell-proposal.md) distinguishes native shell behavior from
  POSIX shell compatibility.
- [Storage and Naming](proposals/storage-and-naming-proposal.md) defines the
  `Directory`, `File`, `Store`, and `Namespace` surfaces that future POSIX and
  language runtimes will consume.
- [Browser/WASM](proposals/browser-wasm-proposal.md) owns the browser-hosted
  WebAssembly experiment.
- [LLVM Target](research/llvm-target.md) records target-triple, Rust `std`,
  Go, C, TLS, and ABI grounding.

## Validation

Current language-runtime validation is Rust-only:

- `tools/check-userspace-runtime-surface.sh` verifies that `capos-rt` owns
  `_start`, panic handling, allocator setup, raw syscalls, and entry macros.
- `make capos-rt-check`, `make init-capos-build`, `make demos-capos-build`,
  `make shell-capos-build`, and `make capos-rt-capos-build` build the booted
  userspace artifacts against the capOS custom target.
- `make run-smoke`, `make run-spawn`, `make run-shell`, and
  `make run-terminal` exercise the runtime surface through QEMU.

No page should claim support for Python, Go, Lua, C, C++, JavaScript, WASI, or
Rust `std` until there is a booted artifact and a validation target for that
runtime.
