Commit graph

69 commits

Author SHA1 Message Date
3a30b4ac3b feat(db-bench): first baseline — the measurement contract (T5)
- bench/baseline.json: 74 metrics from the first full campaign;
  tolerances tuned by a two-run repeatability check (mix* 50%,
  read/query 35%, rest 15% — rationale in _config)
- gate bites: --check mode; doctored copy fails, both real runs 74/0
- headline: durable seed 4.5k/s vs ram 297k/s (23's case); reads
  O(table) at ~1.5k/s; mixread 1280 vs 21 ops/s single-vs-multi
  (the arc's price); msgrate 13.4M vs 2.45M (mutex-inbox number)
- arc delta recorded in story 8; findings in sample README

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:54:13 +02:00
d1cdf3ed6c feat(db-bench): campaign driver + gate + recipes (T4)
- scripts/db-bench.py (one python driver — bash+python deviation,
  disclosed): ram+durable x 1/N shards, msgrate per shard count,
  restart proof, kill -9 battery vs acked high-water, RSS/fd
  sampling from the mix phase, results JSON, relative+floor gates
- just db-bench / db-bench-quick (quick = floors only)
- quick campaign 14 checks green; gate honestly fails without a
  baseline (--write-baseline is Task 5's first full run)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:44:25 +02:00
07c1f7b257 feat: arc stage 3 T7 — transparent DB actor (WO_T_DB hole closed)
- worker DB builtins marshal to shard 0: requester-side slot encode
  (VM heaps never read cross-shard), owner executes serialized in
  adopt, reply unparks via new WO_PARK_INBOX park + envelope 3/4
- engine gains thread-agnostic slot entry points (insert_slots,
  update_field_slot, val_encode/clone, wo_db_exec_req); traps and
  messages byte-identical to the local path
- main.c: engine + replay boot BEFORE shards spawn; workers assert
  rt.db/rt.wal NULL; busy shard adopts inbox once per slice
- latent stage-1 bug fixed: shared io_uring params static raced by
  lazy worker init lost park wakes (~1/20 hangs); params per-vm,
  short submit now fails loud
- new sample docs/examples/db-actor + just db-actor gate 8/0 (multi
  x3, uring/epoll forced, single byte-exact, WAL replay pair);
  ASan+TSan 6/6; full battery green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 13:10:26 +02:00
d24c705860 feat: iterations 19 + 17 — Float/Bytes scalars (.wob v5), library kind + internal/
- Float full stack: literals (fraction/exponent; `0..10` still a range), f64
  opcodes 34-41, @table column, WAL bit-exact replay, json fractions in and
  shortest-round-trip out. IEEE-quiet — FDIV never traps where DIV does.
- Bytes: a wo_str with its own class id, so alloc/free/copy are shared but no
  Text builtin accepts one; len/at/slice/eq/concat, base64 both ways, json
  boundary as base64; TEXT_COPY preserves the kind.
- No implicit Int/Float mixing (WO-E201 in the typechecker, not the emitter,
  which picks the opcode from one side and would misread the other).
- One IEEE deviation: float_cmp total order (NaN last, -0.0 == +0.0) for
  indexes and order-by, keys canonicalized to match. `?Float` nil is a
  reserved quiet NaN — the zero word is +0.0, WO_NIL_SCALAR's bits are -2.0.
- Renderer prefers fixed over exponential in 1e-6..1e21: pure shortest makes
  a price of 900.0 read `9e+02`. One renderer for interp/json/float_to_text.
- Fixed en route: lexer double-counted the leading digit; is_scalar_shaped
  took Float/Bytes as Int-shaped; Bytes ownership needed a shared heap-scalar
  predicate or temps never dropped; order-by bit-compared negatives backwards.
- Iteration 17: `kind = "library"` (absent = program; bad value = WO-E109),
  entry-less check mode retiring the `--emit` workaround, Go's `internal/` as
  WO-E108 at the consumer's `use`. Driver-only; VM/.wob/GC untouched.
- Framework reorg: internal/{parse,serve}.wo; http/form.wo split out to keep
  media_type/form_values public (parse.wo had grown public surface).
- Docs: link audit (97 -> 88 broken, conflict markers resolved, 2 duplicate
  stories removed), 00-code-review verified 26/27, iterations re-sequenced.
- Also carries the pre-staged pub(read)/using/#if work from the index.
- Gates: corpus 103/0, test_wal 156/0, web-app 26/0, oop-accept ALL MET.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 19:24:15 +02:00
ec9d264355 feat: cross-shard actors — placement, envelopes, home-routed frees, WO-E222 (arc T6)
- spawn placement: round-robin across shards (same-shard when the
  engine is absent/single); the actor's mailbox and delivery belong to
  its HOME thread — a spawn to another shard travels as an ADOPT
  envelope, a send as a SEND envelope (mutex-guarded inbox + eventfd
  wake; the spec's lock-free rings stay a disclosed deviation until
  9e measures the mutex)
- workers: first envelope triggers lazy full-vm init UNDER the inbox
  mutex (TSan caught the memset racing a concurrent push, twice — the
  second was inbox_push reading wake_efd outside the lock; both fixed,
  gate x8 + battery clean); serve loop = adopt -> run to drained ->
  wait on the plane (the wake eventfd is watched by io_uring POLL_ADD
  oneshot / epoll level-triggered on BOTH backends)
- ownership across heaps: every allocation stamps rt->shard_id into
  the header (the field reserved since iteration 2); a drop on the
  wrong shard routes home as a FREE envelope — the owner's arena stays
  single-threaded by construction; at teardown routed frees become
  no-ops (arenas die wholesale) which is what un-danced the freed-mutex
  ASan SEGV the first ordering had
- WO-E222: an actor's state or message type that is (or transitively
  contains) an inferred-traced class refuses at the spawn/send — with
  round-robin every actor is potentially remote; corpus-pinned
  (compile-fail/traced-send, inference-aware: Box contains ?Node)
- determinism narrowed per spec: oop-e2e pins WO_SHARDS=1 (exact
  outputs); the fibers gate grows multi-shard SET assertions + a TSan
  run (wovm-tsan target; setarch -R fallback for kernel 6.5+ ASLR)
- NEXT_RUNNABLE honors engine shutdown for parked workers (deadlock
  hole closed); io_wait's adopt-wake (rc 1) no longer reads as fatal
- battery: oop-e2e 93/0, fibers 10/0 x8 (+WO_IO=epoll), log-watcher
  7/0, employee 8/0, web-app 21/0, deps 8/0, runtime tests 16/16

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 12:06:13 +02:00
7a614420f4 feat(examples): fibers — the hybrid scheduler demonstrated + gate
- docs/examples/fibers: part 1 is TIMING-FREE and byte-exact — main
  sends three messages then burns reductions; each budget expiry hands
  the Counter actor exactly one delivery (cooperative mechanics,
  preemptive fairness, BEAM's shape); part 2 parks a Sleeper actor
  mid-receive on the I/O plane while main keeps ticking — the wake
  lands between ticks, proving a sleeping fiber blocks nobody
- the missing "sleeper: up" on the first run was main-return-reap
  working as specced (main ended before the deadline); the demo's
  window widened so the wake is observable
- scripts/fibers-accept.sh + `just fibers` (8 checks): build, part-1
  exact + part-2 ordering invariants on auto/uring/epoll backends,
  and an ASan-runtime rebuild+run
- README points at the doctrine writeup (exploration/fibers)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 10:00:40 +02:00
0dd5fda180 feat(framework): multipart/form-data parsing — Part, multipart_parts, part_named
- http/multipart.wo: RFC 7578 whole-body parsing within BODY_MAX —
  boundary from the raw content-type (quoted or bare, key
  case-insensitive), parts split on --boundary, each part = headers,
  blank line, content; filename + per-part content-type kept (lowercased)
- strict malformed-is-nil: no closing --boundary-- marker, a part
  without content-disposition, missing blank line, no boundary param,
  wrong media type — all nil, the caller's 400
- part_named(parts, name): first matching field's content, caller-owned
- web-app CreateProduct now accepts multipart/form/JSON (curl -F shape)
  into the shared insert path
- probe 13/13 + 3x reuse loop (fields, crlf-in-content, quoted boundary,
  file part, zero-part close, five malformed shapes) release + ASan
- gate grows 19 -> 21: multipart create 201, missing closing marker 400
- README: multipart row ✅ (all three body hooks done), limits updated;
  story 16 + board record the landing
- gates: web-app 21/0, oop-e2e 89/0, deps-accept 8/0, log-watcher 7/0,
  employee 8/0, woc-test green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:33:14 +02:00
a5826495e9 feat(framework): form-encoded body parsing — media_type + form_values
- media_type(req): content-type lowercased, "; charset=..." stripped,
  "" when absent — the content-negotiation hook
- form_values(req): application/x-www-form-urlencoded body -> decoded
  pairs through the existing query decoder ('+' as space, %XX); nil on
  any other content-type so a JSON body is never misread as a form key
- web-app CreateProduct accepts form OR JSON; shared create_product
  insert path; field/number validation answers 400
- probe 7/7 (plus/pct decode, empty value, case + charset param, json
  and missing content-type nil, empty body, media_type strip) + ASan
- gate grows 17 -> 19: form create 201 with decoded name, non-numeric
  price 400; hit() gains a content-type argument
- README: checklist row form ✅ (multipart stays candidate), limits
  paragraph updated; story 16 + board record the landing
- gates: web-app 19/0, oop-e2e 89/0, deps-accept 8/0, log-watcher 7/0,
  employee 8/0, woc-test green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:21:48 +02:00
fb86156d04 feat(framework): auth in core — Bearer/Basic mechanism + principal slot
- http/auth.wo: auth_header (scheme split, case-insensitive, RFC 9110),
  bearer_token, basic_credentials (first-colon split, RFC 7617),
  pure-.wo base64_decode (RFC 4648, strict padding), ct_eq constant-time
  compare (no early exit, both Basic fields always compared)
- req.principal: the blessed "who is this" slot, "" until authenticated;
  Middleware.before now takes mut req so auth can write it
- BearerAuth { token, principal } and BasicAuth { user, pass, realm }
  middlewares; BasicAuth answers the WWW-Authenticate challenge; policy
  (routes/users/secrets) stays app-side on the exposed fns
- web-app dogfoods BearerAuth; its hand-rolled Auth class deleted
- probe matrix 26/26 (RFC 4648 vectors, rfc7617 pair, pass-with-colon,
  bad padding/chars/length, deny paths, challenge header) release+ASan
- gate grows 16 -> 17: wrong bearer token answers 401 over the wire
- README: auth bullet + the core CHECKLIST (done / candidate / parked
  behind 8-11 by design); story 16 + board record the landing
- all gates green: web-app 17/0, oop-e2e 89/0, deps-accept 8/0,
  log-watcher 7/0, employee 8/0, woc-test green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:15:16 +02:00
d84b72f0b4 feat(framework): v1 polish — helpers, 405+Allow, HEAD, Logging, set_header
- App.get/post/put/delete_(pattern, take h: Handler) — the take-interface
  shape probe-proven release + ASan before landing; retires plan-16
  deviation 1; delete_ because delete is the query keyword
- dispatch matches path-first: wrong method on a known path answers 405
  with Allow in registration order; unknown path stays 404
- HEAD routed as GET, body suppressed, Content-Length names the body a
  GET would carry (serialize gains head_only)
- Logging middleware (request line to stderr) ships in router/
- set_header(mut r, name, value) — the builder escape hatch
- web-app registers through the helpers (dogfood); README documents all
- gate grows 14 -> 16: 405+Allow, HEAD-vs-GET content-length equality
- all gates green: web-app 16/0, woc-test 540/0, oop-e2e 89/0,
  deps-accept 8/0, log-watcher 7/0, employee 8/0
- board/story: iteration 17 parked (spec+plan ready on library-internal),
  16 carries the v1-polish landing, order list updated

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:04:57 +02:00
c72b3354ff test: web-app-accept — iteration 16's gate (Task 5)
scripts/web-app-accept.sh: 14 checks, network-free — temp git remote from
the framework dir, file:// substituted into a temp app copy, then fetch +
wo.lock + build; 401-without-token; empty list; 201 create; 409 duplicate
(@unique); 400 malformed json; list/show payloads; 404; 201 order (FK); 409
delete-while-referenced with the server surviving; pipelined keep-alive (2
responses, 1 connection); SIGTERM; WAL restart persistence. Wired as
`just web-app`. 14/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:57:30 +02:00
77d387fc0a test: deps-accept — iteration 15's gate (Task 4)
scripts/deps-accept.sh: 8 checks over local file:// remotes built at run
time (network-free) — cold fetch + lock + use <dep>/<dep>/sub + app-entry-
wins (the dep's fn main returns 99 and must never run), offline rebuild
with the remote deleted, lock-beats-moved-tag, --update-deps refresh,
cache/lock drift WO-E106, transitive refusal, WO-E107 collision, missing-rev
manifest shape. `just deps-accept` wired. 8/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:21:32 +02:00
f0971e1481 feat: installable toolchain — version, wovm self-locate, dist tarball
Close the 3 gaps between "builds in the repo" and "installs from a tarball
like Go", so writeonce can ship to other developers.

- VERSION file at repo root single-sources the toolchain version (0.1.0).
- `woc version` -> "writeonce 0.1.0 linux/amd64"; `wovm --version` -> "wovm
  0.1.0" (stamped by the Makefile from VERSION; --version handled only in the
  plain-wovm path so a built app never shadows its own `version` arg).
- wo.toml `[runtime] wo = ">= X.Y"` is now ENFORCED: woc refuses a project
  requiring a newer toolchain than itself (>= and bare version parsed;
  unknown operators accepted forward-compatibly). Was parsed-and-ignored.
- woc self-locates wovm: --runtime > [build] runtime > $WO_RUNTIME > a `wovm`
  beside the woc binary (Sys.executable_name) > runtime/wovm rel CWD. An
  installed woc in <prefix>/bin finds its sibling wovm from any cwd.
- `just dist` (scripts/mkdist.sh) packages writeonce-<ver>-linux-amd64.tar.gz,
  Go-shaped (archive root writeonce/, bin/{woc,wovm}, README, VERSION), with a
  drift guard asserting VERSION == woc == wovm. dist/ gitignored.
- `just install-accept` (scripts/install-accept.sh) is the gate: extract, PATH,
  version, build+run a project from an unrelated cwd, constraint refusal — 6/0.

Verified: install-accept 6/0; woc-test 565/0; wovm suites + cli_smoke;
log-watcher 7/0. Linux-amd64 only (a cross matrix is future work).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 01:19:20 +02:00
2677c1457e feat: FK restrict on delete + employee sample runs; group-by parked (9b)
- FK restrict: deleting a row a non-nullable `ref` still points at traps
  WO_T_FK (11), catchable. The compiler now records a `ref` field's
  target class in the class-table field_class metadata; the engine
  (wo_row_has_referrers) scans referencing scalar columns before a
  delete. Correctness-first full scan; the backlink-index optimization
  is recorded for later
- docs/examples/employee now COMPILES AND RUNS all six modes against a
  WAL-durable database: seed (+@unique trap across restart), report
  (per-dept aggregates + payroll), staff (unique probe + backlink +
  ref nav), raise (update-through-row), drop (FK restrict), and
  persistence via replay
- group-by SYNTAX parked to a future iteration (user decision): the
  report mode is hand-rolled from the shipped primitives meanwhile
  (same numbers). "table relations and FK" is complete
- scripts/employee-accept.sh (8 checks) + a `just employee` module;
  manifest parser tolerates iteration 9c's [share]/[[share.clients]]
  sections so `woc .` builds the sample on this branch
- fixtures trap/db-fk-restrict (code 11) + run/db-fk-restrict-catch;
  oop-e2e 79/0, woc-test 566/0, 15 runtime suites, log-watcher 7/0,
  employee-accept 8/0
- 9b story + status board updated

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 18:37:23 +02:00
3c53d27347 fix: close per request, soak the daemon, kill five soak-found leaks (Tasks 5+6)
- Task 5: net.close on every path out of a serve iteration (400
  included) and the listener on stop; measured 4 -> 54 fds over 50
  requests before, 4 -> 4 over 200 after. The loop's comment claimed
  the iteration-end drop IS the close -- wrong twice (net.Conn is a
  scalar, and a drop would not close an fd); it now says what is true
- Task 6: LW_SOAK=<seconds> in the acceptance script -- each mode under
  load, resident+descriptor deltas against a WARMED baseline (warm-up
  includes load: cold-to-high-water is not growth), 256 KiB / zero
  tolerance; LW_ACCEPT_WOVM soaks another build
- the soak caught ~1.6 MiB/min of in-arena leaks ASan cannot see (the
  arena is one allocation to LeakSanitizer); an arena size-class
  census + pointer trace attributed five bugs:
  - jparse_string sized every decoded string at "rest of the input"
    and relabeled len after -- blocks filed on free lists their next
    allocation never reads (fs.read_all's mis-size, again); copy out
    exact, free at the taken size
  - `!=` never dropped fresh operands (headers["authorization"] !=
    "Bearer ${key}" leaked both sides per request); Ne now reaps as Eq
  - an Int interpolation segment is a fresh int_to_text, not a borrow;
    is_borrowed_value_t asks the segment's type
  - json.encode(Ctor{...}) had no owner -- record + both field copies
    leaked per tool call; its bespoke lowering now drops the argument
  - a discarded expression statement owns its result: `pop(lines);`
    leaked the popped element; reader builtins excluded
- after: arena live bytes flat per request on every handler; release
  soak 30 s per mode watch 0 / run 0 / mcp +20 KiB, descriptors flat;
  ASan build flat at 14600 KiB across 601686 requests in 90 s past its
  ~1200-request quarantine warm-up
- gates: oop-accept ALL CRITERIA MET, oop-e2e 71/0, woc-test 565/0,
  wovm-test green, log-watcher 7/0 (soak opt-in, fast path <1 min)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 00:32:49 +02:00
22910e3974 fix: a stopping program stops (executable plan, Task 4)
- blocking stdlib calls that PARK (net.accept, socket read/write,
  time.sleep, a child wait) no longer restart the syscall when the
  stop flag is set on an interruption: a server sitting in accept
  ignored SIGTERM and only `kill -9` ended it
- a stop is NOT a trap -- builtin.h's WO_SYS_STOPPED carries no error
  record and no catch handler sees it (`try` must not swallow
  SIGTERM); the VM unwinds the whole stack through the same drop
  machinery an uncaught trap uses, so nothing leaks on the way out
- wo_vm_call gained a third outcome (1 = stopped); the CLI maps it to
  the status the program's own `return 0` would have given, and a
  regular-file read keeps its plain EINTR retry -- it does not park
- an ASSIGNMENT was not an ownership boundary: `api_key =
  j.mcp.apiKey` moved the field pointer into the local, so the local
  aliased the record and the first unwind freed the same string twice
  (SIGSEGV in class_free). `let` copied a Text place, assignment now
  does too -- the same double free was latent on the normal exit path,
  hidden by the order the compiler happens to emit drops in
- log-watcher-accept is 7 checks: the seventh is the stop itself, with
  the hard kill demoted to a fallback whose use is the failure
- measured under ASan: mcp parked, mcp after traffic, watch and run
  all exit rc 0 with zero leaks; SIGINT behaves as SIGTERM
- gates: oop-accept ALL CRITERIA MET, oop-e2e 71/0, woc-test 565/0,
  wovm-test green, log-watcher 7/0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 23:58:21 +02:00
4cf548d6a0 fix: copy-on-push closes the container double-free; line-buffer program output
The unsoundness is closed. All four MCP tools now answer correctly over HTTP
(get_running_crons, list_logs, tail_log -> ["info two","error three"],
search_log -> its match) where `tail_log` used to return
{"isError":true,"text":"tool failed: not a text value"}. corpus 71/0,
woc 565/0, wovm gates green, ASan clean on the container fixtures.

- builtin.c: multi_push, map_set (key AND value) and multi_set COPY a TEXT
  element into the container. The container's declared kinds already make it
  the owner of what it holds, so storing a caller-owned pointer gave one
  string two owners — `push(res, e.log_path)` freed a record's field out from
  under it. OWNED/GCREF elements still move (not copyable; the @gc escape
  keeps their counting), so `set`'s @gc gap is untouched and still recorded
- emit.ml: `drop_fresh_text` — after push/set and the `m[k] = v` / `m[i] = v`
  sugar, a value that was freshly BUILT (call result, `..` chain,
  interpolation) is dropped here, while a value read out of a place is left to
  its owner. That asymmetry is the point: before the copy the borrowed case
  double freed and the fresh case leaked
- obj.c: the runtime's output stream is line-buffered. A long-running program
  writing progress with `print` was invisible when stdout was a file or a pipe
  (full buffering), and a killed one lost its log entirely; byte-exact
  fixtures are unaffected
- scripts/log-watcher-accept.sh + `just log-watcher`: the acceptance test for
  the sample — compile, watch (alert), run (schedule), and three MCP checks.
  Hardened after it lied to me: a per-run port (a stale server on a fixed port
  answered for it), a connect-probe that fails loudly when OUR server did not
  come up, replies read by Content-Length rather than to EOF (the sample never
  closes), and kill -9 on teardown
- docs: the copy rule is in the builtin surface; the status board records the
  gap as closed and adds the new one — a blocking accept/read swallows SIGTERM,
  which belongs to the shard-actor runtime's event loop, not to a patch here

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 18:50:18 +02:00
1ba035397d feat(compiler): iteration 5 Tasks 1-4 — modules, language surface, switch, typedef records + enum variants
- Modules: `use`/`pub`, directory-as-module, per-module symbol resolution (a
  flat first-wins merge silently ran the wrong `pub fn` body), six reserved
  stdlib namespaces typed UNKNOWN-BUT-RESERVED.
- Surface: `and`/`or` (own precedence tier, short-circuit, Bool-only), `${}`
  interpolation desugared at parse time, `const`, break/continue with
  drop-correct exits, do-while, inline-fn rejection.
- switch expr/stmt: required `default` over scalars/Text, arm unification,
  EQ/EQS+JZ lowering, per-arm drop scopes with N-way JOIN-DROP; `default`
  sorted last by a shared lowering order (textual order made arms dead).
- typedef records: structural, same shape = one class entry; `?name: T`
  nullable-by-shape; emit_ctor fills omitted defaults; `type` as field name.
- Enum variants: all-bare unions = int ordinals; any-payload = one class
  entry per variant, tag IS the header class_id (no header field, no format
  bump); exhaustive switch without `default`; arity checked both directions.
- Payload escape modeled as move-out (pointer-kind fields only — a scalar
  escape is a copy); caller reaps owned heap temps passed by borrow: two
  unbounded LSan-blind leaks, 10.5 MB -> 1.5 MB flat over 300k iterations.
- Fixed en route, each with a RED repro: dead E209 builtin-arg check and
  `int_to_text` missing from both types.ml builtin tables (both segfaulted
  wovm), multi-file phantom double-report, emit_ctor's field temp clobbering
  dst in tail position (pre-existing), warnings swallowed without an error.
- Two fenced VM builtins: `int_to_text` (13), `variant_tag` (14).
- 14+565 unit (was 14+401), corpus 71 (was 32) plain and under wovm_asan,
  wovm-test + cli_smoke green. Log-watcher 307 -> 93 diagnostics (85 E101 /
  4 E207 / 1 E208 / 3 W202); the 5 non-E101 residuals await Task 7 grammar.
2026-08-12 14:40:07 +02:00
79605cbb4f feat: milestone 1 complete — .wob emitter, conformance corpus, single binary; GC redesign specced
- `woc` now emits `.wob` that `wovm` runs: emit.ml lowers the typed,
  owner-annotated AST (scope-stack registers with a >64 WO-E401 diagnostic,
  Lua-style call windows, ICALL by slot, dedup const pool, drop maps, line
  tables, implicit terminators); disasm.ml backs `--dump-bc` goldens.
- Ownership lowering consumes the four owner tables verbatim; RESIDUAL is the
  only source of borrow ops, coalesced per operand. Review caught the emitter
  consuming only 2 of owner.ml's 4 residual producers — an assignment-anchored
  aliasing violation ran to exit 0 instead of trapping; fixed, plus a backstop
  raising WO-E404 for any residual region left unconsumed.
- Conformance harness `scripts/oop-e2e.sh` (`just oop-e2e`): four fixture
  kinds with exact outcomes — byte-exact stdout, one WO-E### anchored on
  `error CODE:`, numeric trap code, gc trace. 25 fixtures incl. pricing-demo
  logic, the ownership suite, and DB_STUB's parse-but-trap. `tests/` un-ignored
  so the corpus is actually tracked.
- `woc build` produces a self-contained binary: wovm copy + appended image +
  20-byte trailer, self-exec via /proc/self/exe. Verified relocated outside
  the repo, argless, and against adversarial trailer corruption.
- Milestone 1's five spec criteria all MET (`just oop-accept`). Criterion 3
  closed by WO-E405 — the entry must return `Int`, since program mode already
  says its return value is the exit code — which deletes the leak class
  without adding return-type metadata to the format. `gc/held-cycle` retired:
  an externally-held cycle is not expressible in a post-exit pump.
- New spec: inferred GC + incremental per-shard tri-color mark-sweep, retiring
  `@gc` and reference counting. Story gains iterations 7b (that work) and 9b
  (`@table`, relations, compiler-checked query); `.dev/reference` gains a
  sparse System.Linq checkout. Priority: 5→6→7 (log-watcher) then 7b, 8, 9, 9b.
2026-08-11 19:31:26 +02:00