# spikes/wasm-phase4 — production mechanics kill-test spike

Phase 4 validates the operational mechanics needed before a WASM worker can be
trusted in production. This spike is intentionally smaller than the future UCE
worker integration: it uses tiny WAT modules instead of generated UCE units, but
it exercises the Wasmtime controls and workspace cleanup paths the worker will
use. It is also deliberately the first spike on the Wasmtime-specific C++ API
(`wasmtime.hh`) — fuel, epochs, store limiters, and snapshots do not exist in
the portable `wasm.h` surface, so runtime-agnosticism ends here by design.

Run on `k-uce`:

```bash
bash spikes/wasm-phase4/build_runner.sh
/tmp/uce/wasm-phase4/runner
```

Expected final line:

```text
PHASE4 EXIT CRITERION: PASS
```

What this proves:

- **Reusable compiled artifact / snapshot proxy:** modules are compiled once and
  then instantiated in fresh stores. Not OS CoW yet, but it validates the shape
  of "shared core artifact + per-request workspace birth".
- **Unharmed worker:** after all six kill cases, the same engine serves a
  healthy request that completes with the expected value. This — not merely
  "didn't crash" — is the worker half of the Phase 4 exit criterion.
- **CPU limits, both mechanisms:** fuel (deterministic, per-instruction cost)
  and epoch interruption (near-zero overhead, ticker thread, traps as
  `interrupt`). Production default is **epoch** per the Phase 0 findings; fuel
  is validated as the deterministic fallback. Note: the engine epoch only
  advances, so every store must set its own deadline.
- **Memory limit, actually load-bearing:** the OOM fixture grows by 10 pages —
  within its declared max (100) — so only the store limiter (2 pages) can deny
  the growth. The denial is observed by the guest (`memory.grow` → -1) and
  converted to a trap.
- **Trap-to-error-page data path:** every kill case's gate asserts the captured
  message contains a `wasm backtrace` and the expected cause. Fixture frames
  show `<unknown>` — readable production traces require units to keep their
  name section (or a symbolication side-file in the artifact cache).
- **Trace summarizer (`src/lib/wasm_trace.h`):** trap messages are rendered
  through the production collapse facility (repeated frames → `×N` lines,
  mangled symbols demangled, cause/detail split out). The stack-exhaustion
  case gates it on live trap output here; `site/tests/core.uce` gates the
  parsing on canned messages in the native suite.
- **Handle cleanup with falsifiable checks:** closers must run exactly once per
  handle and *while the store is still alive* (production closers may flush
  guest-resident state); destructor-only cleanup fails the ordering check.

Kill fixtures (all genuinely trap):

- `unreachable`: `__builtin_trap` analog.
- `oob-access`: wild pointer; load at 128 KiB from a 64 KiB memory.
- `stack-exhaustion`: runaway recursion → `call stack exhausted`.
- `infinite-loop-fuel` / `infinite-loop-epoch`: same loop, both CPU limits.
- `oom-limiter`: limiter-denied `memory.grow` → guest converts -1 to a trap.

**A literal C++ null-pointer dereference is deliberately absent:** address 0 is
valid wasm linear memory, so `*(int*)nullptr` does not trap — it silently
writes inside the workspace, which is then dropped at request end (still
strictly better than a native SIGSEGV). See WASM-PROPOSAL §10 for the recorded
risk/policy.

Still deferred to production Phase 4:

- real core snapshot + OS-level CoW birth;
- wiring `wasm_trace.h` summaries into the UCE error-page UI (the summarizer
  itself is done and gated); name-section policy for unit artifacts;
- wiring these traps into `linux_fastcgi.cpp` / the WASM worker backend;
- real runtime handle-table cleanup for sqlite/mysql/sockets/tasks;
- artifact ABI versioning end-to-end;
- memory-limit policy for guest allocators that return failure instead of
  trapping (bump allocator vs dlmalloc behavior under the limiter).
