feat: program mode (argv + exit code); reject + on Text; nil compares by word

docs/examples/log-watcher now COMPILES AND RUNS: `wovm lw.wob watch app.log 2 1`
tails a live file, classifies levels and fires its alert
("last entry is error, quiet for 2s"). corpus 71/0, woc 565/0, wovm gates green.

- program mode: the entry is `fn main` taking nothing or one `multi Text`;
  runtime/src/main.c builds that list from the program's own arguments (not
  the program name, not the image path) and the entry's return value is the
  process exit code (low byte); the loader accepts a 0- or 1-arg free-fn entry
- `+` on Text is now WO-E201 pointing at `..`. This was a memory-safety hole,
  not a style nit: the emitter lowered it to ADD on two heap pointers, and the
  workload's own `out = out + char_of(c)` produced a wild pointer that
  segfaulted the VM inside starts_with. Reported off confident types only
- `x == nil` / `x != nil` lower to EQ (a word compare), never EQS: nil is the
  zero word and EQS dereferences its operands, so a nil guard would trap
  instead of answering
- docs/examples/log-watcher: seven `+`-on-Text sites corrected to `..`
  (logtail sanitize, mcp header/body/carry assembly, supervisor detections
  line) — the sample was carrying the Haxe habit, and the language reserves
  `+` for arithmetic by doctrine
- wovm CLI takes arguments after the image path (`wovm <file.wob> [args...]`)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-14 17:18:01 +02:00
parent 065ac99224
commit b973ea107e
8 changed files with 191 additions and 99 deletions

View file

@ -1339,6 +1339,10 @@ let container_imm (p : pctx) (expected : Ast.field_ty option) (map : bool) : int
| _ -> None)
| None -> None
(* `nil` written literally on either side of a comparison — see emit_binary's
Eq/Ne cases for why the distinction matters. *)
let is_nil_lit (e : Ast.expr) : bool = match e.Ast.kind with Ast.NilLit -> true | _ -> false
let rec emit_expr (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e : Ast.expr) : unit =
f.f_cur_line <- e.pos.line;
(match e.kind with
@ -1600,14 +1604,26 @@ and emit_binary (p : pctx) (f : fstate) (v : views) ~(dst : int) (op : Ast.binop
| Le -> simple op_le
| Gt -> swapped op_lt
| Ge -> swapped op_le
| Eq -> if is_text p f l || is_text p f r then simple op_eqs else simple op_eq
(* A comparison against `nil` is a WORD compare, never a content compare:
nil is the zero word, and EQS would dereference it as a `wo_str*` (the
VM's str_check traps on that, so an `x != nil` guard would trap instead
of answering). Text-vs-Text still uses EQS. *)
| Eq ->
if is_nil_lit l || is_nil_lit r then simple op_eq
else if is_text p f l || is_text p f r then simple op_eqs
else simple op_eq
| Ne ->
(* no NE opcode in the v1 set: `a != b` is `(a == b) == 0`. The
format doc governs, so this is a lowering, not a new opcode. *)
let a = emit_operand p f v l in
let b = emit_operand p f v r in
let t = alloc_temp p f pos in
put f (ins_abc (if is_text p f l || is_text p f r then op_eqs else op_eq) t a b);
put f
(ins_abc
(if is_nil_lit l || is_nil_lit r then op_eq
else if is_text p f l || is_text p f r then op_eqs
else op_eq)
t a b);
let z = alloc_temp p f pos in
put f (ins_abx op_loadk z (check_bx p f pos "constant" (const_int p 0)));
put f (ins_abc op_eq dst t z)
@ -3623,10 +3639,19 @@ let emit ~(syms : Types.symbols) ~(module_of : string -> string)
mr_code = [||]; mr_lines = []; mr_drops = [] }
:: !methods;
bodies := (u, None, m, !nmethods) :: !bodies;
(* the entry point is the zero-argument free fn `main` —
the loader's own rule for an entry (a zero-arg free fn)
plus one fixed name so `wovm image.wob` needs no flag *)
if m.name = "main" && m.params = [] then begin
(* Program mode: the entry is the free fn `main`, taking either
nothing or one `multi Text` of command-line arguments (the
runtime builds it — runtime/src/main.c). One fixed name, so
`wovm image.wob` needs no flag, and its return value is the
process exit code. *)
let entry_shaped =
m.name = "main"
&& match m.params with
| [] -> true
| [ (pa : Ast.param) ] -> ( match pa.Ast.ty with Ast.Multi "Text" -> true | _ -> false)
| _ -> false
in
if entry_shaped then begin
entry := !nmethods;
(match m.ret with
| Some ty when ty <> Ast.Scalar "Int" ->

View file

@ -1314,6 +1314,36 @@ let typecheck_program ~file ~(module_of : string -> string)
())
| _ -> ());
{ typ = TScalar "Bool"; is_nil = false }
| Binary (((Add | Sub | Mul | Div | Mod) as op), left, right) ->
let lres = typecheck_expr env cenv left in
let rres = typecheck_expr env cenv right in
(* `+` is arithmetic, never string addition (docs/plan/oop-vm/
08-builtin-surface.md's operator table) — and a Text operand here
is not a harmless type slip: the emitter would lower it to ADD on
two heap pointers, producing a wild pointer with no diagnostic. So
this is reported off CONFIDENT types (the same "stay silent when
underivable" contract as every other check here) and names the
operator that does concatenate. *)
let text_side (e : expr) (r : expr_type_result) : bool =
match confident_typ cenv e with
| Some t -> unwrap_nullable t = TScalar "Text"
| None -> ( match e.kind with StrLit _ | Interp _ -> true | _ -> ignore r; false)
in
if text_side left lres || text_side right rres then
Diag.Collector.add collector
(Diag.error ~code:type_mismatch_code ~file ~line:e.pos.line ~col:e.pos.col
~message:
(Printf.sprintf
"`%s` is arithmetic and never joins text — use `..` to concatenate"
(match op with
| Add -> "+"
| Sub -> "-"
| Mul -> "*"
| Div -> "/"
| _ -> "%"))
());
{ typ = (match confident_typ cenv left with Some t -> t | None -> TScalar "Int");
is_nil = false }
| Binary (_, left, right) ->
let _ = typecheck_expr env cenv left in
let _ = typecheck_expr env cenv right in

View file

@ -10,10 +10,10 @@ banner and this board change. Every plan and phase doc opens with a
discarded/learnings registers carry none by design.
Update this board in the same change that finishes work — move the item to done
with *what actually landed*, set the next in-progress item, and record any
with _what actually landed_, set the next in-progress item, and record any
rejection in [`discarded.md`](plan/discarded.md) with its reason.
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **parked**
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **hold**
---
@ -34,7 +34,7 @@ optionals throughout in place of the Haxe original's sentinel values, so
nothing else in this plan can land ahead of it.
Two tracks run in this repo. The critical path is the **language track**:
iterations 3 → 4 → 5 → 6 → 7, ending at *compile and run log-watcher*. The
iterations 3 → 4 → 5 → 6 → 7, ending at _compile and run log-watcher_. The
Rust-runtime track is shipped-and-maintained, not advancing.
---
@ -46,29 +46,29 @@ Rust-runtime track is shipped-and-maintained, not advancing.
an unsplittable slice with Given/When/Then acceptance and a pointer to the plan
that sequences its tasks. Read one, approve, then the next starts.
| # | Iteration | State |
| --- | --- | --- |
| 1 | [Principles doc](stories/language-runtime-database/01-principles-doc.md) | ✅ |
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
| 4 | [Single binary end-to-end](stories/language-runtime-database/04-single-binary-e2e.md) | ✅ (known gaps below) |
| 5 | [Language surface](stories/language-runtime-database/05-language-surface.md) | 🔄 **next** |
| 6 | [Program mode + stdlib](stories/language-runtime-database/06-program-mode-stdlib.md) | ⬜ |
| 7 | [log-watcher proof](stories/language-runtime-database/07-logwatcher-proof.md) | ⬜ acceptance |
| 7b | [Inferred GC + mark-sweep](stories/language-runtime-database/07b-inferred-gc-mark-sweep.md) | ⬜ closes iteration 4's gate |
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
| 9b | [`@table`, relations, query](stories/language-runtime-database/09b-table-relations-query.md) | ⬜ needs a spec first |
| 10 | [HTTP service layer](stories/language-runtime-database/10-http-service.md) | ⬜ |
| 11 | [Fibers](stories/language-runtime-database/11-fibers.md) | ⬜ |
| 12 | [Blue-green deploy](stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ |
| # | Iteration | State |
| --- | -------------------------------------------------------------------------------------------- | ---------------------------- | ---- |
| 1 | [Principles doc](stories/language-runtime-database/01-principles-doc.md) | ✅ |
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
| 4 | [Single binary end-to-end](stories/language-runtime-database/04-single-binary-e2e.md) | ✅ (known gaps below) |
| 5 | [Language surface](stories/language-runtime-database/05-language-surface.md) | 🔄 **next** |
| 6 | [Program mode + stdlib](stories/language-runtime-database/06-program-mode-stdlib.md) | ⬜ |
| 7 | [log-watcher proof](stories/language-runtime-database/07-logwatcher-proof.md) | ⬜ acceptance |
| 7b | [Inferred GC + mark-sweep](stories/language-runtime-database/07b-inferred-gc-mark-sweep.md) | ⬜ closes iteration 4's gate |
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
| 9b | [`@table`, relations, query](stories/language-runtime-database/09b-table-relations-query.md) | ⬜ needs a spec first |
| 10 | [HTTP service layer](stories/language-runtime-database/10-http-service.md) | ⬜ | Hold |
| 11 | [Fibers](stories/language-runtime-database/11-fibers.md) | ⬜ | Hold |
| 12 | [Blue-green deploy](stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ | Hold |
---
## In progress
| Track | Item | Where |
| --- | --- | --- |
| Track | Item | Where |
| -------- | ---------------------------------------------------------------------- | ---------------------------------------------------------- |
| Language | Iteration 5 — Haxe-parity language surface, `?T` forced handling first | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
Nothing else should be started until iteration 5 lands. Off-critical-path work
@ -80,16 +80,16 @@ is parked by explicit scope directive (2026-08-08).
### Language track — compiler + VM (OOP track)
| Status | Item | Doc | What actually landed |
| --- | --- | --- | --- |
| ✅ | Principles | [`../00-principles.md`](00-principles.md) | 13 principles, each with a why and a link to the doc that enforces it |
| ✅ | `wovm` VM core | [plan 1](superpowers/plans/2026-08-01-wob-format-and-vm-core.md) | `.wob` v1 loader with full static validation, register interpreter (computed-goto + ISO-C fallback), arena with size-class free lists, borrow word, RC + budgeted Bacon–Rajan cycle collector, drop-map trap unwinding, containers, builtins, ICALL, CLI. 13 suites × 2 dispatch flavors + CLI smoke, ASan/UBSan clean |
| ✅ | `.wob` format contract | [`oop-vm/00-wob-format.md`](plan/oop-vm/00-wob-format.md) | Normative; twinned with `runtime/src/wob.h` |
| ✅ | `woc` compiler front | [plan 2](plan/compiler/2026-08-01-woc-compiler-front.md) | Tasks 1–8: dune scaffold, `diag` (WO-E codes, two-site related errors, ordered dedup), newline-significant lexer at rt parity, declaration + statement/expression parser with skip-on-block and multi-error recovery, typechecker (field kinds, `?T` plumbing, W201, E225, E214), MVS ownership pass with the four emitter tables, driver with directory discovery + cross-file programs. 14 + 264 checks |
| ✅ | Error catalog | [`oop-vm/01-error-catalog.md`](plan/oop-vm/01-error-catalog.md) | 14 emitted codes + 10 reserved, each with the reason it is not yet emitted |
| ✅ | log-watcher `.wo` sample | [`../examples/log-watcher/`](examples/log-watcher/README.md) | Eight-file port authored docs-first with its `.hx` mapping table; compiles for real in iteration 7 |
| ✅ | Scalar cleanup | [`discarded.md`](plan/discarded.md) | `Money`/`SKU`/`Float` and the abstract allowlist removed; `abstract` flipped adopt → reject |
| ✅ | `woc` emitter, corpus, single binary | [plan 3](plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md) | Tasks 1–6 + 8 (Task 7, a parity harness against the Rust runtime, **deferred by explicit user decision** — the two stacks diverge by design). Bytecode emitter (`emit.ml`) + disassembler (`disasm.ml`, `--dump-bc`); three-kind conformance harness (`scripts/oop-e2e.sh`, `just oop-e2e`) over `tests/corpus/{run,compile-fail,trap,gc}`; pricing-demo + ownership/trap corpora (19 fixtures); `@gc` cycle collector's post-exit pump (`WO_GC_BUDGET`/`WO_GC_TRACE`) + 2 gc fixtures (`gc/held-cycle` retired — see criterion-3 closure below); `woc build` single-binary output + relocation/corrupt-trailer smoke; `WO-E405` closing criterion 3's ASan leak (entry must return `Int`); `just oop-accept` wiring all five spec criteria + both unit gates into one command. 14 + 399 compiler checks; `oop-e2e` 25/25 against the release `wovm`. **Milestone-1 acceptance gate is fully green — all five criteria met** (see the dated acceptance note in `docs/superpowers/specs/2026-08-01-oop-compiler-vm-design.md`) |
| Status | Item | Doc | What actually landed |
| ------ | ------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ✅ | Principles | [`../00-principles.md`](00-principles.md) | 13 principles, each with a why and a link to the doc that enforces it |
| ✅ | `wovm` VM core | [plan 1](superpowers/plans/2026-08-01-wob-format-and-vm-core.md) | `.wob` v1 loader with full static validation, register interpreter (computed-goto + ISO-C fallback), arena with size-class free lists, borrow word, RC + budgeted Bacon–Rajan cycle collector, drop-map trap unwinding, containers, builtins, ICALL, CLI. 13 suites × 2 dispatch flavors + CLI smoke, ASan/UBSan clean |
| ✅ | `.wob` format contract | [`oop-vm/00-wob-format.md`](plan/oop-vm/00-wob-format.md) | Normative; twinned with `runtime/src/wob.h` |
| ✅ | `woc` compiler front | [plan 2](plan/compiler/2026-08-01-woc-compiler-front.md) | Tasks 1–8: dune scaffold, `diag` (WO-E codes, two-site related errors, ordered dedup), newline-significant lexer at rt parity, declaration + statement/expression parser with skip-on-block and multi-error recovery, typechecker (field kinds, `?T` plumbing, W201, E225, E214), MVS ownership pass with the four emitter tables, driver with directory discovery + cross-file programs. 14 + 264 checks |
| ✅ | Error catalog | [`oop-vm/01-error-catalog.md`](plan/oop-vm/01-error-catalog.md) | 14 emitted codes + 10 reserved, each with the reason it is not yet emitted |
| ✅ | log-watcher `.wo` sample | [`../examples/log-watcher/`](examples/log-watcher/README.md) | Eight-file port authored docs-first with its `.hx` mapping table; compiles for real in iteration 7 |
| ✅ | Scalar cleanup | [`discarded.md`](plan/discarded.md) | `Money`/`SKU`/`Float` and the abstract allowlist removed; `abstract` flipped adopt → reject |
| ✅ | `woc` emitter, corpus, single binary | [plan 3](plan/compiler/2026-08-01-wob-emit-e2e-single-binary.md) | Tasks 1–6 + 8 (Task 7, a parity harness against the Rust runtime, **deferred by explicit user decision** — the two stacks diverge by design). Bytecode emitter (`emit.ml`) + disassembler (`disasm.ml`, `--dump-bc`); three-kind conformance harness (`scripts/oop-e2e.sh`, `just oop-e2e`) over `tests/corpus/{run,compile-fail,trap,gc}`; pricing-demo + ownership/trap corpora (19 fixtures); `@gc` cycle collector's post-exit pump (`WO_GC_BUDGET`/`WO_GC_TRACE`) + 2 gc fixtures (`gc/held-cycle` retired — see criterion-3 closure below); `woc build` single-binary output + relocation/corrupt-trailer smoke; `WO-E405` closing criterion 3's ASan leak (entry must return `Int`); `just oop-accept` wiring all five spec criteria + both unit gates into one command. 14 + 399 compiler checks; `oop-e2e` 25/25 against the release `wovm`. **Milestone-1 acceptance gate is fully green — all five criteria met** (see the dated acceptance note in `docs/superpowers/specs/2026-08-01-oop-compiler-vm-design.md`) |
**Known gaps carried out of iteration 3** — recorded, not silently owed:
@ -139,7 +139,7 @@ is parked by explicit scope directive (2026-08-08).
a class-returning `main` was never legal — `WO-E405`
(`compiler/src/emit.ml`, `01-error-catalog.md`) now rejects it at
compile time, and `gc/held-cycle` is retired because its premise (an
externally-held cycle survives a *post-exit* pump) is no longer
externally-held cycle survives a _post-exit_ pump) is no longer
expressible — see `oop-vm/02-corpus.md`'s "Retired" note for why, and
for where the scenario it meant to cover is actually proven
(`runtime/test/test_cycle.c`, plus a proper in-flight fixture scheduled
@ -148,23 +148,23 @@ is parked by explicit scope directive (2026-08-08).
### Rust runtime track — Stage 2 shipped, maintained
| Status | Phase | Doc | Notes |
| --- | --- | --- | --- |
| ✅ | 01 crate scaffolding | [done/01](plan/done/01-scafolding-crates.md) | 15 crates |
| ✅ | 02 epoll event loop | [done/02](plan/done/02-event-loop-epoll.md) | `runtime/netpoll_epoll.rs` |
| ✅ | 03 hand-rolled HTTP | [done/03](plan/done/03-hand-rolled-http.md) | + keep-alive & pipelining |
| ✅ | 04 tokio/axum cutover | [done/04](plan/done/04-cutover-remove-tokio-axum.md) | deps now: anyhow, serde, serde_json, libc |
| ✅ | 09a thread-per-core | [09](plan/09-concurrency-scaleout.md) | `scheduler.rs`, `SO_REUSEPORT`, pinned `wo-shard-<t>` workers |
| ✅ | 09b sharded engine | [09](plan/09-concurrency-scaleout.md) | `shard.rs` bus; `Arc<Mutex<Engine>>` deleted; interleaved ids |
| ✅ | 09c per-shard WAL | [09](plan/09-concurrency-scaleout.md) | ack-after-fsync; boot replay; `meta` shard guard |
| ✅ | — keep-alive follow-up | [09](plan/09-concurrency-scaleout.md) | reads ×3.4 → 770k/s |
| ✅ | — io_uring group commit | [09](plan/09-concurrency-scaleout.md) | raw ring; 4.7× durable writes on real disk |
| ✅ | 16a PG wire client | [16](plan/16-postgres-mirror.md) | hand-rolled protocol v3, zero crates |
| ✅ | 16b PG backup mirror | [16](plan/16-postgres-mirror.md) | async JSONB upserts behind the WAL ack; RAM authoritative |
| ✅ | 13a class surface | [13](plan/13-class-model-live-pricing.md) | `class` parses, CRUD serves |
| ✅ | 13b method execution | [13](plan/13-class-model-live-pricing.md) | row-scoped txn per call; abort → 409 rollback |
| ✅ | — `@table` + indexed DML | [13](plan/13-class-model-live-pricing.md) | secondary indexes, `find_by`, `select Type{…}`, REST filters |
| ✅ | C proving ground A–F | [exploration/c-runtime/00-plan.md](plan/exploration/c-runtime/00-plan.md) | 859k reads/s, 618k durable commits/s; found the ack-ordering + fd-ABA bugs the Rust port avoided |
| Status | Phase | Doc | Notes |
| ------ | ------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| ✅ | 01 crate scaffolding | [done/01](plan/done/01-scafolding-crates.md) | 15 crates |
| ✅ | 02 epoll event loop | [done/02](plan/done/02-event-loop-epoll.md) | `runtime/netpoll_epoll.rs` |
| ✅ | 03 hand-rolled HTTP | [done/03](plan/done/03-hand-rolled-http.md) | + keep-alive & pipelining |
| ✅ | 04 tokio/axum cutover | [done/04](plan/done/04-cutover-remove-tokio-axum.md) | deps now: anyhow, serde, serde_json, libc |
| ✅ | 09a thread-per-core | [09](plan/09-concurrency-scaleout.md) | `scheduler.rs`, `SO_REUSEPORT`, pinned `wo-shard-<t>` workers |
| ✅ | 09b sharded engine | [09](plan/09-concurrency-scaleout.md) | `shard.rs` bus; `Arc<Mutex<Engine>>` deleted; interleaved ids |
| ✅ | 09c per-shard WAL | [09](plan/09-concurrency-scaleout.md) | ack-after-fsync; boot replay; `meta` shard guard |
| ✅ | — keep-alive follow-up | [09](plan/09-concurrency-scaleout.md) | reads ×3.4 → 770k/s |
| ✅ | — io_uring group commit | [09](plan/09-concurrency-scaleout.md) | raw ring; 4.7× durable writes on real disk |
| ✅ | 16a PG wire client | [16](plan/16-postgres-mirror.md) | hand-rolled protocol v3, zero crates |
| ✅ | 16b PG backup mirror | [16](plan/16-postgres-mirror.md) | async JSONB upserts behind the WAL ack; RAM authoritative |
| ✅ | 13a class surface | [13](plan/13-class-model-live-pricing.md) | `class` parses, CRUD serves |
| ✅ | 13b method execution | [13](plan/13-class-model-live-pricing.md) | row-scoped txn per call; abort → 409 rollback |
| ✅ | — `@table` + indexed DML | [13](plan/13-class-model-live-pricing.md) | secondary indexes, `find_by`, `select Type{…}`, REST filters |
| ✅ | C proving ground A–F | [exploration/c-runtime/00-plan.md](plan/exploration/c-runtime/00-plan.md) | 859k reads/s, 618k durable commits/s; found the ack-ordering + fd-ABA bugs the Rust port avoided |
Ecommerce sample (verified 2026-06-13): `api.rest` 17/17 expected statuses pass.
@ -174,18 +174,18 @@ Ecommerce sample (verified 2026-06-13): `api.rest` 17/17 expected statuses pass.
### Language track — sequenced, on the critical path
| # | Item | Plan |
| --- | --- | --- |
| 5 | Haxe-parity language surface — **`?T` forced handling first**, then switch expressions, records, enum payloads, try/catch, statics, `using`, modules, `is`, `pub(read)`, `#if` | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
| 6 | Program mode + systems stdlib — `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` | [plan 9](superpowers/plans/2026-08-01-program-mode-stdlib.md) |
| 7 | log-watcher proof — the sample compiles and detects a silent death live | [plan 10](superpowers/plans/2026-08-01-log-watcher-sample.md) |
| 7b | Inferred GC + incremental mark-sweep — `@gc` removed, GC-ness inferred, RC retired | [spec](superpowers/specs/2026-08-11-inferred-gc-mark-sweep-design.md) — plan to be written |
| 8 | Shard-actor runtime | [plan 4](superpowers/plans/2026-08-01-shard-actor-vm-runtime.md) |
| 9 | Database engine binding | [plan 5](superpowers/plans/2026-08-01-db-engine-binding.md) |
| 9b | `@table` + relations + language-integrated query | **no spec yet** — three open forks recorded in the iteration; brainstorm before planning |
| 10 | HTTP service layer | [plan 6](superpowers/plans/2026-08-01-http-service-layer.md) |
| 11 | Fibers | vision §3, [blue-green exploration](plan/exploration/blue-green-vm/00-vision.md) |
| 12 | Blue-green deploy | [spec](superpowers/specs/2026-08-03-blue-green-vm-design.md) — plan authored after iterations 9–10 |
| # | Item | Plan |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| 5 | Haxe-parity language surface — **`?T` forced handling first**, then switch expressions, records, enum payloads, try/catch, statics, `using`, modules, `is`, `pub(read)`, `#if` | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
| 6 | Program mode + systems stdlib — `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` | [plan 9](superpowers/plans/2026-08-01-program-mode-stdlib.md) |
| 7 | log-watcher proof — the sample compiles and detects a silent death live | [plan 10](superpowers/plans/2026-08-01-log-watcher-sample.md) |
| 7b | Inferred GC + incremental mark-sweep — `@gc` removed, GC-ness inferred, RC retired | [spec](superpowers/specs/2026-08-11-inferred-gc-mark-sweep-design.md) — plan to be written |
| 8 | Shard-actor runtime | [plan 4](superpowers/plans/2026-08-01-shard-actor-vm-runtime.md) |
| 9 | Database engine binding | [plan 5](superpowers/plans/2026-08-01-db-engine-binding.md) |
| 9b | `@table` + relations + language-integrated query | **no spec yet** — three open forks recorded in the iteration; brainstorm before planning |
| 10 | HTTP service layer | [plan 6](superpowers/plans/2026-08-01-http-service-layer.md) |
| 11 | Fibers | vision §3, [blue-green exploration](plan/exploration/blue-green-vm/00-vision.md) |
| 12 | Blue-green deploy | [spec](superpowers/specs/2026-08-03-blue-green-vm-design.md) — plan authored after iterations 9–10 |
### Language track — parked until after iteration 12
@ -207,27 +207,27 @@ log-watcher proof.
### Rust runtime track — not advancing while the language track runs
| Status | Phase | Doc | Notes |
| --- | --- | --- | --- |
| ⬜ | 05 hand-rolled JSON | [05](plan/05-hand-rolled-json.md) | removes serde/serde_json |
| ⬜ | 06 bespoke error type | [06](plan/06-bespoke-error.md) | removes anyhow |
| ⬜ | 07 inotify content watcher | [07](plan/07-inotify-content-watcher.md) | `wo dev` hot reload |
| ⬜ | 08 sendfile static assets | [08](plan/08-sendfile-static-assets.md) | needed by the parked UI track |
| ⬜ | 09d cross-shard subscriptions | [09](plan/09-concurrency-scaleout.md) | LIVE fan-out; pairs with Stage 3 |
| ⬜ | 09e cross-shard transactions (2PC) | [09](plan/09-concurrency-scaleout.md) | needed by `fn checkout` spanning shards |
| ⬜ | 09f observability & reshard | [09](plan/09-concurrency-scaleout.md) | per-shard metrics, `WO_RESHARD` |
| ⬜ | 10–12 storage completion | [10](plan/10-storage-foundations.md), [11](plan/11-wal-and-recovery.md), [12](plan/12-engine-disk-cutover.md) | snapshots, compaction, WAL rotation, mmap arena engine |
| ⬜ | 13c LIVE pricing push · Stage 3 wire layer · 13e at scale | [13](plan/13-class-model-live-pricing.md) | replaces the 501 stub |
| ⬜ | 15a–15e MCP over streamable HTTP | [15](plan/15-mcp-streamable-http.md) | 15e needs 13c + 09d |
| ⬜ | 16c–16f typed columns, lossless resync, restore, SCRAM | [16](plan/16-postgres-mirror.md) | |
| Status | Phase | Doc | Notes |
| ------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| ⬜ | 05 hand-rolled JSON | [05](plan/05-hand-rolled-json.md) | removes serde/serde_json |
| ⬜ | 06 bespoke error type | [06](plan/06-bespoke-error.md) | removes anyhow |
| ⬜ | 07 inotify content watcher | [07](plan/07-inotify-content-watcher.md) | `wo dev` hot reload |
| ⬜ | 08 sendfile static assets | [08](plan/08-sendfile-static-assets.md) | needed by the parked UI track |
| ⬜ | 09d cross-shard subscriptions | [09](plan/09-concurrency-scaleout.md) | LIVE fan-out; pairs with Stage 3 |
| ⬜ | 09e cross-shard transactions (2PC) | [09](plan/09-concurrency-scaleout.md) | needed by `fn checkout` spanning shards |
| ⬜ | 09f observability & reshard | [09](plan/09-concurrency-scaleout.md) | per-shard metrics, `WO_RESHARD` |
| ⬜ | 10–12 storage completion | [10](plan/10-storage-foundations.md), [11](plan/11-wal-and-recovery.md), [12](plan/12-engine-disk-cutover.md) | snapshots, compaction, WAL rotation, mmap arena engine |
| ⬜ | 13c LIVE pricing push · Stage 3 wire layer · 13e at scale | [13](plan/13-class-model-live-pricing.md) | replaces the 501 stub |
| ⬜ | 15a–15e MCP over streamable HTTP | [15](plan/15-mcp-streamable-http.md) | 15e needs 13c + 09d |
| ⬜ | 16c–16f typed columns, lossless resync, restore, SCRAM | [16](plan/16-postgres-mirror.md) | |
### Frontend — parked
| Status | Phase | Doc |
| --- | --- | --- |
| ⏸ | 13d pricing UI | [13](plan/13-class-model-live-pricing.md) |
| ⏸ | 14 MVC UI implementation (14a–f) | [14](plan/14-mvc-ui-implementation.md) |
| ⏸ | UI exploration track | [exploration/ui/00-overview.md](plan/exploration/ui/00-overview.md) |
| Status | Phase | Doc |
| ------ | -------------------------------- | ------------------------------------------------------------------- |
| ⏸ | 13d pricing UI | [13](plan/13-class-model-live-pricing.md) |
| ⏸ | 14 MVC UI implementation (14a–f) | [14](plan/14-mvc-ui-implementation.md) |
| ⏸ | UI exploration track | [exploration/ui/00-overview.md](plan/exploration/ui/00-overview.md) |
---

View file

@ -129,7 +129,7 @@ pub fn sanitize(line: Text) -> Text {
}
continue;
}
if c >= 32 or c == 9 { out = out + char_of(c); }
if c >= 32 or c == 9 { out = out .. char_of(c); }
i = i + 1;
}
return out;

View file

@ -125,9 +125,9 @@ class Mcp {
}
let resp = self.handle(req);
let head = "HTTP/1.1 ${resp.status} ${status_text(resp.status)}\r\n";
head = head + "Content-Type: application/json\r\nConnection: close\r\n";
head = head .. "Content-Type: application/json\r\nConnection: close\r\n";
head = head + "Content-Length: ${len(resp.body)}\r\n\r\n";
try net.write(c, head + resp.body) catch (e) {}
try net.write(c, head .. resp.body) catch (e) {}
}
}
@ -140,7 +140,7 @@ class Mcp {
while header_end == -1 {
let got = net.read(c, 8192);
if len(got) == 0 { return nil; } -- peer closed mid-headers
buf = buf + got;
buf = buf .. got;
header_end = index_of(buf, "\r\n\r\n");
if len(buf) > BODY_MAX * 2 { return nil; } -- runaway header block
}
@ -169,7 +169,7 @@ class Mcp {
while len(body) < want {
let got = net.read(c, want - len(body));
if len(got) == 0 { break; }
body = body + got;
body = body .. got;
}
let method = "";
if len(req_line) > 0 { method = req_line[0]; }
@ -329,7 +329,7 @@ class Tools {
if want > CHUNK { want = CHUNK; }
let chunk = fs.read_at(path, offset, want);
if len(chunk) == 0 { break; }
let lines = split(carry + chunk, "\n");
let lines = split(carry .. chunk, "\n");
carry = pop(lines); -- last piece has no newline yet
for line in lines {
if index_of(to_lower(line), lc) >= 0 {

View file

@ -203,7 +203,7 @@ class Supervisor {
}
let line = json.encode(Detection { ts: time.iso(now), path: log_path,
source: source, event: event, lastError: last_error });
try fs.append(self.cfg.detections, line + "\n") catch (e) {
try fs.append(self.cfg.detections, line .. "\n") catch (e) {
print("DETECTIONS-SINK-ERROR ${self.cfg.detections}: ${e.msg}");
}
}

View file

@ -482,9 +482,12 @@ int wo_load_buf(wo_module *m, const uint8_t *buf, size_t len, char *err,
/* entry: a zero-arg free fn */
if (entry != WOB_NONE) {
if (entry >= m->method_cnt) BAIL("entry method out of range");
if (m->methods[entry].arg_cnt != 0 ||
/* program mode: an entry is a free fn taking nothing, or one
argument — the `multi Text` of command-line arguments the runtime
builds (runtime/src/main.c). */
if (m->methods[entry].arg_cnt > 1 ||
m->methods[entry].class_id != WOB_NONE)
BAIL("entry must be a zero-arg free fn");
BAIL("entry must be a free fn taking no arguments or one argv `multi Text`");
}
m->entry = entry;
return 0;

View file

@ -11,6 +11,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include "cont.h"
#include "gc.h"
#include "vm.h"
@ -128,9 +129,10 @@ int main(int argc, char **argv) {
return 2;
}
if (self_rc == 0) {
/* no embedded image: today's contract, unchanged */
if (argc != 2) {
fprintf(stderr, "usage: wovm <file.wob>\n");
/* no embedded image: the image path is argv[1], and program mode
passes everything after it to the program itself */
if (argc < 2) {
fprintf(stderr, "usage: wovm <file.wob> [args...]\n");
return 2;
}
if (wo_load_file(&mod, argv[1], err, sizeof err) != 0) {
@ -155,14 +157,46 @@ int main(int argc, char **argv) {
wo_module_free(&mod);
return 2;
}
/* Program mode: an entry that declares one parameter gets the program's
* OWN arguments as a `multi Text` — not the program name, and not the
* image path a plain `wovm image.wob args...` invocation carries. So
* `args[0]` is the first real argument (the workload's own contract:
* `args[0] == "watch"`, `args[1]` the log file). An entry with no
* parameters is called exactly as before. */
uint64_t argv_val = 0;
uint32_t entry_argc = mod.methods[mod.entry].arg_cnt;
if (entry_argc == 1) {
wo_multi *args = wo_multi_new(&VM.rt, WO_K_TEXT);
if (!args) {
fprintf(stderr, "wovm: cannot allocate the argument list\n");
wo_vm_destroy(&VM);
wo_module_free(&mod);
return 2;
}
int first = self_rc == 0 ? 2 : 1; /* skip the image path when there is one */
for (int i = first; i < argc; i++) {
wo_str *s = wo_str_new(&VM.rt, argv[i], (uint32_t)strlen(argv[i]));
if (!s || wo_multi_push(args, (uint64_t)(uintptr_t)s) != 0) {
fprintf(stderr, "wovm: cannot allocate the argument list\n");
wo_vm_destroy(&VM);
wo_module_free(&mod);
return 2;
}
}
argv_val = (uint64_t)(uintptr_t)args;
}
uint64_t ret = 0;
wo_err terr;
int rc = wo_vm_call(&VM, mod.entry, NULL, 0, &ret, &terr);
int rc = wo_vm_call(&VM, mod.entry, entry_argc == 1 ? &argv_val : NULL, entry_argc, &ret,
&terr);
if (rc != 0)
fprintf(stderr, "trap %u in %s at line %u: %s\n", (unsigned)terr.code,
terr.method, (unsigned)terr.line, terr.msg);
/* the entry's return value IS the exit code (docs/plan/oop-vm/
* 08-builtin-surface.md's "Program entry"): 0..255, a trap is 1 */
int exit_code = rc == 0 ? (int)((uint64_t)ret & 0xFF) : 1;
gc_pump(&VM);
wo_vm_destroy(&VM);
wo_module_free(&mod);
return rc == 0 ? 0 : 1;
return exit_code;
}