docs: compiler front-end specifications and plans (Tasks 2-6)
- Plan docs: architecture, woc front (Tasks 2-7), emit+e2e (Plan 3), Haxe parity (Plan 8) - nullable-types implementation plan - Iteration 3: compiler front story - Specs: systems track, blue-green VM, log-watcher sample, OOP compiler/VM - Principles + project structure - Kanban updated with compiler front-end progress
This commit is contained in:
parent
012562290d
commit
a47f5bcd13
12 changed files with 1344 additions and 4 deletions
137
docs/00-principles.md
Normal file
137
docs/00-principles.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# The writeonce principles
|
||||
|
||||
The doctrine in one page. Every design argument in this repo eventually
|
||||
lands on one of these thirteen; later documents link here instead of
|
||||
re-arguing them. Each principle: what it is, why it holds, where it is
|
||||
enforced.
|
||||
|
||||
## 1. One binary is the whole system
|
||||
|
||||
The application, the database, the API, and (later) the UI ship as a single
|
||||
deployable — there is nothing else to install, operate, or version-skew.
|
||||
*Why:* the assembled-stack tax (app + DB server + proxy + glue) is the
|
||||
problem writeonce exists to delete.
|
||||
*Enforced by:* [`01-problem.md`](01-problem.md), the single-binary story in
|
||||
[the OOP spec](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
|
||||
|
||||
## 2. Zero dependencies — kernel primitives only
|
||||
|
||||
The runtime is C on libc; the compiler is OCaml on its stdlib; everything
|
||||
else is epoll/io_uring, inotify, eventfd, signalfd, sendfile, mmap. The
|
||||
kernel is the framework.
|
||||
*Why:* every dependency is a supply chain, an upgrade treadmill, and a
|
||||
black box in the one binary that must be understood end to end.
|
||||
*Enforced by:* [the kernel-primitives catalogue](plan/exploration/linux/00-linux.md),
|
||||
the dependency doctrine in [the OOP spec](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
|
||||
|
||||
## 3. Memory safety without a GC tax
|
||||
|
||||
Objects are owned values: one owner, moves on assignment, second-class
|
||||
borrows checked mostly at compile time (mutable value semantics — the
|
||||
Rust-borrow shape without lifetime inference). `@gc` is a per-class opt-in,
|
||||
reference-counted with budgeted per-shard cycle collection — no global
|
||||
pause exists by construction.
|
||||
*Why:* deterministic memory for the default case, aliasing freedom where
|
||||
the design wants it, and never a stop-the-world in a runtime that is also
|
||||
the database.
|
||||
*Enforced by:* [the OOP spec §4](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
|
||||
|
||||
## 4. No inheritance, ever
|
||||
|
||||
No `extends`, no `override`, no virtual hierarchies. Is-a is a tagged
|
||||
union; has-a is composition; polymorphism is structural interfaces.
|
||||
*Why:* hierarchies fossilize early guesses and make dispatch, ownership,
|
||||
and diagnostics all harder; composition keeps every unit flat and movable.
|
||||
*Enforced by:* [the OOP spec](superpowers/specs/2026-08-01-oop-compiler-vm-design.md),
|
||||
the reject rows of [the systems-track verdict table](superpowers/specs/2026-08-01-systems-track-design.md).
|
||||
|
||||
## 5. Thread-per-core shards; ownership moves, data never shares
|
||||
|
||||
One pinned worker per core, each owning its engine, heap, and event loop.
|
||||
Cross-shard work is a message send that moves ownership. There is no
|
||||
`Arc<Mutex<…>>` anywhere and never will be.
|
||||
*Why:* sharing mutable state buys contention, locks, and heisenbugs;
|
||||
moving ownership buys linear scaling and per-shard GC.
|
||||
*Enforced by:* [plan 09](plan/09-concurrency-scaleout.md) (shipped on the
|
||||
Rust runtime), [the shard-actor plan](superpowers/plans/2026-08-01-shard-actor-vm-runtime.md).
|
||||
|
||||
## 6. The runtime never stops
|
||||
|
||||
The executable is a systemd service that deploys without restarting: two
|
||||
VM slots (Blue/Green), in-runtime compile of an approved proposal, atomic
|
||||
dispatch switch, previous version resident for instant rollback — and the
|
||||
binary embeds its own source, so prod is always self-describing.
|
||||
*Why:* restarts drop connections, dump caches, and turn deploys into
|
||||
events; a database that is also the app must not blink.
|
||||
*Enforced by:* [the blue-green spec](superpowers/specs/2026-08-03-blue-green-vm-design.md).
|
||||
|
||||
## 7. RAM is authoritative; the WAL makes it durable
|
||||
|
||||
All reads serve from memory. Every mutation is WAL-logged and fsynced
|
||||
before acknowledgment; boot replays the log. Mirrors (Postgres) are
|
||||
reconstructible backups that reads and acks never depend on.
|
||||
*Why:* one source of truth with predictable latency; durability is a
|
||||
sequential append, not a storage engine bolted to the side.
|
||||
*Enforced by:* [plan 11](plan/11-wal-and-recovery.md),
|
||||
[plan 16](plan/16-postgres-mirror.md) (mirror-is-backup doctrine).
|
||||
|
||||
## 8. Samples force the grammar
|
||||
|
||||
Language features exist when a sample program exercises them; the examples
|
||||
directory is the de facto integration suite, and new surface is proven by
|
||||
re-expressing real workloads (blog, ecommerce, pricing, log-watcher).
|
||||
*Why:* grammars designed in the abstract grow features nobody needs and
|
||||
miss the ones real programs demand.
|
||||
*Enforced by:* [the blog sample](examples/blog/README.md),
|
||||
the sample-workload acceptance in [the systems-track spec](superpowers/specs/2026-08-01-systems-track-design.md).
|
||||
|
||||
## 9. Linux is the target
|
||||
|
||||
Not POSIX, not portable-someday: Linux syscalls, Linux fd semantics,
|
||||
systemd as the process manager. Portability abstractions are refused.
|
||||
*Why:* targeting one kernel lets the runtime use its sharpest primitives
|
||||
directly instead of the lowest common denominator.
|
||||
*Enforced by:* [the kernel-primitives catalogue](plan/exploration/linux/00-linux.md).
|
||||
|
||||
## 10. Capabilities are typed builtins — no FFI
|
||||
|
||||
Programs reach the system only through audited stdlib builtins (`fs`,
|
||||
`proc`, `net`, `time`, `json`): bounded reads, args-array-only process
|
||||
runs, handles that close on drop. There is no `extern`, no escape hatch.
|
||||
*Why:* one FFI hole voids the entire memory-safety and security story;
|
||||
typed capabilities make the safe path the only path.
|
||||
*Enforced by:* [the systems-track spec Parts 2–3](superpowers/specs/2026-08-01-systems-track-design.md).
|
||||
|
||||
## 11. Plain diagnostics are the product
|
||||
|
||||
Stable `WO-E###` codes, `file:line:col`, source excerpts, ownership errors
|
||||
naming both sites, many errors per run.
|
||||
*Why:* mutable value semantics only beats Rust ergonomics if the errors
|
||||
read like sentences; the compiler's error text is a first-class feature.
|
||||
*Enforced by:* [the OOP spec §6](superpowers/specs/2026-08-01-oop-compiler-vm-design.md),
|
||||
[the compiler architecture doctrine](plan/compiler/architecture.md).
|
||||
|
||||
## 12. The runtime is a recipe box
|
||||
|
||||
Transports, fibers, routing, subscriptions, the DB engine, deploy
|
||||
machinery — each stays a separable capability. A web framework or a custom
|
||||
database experience is a `.wo` library composing them; the runtime itself
|
||||
stays framework-agnostic.
|
||||
*Why:* the next stories (web framework, richer database surfaces) must be
|
||||
buildable *on* the runtime without forking it.
|
||||
*Enforced by:* [the blue-green vision §2](plan/exploration/blue-green-vm/00-vision.md).
|
||||
|
||||
## 13. Statically typed, all the way to the register
|
||||
|
||||
Every slot's type is known at compile time: no `Dynamic`, no `untyped`, no
|
||||
`cast`, no runtime reflection. The VM runs untagged 64-bit registers
|
||||
because the compiler already knows; JSON enters through checked decodes
|
||||
(`as T` yielding `?T`), never through dynamic objects.
|
||||
*Why:* the type system is the foundation the untagged VM, the borrow
|
||||
checker, and the annotation ORM (`@table` classes, `ref`/`multi`
|
||||
relations) all stand on — one dynamic hole collapses all three. The Haxe
|
||||
reference workload shows the alternative: its transcompiled C++ pays a
|
||||
hashed `__Field` lookup on every typedef access.
|
||||
*Enforced by:* the `Dynamic`/`untyped`/`cast` reject rows of
|
||||
[the systems-track verdict table](superpowers/specs/2026-08-01-systems-track-design.md),
|
||||
untagged registers in [the OOP spec §5](superpowers/specs/2026-08-01-oop-compiler-vm-design.md).
|
||||
147
docs/08-project-structure.md
Normal file
147
docs/08-project-structure.md
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
# 08 — Project structure: the writeonce monorepo
|
||||
|
||||
Canonical map of the repository: what every root directory is, who writes to it, and where it is headed as the OOP + systems tracks land. Companion to [`CLAUDE.md`](../CLAUDE.md) (working rules) and the two track specs ([`superpowers/specs/2026-08-01-oop-compiler-vm-design.md`](superpowers/specs/2026-08-01-oop-compiler-vm-design.md), [`superpowers/specs/2026-08-01-systems-track-design.md`](superpowers/specs/2026-08-01-systems-track-design.md)). Plan documents govern the *target* entries; this doc is the one place the whole shape is visible.
|
||||
|
||||
## The one-page map
|
||||
|
||||
```
|
||||
writeonce-all/
|
||||
├── compiler/ ⏳ OCaml `woc` — lexer→parser→types→owner→emit (plans 2, 8)
|
||||
├── runtime/ ✅ C runtime — wo-rt.c event-loop reference + ⏳ src/ wovm VM (plans 1, 4–7, 9)
|
||||
├── crates/ ✅ Rust runtime `rt` + 14 phase-scaffold crates — active until C-stack parity
|
||||
├── tests/ ⏳ corpus/ — conformance fixtures: run / compile-fail / trap / gc / sys / actor / db
|
||||
├── client/ ⏳ wo-live.js — the ~20 KB live-patch browser runtime (plan 7)
|
||||
├── scripts/ ⏳ oop-e2e.sh, oop-parity.sh — corpus + parity harnesses (plan 3)
|
||||
├── prototypes/ ✅ wo-db C++ query-engine reference; ⚠ wo-rt-c = stale duplicate of runtime/
|
||||
├── .dev/reference/ ✅ v1 crates workspace, colibri, llama-cpp; linux/ + go/ symlinks (per-dev)
|
||||
├── docs/ ✅ ALL documentation: numbered design docs, plan/, runtime/, examples/, superpowers/
|
||||
├── content/ data/ wo-data/ static/ templates/ infra/ ✅ v1 blog operating assets (migration-plan governed)
|
||||
├── justfile recipes: rt-c-demo/bench today; woc-/wovm-/oop-accept as plans land
|
||||
├── Cargo.toml the Rust workspace root (crates/*; .dev/reference/crates excluded)
|
||||
└── .dev/ gitignored per-developer links + commit.md draft (see .dev/README.md)
|
||||
```
|
||||
|
||||
✅ exists today · ⏳ created by a named plan · ⚠ cleanup note (below).
|
||||
|
||||
## Root directories in detail
|
||||
|
||||
### `compiler/` — the OCaml `woc` compiler (target)
|
||||
|
||||
Created by plan 2 (`docs/plan/compiler/2026-08-01-woc-compiler-front.md`), grown by plans 3 and 8.
|
||||
|
||||
```
|
||||
compiler/
|
||||
├── dune-project
|
||||
├── README.md orientation: pipeline map, build/test commands
|
||||
├── plan/ compiler-track docs: architecture.md + plans 2, 3, 8
|
||||
│ (recorded exception to the docs-under-docs/ rule)
|
||||
├── src/ one module per stage: diag, token, lexer, ast, parser,
|
||||
│ types, owner, emit, disasm, dump
|
||||
├── bin/main.ml the woc executable (check/emit/build modes)
|
||||
└── test/ golden runner + golden/ fixtures per stage
|
||||
```
|
||||
|
||||
Doctrine: OCaml stdlib only — no Menhir, no ppx, no opam libraries; handwritten lexer and recursive-descent parser. Architecture map with the marked reference-study paths: [`docs/plan/compiler/architecture.md`](plan/compiler/architecture.md).
|
||||
|
||||
### `runtime/` — the C runtime (canonical, partially landed)
|
||||
|
||||
The root-level home of the C track. Today it holds the shipped thread-per-core io_uring reference (`wo-rt.c`, phases A–F done — see [`plan/exploration/c-runtime/00-plan.md`](plan/exploration/c-runtime/00-plan.md)) plus its `bench/` and Makefile. The VM and every runtime module land beside it under `src/`:
|
||||
|
||||
```
|
||||
runtime/
|
||||
├── Makefile wo-rt today; wovm + test targets as plan 1 lands
|
||||
├── README.md orientation only (docs rule: real docs live under docs/)
|
||||
├── wo-rt.c UNTOUCHED phase A–F event-loop reference
|
||||
├── bench/ HTTP bench + goref/ Go comparison server
|
||||
├── src/ ⏳ wob.h, obj, borrow, cont, gc, loader, vm, builtin, main (plan 1)
|
||||
│ ⏳ sched, mailbox, shard (plan 4)
|
||||
│ ⏳ table, wal, db (plan 5)
|
||||
│ ⏳ http, json, router, handlers (plan 6)
|
||||
│ ⏳ ws, sub, htmlx, assets (plan 7)
|
||||
│ ⏳ sys_env, sys_fs, sys_proc, sys_net, sys_time (plan 9)
|
||||
└── test/ ⏳ t.h harness, wob_build assembler, test_*.c, cli_smoke.sh
|
||||
```
|
||||
|
||||
Doctrine: C11, libc only, direct syscalls; computed-goto dispatch with `-DWO_ISO_C` fallback; ASan (+TSan from plan 4) gates.
|
||||
|
||||
### `crates/` — the Rust runtime (active, retiring on parity)
|
||||
|
||||
Unchanged from CLAUDE.md's description: `rt` is the monolithic Stage-2 runtime producing the `wo` binary; the 14 unprefixed siblings (`ql`, `value`, `engine`, `txn`, `db`, `wal`, `sub`, `http`, `gen`, `policy`, `logic`, `service`, `ui`, `app`) are phase scaffolds. **Retirement path:** stays authoritative until the C stack passes the parity harness (plan 3 task 7, plan 6 blog smoke); then moves under `.dev/reference/` the way v1 did. Until that day, nothing here is refactored to accommodate the C track.
|
||||
|
||||
### `tests/`, `scripts/`, `client/` — the proof layer (target)
|
||||
|
||||
- `tests/corpus/` — the conformance spine (plan 3): `run/`, `compile-fail/`, `trap/`, then `gc/` (plan 3), `db/` (plan 5), `actor/` (plan 4), `sys/` (plan 9), `sample-logwatcher/` (plan 10), `lang/` (plan 8). Exact-outcome matching: byte-equal stdout, exact `WO-E###`, exact trap code.
|
||||
- `scripts/` — `oop-e2e.sh` (corpus runner), `oop-parity.sh` (Rust-overlap manifest runner).
|
||||
- `client/` — `wo-live.js`, the hand-written no-framework live-patch runtime (plan 7).
|
||||
|
||||
### `prototypes/` — reference implementations (frozen)
|
||||
|
||||
- `wo-db/` — the ~2k-line C++ query-layer prototype (SQL + Cypher + document paths). Stays: it is the semantic reference plan 5 cites. `make test` must keep passing.
|
||||
- `wo-rt-c/` — ⚠ **stale duplicate.** The content moved to root `runtime/` (both are currently git-tracked). Slated for deletion once the user confirms nothing references it; `justfile`/doc references already point at `runtime/`. Nothing new lands here — repo rule: prototypes receive nothing new.
|
||||
|
||||
### `.dev/reference/` — read-only study material
|
||||
|
||||
- `crates/` — the 13 v1 `wo-*` crates, a nested Cargo workspace (build with `cd .dev/reference/crates && cargo build`). Preserved per the wo-seg migration plan ([`runtime/database/07-wo-seg-migration.md`](runtime/database/07-wo-seg-migration.md)).
|
||||
- `colibri/`, `llama-cpp/` — vendored study trees (zero-dep C inference engine; MoE runtime) — see [`plan/exploration/colibri/`](plan/exploration/colibri/).
|
||||
- `linux/`, `go/` — per-developer symlinks to kernel and Go sources (gitignored; recreate per `CLAUDE.md`).
|
||||
- `rest/` — `.rest` HTTP files driving manual smoke against a running runtime; plan 6's blog smoke scripts the same sequences.
|
||||
|
||||
### `docs/` — all documentation
|
||||
|
||||
```
|
||||
docs/
|
||||
├── 01…08-*.md numbered design docs (this file is 08)
|
||||
├── writeonce-pl.md language positioning
|
||||
├── runtime/ user-facing language overview + the 7-phase database series
|
||||
├── examples/ blog/, ecommerce/, pricing/ samples; ⏳ log-watcher/ (plan 10)
|
||||
├── plan/ numbered engineering plans 00–16, linux/ cards, assembly/,
|
||||
│ ├── exploration/ c-runtime/ (A–F, done), ui/ (htmlx track), colibri/
|
||||
│ └── oop-vm/ ⏳ the OOP-track contracts: 00-wob-format, 01-error-catalog,
|
||||
│ 02-corpus, 03-shard-actor, 04-db-binding, 05-http-service,
|
||||
│ 06-ui-live, 07-systems-stdlib
|
||||
├── superpowers/
|
||||
│ ├── specs/ the two approved track specs (2026-08-01)
|
||||
│ └── plans/ implementation plans 1–10 (2026-08-01, prose-only)
|
||||
└── future-scope/, cm.md legacy notes
|
||||
```
|
||||
|
||||
Repo rule restated: documentation belongs here; code directories keep one orientation README each.
|
||||
|
||||
### v1 blog operating assets — `content/`, `data/`, `wo-data/`, `static/`, `templates/`, `infra/`
|
||||
|
||||
The original writeonce blog's articles (`content/`), its data directories, `.htmlx` templates (`templates/` — cited by the UI track as concrete v1 usage), static files, and deploy config. Governed by the wo-seg migration plan; untouched by the OOP/systems tracks except as reference. `target/` is cargo build output (ignored).
|
||||
|
||||
### `.dev/` — developer-local state
|
||||
|
||||
Gitignored symlinks into per-machine AI-tooling state plus `commit.md`, the running commit-message draft (the user commits; agents only append drafts). See `.dev/README.md`.
|
||||
|
||||
## Lifecycle: how the shape evolves
|
||||
|
||||
| Stage | What changes at the root |
|
||||
| --- | --- |
|
||||
| Today | `runtime/` holds wo-rt.c; `crates/rt` serves Stage 2; plans are paper. |
|
||||
| After plans 1–3 (milestone 1) | `compiler/` + `runtime/src/` + `tests/corpus/` + `scripts/` exist; `woc build` emits single binaries; `prototypes/wo-rt-c` deleted. |
|
||||
| After plans 4–7 | `runtime/src/` carries shard/db/http/ui modules; `client/` exists; the C stack serves the blog sample end to end. |
|
||||
| After plans 8–10 | systems stdlib in `runtime/src/sys_*`; `docs/examples/log-watcher/` proves program mode. |
|
||||
| Parity | `crates/` moves to `.dev/reference/crates-v2/` (naming decided then); the `wo` toolchain name transfers to the C stack; Cargo.toml shrinks accordingly. |
|
||||
|
||||
## Build sequence
|
||||
|
||||
The order the project completes, with the two parallel windows made explicit:
|
||||
|
||||
1. **`runtime/` VM core** — plan 1 (`.wob` format + `wovm`: memory model, loader, interpreter). No dependencies; the format doc it pins is everyone's contract.
|
||||
2. **`compiler/` front** — plan 2 (`woc`: lexer→parser→types→owner). Independent of plan 1 — *may run in parallel with it*; needs `apt install ocaml dune`.
|
||||
3. **Emit + end-to-end** — plan 3 (bytecode emitter, conformance corpus, `woc build` single binary, acceptance gate). Needs 1 + 2. **Milestone 1 done here.**
|
||||
4. Two tracks fork and *can proceed in parallel*:
|
||||
- **Server track (sequential within):** plan 4 shard-actor runtime → plan 5 DB engine binding → plan 6 HTTP service layer → plan 7 UI/.htmlx/LIVE.
|
||||
- **Systems track (sequential within):** plan 8 Haxe-parity language → plan 9 program mode + stdlib → plan 10 log-watcher sample. Only seam with the server track: the JSON codec shared between plans 6 and 9 (either lands it, noted in both).
|
||||
5. **Parity + retirement** — parity harnesses green (plans 3/6), then `crates/` (Rust) retires to `.dev/reference/` and the `wo` name transfers to the C toolchain.
|
||||
|
||||
Rule of thumb: `runtime → compiler → emit → {shard → db → http → ui} ∥ {language → stdlib → sample} → parity`.
|
||||
|
||||
## Naming conventions (recap)
|
||||
|
||||
- New runtime crates: unprefixed (`ql`, `db`, …). V1 crates: `wo-` prefixed, in `.dev/reference/crates/`.
|
||||
- Binaries: `wo` (Rust toolchain today), `woc` (OCaml compiler), `wovm` (C VM); at parity the `wo` name moves to the C toolchain.
|
||||
- C prototype directory names keep their historical `wo-` prefixes (`wo-db`).
|
||||
- Plan/spec files: `YYYY-MM-DD-<topic>.md` under `docs/superpowers/{specs,plans}/`; numbered engineering plans under `docs/plan/`.
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Status board for every phase doc under `docs/plan/`. **Focus: the backend** — the runtime, the database engine, and the REST/`.wo`-language API. Frontend phases are parked, not deleted. Each phase file carries a matching status banner; this board is the index.
|
||||
|
||||
Related planning surfaces this board indexes across: the mission story + review-in-order iterations at [`docs/stories/language-runtime-database/`](../stories/language-runtime-database/00-story.md); approved designs at [`docs/superpowers/specs/`](../superpowers/specs/); their implementation plans at [`docs/superpowers/plans/`](../superpowers/plans/) and [`docs/plan/compiler/`](compiler/architecture.md). Entry chain: [`docs/00-principles.md`](../00-principles.md) → [`docs/08-project-structure.md`](../08-project-structure.md) → this board.
|
||||
|
||||
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **not started** · ⏸ **parked (out of backend focus)**
|
||||
|
||||
## Board
|
||||
|
|
@ -32,7 +34,7 @@ Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **not started** · ⏸ **p
|
|||
| ⬜ | 09e cross-shard transactions (2PC) | needed by `fn checkout` spanning shards |
|
||||
| ⬜ | 09f observability & reshard | per-shard metrics, `WO_RESHARD` |
|
||||
|
||||
All numbers + find-and-fix stories: [09-concurrency-scaleout.md](09-concurrency-scaleout.md) shipped notes and the [benchmark table](../../prototypes/wo-rt-c/README.md).
|
||||
All numbers + find-and-fix stories: [09-concurrency-scaleout.md](09-concurrency-scaleout.md) shipped notes and the [benchmark table](../../runtime/README.md).
|
||||
|
||||
### Track 3 — Storage & durability (plans 10–12, 16) — 🔄 in progress
|
||||
|
||||
|
|
|
|||
114
docs/plan/compiler/2026-08-01-haxe-parity-language.md
Normal file
114
docs/plan/compiler/2026-08-01-haxe-parity-language.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Haxe-Parity Language Adoptions Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
>
|
||||
> **Style rule (user convention):** concept, reason, and required behavior in words only; the executor writes the code.
|
||||
|
||||
**Goal:** Plan 8 — implement every **adopt** row of the systems-track spec's Haxe keyword verdict table: the language grows switch expressions, records, optionals, try/catch/throw, enum payloads, abstracts, static members, using-extensions, modules, `is`, `pub(read)`, build flags, and interpolation — with the reject rows enforced as diagnostics.
|
||||
|
||||
**Architecture:** Plan 8 of the roadmap. Depends on OOP plans 1–3 (woc + wovm + corpus). Overwhelmingly compiler work in `compiler/src/`; the VM changes are exactly three, called out in their tasks: catch frames (try/catch), variant objects (enum payloads), and boxed optionals for scalars. Everything else lowers onto existing opcodes. The spec's verdict table (`docs/superpowers/specs/2026-08-01-systems-track-design.md` Part 1) is normative — this plan sequences it.
|
||||
|
||||
**Tech Stack:** OCaml stdlib (compiler), C11 libc (the three VM changes), conformance corpus.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- All OOP-track constraints carry over (stdlib-only OCaml, libc-only C, no commits — drafts to `.dev/commit.md`, ASan gate, docs under `docs/`).
|
||||
- **The verdict table is normative:** adopt rows land exactly as specified; reject rows produce diagnostics where they would parse (`extends`, `cast`, `Dynamic` as a type name) or stay absent where they would not.
|
||||
- **Every adoption ships with corpus fixtures** (golden run + must-fail) and an error-catalog entry for its new `WO-E` codes, in the same task.
|
||||
- **Doctrine unbroken:** no inheritance, no `Dynamic`, no macros — a task that finds itself needing one has found a plan defect; stop and ask.
|
||||
- **Format doc governs VM changes:** catch frames, variant layout, and boxed optionals each update `docs/plan/oop-vm/00-wob-format.md` in the task that introduces them.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
compiler/src/ lexer/parser/types/owner/emit extensions per task
|
||||
runtime/src/vm.c catch frames (Task 5)
|
||||
runtime/src/obj.c variant objects, boxed scalar optionals (Tasks 4, 6)
|
||||
tests/corpus/lang/ one fixture directory per adoption
|
||||
docs/plan/oop-vm/01-error-catalog.md grows per task
|
||||
docs/plan/oop-vm/00-wob-format.md grows with the three VM changes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Modules — `use` + directory-as-module
|
||||
|
||||
**Concept & reason:** foundation first: later tasks and the whole stdlib import through it. A `.wo` file's module is its directory; `use fs` (stdlib namespace) or `use shared/util` (project-relative) brings a module's public names into scope. Symbol resolution goes file → module → used modules → stdlib; collisions diagnose rather than shadow silently. Public means `pub`-marked (this task adds the `pub` marker for declarations; field accessors come in Task 8). Unused `use` warns. The stdlib namespaces resolve even though their members arrive in plan 9 — the resolver knows reserved namespace names now so plan 9 slots in without resolver changes.
|
||||
|
||||
- [ ] Failing fixtures: cross-module call via `use`; collision diagnostic; private-name-access diagnostic; unused-use warning golden.
|
||||
- [ ] Implement resolver + `pub`; corpus green; catalog entries.
|
||||
- [ ] Record commit draft: `feat(compiler): module system — directory-as-module, use resolution with collision/privacy diagnostics, pub marker, reserved stdlib namespaces.`
|
||||
|
||||
### Task 2: Small control surface — `break`/`continue`, `do…while`, interpolation, `const`
|
||||
|
||||
**Concept & reason:** the low-risk parity gaps, batched because each is a lexer/parser/emit touch with no typing subtlety. Loop control lowers to jumps with correct drop-set handling at early exits (the owner pass already computes scope-end drops for `return`; `break`/`continue` reuse that machinery — the one non-trivial bit, and its fixture proves an owned value dropped on `break`). String interpolation desugars to concatenation at parse time. `const NAME = literal` declares compile-time values usable in expressions and `#if`-adjacent contexts; `inline`-function requests are rejected with the table's reason.
|
||||
|
||||
- [ ] Failing fixtures: loop-control goldens incl. the owned-drop-on-break ASan case; do-while; interpolation with expressions; const usage; `inline fn` must-fail.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): break/continue/do-while with drop-correct early exits, string interpolation desugar, const values, inline-fn rejection.`
|
||||
|
||||
### Task 3: `switch` as expression
|
||||
|
||||
**Concept & reason:** Haxe's strongest habit and log-watcher's core idiom. `switch subject { case pattern: expr … default: expr }` is an expression; arms yield values of one unified type. Subjects: scalars, texts, union values. Exhaustiveness: switching a union without `default` requires every variant covered (diagnostic names the missing ones); scalars/texts require `default`. Lowering: compare-and-jump chains on existing opcodes (EQ/EQS/JZ); each arm is its own drop scope. Statement-position switch is the expression with a discarded value — one construct, not two.
|
||||
|
||||
- [ ] Failing fixtures: value-yielding switch goldens over ints/texts/unions; missing-variant must-fail; missing-default-on-scalar must-fail; arm-type-mismatch must-fail.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): switch expressions — exhaustive over unions (missing variants named), unified arm typing, EQ/JZ chain lowering with per-arm drop scopes.`
|
||||
|
||||
### Task 4: `typedef` records + enum payload variants
|
||||
|
||||
**Concept & reason:** the data-shape pair, together because both lower to the same VM notion (a class-table entry the source never declared as `class`). Records: `typedef Name = { field: Type, ?opt: Type }` — structural aliases; two typedefs with the same shape are the same type; record literals use the existing constructor-brace form; `?fields` type as optionals (Task 6 semantics — this task lands them as nullable-by-shape, Task 6 tightens handling). Enum payloads: union variants gain fields (`Pending | Failed(reason: Text)`); construction by variant name with arguments; a payload variant is a small heap object whose layout is a compiler-generated class entry with a variant tag the VM's existing header accommodates (format-doc update: the variant-tag convention). Switch (Task 3) binds payload fields in arms.
|
||||
|
||||
- [ ] Failing fixtures: record round-trips incl. optional-field omission; structural-equivalence golden (same shape interchangeable); payload construction + switch destructuring; wrong-payload-arity must-fail.
|
||||
- [ ] Implement compiler + the variant-object VM piece; green; format doc updated.
|
||||
- [ ] Record commit draft: `feat: typedef records (structural, ?fields) + enum payload variants (tagged variant objects, switch destructuring); format doc variant convention.`
|
||||
|
||||
### Task 5: `try` / `catch` / `throw` over the trap system
|
||||
|
||||
**Concept & reason:** the biggest VM change of the plan: **catch frames**. A `try` region registers a handler; a trap raised inside unwinds frames — running drop maps exactly as today — but stops at the nearest handler instead of the entry boundary, delivering the structured error record `{code, method, line, msg}` bound to the catch variable. `throw value` raises EXPLICIT with the value attached (the error record grows an optional payload slot — format doc + wob version note; coordinate like the plan-6 route-section bump). Uncaught behavior is byte-for-byte today's trap surface. Compiler side: `try expr catch (e) expr` expression form, both arms unified in type; the owner pass treats the catch arm as an alternate flow join.
|
||||
|
||||
- [ ] Failing fixtures: caught trap yields fallback (div0 probe pattern); drop maps still fire for frames skipped by the unwind (ASan big-class proof — the load-bearing test); throw-with-value caught upstream; uncaught still exits 1 with the plan-6 shaped error; nested try picks the nearest handler.
|
||||
- [ ] Implement VM catch frames + compiler lowering; all prior trap fixtures re-run unchanged; green.
|
||||
- [ ] Record commit draft: `feat: try/catch/throw — VM catch frames (unwind stops at nearest handler, drop maps intact, error payload slot), expression-form catch with flow-join ownership; uncaught surface unchanged.`
|
||||
|
||||
### Task 6: `?T` optionals with forced handling
|
||||
|
||||
**Concept & reason:** the null-safety story. `?T` admits nil; `T` never does — the diagnostic-enforced boundary. Representation: heap kinds use the zero word (the VM's existing null checks already trap on it — optionals make those unreachable by typing); scalar optionals box into a one-field cell (the VM piece — obj.c gains the box; format doc notes the convention). Narrowing: comparing against `null` narrows in the branch (`if x != null` makes `x` a `T` inside — Haxe's exact idiom); using a `?T` un-narrowed where `T` is required diagnoses. Stdlib returns (plan 9) and record `?fields` (Task 4) type as `?T` from here on.
|
||||
|
||||
- [ ] Failing fixtures: narrowing goldens; un-narrowed-use must-fail; nil propagation through record optional fields; boxed scalar optional round-trip; assignment of null to plain `T` must-fail.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat: ?T optionals — null-narrowing control flow, forced handling diagnostics, zero-word heap nil + boxed scalar cells; record ?fields and future stdlib returns typed ?T.`
|
||||
|
||||
### Task 7: `abstract` newtypes + `is`
|
||||
|
||||
**Concept & reason:** type-safety sugar pair, compiler-only. Abstracts: `abstract Money = Int` — a distinct compile-time type over a scalar representation, zero-cost at runtime (registers hold the raw scalar); mixing `Money` and `Int` diagnoses unless the declaration lists explicit `from`/`to` conversions; the existing stdlib scalars (Money, SKU) re-declare in-language, removing magic. `is`: runtime test on union values (variant membership — reads the Task-4 tag) and interface values (vtable membership — reuses the loader's satisfaction data); statically-decidable `is` diagnoses as always-true/false instead of compiling to a runtime check.
|
||||
|
||||
- [ ] Failing fixtures: abstract mixing must-fail + explicit-conversion golden; zero-cost proof (disassembly golden shows raw scalar ops); `is` on unions/interfaces; statically-known `is` must-fail.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): abstract newtypes (zero-cost, explicit from/to; Money/SKU de-magicked) + is on unions/interfaces with static-decidability diagnostic.`
|
||||
|
||||
### Task 8: `static` members, `using` extensions, `pub(read)` accessors
|
||||
|
||||
**Concept & reason:** the organization trio. Statics: `static fn`/`static const` on classes — namespaced calls (`Flock.held(path)`) with no instance, no `self`; lower as free fns with mangled names. Using: `using shared/textutil` makes that module's free fns whose first parameter matches a type callable as methods on it (`s.words()` for `words(s: Text)`) — resolution is compile-time only, no dispatch table, collisions with real methods diagnose (real method wins is a lie surface; error instead). Accessors: `pub(read) field` exports read access, writes stay owner-class-only — the Haxe `(default, null)` pattern; enforcement in the typechecker at field-write sites.
|
||||
|
||||
- [ ] Failing fixtures: static call goldens + self-in-static must-fail; using-extension call golden + collision must-fail; pub(read) external-write must-fail + internal-write golden.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): static members (mangled free-fn lowering), using static-extensions (compile-time, collision-diagnosed), pub(read) accessor enforcement.`
|
||||
|
||||
### Task 9: `#if` build flags + reject-row enforcement + closeout
|
||||
|
||||
**Concept & reason:** last adoptions and the table's other half. Build flags: `woc -D name` defines flags; `#if name / #else / #end` sections include/exclude at the token stream level (flag names only, no expression language — the spec's limit); undefined flags are false; nesting allowed. Reject enforcement: the keywords that would otherwise parse get targeted diagnostics with the table's reasons — `extends`/`implements`/`super`/`override` on class declarations, `cast`, `Dynamic`/`untyped` as type/expression, `macro`, `extern`, `operator` — each cites the spec section. Closeout: error catalog complete for the track, keyword table in the spec annotated with shipped status, `just oop-accept` runs the grown corpus, CLAUDE.md language notes synced.
|
||||
|
||||
- [ ] Failing fixtures: #if inclusion/exclusion goldens (portable-style flag), nesting, undefined-flag default; one must-fail per reject keyword with the doctrine message.
|
||||
- [ ] Implement; full corpus green; docs synced.
|
||||
- [ ] Record commit draft: `feat(compiler): #if build flags (token-level, flag names only) + reject-row diagnostics citing doctrine (extends/cast/Dynamic/macro/extern/operator...); systems-track language surface complete, catalog + docs synced.`
|
||||
|
||||
---
|
||||
|
||||
## Plan self-review notes
|
||||
|
||||
- **Spec coverage (Part 1 + success criterion 1):** every adopt row has a task (modules T1, control/const/interp T2, switch T3, typedef+enum T4, try/catch/throw T5, optionals T6, abstract+is T7, static/using/pub(read) T8, #if T9); every reject row enforced in T9 or absent by construction. Criterion 1's "corpus coverage per row" is each task's fixture requirement.
|
||||
- **VM changes fenced:** exactly three (catch frames, variant objects, boxed scalar optionals), each with a format-doc update in its task; everything else is lowering.
|
||||
- **Order rationale:** modules first (everything imports), data shapes before optionals (records carry ?fields), try/catch after switch (arms reuse unified-type machinery), rejects last when all parse paths exist to hang diagnostics on.
|
||||
137
docs/plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md
Normal file
137
docs/plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Bytecode Emit + End-to-End Corpus + Single Binary Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
>
|
||||
> **Style rule (user convention):** this plan states concept, reason, and required behavior in words. The executor writes the actual code at implementation time; nothing here is copy-paste source.
|
||||
|
||||
**Goal:** Close milestone 1: `woc` emits `.wob` bytecode that `wovm` executes, a three-kind conformance corpus proves the whole spec's semantics end to end, and `woc build` produces the single self-contained binary — then the spec's five success criteria are checked off as the acceptance gate.
|
||||
|
||||
**Architecture:** Plan 3 of 3 for `docs/superpowers/specs/2026-08-01-oop-compiler-vm-design.md`. Depends on plan 1 (`runtime/` wovm — loader, interpreter, memory model) and plan 2 (`compiler/` front — typed AST plus the four owner tables). This plan adds the emitter module to `compiler/`, the conformance corpus and its runner at the repo root (`tests/corpus/`), and the packaging path. The corpus is the spine: every language semantic lands as a fixture with an expected outcome, and the same harness carries forward to sub-projects 2–5.
|
||||
|
||||
**Tech Stack:** OCaml stdlib (emitter), C (small wovm additions: gc pump, self-exec trailer), bash + just (harness), ASan gate from plan 1.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- All plan-1 and plan-2 global constraints carry over verbatim (libc-only C, stdlib-only OCaml, no commits — drafts to `.dev/commit.md`, docs under `docs/`).
|
||||
- **The format doc governs:** every emitted byte follows `docs/plan/oop-vm/00-wob-format.md`; any needed format change is a stop-and-ask, not a local invention.
|
||||
- **Every emitted module must pass the plan-1 loader's validation** — a `woc`-produced image rejected by `wovm` is always an emitter bug (round-trip rule).
|
||||
- **Register budget:** methods needing more than 64 registers are a compile-time diagnostic (WO-E4xx range for emitter limits), never a truncation.
|
||||
- **Corpus outcomes are exact:** expected stdout byte-for-byte, expected `WO-E###` code, or expected trap code — no substring-ish matching.
|
||||
- **Spec success criteria are the acceptance gate** (spec "Success criteria" 1–5); the final task runs all five.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
compiler/src/
|
||||
emit.ml lowering + register allocation + .wob serialization (Task 1)
|
||||
disasm.ml .wob disassembler backing --dump-bc goldens (Task 1)
|
||||
tests/corpus/
|
||||
run/ fixture.wo + fixture.out (expected stdout) (Tasks 2–3)
|
||||
compile-fail/ fixture.wo + fixture.code (expected WO-E###) (Task 4)
|
||||
trap/ fixture.wo + fixture.trap (expected trap code) (Task 4)
|
||||
gc/ cycle fixtures with budget expectations (Task 5)
|
||||
scripts/oop-e2e.sh corpus runner (invoked by just oop-e2e) (Task 2)
|
||||
runtime/src/main.c gc pump + self-exec trailer detection (Tasks 5–6)
|
||||
compiler/bin/main.ml emit mode, woc build packaging (Tasks 1, 6)
|
||||
docs/plan/oop-vm/ 02-corpus.md (how to add fixtures) (Task 2)
|
||||
justfile oop-e2e, oop-accept recipes (Tasks 2, 8)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1: The emitter
|
||||
|
||||
**Files:** create `compiler/src/emit.ml`, `compiler/src/disasm.ml`; modify `compiler/bin/main.ml` (emit mode, `--dump-bc`); golden fixtures; a round-trip check against the plan-1 loader.
|
||||
|
||||
**Concept & reason:** lower the typed, owner-annotated AST into `.wob` per the format doc. The pieces, each stated as a requirement:
|
||||
|
||||
- **Register allocation:** a simple scope-stack allocator — parameters first (the window convention fixes their slots), locals on declaration, expression temporaries from a high-water pool, freed on statement end. Over-budget methods (>64) diagnose, never truncate.
|
||||
- **Calls:** the window convention — arguments placed at consecutive registers, callee index for direct calls, global slot id for interface calls (from the plan-2 satisfaction sets, which also serialize as the vtable section).
|
||||
- **Ownership lowering** consumes the four plan-2 tables literally: real transfers become plain moves (the VM treats MOVE as the move); scope-end drop sets place DROP ops including on early-return paths; rc sites emit RC_INC/RC_DEC except where marked elided; residual sites — and only residual sites — emit borrow/release ops around the region. Zero-cost-when-provable is the spec's core promise: a golden fixture must show a fully-proven method emitting no borrow or rc ops at all.
|
||||
- **Drop maps and line tables:** at every call- or trap-capable pc, the live owned/gc registers serialize as the drop-table masks; source lines serialize per the format. This is what makes plan-1's "traps never leak" hold for compiled code.
|
||||
- **Constants and classes:** deduplicated constant pool; class table with the derived field kinds; `DB_STUB` for DbStub nodes; builtins lowered to the BUILTIN ids of the format doc.
|
||||
- **Terminator rule:** every method's code ends in a terminator (the loader rejects otherwise — the emitter appends the implicit void return where control can fall off).
|
||||
- **Disassembler:** renders a `.wob` back to readable mnemonics for `--dump-bc` goldens — pinned dumps are how emitter regressions surface before the corpus even runs.
|
||||
|
||||
- [ ] Failing goldens: disassembly of an arithmetic method, a method with owned locals (visible DROPs + drop-table rendering), an elision fixture (no borrow/rc ops), a residual fixture (borrow ops present), an interface fixture (vtable section rendered).
|
||||
- [ ] Implement; goldens green. New `WO-E4xx` emitter-limit codes (register over-budget) are appended to `docs/plan/oop-vm/01-error-catalog.md` in the same change — the catalog stays complete.
|
||||
- [ ] Round-trip gate: every corpus-bound fixture emitted so far loads clean in `wovm` (build plan-1's runtime if not built).
|
||||
- [ ] Record commit draft: `feat(compiler): .wob emitter — scope-stack register allocation with 64-cap diagnostic, window calls + vtable serialization, ownership lowering from owner tables (moves/drops/rc-elision/residual-only borrow ops), drop maps + line tables, dedup const pool, DB_STUB, implicit terminators; disasm.ml for --dump-bc goldens; loader round-trip gate.`
|
||||
|
||||
### Task 2: Conformance harness
|
||||
|
||||
**Files:** create `scripts/oop-e2e.sh`, `tests/corpus/run/` seeds, `docs/plan/oop-vm/02-corpus.md`; modify `justfile`.
|
||||
|
||||
**Concept & reason:** the spec's testing spine, mechanized. The runner walks the three corpus kinds and enforces exact outcomes: `run/` fixtures compile with `woc`, execute with `wovm`, and their stdout must equal the `.out` file byte-for-byte; `compile-fail/` fixtures must fail compilation with exactly the `WO-E###` named in their `.code` file; `trap/` fixtures must exit 1 with the trap code named in their `.trap` file parsed from wovm's fixed stderr line. Any other outcome — wrong code, unexpected success, loader rejection — is a failure naming the fixture. The runner prints a one-line-per-fixture summary and a final tally; `just oop-e2e` wires it. The corpus doc explains how to add a fixture of each kind (the contribution path for every later sub-project). Seeds: a hello (print/print_int), arithmetic + control flow, a method-call fixture, and an interface-dispatch fixture.
|
||||
|
||||
- [ ] Failing: runner exists, seeds in place, runs against the Task-1 emitter — seed fixtures green or their failures fixed.
|
||||
- [ ] Record commit draft: `feat(tests): conformance harness — three-kind corpus (run/compile-fail/trap) with exact-outcome matching, just oop-e2e, corpus contribution doc; seed fixtures (hello, arithmetic, methods, interface dispatch).`
|
||||
|
||||
### Task 3: Pricing-demo corpus
|
||||
|
||||
**Files:** add `tests/corpus/run/` and `tests/corpus/trap/` fixtures derived from `docs/examples/pricing/`.
|
||||
|
||||
**Concept & reason:** the spec names the pricing demo's logic subset as the milestone-1 workload — it becomes executable truth here. Fixtures: the pure `discounted` computation; `current_price` through a `multi` with `latest`; container round-trips (multi push/count, map set/get over SKU keys); text handling (`words`, concat); and `set_price` — whose `insert` lowers to DB_STUB — as a trap fixture expecting the DB code (the spec's parse-but-trap story, proven end to end). Where the original demo files use surface not in milestone 1, the fixture carries the minimal adaptation with a comment naming what was trimmed — the corpus never silently diverges from the sample it mirrors.
|
||||
|
||||
- [ ] Add fixtures; corpus green; ASan-built wovm run of the whole corpus stays clean.
|
||||
- [ ] Record commit draft: `test(corpus): pricing-demo logic subset — discounted, current_price via multi/latest, container + text builtins, set_price DB_STUB trap fixture.`
|
||||
|
||||
### Task 4: Ownership + trap corpora
|
||||
|
||||
**Files:** add `tests/corpus/compile-fail/` and `tests/corpus/trap/` fixtures.
|
||||
|
||||
**Concept & reason:** spec success criterion 3, verbatim: every must-fail program fails with its expected code, every must-trap program traps with its expected code, ASan reports nothing. Compile-fail seeds mirror the plan-2 ownership suite as end-user programs (move-after-use, borrow escape via return, double `mut` on a provable alias, plus a type error and an unsatisfied interface for the E2xx range). Trap seeds exercise the runtime's residual checks through compiled code: aliased `mut` through runtime indices (the canonical residual — traps BORROW), missing map key (KEY), division by zero (DIV0, and the error line must match the fixture's marked source line, proving line tables survive emission). The distinction this task pins: provable violations fail at compile time, unprovable ones trap at runtime — the hybrid boundary made testable.
|
||||
|
||||
- [ ] Add fixtures; corpus green under the ASan gate; the div0 fixture asserts the reported line.
|
||||
- [ ] Record commit draft: `test(corpus): ownership compile-fail suite (E3xx as user programs) + runtime trap suite (residual mut-alias BORROW, map KEY, DIV0 with line assertion) — the hybrid compile/runtime boundary pinned.`
|
||||
|
||||
### Task 5: @gc cycle collection end to end
|
||||
|
||||
**Files:** add `tests/corpus/gc/` fixtures; modify `runtime/src/main.c` (gc pump).
|
||||
|
||||
**Concept & reason:** spec success criterion 4 needs an observable collector in a real program. wovm gains a minimal gc pump: after the entry method returns, it drives collection steps until the candidate buffer empties, with the per-step budget from a `WO_GC_BUDGET` environment variable (defaulting sensibly); a `WO_GC_TRACE` variable makes each step print freed/visited counts to stderr — the observable the fixtures assert. Fixtures: a `@gc` cycle built and abandoned in `.wo` (collected — ASan proves the frees); an externally-held cycle (survives); and a budget fixture (trace shows multiple bounded steps rather than one unbounded sweep — "no pause longer than the configured slice" made visible). This pump is deliberately minimal: the real scheduler-integrated pacing belongs to sub-project 2; the interface (budgeted step calls) is already the plan-1 collector's.
|
||||
|
||||
- [ ] Failing fixtures; implement the pump; green + ASan-clean.
|
||||
- [ ] Record commit draft: `feat(runtime)+test(corpus): wovm gc pump (WO_GC_BUDGET steps after entry, WO_GC_TRACE observability); gc fixtures — abandoned cycle collected, held cycle survives, budget slicing visible.`
|
||||
|
||||
### Task 6: Single binary — `woc build`
|
||||
|
||||
**Files:** modify `compiler/bin/main.ml` (build mode), `runtime/src/main.c` (self-exec detection); smoke additions to the harness.
|
||||
|
||||
**Concept & reason:** spec success criterion 5 and the language's one-binary promise. `woc build <dir> -o app`: compile, then copy the `wovm` executable and append the `.wob` image plus a fixed-size trailer (magic + payload offset/length). wovm startup order becomes: read its own executable (via /proc/self/exe), check for the trailer — if present, load the embedded image and ignore argv; otherwise require the `.wob` path argument as today. Locating `wovm` to copy: an explicit `--runtime` flag wins, else a repo-relative default; a missing runtime binary is a clear error telling the user to build it. Smoke: build the hello fixture into a single file, move it to a temp directory (proving self-containment), run it with no arguments, diff output; corrupt the trailer and confirm the clear failure mode.
|
||||
|
||||
- [ ] Failing smoke; implement both halves; green.
|
||||
- [ ] Record commit draft: `feat: woc build single binary — wovm copy + appended .wob + trailer, self-exec detection via /proc/self/exe with argv fallback; relocation smoke + corrupt-trailer failure mode.`
|
||||
|
||||
### Task 7: Parity harness against the Rust runtime
|
||||
|
||||
**Files:** create `scripts/oop-parity.sh`; a small overlap manifest in `tests/corpus/`.
|
||||
|
||||
**Concept & reason:** the spec's cheap insurance — where milestone-1 semantics overlap the shipped 13b method executor in `crates/rt`, both stacks must agree until the Rust runtime retires. The harness takes the manifest of overlap fixtures (pure method logic: arithmetic, text, control flow — no containers or interfaces, which 13b lacks), runs each through the new stack directly, and through the Rust runtime by starting `wo run` against a fixture-derived project and invoking the method over its existing RPC route, then compares results. Non-overlapping features are out of manifest by construction, not skipped at runtime. This stays a separate opt-in recipe (`just oop-parity`) — it needs a cargo build and a port, too heavy for the per-change gate.
|
||||
|
||||
- [ ] Implement harness + manifest with the overlap fixtures; run once green; document the manifest criteria in the corpus doc.
|
||||
- [ ] Record commit draft: `test: parity harness — overlap manifest run on both stacks (wovm direct vs crates/rt 13b RPC), just oop-parity opt-in recipe.`
|
||||
|
||||
### Task 8: Acceptance gate + docs closeout
|
||||
|
||||
**Files:** modify `justfile` (`oop-accept`), `CLAUDE.md`, `compiler/README.md`, `runtime/README.md`, `docs/plan/00-kanban.md`; the spec gets its criteria checked.
|
||||
|
||||
**Concept & reason:** run milestone 1's definition of done as one command. `just oop-accept` executes, in order: compile-time measurement of the pricing subset (must be < 100 ms — criterion 1); the full conformance corpus under ASan including gc fixtures (criteria 2–4); the single-binary smoke (criterion 5); plus plan-1's `wovm-test` and plan-2's `woc-test` full gates. Docs closeout: CLAUDE.md gains the three-directory story (compiler/, runtime/, corpus) and the recipes; both READMEs cross-link; the kanban records the milestone. Anything failing here is a defect in an earlier task — this task adds no functionality, only the gate and the paper trail.
|
||||
|
||||
- [ ] Wire the recipe; run it; fix nothing here — route failures back to their tasks.
|
||||
- [ ] Sync docs; check the five criteria off in a dated note appended to the spec.
|
||||
- [ ] Record commit draft: `chore: oop-accept acceptance gate (5 spec criteria + both unit gates in one recipe); docs closeout — CLAUDE.md three-directory story, README cross-links, kanban milestone note, spec criteria checked.`
|
||||
|
||||
---
|
||||
|
||||
## Plan self-review notes
|
||||
|
||||
- **Spec coverage:** every success criterion has a task (1→Task 8 measurement, 2→Tasks 2–3, 3→Task 4, 4→Task 5, 5→Task 6); ownership-lowering zero-cost promise pinned by Task 1 goldens; parse-but-trap proven in Task 3; hybrid boundary pinned in Task 4; parity per spec's "later, cheap" in Task 7.
|
||||
- **Dependency honesty:** Tasks 1–8 need plans 1 and 2 complete. Task 5 and 6 modify `runtime/src/main.c` — small, contained additions to plan-1 code, called out rather than hidden.
|
||||
- **Known accepted simplifications, documented in their tasks:** gc pump is post-exit stepping (scheduler pacing is sub-project 2); parity manifest excludes features 13b lacks by construction; single-binary trailer is append-based (no ELF section games).
|
||||
|
||||
## Execution note
|
||||
|
||||
Execution order across plans: plan 1 (C runtime) and plan 2 (OCaml front) are independent of each other; plan 3 requires both. Nothing in this plan runs today — documents only, per the user's instruction.
|
||||
151
docs/plan/compiler/2026-08-01-woc-compiler-front.md
Normal file
151
docs/plan/compiler/2026-08-01-woc-compiler-front.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# woc Compiler Front (OCaml) Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
>
|
||||
> **Style rule (user convention):** this plan states concept, reason, and required behavior in words. The executor writes the actual code at implementation time; nothing here is copy-paste source.
|
||||
|
||||
**Goal:** Build the OCaml compiler front — lexer, parser, typechecker, and the mutable-value-semantics ownership pass — so milestone-1 `.wo` programs typecheck, ownership-check, and produce the analysis tables the plan-3 emitter consumes, with the diagnostic quality the spec calls "the product".
|
||||
|
||||
**Architecture:** Plan 2 of 3 for the approved spec `docs/superpowers/specs/2026-08-01-oop-compiler-vm-design.md`. New root-level `compiler/` directory: a dune project with one library (token, lexer, ast, parser, types, owner, diag, dump modules) and one executable (`woc`). Handwritten lexer and recursive-descent parser mirroring the conventions of the Rust runtime's front end (`crates/rt/src/{token,lexer,ast,parser}.rs`) — same newline significance, same keyword gotchas. No bytecode yet: plan 3 owns emission; this plan's contract with plan 3 is the typed AST plus per-site ownership decision tables, exposed through stable dump formats.
|
||||
|
||||
**Tech Stack:** OCaml (≥ 4.14) + dune, both from apt — NOT currently installed on the dev box; Task 1 installs them. OCaml stdlib only: no opam packages, no Menhir, no ppx. Golden-file testing under `dune runtest` with a tiny hand-rolled assert/diff runner.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- **OCaml stdlib only** — no Menhir, no ppx, no opam libraries; dune is the build runner only (spec dependency doctrine).
|
||||
- **Handwritten lexer + recursive-descent parser** (spec approach A).
|
||||
- **Newline-significant lexing** — newline tokens are real and parsers use them, exactly like the Rust runtime (CLAUDE.md gotcha).
|
||||
- **Keyword discipline** — `self`, `me`, `subscribe`, `receive`, lowercase `insert`/`select` stay identifiers; the parser recognizes them positionally (CLAUDE.md gotcha; breaking this breaks existing `.wo` samples).
|
||||
- **Diagnostics carry stable `WO-E###` codes**, file:line:col, a source excerpt, and — for ownership errors — both conflicting sites (spec section 6: "these messages are the product").
|
||||
- **Multi-error reporting** — the parser recovers at declaration/statement sync points; first-error-stop is a defect.
|
||||
- **Compile-speed budget:** the pricing-demo logic subset must lex+parse+check in well under 100 ms (spec success criterion 1 measures the full pipeline; the front end must leave headroom).
|
||||
- **Git: the executing agent NEVER runs `git commit`** — append the given draft to `.dev/commit.md`; the user commits.
|
||||
- **Docs rule:** documentation under `docs/`; `compiler/README.md` stays an orientation README.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
compiler/
|
||||
dune-project
|
||||
README.md orientation: pipeline map, build/test commands
|
||||
src/
|
||||
dune library stanza
|
||||
diag.ml diagnostic type, WO-E code registry, rendering, exit codes (Task 2)
|
||||
token.ml lexer.ml token variants + newline-significant lexer (Task 3)
|
||||
ast.ml parser.ml AST + declaration/statement/expression parsers (Tasks 4–5)
|
||||
types.ml symbols, field-kind derivation, structural interfaces, expression typing (Task 6)
|
||||
owner.ml MVS flow analysis, ownership errors, residual/drop/rc tables (Task 7)
|
||||
dump.ml stable text dumps of tokens/AST/typed info/owner decisions (grows Tasks 3–7)
|
||||
bin/
|
||||
dune main.ml the woc executable: CLI, file discovery, pipeline driver (Task 1, polished Task 8)
|
||||
test/
|
||||
dune runner.ml golden runner: compile fixture with a dump flag, diff expected (Task 3 onward)
|
||||
golden/ fixture .wo files + .expected files, one directory per stage
|
||||
docs/plan/oop-vm/01-error-catalog.md every WO-E code with meaning (Task 8)
|
||||
justfile woc-build / woc-test recipes (Task 1)
|
||||
```
|
||||
|
||||
## Contract with plan 3 (what "done" hands over)
|
||||
|
||||
The emitter consumes: (1) the typed AST; (2) a per-class field-kind table using the six `.wob` kinds of `docs/plan/oop-vm/00-wob-format.md`; (3) the structural-satisfaction set (which class satisfies which interface via which methods); (4) the owner pass's per-site tables — moves, scope-end drop sets, rc inc/dec sites with elision marks, and residual-borrow sites. All four have stable dump renderings, so plan 3 can pin them with goldens before emitting a single opcode.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Toolchain + project scaffold
|
||||
|
||||
**Files:** create `compiler/dune-project`, `compiler/src/dune`, `compiler/bin/{dune,main.ml}`, `compiler/README.md`; modify `justfile`.
|
||||
|
||||
**Concept & reason:** OCaml and dune are not on the dev box — install both from apt (Ubuntu 24.04 ships OCaml 4.14-era packages; that is the version floor). Scaffold the dune project: an empty library and a `woc` executable that prints usage and exits 2 — establishing the exit-code contract early (0 = clean, 1 = diagnostics reported, 2 = usage/IO failure). Root justfile gains `woc-build` and `woc-test`. The README states the pipeline map and the one-command build/test story, mirroring how `runtime/README.md` orients its half.
|
||||
|
||||
- [ ] Install: `sudo apt install ocaml dune` (or confirm present); record versions in the README's requirements line.
|
||||
- [ ] Scaffold project; `dune build` produces the `woc` binary; running it with no args prints usage, exit 2.
|
||||
- [ ] Add just recipes; verify both work from repo root.
|
||||
- [ ] Record commit draft: `feat(compiler): OCaml/dune scaffold — woc executable stub with exit-code contract (0 clean/1 diagnostics/2 usage), just woc-build/woc-test, README orientation.`
|
||||
|
||||
### Task 2: Diagnostics module
|
||||
|
||||
**Files:** create `compiler/src/diag.ml`; test via the runner (unit assertions).
|
||||
|
||||
**Concept & reason:** every later stage reports through one channel, so it comes first. A diagnostic is: stable code (`WO-E###`), severity, file, line, column, message, and zero or more *related sites* (file/line/col + short label) — the ownership pass needs two-site errors ("moved here … used here"). Rendering shows the source excerpt with a caret under the column, the way rustc/OCaml do; related sites render beneath the primary. A collector accumulates diagnostics in source order across recovery, deduplicates identical (code, site) pairs, and decides the process exit code. Code ranges are reserved per stage now — lexing E0xx, parsing E1xx, types E2xx, ownership E3xx — so the Task-8 catalog is an enumeration, not an archaeology dig.
|
||||
|
||||
- [ ] Failing unit tests: rendering shape (code, position, caret excerpt, related site), collector ordering and dedup, exit-code decision.
|
||||
- [ ] Implement; green under `dune runtest`.
|
||||
- [ ] Record commit draft: `feat(compiler): diag module — WO-E coded diagnostics with excerpts, related sites (two-site ownership errors), ordered dedup collector, exit-code decision.`
|
||||
|
||||
### Task 3: Tokens + lexer
|
||||
|
||||
**Files:** create `compiler/src/token.ml`, `compiler/src/lexer.ml`, `compiler/src/dump.ml` (token dump), `compiler/test/runner.ml` + golden fixtures.
|
||||
|
||||
**Concept & reason:** the lexer defines what the language literally is, and it must agree with the Rust runtime's lexer on every convention or the two front ends will diverge on the same samples. Behaviors: position-tracked tokens; **newline tokens are emitted, never filtered**; `--` line comments; integer and string literals; annotation introducer `@`; the milestone-1 keyword set (declaration and statement words like type, class, interface, fn, let, mut, take, return, if, else, while, for, in, plus uppercase SQL-layer INSERT/SELECT) — and, critically, the *non-keywords*: `self`, `me`, `subscribe`, `receive`, lowercase `insert`/`select` lex as plain identifiers (CLAUDE.md gotcha — adding them to the keyword map breaks `expose subscribe` lists and method bodies). Unknown characters produce a lexing diagnostic and skip, so one bad byte doesn't kill the file. The golden framework arrives here: fixture `.wo` in, `--dump-tokens` out, diffed against an `.expected` file; a bless mode (environment variable) rewrites expectations intentionally.
|
||||
|
||||
- [ ] Failing goldens: a representative fixture covering comments, newlines, literals, annotations; a gotcha fixture proving the non-keyword identifiers.
|
||||
- [ ] Implement lexer + dump + runner; goldens green.
|
||||
- [ ] Record commit draft: `feat(compiler): newline-significant lexer mirroring rt conventions (self/me/subscribe/insert stay idents), --dump-tokens, golden test framework with bless mode.`
|
||||
|
||||
### Task 4: AST + declaration parser
|
||||
|
||||
**Files:** create `compiler/src/ast.ml`, `compiler/src/parser.ml` (declarations); extend `dump.ml` (`--dump-ast`); golden + must-fail fixtures.
|
||||
|
||||
**Concept & reason:** recursive descent over declarations: `interface` (method signatures only), `class` and `type` (identical field grammar — scalars, defaults including the explicit now() form, `ref T`, `multi T`, `map<K,V>`, field annotations like unique), type-level annotations (`@table` with its known keys, `@gc` bare, unknown annotation *names* skip silently — rt convention), and `fn` methods with the three parameter conventions (default borrow, `mut`, `take`). Two behaviors are load-bearing and get their own fixtures: **skip-on-block** — `service`/`policy`/`on <event>` blocks parse-and-discard with a brace-depth counter, because object literals inside trigger actions contain `}` that must not close the type (the exact bug rt's counter exists for); and **recovery** — a broken declaration syncs to the next top-level keyword and parsing continues, so one bad class yields one diagnostic, not a cascade. AST nodes carry positions and unique ids (the owner pass and emitter key their side tables on node ids).
|
||||
|
||||
- [ ] Failing goldens: pricing-demo-shaped class/interface fixtures dump correctly; skip-on-block fixture with a nested object literal; a two-error fixture reports both.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): declaration parser — class/type/interface/fn signatures, @gc/@table annotations, brace-depth skip-on-block (service/policy/on), decl-level recovery, --dump-ast goldens.`
|
||||
|
||||
### Task 5: Statement + expression parser
|
||||
|
||||
**Files:** extend `parser.ml`, `ast.ml`, `dump.ml`; golden + must-fail fixtures.
|
||||
|
||||
**Concept & reason:** method bodies. Statements: `let` (with optional type ascription), assignment, `if`/`else`, `while`, `for … in`, `return`, expression statements. Expressions with a precedence ladder: arithmetic, comparison, text concatenation, unary minus, calls (free, method, and interface-typed method calls share one call node), field access chains, indexing (the residual-borrow trigger later), and **constructor literals** — `ClassName { field: expr, … }`, recognized by the two-token shape identifier-then-brace in expression position (the same shape trick rt uses for select expressions; this decision is normative here: milestone-1 construction syntax is the brace literal, no `new` keyword). SQL-layer statements (`insert …`, `select …`) parse into a single opaque DbStub node capturing their token span — the grammar stays whole, plan 3 emits the DB_STUB trap for them (spec's parse-but-trap story). Statement-level recovery syncs at newlines/semicolons.
|
||||
|
||||
- [ ] Failing goldens: a body fixture exercising every statement and precedence level; a constructor-literal fixture; an insert/select fixture dumping DbStub nodes; a recovery fixture with two body errors.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): statement/expression parser — precedence ladder, constructor literals by ident-brace shape, indexing, insert/select as DbStub spans, statement recovery.`
|
||||
|
||||
### Task 6: Typechecker
|
||||
|
||||
**Files:** create `compiler/src/types.ml`; extend `dump.ml` (typed-info dump); golden + must-fail (WO-E2xx) fixtures.
|
||||
|
||||
**Concept & reason:** three products. (1) **Symbols and field kinds:** a two-pass walk (declare all, then check bodies) builds class/interface/free-fn tables and derives each field's `.wob` kind — Int/Bool/Money/Timestamp/Id scalars → SCALAR, Text-like → TEXT, class-typed field → OWNED, `@gc`-class-typed → GCREF, `multi` → MULTI, `map` → MAP, and `ref T` → SCALAR (it is an id link, per spec section 3 rule 4). (2) **Structural interface satisfaction:** a class satisfies an interface exactly when it has a method matching every signature (name, arity, parameter types, return type); the satisfaction set — with the concrete method chosen per slot — is recorded for the emitter's vtables, and calling an interface method on a non-satisfying class is an error naming the missing/mismatched signature. (3) **Expression typing:** every expression node gets a type; checks cover operator operand types, call arity/types, field existence, constructor completeness (every field initialized or defaulted), method receiver rules, and builtin signatures (now, latest, count, words, print, print_int and the container operations) matching the VM's builtin table in the format doc. `self` types as the enclosing class; whether a method *mutates* (writes any field of self, directly or via a mut call) is computed here and recorded — the spec says self mutability is inferred, and the owner pass consumes the flag.
|
||||
|
||||
- [ ] Failing fixtures: typed-dump goldens for the pricing shapes; must-fail suite — type mismatch, unknown field, bad arity, unsatisfied interface (message names the missing method), incomplete constructor — each asserting its WO-E2xx code.
|
||||
- [ ] Implement; green.
|
||||
- [ ] Record commit draft: `feat(compiler): typechecker — two-pass symbols, .wob field-kind derivation (ref=scalar id, @gc=gcref), structural interface satisfaction sets for vtables, expression typing incl. builtins and constructor completeness, inferred self-mutability.`
|
||||
|
||||
### Task 7: Ownership pass (mutable value semantics)
|
||||
|
||||
**Files:** create `compiler/src/owner.ml`; extend `dump.ml` (`--dump-owner`); ownership must-fail suite (WO-E3xx) + decision goldens.
|
||||
|
||||
**Concept & reason:** the spec's novel core, and the reason this plan exists. A per-function forward dataflow walk (loops analyzed to a fixpoint by re-running the body once against joined states — conservative and simple) tracks each owned local through states: live, moved, borrowed. The MVS rules it enforces, each with a two-site diagnostic: use-after-move (assignment, return, `take` argument passing, and constructor field initialization are the move sites); move-while-borrowed; conflicting `mut` borrows of the same place where the analysis can prove aliasing; and the escape rule — borrows cannot be stored or returned (field kinds already forbid storage; returning a borrowed parameter's alias is the case caught here). `@gc`-typed values are exempt from all of it — aliasing them is legal by design.
|
||||
|
||||
Beyond errors, the pass computes the four tables plan 3's emitter consumes, keyed by AST node id: **move sites** (which MOVE is a real transfer); **scope-end drop sets** (which owned locals die at each scope exit — the source of DROP placement and of drop-map masks at every call/trap-capable site); **rc sites** (gcref alias creation/destruction, marked elided when creation and destruction are provably balanced in the same scope); and **residual sites** (places static proof fails — the canonical case: two `mut` element accesses through runtime indices in one region — where the emitter must emit runtime borrow ops). The `--dump-owner` rendering lists all four tables in source order; goldens pin them so emitter changes can never silently shift semantics.
|
||||
|
||||
- [ ] Failing must-fail suite: move-after-use, move-while-borrowed, double-mut on a provable alias, borrow escape via return — each with its WO-E3xx code and both sites in the message.
|
||||
- [ ] Failing decision goldens: a fixture per table — moves, drops (including early-return paths), rc elision (balanced) vs kept (escaping alias), residual marking on indexed double-mut.
|
||||
- [ ] Implement; all green. Then the perf smoke: time `woc` (check-only) on the pricing logic subset — comfortably inside the 100 ms budget.
|
||||
- [ ] Record commit draft: `feat(compiler): MVS ownership pass — flow analysis with loop fixpoint, two-site errors (use-after-move, move-while-borrowed, double-mut, borrow escape), @gc exemption, and the four emitter tables (moves, scope-end drops, rc elision, residual borrow sites) with --dump-owner goldens.`
|
||||
|
||||
### Task 8: Driver polish + error catalog
|
||||
|
||||
**Files:** modify `compiler/bin/main.ml`; create `docs/plan/oop-vm/01-error-catalog.md`; modify `CLAUDE.md` (commands section), `compiler/README.md`.
|
||||
|
||||
**Concept & reason:** make `woc` behave like the toolchain the spec promises. Directory input discovers every `.wo` file under the path (same discovery contract as `wo run`); multiple files compile as one program (symbols span files); diagnostics print in (file, line) order regardless of discovery order; the dump flags operate per stage and compose with check-only mode. The error catalog documents every WO-E code shipped in Tasks 2–7 with a one-line meaning and an example message — the doc the conformance corpus (plan 3) and future users cite. CLAUDE.md's commands section gains the two just recipes and the one-line pipeline description.
|
||||
|
||||
- [ ] Failing tests: directory discovery + cross-file symbol resolution fixture; diagnostic ordering fixture.
|
||||
- [ ] Implement; write the catalog (enumerating the registry — no code left uncataloged); sync docs.
|
||||
- [ ] Full gate: `just woc-test` green; perf smoke re-run.
|
||||
- [ ] Record commit draft: `feat(compiler): woc driver — directory discovery, cross-file programs, ordered diagnostics; docs/plan/oop-vm/01-error-catalog.md (full WO-E registry); CLAUDE.md commands sync.`
|
||||
|
||||
---
|
||||
|
||||
## Plan self-review notes
|
||||
|
||||
- **Spec coverage (plan-2 slice):** handwritten OCaml front end, stdlib-only, newline/keyword conventions, structural interfaces, field-kind derivation, MVS with second-class borrows, two-site ownership diagnostics, residual-site marking, drop/rc computation, multi-error recovery, sub-100 ms budget — all mapped. Deliberately deferred: bytecode emission, .wob output, conformance corpus, single binary (plan 3); `spawn`/messaging (sub-project 2).
|
||||
- **Order rationale:** diagnostics before lexer (everything reports through it); parser split decl/body so skip-on-block lands before expression complexity; typechecker before owner (kinds and self-mutability feed the flow analysis); driver last when all stages exist.
|
||||
- **Known accepted simplifications, documented in their tasks:** loop fixpoint by single re-run join; provable-aliasing only (residual sites cover the rest — that is the spec's hybrid design, not a gap); constructor syntax fixed as brace literal.
|
||||
|
||||
## Execution note
|
||||
|
||||
Requires `sudo apt install ocaml dune` (Task 1) — the only new toolchain on the box. Plan 1 (`runtime/`, wovm) need not be built for any task here; the two plans meet in plan 3.
|
||||
165
docs/plan/compiler/architecture.md
Normal file
165
docs/plan/compiler/architecture.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# `woc` compiler architecture
|
||||
|
||||
> The stable map of the OCaml compiler: pipeline, module contracts, data
|
||||
> forms, invariants, and the marked reference paths that inform each stage.
|
||||
> Task-level detail lives in the plans (see the index at the bottom); this
|
||||
> document carries the shape that outlives them.
|
||||
|
||||
## 1. Purpose & doctrine
|
||||
|
||||
`woc` compiles `.wo` source to `.wob` register bytecode for the C VM
|
||||
(`runtime/`, the `wovm` binary). Doctrine, locked by the spec:
|
||||
|
||||
- **OCaml stdlib only.** Handwritten lexer and recursive-descent parser — no
|
||||
Menhir, no ppx, no parser generators. dune is the build runner, nothing more.
|
||||
- **Fast compiles are a feature.** No LLVM, no native backend, no
|
||||
monomorphization. Budget: the pricing demo compiles in under 100 ms.
|
||||
- **Diagnostics are the product.** Mutable value semantics only beats Rust
|
||||
ergonomics if the errors are plain: stable `WO-E###` codes, two-site
|
||||
ownership messages, many errors per run, never abort.
|
||||
- **The VM loader is the executable spec.** Every image `emit` produces must
|
||||
pass `wovm`'s loader validation; the golden end-to-end suite enforces this
|
||||
round-trip rule.
|
||||
|
||||
## 2. Pipeline
|
||||
|
||||
```
|
||||
.wo files
|
||||
│ lexer text → tokens (newline-significant)
|
||||
│ parser tokens → AST (recursive descent, skip-on-block)
|
||||
│ types AST → typed AST (class/interface tables, field kinds)
|
||||
│ owner typed AST → annotations (MVS flow analysis, four tables)
|
||||
│ emit annotations → bytes (registers, drop maps, vtables)
|
||||
▼
|
||||
app.wob ──► wovm (runtime/, plan 1) — loads, validates, executes
|
||||
```
|
||||
|
||||
Side modules: `diag` (every stage reports into it), `dump`/`disasm`
|
||||
(golden-test observation seams), `bin/woc` (driver).
|
||||
|
||||
## 3. Module contracts
|
||||
|
||||
One module per stage under `src/`; each block states what it does, what it
|
||||
consumes/produces, and what it depends on.
|
||||
|
||||
**`diag`** — diagnostic records: stable code (`WO-E###`), `file:line:col`,
|
||||
source excerpt, optional second site. A collector accumulates; nothing in the
|
||||
compiler aborts on the first error. Depends on nothing.
|
||||
|
||||
**`token`** — token kinds and source positions. Depends on nothing.
|
||||
|
||||
**`lexer`** — source text → token stream. Newline-significant (`Newline`
|
||||
tokens end policy/trigger lines — never filtered globally). Only SQL-layer
|
||||
uppercase keywords are keywords; `self`, `insert`, `subscribe`, `receive`,
|
||||
`me` stay plain identifiers (grammar parity with `crates/rt`). Consumes text;
|
||||
produces tokens; depends on `token`, `diag`.
|
||||
|
||||
**`ast`** — untyped AST: declarations (class, interface, type, service,
|
||||
policy, trigger), statements, expressions, each carrying a span. Depends on
|
||||
`token` (positions).
|
||||
|
||||
**`parser`** — tokens → AST. Handwritten recursive descent. Brace-depth-aware
|
||||
skip-on-block for constructs that parse but don't execute in milestone 1.
|
||||
Error recovery at declaration/statement sync points so one bad line doesn't
|
||||
eat the file. Consumes tokens; produces AST; depends on `lexer`, `ast`,
|
||||
`diag`.
|
||||
|
||||
**`types`** — AST → typed AST. Builds the class table (field kinds derived:
|
||||
SCALAR / OWNED / GCREF / TEXT / MULTI / MAP) and the interface table; checks
|
||||
structural satisfaction (Go-style — a class satisfies an interface by method
|
||||
shape, no declaration); infers `self` mutability per method (reads-only =
|
||||
shared, writes = exclusive). Consumes AST; produces typed AST + tables;
|
||||
depends on `ast`, `diag`.
|
||||
|
||||
**`owner`** — typed AST → ownership annotations. The MVS flow pass, per
|
||||
function: borrows are second-class (cannot escape scope, cannot be stored or
|
||||
returned). Produces four tables keyed to AST nodes: moves, drop points
|
||||
(scope-end drops plus per-pc live-register masks for trap unwinding), rc
|
||||
inc/dec pairs with provably-balanced elisions, and residual runtime-check
|
||||
sites (the only places `emit` emits `BORROW_*` ops). Ownership errors name
|
||||
both sites. Consumes typed AST; depends on `types`, `diag`.
|
||||
|
||||
**`emit`** — annotations → `.wob` image. Per-method register allocation
|
||||
(≤ 64 registers, loader-enforced), instruction selection against the opcode
|
||||
set, line tables, drop tables from `owner`'s masks, vtable rows from `types`'
|
||||
satisfaction results. Consumes annotated AST; produces bytes per the format
|
||||
contract; depends on `owner` and the format constants.
|
||||
|
||||
**`disasm`** — `.wob` → readable listing; drives `--dump-bc` golden tests.
|
||||
|
||||
**`dump`** — `--dump-ast` / `--dump-typed` printers; drives front-end golden
|
||||
tests.
|
||||
|
||||
**`bin/woc`** — the driver. Three modes: `check` (front end only, exit code +
|
||||
diagnostics), `emit` (write `.wob`), `build` (copy `wovm`, append the image +
|
||||
offset trailer — the single self-contained binary).
|
||||
|
||||
## 4. Data forms
|
||||
|
||||
Six representations, five handoffs — each observable by a dump flag or a
|
||||
golden fixture:
|
||||
|
||||
1. **Source text** (UTF-8 `.wo` files)
|
||||
2. **Token stream** — kind, lexeme, position; `Newline` tokens significant
|
||||
3. **AST** — untyped, spanned (`--dump-ast`)
|
||||
4. **Typed AST** — every expression typed; class/interface tables attached
|
||||
(`--dump-typed`)
|
||||
5. **Ownership-annotated AST** — the four `owner` tables keyed to nodes
|
||||
6. **`.wob` image** — per the format contract (`--dump-bc` disassembles)
|
||||
|
||||
## 5. Architectural invariants
|
||||
|
||||
1. OCaml stdlib only; dune as runner; no generated parser, no ppx.
|
||||
2. Diagnostics collect and continue; stable codes; never abort; ownership
|
||||
errors always name both sites.
|
||||
3. Grammar parity with the Rust runtime's lexer/parser gotchas (newline
|
||||
significance, skip-on-block, keyword/ident split) until `crates/rt`
|
||||
retires — the shared conformance corpus enforces parity.
|
||||
4. The loader is the contract: an emitted image the `wovm` loader rejects is
|
||||
an `emit` bug by definition, caught by the golden e2e suite.
|
||||
5. No LLVM linkage, no native codegen — `.wob` only. The LLVM tree is study
|
||||
material (see §6), never a dependency.
|
||||
6. Compile-speed budget: `woc` on the pricing demo < 100 ms on a developer
|
||||
laptop.
|
||||
|
||||
## 6. Reference-study map
|
||||
|
||||
Marked paths per stage. **porting source** = code this stage is ported from;
|
||||
**contract** = artifact the stage must satisfy byte-for-byte; **study** =
|
||||
architecture to learn from, never link against.
|
||||
|
||||
| Stage | Path | What to study | Role |
|
||||
| --- | --- | --- | --- |
|
||||
| lexer | `crates/rt/src/lexer.rs`, `crates/rt/src/token.rs` | newline tokens, keyword map, ident gotchas | **porting source** |
|
||||
| lexer | `.dev/reference/go/src/go/scanner/` | handwritten stdlib scanner shape | study |
|
||||
| lexer | `.dev/reference/llvm-project/clang/lib/Lex/` | keyword tables, performance tricks | study |
|
||||
| parser | `crates/rt/src/parser.rs` | the `.wo` grammar, brace-depth skip-on-block | **porting source** |
|
||||
| parser | `.dev/reference/go/src/go/parser/` | recursive descent, error-recovery sync points | study |
|
||||
| parser | `.dev/reference/llvm-project/clang/lib/Parse/` | recovery at scale | study |
|
||||
| ast / dump | `.dev/reference/go/src/go/ast/`, `crates/rt/src/ast.rs` | node + span design | study / porting source |
|
||||
| diag | `.dev/reference/llvm-project/clang/include/clang/Basic/Diagnostic*.td` | stable error codes, severities, notes attached to errors | study |
|
||||
| types | `.dev/reference/go/src/go/types/` | stdlib-only structural typechecker — the closest cousin to `woc types` | **primary study** |
|
||||
| types | `.dev/reference/llvm-project/clang/lib/Sema/` | protocol-conformance checking (interface-satisfaction analogue) | study |
|
||||
| owner | `runtime/src/borrow.c`, `runtime/src/gc.c`, spec §4 | the semantic target the four owner tables must satisfy | **contract** |
|
||||
| owner | Hylo/Val mutable-value-semantics papers (external, not vendored) | second-class borrows theory | study |
|
||||
| emit | `docs/plan/oop-vm/00-wob-format.md`, `runtime/src/wob.h` | the target format | **contract** |
|
||||
| emit | `runtime/test/wob_build.c` | the second, independent encoder — the model for emit's section writing | **porting source** |
|
||||
| emit | `runtime/src/loader.c` | the validation battery every image must pass | **contract** |
|
||||
| pipeline | `.dev/reference/go/src/cmd/compile/README.md` | how a production compiler documents its pass pipeline | study |
|
||||
| query layer (plan 5, deferred) | `prototypes/wo-db/`, `.dev/reference/postgresql/src/backend/parser/` | SQL/Cypher grammar semantics | deferred |
|
||||
|
||||
Deliberately excluded: `.dev/reference/colibri`, `.dev/reference/llama-cpp`,
|
||||
`.dev/reference/linux` — runtime/kernel references, not compiler material.
|
||||
|
||||
## 7. Governing docs
|
||||
|
||||
- Spec: [`docs/superpowers/specs/2026-08-01-oop-compiler-vm-design.md`](../../superpowers/specs/2026-08-01-oop-compiler-vm-design.md)
|
||||
- Plan 2 — compiler front: [`2026-08-01-woc-compiler-front.md`](./2026-08-01-woc-compiler-front.md)
|
||||
- Plan 3 — emit + e2e + single binary: [`2026-08-01-wob-emit-e2e-single-binary.md`](./2026-08-01-wob-emit-e2e-single-binary.md)
|
||||
- Plan 8 — Haxe-parity language surface: [`2026-08-01-haxe-parity-language.md`](./2026-08-01-haxe-parity-language.md)
|
||||
- Format contract: [`docs/plan/oop-vm/00-wob-format.md`](../../plan/oop-vm/00-wob-format.md)
|
||||
- VM counterpart (shipped): [`docs/superpowers/plans/2026-08-01-wob-format-and-vm-core.md`](../../superpowers/plans/2026-08-01-wob-format-and-vm-core.md)
|
||||
|
||||
> **Docs-location note:** compiler plan documents live here in
|
||||
> `docs/plan/compiler/` — a recorded exception to the repo's "documentation under
|
||||
> `docs/`" rule (see CLAUDE.md and `docs/08-project-structure.md`).
|
||||
|
|
@ -43,12 +43,12 @@
|
|||
|
||||
- Newline-significant lexing and the identifier gotchas mirror
|
||||
`crates/rt`'s lexer — grammar parity is a stated contract.
|
||||
- Architecture map: `compiler/plan/architecture.md` (pipeline, module
|
||||
- Architecture map: `docs/plan/compiler/architecture.md` (pipeline, module
|
||||
contracts, study references).
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
- Execute the existing plan: `compiler/plan/2026-08-01-woc-compiler-front.md`
|
||||
- Execute the existing plan: `docs/plan/compiler/2026-08-01-woc-compiler-front.md`
|
||||
(diagnostics module, lexer, declaration/statement/expression parsers with
|
||||
skip-on-block, typechecker with field-kind derivation, MVS ownership pass
|
||||
producing the four emitter tables, driver + error catalog).
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ writeonce today is a declarative language executed by the Rust runtime (`crates/
|
|||
|
||||
| Question | Decision |
|
||||
| --- | --- |
|
||||
| Fate of Rust runtime | **Evolve `wo-rt-c` into the C runtime.** OCaml compiler targets it. `crates/rt` stays active until parity, then retires to `reference/` like v1 did. |
|
||||
| Fate of Rust runtime | **Evolve `wo-rt-c` into the C runtime.** OCaml compiler targets it. `crates/rt` stays active until parity, then retires to `.dev/reference/` like v1 did. |
|
||||
| OOP shape | **Keep plan 13 doctrine: no inheritance, no override, no virtual class hierarchies — ever.** OOP = `class` (state + methods) + structural **interfaces** (Go-style) + composition (`ref`/`multi`). |
|
||||
| Borrow enforcement | **Hybrid.** Compiler proves most sites statically and emits nothing; VM enforces residual sites with borrow-word checks at runtime. |
|
||||
| GC opt-out granularity | **Per-class annotation** `@gc` — all instances of that class are GC-managed and freely aliased. |
|
||||
|
|
@ -161,6 +161,8 @@ struct wo_hdr {
|
|||
|
||||
## Section 5 — Bytecode and VM
|
||||
|
||||
> Normative format reference (pinned by plan 1): [`docs/plan/oop-vm/00-wob-format.md`](../../plan/oop-vm/00-wob-format.md) · machine-readable twin: [`runtime/src/wob.h`](../../../runtime/src/wob.h)
|
||||
|
||||
**Registers:** untyped 64-bit slots. The language is statically typed — the compiler knows every slot's type, so no tagging and no NaN-boxing. Scalars inline (`Int`/`Money`/`Timestamp` = i64, `Bool`), heap values as pointers (the header supplies the class at runtime for interface dispatch and traps).
|
||||
|
||||
**`.wob` module format:** magic + version, then sections — constant pool (texts, numerics), class table (field layout, size, `@gc` bit, drop plan), interface table, per-(class, interface) vtables, method code (arg count, register count, bytecode), line table (for error reporting). The loader `mmap`s the file, bounds-validates every index once, and links class ids.
|
||||
|
|
|
|||
123
docs/superpowers/specs/2026-08-01-systems-track-design.md
Normal file
123
docs/superpowers/specs/2026-08-01-systems-track-design.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# writeonce systems track — Haxe keyword study, program mode, systems stdlib (design)
|
||||
|
||||
**Date:** 2026-08-01
|
||||
**Status:** approved design, pre-implementation
|
||||
**Companion spec:** [`2026-08-01-oop-compiler-vm-design.md`](2026-08-01-oop-compiler-vm-design.md) (the OOP core this track extends)
|
||||
**Driving workload:** `~/projects/log-watcher` — ~1,200 lines of Haxe compiled to C++ (`--cpp`), a single-binary systems daemon: log-tail watcher, cron.d supervisor, flock/pgrep probes, hand-rolled MCP-over-HTTP server, JSONL detection sink.
|
||||
|
||||
## Motivation
|
||||
|
||||
writeonce today can only be a full-stack server. log-watcher is the counterexample class: a CLI daemon that reads files, probes processes, serves a small TCP protocol, and sleeps in a poll loop. The language should build such applications too. Method: study the language log-watcher is written in — **Haxe** — keyword by keyword, adopt broadly what fits, reject explicitly what breaks doctrine, and prove the result by re-expressing log-watcher in `.wo`.
|
||||
|
||||
## Decisions locked during brainstorming
|
||||
|
||||
| Question | Decision |
|
||||
| --- | --- |
|
||||
| "Hexa" meaning | **Haxe** — log-watcher's language. |
|
||||
| Deliverable | **Spec + `.wo` sample workload.** Repo pattern: samples force the grammar (blog/ecommerce/pricing precedent). |
|
||||
| Adoption stance | **Broad Haxe parity MINUS doctrine breakers.** No inheritance (`extends`/`override`/`super`), no `Dynamic`/`untyped`, no macros — plan-13 doctrine and the OOP spec stay locked. Everything else adopts liberally. |
|
||||
| Systems access | **Program mode + safe builtin stdlib.** No FFI/extern — capabilities are typed builtins implemented in the C runtime. |
|
||||
|
||||
## Part 1 — The Haxe keyword verdict table
|
||||
|
||||
Every Haxe keyword (plus the contextual ones), one verdict each: **have** (writeonce equivalent exists), **adopt** (new surface this track adds), **reject** (with the reason). This table is normative for the plan documents.
|
||||
|
||||
| Haxe keyword | Verdict | writeonce mapping / reason |
|
||||
| --- | --- | --- |
|
||||
| `class` | have | `class` (state + methods, no hierarchy) |
|
||||
| `interface` | have | structural interfaces (OOP spec section 3) |
|
||||
| `function` | have | `fn` |
|
||||
| `var` (locals) | have | `let`; mutability via MVS rules, no second keyword |
|
||||
| `this` | have | `self` (identifier, positionally bound) |
|
||||
| `if` / `else` | have | same |
|
||||
| `for` / `in` | have | same |
|
||||
| `while` | have | same |
|
||||
| `return` | have | same |
|
||||
| `true` / `false` | have | same |
|
||||
| `enum` | have+adopt | tagged unions exist; **adopt payload variants** (`Pending \| Failed(reason: Text)`) with exhaustive switch |
|
||||
| `final` | have | MVS immutability by default; `const` for named constants |
|
||||
| `new` | have | constructor brace literal `Type { … }`; no keyword |
|
||||
| `switch` / `case` / `default` | **adopt** | expression-form switch, exhaustive over unions; `default` optional when exhaustive |
|
||||
| `typedef` | **adopt** | structural record aliases with optional fields (`?field`) — the `SupConfig`/`TailState` pattern |
|
||||
| `null` / `Null<T>` | **adopt** | `?T` optional types; forced handling before use (no nil deref trap possible); bare `null` only assignable to `?T` |
|
||||
| `try` / `catch` / `throw` | **adopt** | expression-form over the trap system: `catch` binds the structured error `{code, method, line, msg}`; `throw value` raises an EXPLICIT trap carrying the value; uncaught = existing trap surface |
|
||||
| `break` / `continue` | **adopt** | loop control |
|
||||
| `do` (do-while) | **adopt** | parity, trivial |
|
||||
| `static` | **adopt** | class-level `fn`/`const` — namespaced functions without instances (`Flock.held`, `Pgrep.alive` pattern) |
|
||||
| `abstract` | **adopt** | newtype over a scalar, zero-cost, unit-safe (`Money`, `SKU` become in-language, not magic stdlib scalars); `from`/`to` conversion rules explicit |
|
||||
| `using` | **adopt** | static extension methods — doctrine-safe reuse (composition sugar, the inheritance substitute) |
|
||||
| `import` / `package` | **adopt** | `use` + directory-as-module; stdlib namespaces (`fs`, `proc`, `net`, `time`, `env`, `json`) |
|
||||
| `is` | **adopt** | runtime type test restricted to union variants and interface values; a compile error on statically-known types |
|
||||
| `inline` | **adopt (values)** | `const` compile-time values; inline *functions* rejected — optimization is the compiler's job |
|
||||
| `public` / `private` | **adopt (as `pub`)** | default private; `pub` exports; property-accessor pattern `(default, null)` becomes `pub(read)` — public read, owner-only write |
|
||||
| `#if` / `#else` / `#end` | **adopt** | build-flag conditional compilation only (the `-D portable` pattern); flags from the build command, no expression language beyond flag names |
|
||||
| string interpolation `'${}'` | **adopt** | in string literals |
|
||||
| `extends` | **reject** | no-inheritance doctrine (plan 13, OOP spec) — is-a via unions, has-a via composition |
|
||||
| `super` | **reject** | no hierarchy to call up |
|
||||
| `override` | **reject** | nothing to override |
|
||||
| `overload` | **reject** | one name, one signature; keeps dispatch and diagnostics simple |
|
||||
| `implements` | **reject (keyword)** | satisfaction is structural and implicit; declaring it adds a lie surface |
|
||||
| `dynamic` / `Dynamic` | **reject** | static typing is the VM's foundation (untagged registers); typed `json.decode` covers the real use |
|
||||
| `untyped` | **reject** | no escape hatch from the type system |
|
||||
| `macro` | **reject** | kills the fast-compile promise; codegen belongs to `wo gen` tooling |
|
||||
| `extern` | **reject** | no FFI hole in the memory-safety story; capabilities are audited builtins |
|
||||
| `cast` | **reject** | no unsafe casts; conversions are typed (abstract `from`/`to`, explicit builtins) |
|
||||
| `operator` | **reject** | no operator overloading; KISS |
|
||||
|
||||
## Part 2 — Program mode
|
||||
|
||||
**Entry.** A project containing a free `fn main(args: multi Text) -> Int` compiles as a **program**; the return value is the exit code. `wo run <dir>` executes it. `woc build` produces the single binary (plan-3 trailer mechanics unchanged). A project with `service` blocks and no `main` remains a server. Both present: `main` runs first and decides what to start — exactly log-watcher's shape (`watch`/`run`/`mcp` subcommands selecting the daemon flavor).
|
||||
|
||||
**Blocking model — the load-bearing decision.** Program mode runs one shard and blocking builtins are legal (`time.sleep`, `proc.run`, blocking `net.accept`): the Haxe original is a poll loop around `Sys.sleep`, and writeonce expresses that directly. Server shards keep the never-block doctrine — the *same* stdlib calls are loop-integrated (io_uring) there. One API, two execution disciplines, selected by mode. No `async`/`await` keyword exists in either mode.
|
||||
|
||||
**CLI surface.** `env.args() -> multi Text`, `env.get(name) -> ?Text`, `env.exit(code)` (never returns), `print`/`print_int` (exist) plus `print_err`; output flushes on newline (the reason for log-watcher's `Util.say` disappears).
|
||||
|
||||
**Daemon idiom.** `while true { …; time.sleep(ms) }` is supported and expected. Signals: the runtime owns signalfd (doctrine); SIGTERM/SIGINT set a shutdown flag programs poll via `env.stopping() -> Bool`. No signal callbacks.
|
||||
|
||||
## Part 3 — Systems stdlib
|
||||
|
||||
Five builtin modules, scoped to what log-watcher's code actually uses. **Every handle (file, socket, process) is an owned object whose drop closes it** — MVS deterministic destruction is RAII: no close bookkeeping, no leaked fds by construction, and a handle sent nowhere dies at scope end.
|
||||
|
||||
| Module | Surface | log-watcher use it covers |
|
||||
| --- | --- | --- |
|
||||
| `fs` | `exists(path)`; `stat(path) -> ?{size, inode, mtime}`; `read_at(path, offset, max) -> Text`; `read_all(path, cap)`; `append(path, text)`; `list(dir) -> multi Text` | rotation detection needs the inode; bounded tail-chunk reads (never front-to-back scans); JSONL detection sink (open-append-close); cron.d directory scan. **No write/truncate/delete in v1** — the read-only posture is the default posture. |
|
||||
| `proc` | `run(cmd, args: multi Text) -> {code: Int, out: Text, err: Text}`, bounded capture | the `flock -n` exit-code probe and `pgrep -f`. Args-array only — no shell-string form, command injection unrepresentable. |
|
||||
| `net` | `listen(addr, port) -> Listener`; `accept(listener) -> Conn`; `read(conn, max) -> Text`; `write(conn, text)` | the hand-rolled MCP HTTP subset (127.0.0.1 accept loop, one request per connection). TCP only in v1. |
|
||||
| `time` | `now()` wall ms (exists); `mono()` monotonic ms; `sleep(ms)` | poll-interval math on a monotonic clock; the daemon sleep. |
|
||||
| `json` | `json.decode(text) as RecordType -> ?RecordType`; `json.encode(value) -> Text` | config loading and JSON-RPC — **typed**, replacing Haxe's `Dynamic` idiom: missing optional fields are fine, shape mismatches yield nil, never a trap. The `as` here is the decode-target position only — a checked conversion returning `?T`, not a cast; it exists nowhere else (the `cast` rejection stands). Reuses the HTTP plan's C codec as builtins. |
|
||||
|
||||
## Part 4 — The sample workload
|
||||
|
||||
`docs/examples/log-watcher/` — the Haxe original re-expressed in `.wo`, file-for-file:
|
||||
|
||||
| `.wo` file | `.hx` sibling | carries |
|
||||
| --- | --- | --- |
|
||||
| `main.wo` | Main.hx | subcommand dispatch, config decode into a `typedef` record with `?fields` |
|
||||
| `watcher.wo`, `logtail.wo` | Watcher.hx, LogTail.hx | `TailState` record, bounded tail reads, rotation-by-inode, quiet-period rule |
|
||||
| `cron.wo` | Cron.hx | cron.d parse, next-fire computation |
|
||||
| `probes.wo` | Flock.hx, Pgrep.hx | `proc.run` exit-code probes as `static fn`s |
|
||||
| `mcp.wo` | Mcp.hx, Tools.hx | typed request/response records; **pure `handle(req) -> resp` kept socket-free** (the original's best design decision, preserved); serve loop over `net` |
|
||||
|
||||
A README table records the mapping and what (if anything) each file could not express — an empty "could not express" column is this track's acceptance criterion.
|
||||
|
||||
## Error handling
|
||||
|
||||
One system, two surfaces. Traps remain the runtime truth (OOP spec section 6). This track adds the language surface: `try expr catch (e) fallback-expr` — `e` is the structured error record; `throw value` raises EXPLICIT with the value attached. Optionals (`?T`) handle *expected* absence (missing file stat, failed decode, missing env var) — the stdlib returns nil for those, reserving traps/throw for genuine faults. The Haxe original's `try … catch (e:Dynamic) return false` probes become optional-returning calls — clearer than the original.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Language adoptions:** each feature lands with conformance-corpus fixtures — golden (runs, expected stdout) and must-fail (expected `WO-E###`) — extending the OOP track's corpus and error catalog.
|
||||
- **Stdlib:** corpus fixtures against real resources — tempdir files (stat/inode/rotation simulation via rename), spawned `/bin/true`-class processes, loopback sockets. Handle-drop RAII proven under ASan (a leaked fd test: open many handles in a loop, assert no fd growth).
|
||||
- **Acceptance:** the log-watcher sample compiles; its testable cores (tail state machine, cron next-fire, MCP `handle`) pass fixtures ported from the Haxe test suite's cases; the sample binary runs `watch` against a growing tempfile and detects an error-final quiet period.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. The keyword table is fully implemented: every **adopt** row parses, typechecks, and executes with corpus coverage; every **reject** row has a diagnostic or a documented absence.
|
||||
2. `fn main` program mode: `wo run` executes a CLI program; exit codes propagate; `woc build` produces a self-contained binary for it.
|
||||
3. All five stdlib modules pass their corpus fixtures; handle RAII is ASan-proven.
|
||||
4. `docs/examples/log-watcher/` compiles and its README mapping table has an empty "could not express" column.
|
||||
5. The sample's watch mode detects a silent death (error-final + quiet period) end to end on a real tempfile.
|
||||
|
||||
## Out of scope (named)
|
||||
|
||||
Threads/worker pools in program mode (the shard-actor track owns concurrency); UDP/TLS; `fs` mutation beyond append; signal callbacks; sqlite-equivalent embedded SQL over RAM (that is the DB engine's job — a future sample can wire MiniLog's idea to `select`); Haxe macro-based reflection idioms.
|
||||
202
docs/superpowers/specs/2026-08-03-blue-green-vm-design.md
Normal file
202
docs/superpowers/specs/2026-08-03-blue-green-vm-design.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# Blue/Green VM deployment — design spec
|
||||
|
||||
**Date:** 2026-08-03
|
||||
**Status:** approved (2026-08-03); implementation plan deferred until plans 5 + 6 ship
|
||||
**Scope:** the writeonce runtime's in-process deployment subsystem
|
||||
**Supersedes:** §5–§6 of [`docs/plan/exploration/blue-green-vm/00-vision.md`](../../plan/exploration/blue-green-vm/00-vision.md)
|
||||
|
||||
## Motivation
|
||||
|
||||
The writeonce executable is a systemd service that never stops. It embeds its
|
||||
own source; a developer manages that source remotely through the `wo` CLI over
|
||||
an exposed management port. An approved change — code **and** its database
|
||||
schema consequence — compiles and migrates inside the runtime, loads into the
|
||||
idle VM slot, and deploys by an atomic switch. If anything fails, the active
|
||||
VM is untouched. Rollback is the same switch back, because the previous
|
||||
version never left memory.
|
||||
|
||||
## Decisions locked during brainstorming
|
||||
|
||||
| Question | Decision |
|
||||
| --- | --- |
|
||||
| Blue/Green semantics | **Fixed slots; activity alternates.** Blue and Green are two physical VM slots; each deploy loads the idle one and switches to it. "Which is live" is status info (`wo remote status`). |
|
||||
| Schema migration | **Auto-diff, additive-only in v1.** Additive changes migrate automatically; destructive changes reject the deploy. Script-based migrations are a recorded later phase. |
|
||||
| Management protocol | **HTTP + JSON + SSE** on a loopback management transport, bearer token — hand-rollable on the runtime's own HTTP machinery, curl-debuggable. MCP wrapper for agents is a later layer over the same endpoints. |
|
||||
| Edit model | **Local-first, CLI push.** `wo remote pull` fetches the embedded source; the developer edits locally; `wo remote propose` pushes a staged proposal. The runtime never hosts a mutable workspace. |
|
||||
| Architecture | **Approach A: management plane as a runtime module + thin CLI.** In-process slots; rejected: external deployer daemon (violates two-VMs-in-one-runtime, splits data), self-hosted `.wo` deploy logic (bootstrap problem — later). |
|
||||
|
||||
## 1. Scope & positioning
|
||||
|
||||
**In scope:** two VM slots, the deploy state machine, additive schema
|
||||
migration, the management HTTP+SSE surface, `wo remote` CLI verbs, the
|
||||
source-in-binary trailer section.
|
||||
|
||||
**Out of scope (recorded extensions):** script-based/destructive migrations,
|
||||
an in-runtime editing workspace, the MCP/agent wrapper, fibers (plan 4 owns
|
||||
them).
|
||||
|
||||
**Prerequisites:** plan 5 (DB engine binding — a catalog to diff), plan 6
|
||||
(HTTP machinery). This spec is written ahead; its implementation plan is
|
||||
authored when those ship.
|
||||
|
||||
## 2. Runtime anatomy
|
||||
|
||||
- **Two fixed VM slots — Blue and Green.** One is *active*: all new requests
|
||||
dispatch into it. The other is *standby*: the previous version, loaded and
|
||||
warm, the instant-rollback target. Activity alternates on each deploy.
|
||||
- **VMs own code; the engine owns data.** Tables, WAL, subscriptions, and
|
||||
arena slabs live below both slots. A switch swaps the dispatch pointer at
|
||||
the request boundary; in-flight work drains on the old slot under a bounded
|
||||
timeout. No data moves on deploy or rollback.
|
||||
- **A runtime is not tied to a port.** Transports (application HTTP, the
|
||||
management endpoint, future ones) attach at boot; systemd **socket
|
||||
activation** is supported — the unit owns the sockets, the runtime accepts
|
||||
whatever fds it inherits. The runtime also runs with zero listeners.
|
||||
- **The binary contains its source.** The `woc build` trailer carries the
|
||||
`.wo` source tree beside the `.wob` image. After a successful deploy the
|
||||
runtime rewrites its own trailer (write temp, fsync, rename) so the binary
|
||||
on disk always matches the active slot. `wo remote pull` serves from this
|
||||
section; there is no "which commit is prod running" question.
|
||||
- **Always running.** The executable maps to a systemd service
|
||||
(`Restart=always`). Deploys never restart the process. After a crash or
|
||||
reboot, the unit boots the active version from the trailer; the standby
|
||||
slot refills on the next deploy.
|
||||
- **Recipe-box rule.** Each capability here (transports, slots, proposal
|
||||
store, differ, deploy machine) stays a separable runtime module — a custom
|
||||
web framework in later phases composes them; the runtime stays
|
||||
framework-agnostic.
|
||||
|
||||
## 3. Deploy pipeline
|
||||
|
||||
States, WAL-logged at every transition:
|
||||
|
||||
```
|
||||
DRAFT ──propose──► STAGED ──approve──► COMPILING ──ok──► MIGRATING ──ok──► LOADING
|
||||
│ │fail │fail │fail
|
||||
│reject ▼ ▼ ▼
|
||||
▼ FAILED (active slot untouched, standby unchanged)
|
||||
REJECTED
|
||||
LOADING ──ok──► HEALTH ──ok──► SWITCHING ──drained──► DEPLOYED
|
||||
│fail (atomic dispatch swap;
|
||||
▼ old active becomes standby)
|
||||
FAILED
|
||||
```
|
||||
|
||||
- **propose** — the CLI pushes the changed file set plus the base-version
|
||||
hash it was edited against. A stale base (someone deployed since the
|
||||
`pull`) rejects at propose time, not after approval. The runtime computes
|
||||
the catalog diff immediately and stores it with the proposal.
|
||||
- **approve** — an explicit CLI verb covering BOTH the code change and the
|
||||
migration plan: `wo remote diff` shows the source diff *and* the schema
|
||||
consequence, so approval is informed. Approval triggers the pipeline.
|
||||
- **compile** — in-runtime `woc emit` against the proposal. Any diagnostic →
|
||||
`FAILED`; diagnostics stream to the developer; the active slot is never
|
||||
touched.
|
||||
- **migrate** — additive catalog changes apply to the engine (§4), **before**
|
||||
the switch, while old code still serves. Safe because additive changes are
|
||||
invisible to old code.
|
||||
- **load + health** — the new image passes the standard loader validation
|
||||
battery (a bad image cannot boot), then a smoke subset runs against the
|
||||
idle slot.
|
||||
- **switch** — dispatch-pointer swap; drain with bounded timeout; the old
|
||||
active becomes standby; the trailer rewrites.
|
||||
- **rollback** — `wo remote rollback` switches back to standby: no compile,
|
||||
no load, code already resident. The schema stays as migrated —
|
||||
additive-only (§4) guarantees the older code runs correctly against it.
|
||||
- **streaming** — every stage's output (compiler diagnostics, migration
|
||||
progress, health results, switch/drain status) streams to the developer as
|
||||
it happens (§5).
|
||||
|
||||
Failure semantics, uniform: a failure at any stage leaves the active slot
|
||||
serving and the standby slot unchanged; the proposal lands in `FAILED` with
|
||||
its stream and WAL trail intact.
|
||||
|
||||
## 4. Migration rules (additive-only v1)
|
||||
|
||||
The engine diffs the old catalog against the proposal's catalog at propose
|
||||
time:
|
||||
|
||||
| Change | Verdict |
|
||||
| --- | --- |
|
||||
| new class or type | auto-migrates |
|
||||
| new field **with a default** | auto-migrates — backfilled with the default in RAM + WAL |
|
||||
| new index | auto-migrates (built before switch) |
|
||||
| new interface / method / service block | auto-migrates (code-only) |
|
||||
| new field **without a default** | reject: "add a default" |
|
||||
| drop / rename / retype a field | reject in v1 |
|
||||
| drop a class; remove a union variant; flip `@gc` | reject in v1 |
|
||||
|
||||
- Rejections happen at **propose** time and name the offending declaration —
|
||||
the developer never waits for an approval to learn the change can't ship.
|
||||
- The migration itself is a WAL-logged engine transaction: backfills run per
|
||||
shard and ack like any commit. A crash mid-migration replays or discards
|
||||
the whole migration on boot — never half.
|
||||
- The additive-only rule is exactly what makes rollback unconditional: the
|
||||
previous version ignores fields and classes it never knew.
|
||||
- Script-based migrations (destructive changes, data transforms, down
|
||||
scripts) are the recorded follow-up phase; nothing in this design blocks
|
||||
them.
|
||||
|
||||
## 5. Management surface & CLI
|
||||
|
||||
Endpoints under `/manage` on the management transport (loopback by default):
|
||||
|
||||
| Endpoint | Verb | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `/manage/source` | GET | the embedded source tree (paths + contents) of the active slot |
|
||||
| `/manage/proposals` | POST | submit a proposal (file set + base hash) → id, catalog diff computed |
|
||||
| `/manage/proposals/<id>` | GET | proposal state, source diff, schema diff |
|
||||
| `/manage/proposals/<id>/approve` | POST | approve → pipeline starts |
|
||||
| `/manage/proposals/<id>/stream` | GET | **SSE**: stage events, compiler diagnostics, migration progress, health, switch/drain — replayable from the WAL after reconnect |
|
||||
| `/manage/deploy/rollback` | POST | switch back to standby |
|
||||
| `/manage/status` | GET | which slot is active, versions (source hashes), drain state, last deploy record |
|
||||
|
||||
`wo` CLI verbs mapping 1:1: `wo remote pull / status / propose / diff /
|
||||
approve / deploy-log / rollback`. `propose` prints the id; `approve` attaches
|
||||
to the SSE stream and renders it live — the developer watches compile,
|
||||
migration, health, and switch scroll by; a failure shows the exact
|
||||
diagnostics inline.
|
||||
|
||||
## 6. Security & audit
|
||||
|
||||
- Management transport binds loopback by default; reaching it remotely is an
|
||||
SSH tunnel (the log-watcher posture). Bearer token from the service's
|
||||
environment file — config stays world-readable, the secret does not.
|
||||
- The application transport and management transport are separate listeners:
|
||||
app traffic can never reach `/manage` routes.
|
||||
- Approval is a deliberate second step, not implied by propose — and the two
|
||||
verbs can carry distinct tokens later (agent proposes, human approves)
|
||||
without changing the design.
|
||||
- Everything is WAL-logged: proposals, diffs, approvals, every stage
|
||||
transition, switches, rollbacks. The deploy history survives crashes like
|
||||
any other committed data and is queryable via `/manage/status`.
|
||||
|
||||
## 7. Testing
|
||||
|
||||
- **State-machine unit tests** (runtime): every transition and every failure
|
||||
edge — compile fail, migration reject, load reject, health fail, drain
|
||||
timeout — asserting the active slot is untouched after each.
|
||||
- **Catalog-differ table tests:** one fixture per row of the §4 verdict
|
||||
table, both verdicts.
|
||||
- **Deploy e2e** (corpus harness): boot a fixture app, propose an additive
|
||||
change, approve, assert the SSE stream's stage sequence, assert new code
|
||||
serves and old data survives; then rollback and assert the previous
|
||||
behavior returns with the migrated schema intact.
|
||||
- **Crash battery:** kill the process in COMPILING / MIGRATING / SWITCHING /
|
||||
trailer-rewrite; on reboot the runtime serves the correct version and the
|
||||
WAL shows whole migrations only.
|
||||
- **Stale-base test:** two pulls, one deploys, the other's propose rejects.
|
||||
- ASan on the runtime side throughout, as established by the wovm gate.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. A fixture app deploys an additive change with zero dropped requests
|
||||
(in-flight drain proven by the e2e harness).
|
||||
2. Every failure stage leaves the active slot serving and is visible in the
|
||||
SSE stream and the WAL audit trail.
|
||||
3. `wo remote rollback` restores the previous version in under one second
|
||||
with no compile and no data change.
|
||||
4. Kill -9 at any pipeline stage: reboot serves a consistent version; no
|
||||
half-applied migration exists.
|
||||
5. The binary's trailer always matches the active slot after DEPLOYED
|
||||
(verified by `wo remote pull` hash comparison in the e2e).
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
# log-watcher `.wo` sample (docs-first) + principles doc — design spec
|
||||
|
||||
**Date:** 2026-08-07
|
||||
**Status:** approved design, pre-implementation
|
||||
**Scope:** authoring two docs artifacts — the `docs/examples/log-watcher/` sample and `docs/00-principles.md`
|
||||
**Companion specs:** [`2026-08-01-systems-track-design.md`](2026-08-01-systems-track-design.md) (language surface + stdlib + sample mapping), [`2026-08-01-oop-compiler-vm-design.md`](2026-08-01-oop-compiler-vm-design.md) (OOP core, memory model), [`2026-08-03-blue-green-vm-design.md`](2026-08-03-blue-green-vm-design.md) (in-runtime deployment)
|
||||
**Companion plan:** [`docs/superpowers/plans/2026-08-01-log-watcher-sample.md`](../plans/2026-08-01-log-watcher-sample.md) (plan 10 — the executable acceptance plan this authoring feeds)
|
||||
|
||||
## Motivation
|
||||
|
||||
The systems track names `~/projects/log-watcher` (~1,200 lines of Haxe, a
|
||||
single-binary Linux daemon) as its driving workload and defines the sample
|
||||
port in Part 4 of its spec. Plan 10 sequences that port but gates on plans 8
|
||||
(language adoptions) and 9 (stdlib) shipping, because its acceptance requires
|
||||
compiling fixtures. This session does not wait: it authors the sample as a
|
||||
**docs-first artifact** — the same pattern by which `docs/examples/blog/` and
|
||||
`docs/examples/ecommerce/` existed before the runtime executed them and
|
||||
thereby forced the grammar. Alongside it, the repo gains its first canonical
|
||||
principles document, `docs/00-principles.md`, distilling doctrine currently
|
||||
scattered across specs, plan docs, and CLAUDE.md.
|
||||
|
||||
## Decisions locked during brainstorming
|
||||
|
||||
| Question | Decision |
|
||||
| --- | --- |
|
||||
| Session deliverable | **Sample `.wo` files + principles doc now, docs-first.** No compiler/runtime code; fixtures stay with plan 10. Rejected: waiting for plans 8/9; revising approved specs; starting compiler implementation. |
|
||||
| principles doc placement | **Repo-level:** `docs/00-principles.md`. The example README links to it. Rejected: per-example principle file. |
|
||||
| Sample scope | **Approach A + short deploy note:** all 8 files per plan 10's structure, strict approved syntax, README mapping table, plus one README paragraph linking the blue-green spec. Rejected: minimal 3-file sample (breaks the mapping, leaves could-not-express unproven); full speculative `wo remote` walkthrough. |
|
||||
|
||||
## Section 1 — Scope & positioning
|
||||
|
||||
**In scope:** `docs/examples/log-watcher/` — eight `.wo` files plus an
|
||||
orientation README — and `docs/00-principles.md`.
|
||||
|
||||
**Out of scope:** corpus fixtures, `just` recipes, compiler or runtime code,
|
||||
kanban restructuring. Plan 10 keeps ownership of fixtures and of acceptance
|
||||
criteria 4–5 (empty could-not-express column verified by a real compile; live
|
||||
silent-death detection) when plans 8/9 ship.
|
||||
|
||||
**Authoring contract:** every construct used in the sample must be traceable
|
||||
to an approved surface — the OOP spec's section 3 (classes, interfaces,
|
||||
methods, MVS parameter conventions), the systems-track verdict table's adopt
|
||||
rows (switch expressions, typedef records, `?T` optionals, enum payloads,
|
||||
try/catch/throw, `static fn`, `using`, `use` modules, `pub`, abstracts,
|
||||
interpolation, `#if`), Part 2 program mode (`fn main`, blocking builtins,
|
||||
`env.stopping()`), and Part 3's five stdlib modules (`fs`, `proc`, `net`,
|
||||
`time`, `json`). No construct may be invented here. If the port cannot
|
||||
express a behavior inside that surface, that is a **defect report against
|
||||
plans 8/9**, recorded in the README's could-not-express column — the same
|
||||
feedback-loop role plan 10 assigns, run early.
|
||||
|
||||
**Behavioral reference:** the Haxe source at `~/projects/log-watcher/src/`.
|
||||
The port is behavior-faithful, not line-faithful; each `.hx` file is read
|
||||
before its `.wo` sibling is written. Deliberate divergences (optionals over
|
||||
sentinels, records, switch expressions, RAII handles, the stopping-flag
|
||||
daemon idiom) are recorded in the README table, never silent.
|
||||
|
||||
## Section 2 — The sample
|
||||
|
||||
File set and content, matching plan 10's structure:
|
||||
|
||||
| `.wo` file | `.hx` sibling | carries |
|
||||
| --- | --- | --- |
|
||||
| `logtail.wo` | LogTail.hx | `TailState` record; bounded `fs.read_at` tail polls (never front-to-back); rotation restart on inode change or shrink; burst jump to tail; torn-final-line holdback; line classification by level prefix incl. timestamped app-log lines |
|
||||
| `watcher.wo` | Watcher.hx | the alert rule: last entry `error` + quiet period elapsed → alert transition; injected clock parameters |
|
||||
| `cron.wo` | Cron.hx | cron.d entry parse (five-field schedules, user column); `>> logfile` redirection extraction (the zero-config watch derivation); same-log collapse; next-fire computation; unreadable directory reported as skipped data, never a throw |
|
||||
| `probes.wo` | Flock.hx, Pgrep.hx | `static fn` probes over `proc.run`: flock exit-1-means-held with exists-guard, pgrep exit-0-means-alive, unknown codes falling in the safe direction |
|
||||
| `supervisor.wo` | Supervisor.hx | single-threaded tick loop; rescan interval; pre-fire lock probes (PROBE_LEAD); watch activation/completion; error-only JSONL detections through `fs.append`; daemon shape `while !env.stopping() { tick; time.sleep }` |
|
||||
| `mcp.wo` | Mcp.hx | typed request/response records; **pure `handle(req) -> resp`, socket-free** (the original's best design decision, preserved); Bearer auth first; method/path/size gates; JSON-RPC envelope (initialize, ping, tools/list, tools/call; notifications answered 202); serve loop over `net.listen`/`accept` on 127.0.0.1, one request per connection |
|
||||
| `tools.wo` | Tools.hx | tool subset over shipped capability: `list_logs`, `tail_log`, `search_log` as bounded windows over `fs.read_at`; the sqlite-backed minilog tools are scoped out to the DB track (recorded as scoped-out, not could-not-express) |
|
||||
| `main.wo` | Main.hx | `fn main(args: multi Text) -> Int`; subcommands `watch` / `run` / `mcp`; config decoded via `json.decode … as` into a record with `?fields`; usage text and exit codes on bad invocation |
|
||||
|
||||
**README.md** (orientation only, per repo docs rule):
|
||||
|
||||
- the mapping table above with two more columns: **divergences** (each
|
||||
deliberate `.wo`-idiom improvement) and **could-not-express** (defects
|
||||
against plans 8/9; target empty);
|
||||
- a status line: authored ahead of the compiler; plan 10 verifies by
|
||||
compilation and fixtures when plans 8/9 ship;
|
||||
- one paragraph linking the blue-green spec: this daemon is the shape of
|
||||
program the runtime updates in place — propose, approve, atomic switch,
|
||||
resident rollback — via `wo remote`, once that subsystem ships;
|
||||
- a pointer to `docs/00-principles.md`.
|
||||
|
||||
## Section 3 — `docs/00-principles.md`
|
||||
|
||||
One page; each principle is a short statement, a one-line why, and a link to
|
||||
the spec or doc that enforces it. The thirteen principles (the thirteenth
|
||||
added 2026-08-08 by story amendment):
|
||||
|
||||
1. **One binary is the whole system.** App, database, API, and UI ship as a
|
||||
single deployable; there is nothing else to operate.
|
||||
2. **Zero dependencies — kernel primitives only.** libc-only C runtime,
|
||||
stdlib-only OCaml compiler; epoll/io_uring, inotify, signalfd are the
|
||||
framework.
|
||||
3. **Memory safety without a GC tax.** Mutable value semantics: single
|
||||
owner, second-class borrows (the Rust-borrow shape without lifetime
|
||||
inference), hybrid static+runtime enforcement; `@gc` is a per-class
|
||||
opt-in collected per shard with no global pause.
|
||||
4. **No inheritance, ever.** Composition, structural interfaces, and tagged
|
||||
unions; no `extends`, no `override`, no virtual hierarchies.
|
||||
5. **Thread-per-core shards; ownership moves, data never shares.** Cross-
|
||||
shard communication is a message send; no shared mutable engine state.
|
||||
6. **The runtime never stops.** Blue/Green VM slots, in-runtime compile,
|
||||
atomic dispatch switch, resident rollback; the binary embeds its own
|
||||
source.
|
||||
7. **RAM is authoritative; the WAL makes it durable.** Ack after fsync;
|
||||
mirrors (Postgres) are reconstructible backups, never a commit path.
|
||||
8. **Samples force the grammar.** Examples are the de facto integration
|
||||
tests; a feature exists when a sample exercises it.
|
||||
9. **Linux is the target.** The kernel is the substrate, not an abstraction
|
||||
boundary to hide.
|
||||
10. **Capabilities are typed builtins.** No FFI, no shell strings, no
|
||||
escape hatches; the read-only posture is the default posture.
|
||||
11. **Plain diagnostics are the product.** Stable error codes, two-site
|
||||
ownership messages; MVS only beats Rust ergonomics if the errors are
|
||||
plain.
|
||||
12. **The runtime is a recipe box.** Web frameworks and databases arrive
|
||||
later as `.wo` libraries composing separable runtime capabilities, not
|
||||
as monoliths.
|
||||
13. **Statically typed, all the way to the register.** No `Dynamic`, no
|
||||
`untyped`, no `cast`; untagged VM registers because the compiler knows
|
||||
every slot's type; `@`-annotations are the compile-time ORM.
|
||||
|
||||
Placement note: `docs/` currently starts at `01-problem.md`; `00-` is free
|
||||
and reads as "start here". CLAUDE.md's "Where to read next" gains one line
|
||||
pointing at it (smallest possible touch).
|
||||
|
||||
## Section 4 — Error handling (in the authored artifacts)
|
||||
|
||||
The sample demonstrates the approved error doctrine rather than inventing
|
||||
one: optionals (`?T`) for expected absence (missing stat, failed decode,
|
||||
missing env var), try/catch over traps for genuine faults, `throw` only
|
||||
where the original throws. The probes' Haxe `catch (e:Dynamic) return false`
|
||||
idiom becomes optional-returning calls — a README-tabled divergence.
|
||||
|
||||
## Section 5 — Verification (docs artifact, this session)
|
||||
|
||||
- **Surface audit:** every construct in every `.wo` file traceable to a
|
||||
verdict-table row, OOP spec section 3, or Part 2/3 of the systems spec;
|
||||
anything else is removed or logged as could-not-express.
|
||||
- **Behavior audit:** every `.wo` function names its `.hx` source behavior;
|
||||
the mapping table is complete (eight rows, no blank cells).
|
||||
- **Principles audit:** every principle's link resolves to an existing doc;
|
||||
no principle contradicts a locked decision.
|
||||
- **Spec self-review** per the brainstorming skill, then user review.
|
||||
- Compilation, fixtures, and the live silent-death scenario remain plan 10
|
||||
acceptance — explicitly not claimed here.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. `docs/examples/log-watcher/` holds the eight `.wo` files and README; the
|
||||
mapping table's could-not-express column is empty or contains only
|
||||
defect reports filed against plans 8/9.
|
||||
2. Every construct used is traceable to approved specs (surface audit
|
||||
passes).
|
||||
3. `docs/00-principles.md` exists with the thirteen principles, each linked
|
||||
to its enforcing doc; CLAUDE.md points at it.
|
||||
4. The example README links the blue-green spec and the principles doc.
|
||||
5. Nothing outside `docs/` and CLAUDE.md is touched; no fixtures, no code.
|
||||
Loading…
Reference in a new issue