# Python on danos: the milestone plan

The execution plan for [python-on-danos.md](python-on-danos.md). That note holds
the *why* and the design decisions; this one slices the work into milestones with
concrete deliverables, tests, and exit criteria. Milestones are numbered **P0–P5**
(track-local — the global M-series stays with the driver/lifecycle tracks).

Dependencies at a glance:

```
P0 toolchain + mini-libc ──┐
P1 streams + console + seam ─┴─→ P2 CPython minimal ─→ P3 terminal + REPL
                                        │                    │
                                        └─→ P4 danos module  │
                                             + Python service│
P5 process control + shell ←─────────────────────────────────┘
```

P0 and P1 are independent of each other and can proceed in parallel. P1 is shared
work — it is also Zig self-hosting Phase 1 and the first three slices of
[character-devices-and-tty.md](character-devices-and-tty.md).

## P0 — Toolchain + the C library compatibility layer

**Goal:** a C hello-world, cross-compiled on the host with `zig cc`, runs on danos.

Design and slicing live in
[c-library-compatibility.md](c-library-compatibility.md): the **libdanos-c**
sysroot (hand-written danos-native headers + `libc.a`) as a `library/c/` build
package — pure computation (string, libm, `strtod`, the printf/scanf engines)
lifted from a vendored, pinned musl subtree; the OS plumbing written in Zig over
the `runtime` surface (re-targeting `runtime.os` when the Zig track authors it);
`malloc` over danos `mmap`; a `crt0` bridging the danos entry shim to C `main`.
Driven by `zig cc -target x86_64-freestanding-none -isystem` (the triple becomes
`x86_64-danos` if the Zig fork lands first; nothing else changes).

Its five slices (sysroot-skeleton, fd-plumbing, malloc, stdio,
mathematics-and-time) carry their own tests — host-side oracle suites for the
computation layer, QEMU cases (`c-hello`, `c-file-io`, `c-stdio`, `c-time`) for
the plumbing.

**Exit:** `c-hello` and `c-file-io` green in the QEMU suite; host computation
tests green.

## P1 — Stream nodes, console, and the seam pieces

**Goal:** the shared Phase-1 surface exists: byte-stream stdio, cwd, environment,
entropy. Design and slicing live in
[character-devices-and-tty.md](character-devices-and-tty.md); this milestone is
its slices 1–3 plus three small seam pieces:

- **cwd/chdir** — per-process current directory used by path resolution (the
  kernel already anchors a VFS root per `fs_resolve`; the cwd is the same idea,
  process-scoped, with `getcwd`/`chdir` exposed through `runtime` and the libc).
- **Environment** — spawn carries an environment block; the SysV entry stack's
  `envp` slot ([sysv.md](os-development/sysv.md)) stops being empty; `getenv`
  reads it. An empty block stays valid.
- **Entropy** — a kernel `entropy` syscall (RDSEED/RDRAND with a jitter fallback,
  mirroring the TSC-reliability posture of not trusting one CPU feature blindly);
  the libc exposes `getentropy`.

- **Tests.** QEMU: the character-device tests from the tty note (offsetless
  read/write, blocking read, cooked/raw control round-trip), plus `cwd-basics`
  (chdir + relative open), `env-roundtrip` (spawn with env, child reads it),
  `entropy-sane` (nonzero, changing, correct length).

**Exit:** a C program reads a cooked line from fd 0 and echoes it to fd 1 —
injected key events in, bytes read back through the console's in-memory sink,
all under QEMU with no hardware involved — and `getcwd`/`getenv`/`getentropy`
return real answers.

## P2 — CPython, minimal configuration

**Goal:** `python -c 'print(2**100)'` runs on danos under QEMU.

- Pin **CPython 3.13.x**; vendor as `third-party/cpython/` or fetch via the build
  (decide with the build-packages conventions).
- Host build-Python of the same version (`--with-build-python`).
- `config.site` cache for the cross answers; `config.sub` patch so
  `x86_64-unknown-danos` parses; a small `configure`/`pyconfig` patch set kept as
  rebasable diffs, WASI-style.
- `--disable-shared`; static `Modules/Setup`: `posix errno _io _codecs _weakref
  time math _stat _collections itertools _functools _locale _sre` plus what the
  interpreter core insists on; threadless build (WASI precedent).
- `Lib/` on the FAT image under the hierarchy (e.g. `/system/python/lib`);
  `PYTHONHOME` set accordingly; `.pyc` written with **checked-hash
  invalidation** (FAT's 2-second mtime granularity makes mtime-based validation
  lie during fast edit-run cycles).
- `PYTHONHASHSEED` pinned only if P1's entropy slipped — otherwise real
  hash randomization from day one.
- **Tests.** QEMU: `python-expr` (the exit criterion), `python-file` (run a
  script from FAT, write a file, read it back), then a curated slice of CPython's
  own suite (`test_int`, `test_float`, `test_io`, `test_dict`) as a
  longer-running target — the suite is the porting harness.

**Exit:** the four QEMU cases green; the CPython test slice green or with a
short, documented skip list.

## P3 — Terminal + REPL: the first real application

**Goal:** an interactive `python` REPL in a graphical danos terminal — the
milestone demo for the OS.

- Depends on the display track's font rendering (its stated next step) — until
  that lands, the REPL is exercised end-to-end through the pseudo-device
  harness from P1+P2 (scripted input in, output read back), so P2's exit is
  never blocked on graphics; the graphical terminal is the *interactive* debut.
- The terminal application: draws with the UI toolkit / display client, consumes
  keyboard `InputEvent`s, and — per the tty note's load-bearing decision —
  **serves the VFS stream protocol itself** to its children, reusing the console's
  line-discipline library. Spawns `python` with its endpoints as fd 0/1/2.
- Raw mode + the control set give the REPL line editing; window-size control
  gives it wrapping.
- **Tests.** QEMU: scripted terminal session (inject key events, assert rendered
  or captured output). Real-hardware smoke on the Intel box joins the existing
  checklist.

**Exit:** typing `2+2` into the terminal on the QEMU GPU target prints `4`.

## P4 — The `danos` extension module + a Python service

**Goal:** Python can speak danos: IPC, capabilities, spawn.

- The `danos` module, **written in Zig against `Python.h`**, statically linked
  via `Modules/Setup`: endpoints (create/send/receive), capability passing,
  spawn + exit-notification, and the service bootstrap (announce, supervision
  handshake) — the same surface Zig services use, re-exposed.
- UI-toolkit bindings as a second module once the toolkit's API settles.
- Prototype **one real service in Python** — policy-shaped, not data-plane
  (candidates: hot-plug policy, a settings service) — speaking an existing wire
  protocol, supervised by the device manager like any service.
- **Tests.** QEMU: `python-ipc-echo` (Python service echoes over an endpoint, a
  Zig client asserts), plus the prototype service's own protocol test.

**Exit:** a Python process runs as a supervised danos service exchanging IPC
with Zig peers.

## P5 — Process control, then the shell

**Goal:** danos can spawn arbitrary programs with arguments and pipes; a small
Python shell uses it.

The kernel/VFS cluster a shell forces (any shell, any language):

- **exec-of-path** — spawn an arbitrary VFS path, not a named ramdisk binary;
- **argv/envp** — carried through spawn onto the child's entry stack (env from
  P1, argv new);
- **numeric exit status** — extend the exit record beyond the categorical
  `ExitReason` (the gotcha the Zig roadmap flagged: `WEXITSTATUS` must be real);
- **fd inheritance + pipes** — a kernel or service pipe (a character device by
  the tty note's definition) and spawn-time fd mapping.

Then, in order: `subprocess` enabled in CPython (maps onto spawn + the
exit-notification endpoint — no fork, Windows-style); a **small Python shell** (a
few hundred lines over `subprocess` + the console: prompt, argv parsing, pipes,
cwd) as the forcing function that reveals what job control actually needs.

**Explicitly deferred past P5:** the pthread subset over `thread_spawn`/futex,
signals-in-libc via M17, termios job control (Ctrl-C to foreground child), and
**xonsh** — which wants all three and is the arc's endpoint, not a milestone.

**Tests.** QEMU: `spawn-argv-exit` (child echoes argv, exits 42, parent sees
42), `pipe-through` (parent → child → parent), `python-subprocess`, and a
scripted shell session.

**Exit:** the Python shell runs `program | program` typed at the terminal and
reports the exit status.

## Post-P5 outlook

Two tracks continue past this plan, each with its own design doc rather than a
P-number here:

- **Dynamic libraries** ([dynamic-libraries.md](dynamic-libraries.md), D1–D4) —
  an application-layer facility (the OS stays static and lean): `dlopen` in the
  libc, then libffi + `ctypes` + loadable extension modules, then shared
  read-only mappings so N Python services hold one physical `libpython`.
- **The full C compatibility layer**
  ([c-library-compatibility.md](c-library-compatibility.md), stages 2–3) — the
  standing rule that every system capability ships with its C spelling, draining
  the absence table toward "portable C builds on danos"; `fork` is the one
  permanent exception.

## Related

- [python-on-danos.md](python-on-danos.md) — the design note this executes.
- [c-library-compatibility.md](c-library-compatibility.md) — P0's design.
- [character-devices-and-tty.md](character-devices-and-tty.md) — P1's design.
- [zig-self-hosting.md](zig-self-hosting.md) — shares P1; its fork makes P0's
  triple prettier but gates nothing here.
- [os-development/process-management.md](os-development/process-management.md) —
  the spawn/exit surface P5 extends.
