# M0 — baseline inventory

What the *current* Python integration does, measured rather than remembered, so
the owned runtime has something to be compared against. Every entry is pinned by
a test in `test/python-abi/baseline.test.ts`, which asserts today's behaviour —
including the wrong answers, on purpose. Turning one of those assertions around
is what a milestone landing looks like.

Setup: `src/runtime/cpython.ts` caches **one Pyodide interpreter per container**
(keyed on the VFS) and runs each program with a fresh globals dict.

## Findings

| # | Behaviour | Verified | Cause | Fixed by |
|---|---|---|---|---|
| 1 | A module left in `sys.modules` by one program is importable by the next, unrelated one | yes | fresh globals do not reset `sys.modules` | **fixed in M2** — one interpreter per process |
| 2 | `os.environ` does **not** leak between programs | yes | the environment is explicitly rebound per run | — (the exception, not the rule) |
| 3 | `asyncio.start_server` fails | yes | `python-syscalls.ts` replaces parts of `asyncio` and refuses server creation | M6 — virtual sockets |
| 4 | Every program reports the same `os.getpid()` | yes | one interpreter is one process | **fixed in M2** — a process per program, with `getpid` wrapped to ask the kernel |
| 5 | `subprocess.run` succeeds, via the container's command table | yes | a bridge, not a process: no fresh interpreter, no descriptor inheritance, no process groups | M3 |

Fresh globals reset none of: `sys.modules`, logging handlers, registered
callbacks, native extension state, or running tasks. Finding 2 is worth naming
precisely because it is the one piece of state the integration *does* rebind —
which is why "each program gets a clean slate" feels true until it isn't.

## What this means for the plan

- Findings 1 and 4 are integration failures, not interpreter failures. A
  differently-built CPython dropped into the same lifecycle would reproduce both.
  That is the argument for building the kernel first.
- Finding 3 is a missing subsystem. No amount of interpreter work reaches it.
- Finding 5 is the one that reads as working and is not, which makes it the most
  expensive to leave in place.

## Not yet measured

Concurrency and server behaviour under load, native extension state across
process exits, and interpreter memory growth over repeated runs. These need the
process model of M3 before the measurement means anything.

## Known gaps in the owned runtime (M2)

Recorded so they are not rediscovered as surprises. None of them is hidden at
runtime: each either refuses with a reason or is declared absent in
`runtime.json`.

| Gap | Symptom | Milestone |
|---|---|---|
| No packaging | `pip` is micropip and targets the Pyodide interpreter; under this backend it refuses rather than installing into the wrong interpreter | M4 |
| No `rlcompleter` / `_pyrepl` in the stdlib image | the REPL prints `warning: can't use pyrepl` and falls back to the basic prompt, which works | M2 follow-up — a `wasm_assets` inclusion |
| No sockets | anything binding a port, Uvicorn included, cannot start | M6 |
| No spawn from Python | `subprocess` has no backend on this runtime | M3 |
| Symlinks | `symlink()` returns ENOSYS through the kernel filesystem | M3 |
