The disclosed "move-on-push" gap detonated on iteration 16's route-table
pattern: `push(self.routes, r)` moved the Route into the container while the
`take r` parameter's scope-end DROP still fired — the container's own drop
plan (multi_free) then freed the element a second time. ASan: SEGV in
class_free during trap unwind; latent until now because pushed elements were
Texts, which copy at the boundary (2026-08-14).
owner.ml analyze_call: `push`'s value slot and `set`'s key/value slots now
TRANSFER an Owned, non-copy-stored place (record_move, exactly the take-arg
shape), so the pusher's drop disappears. Text/json.Value keep the copy-store
path (stores_by_copy) and the caller still drops the fresh copy. Traced (gc)
values remain exempt (tracing owns them). A user-declared push/set fn of the
same name wins, per the builtin shadowing rule.
Pinned by tests/corpus/run/container-owned-move (route table: interface-
typed field values pushed via take params, dispatched by ICALL, mixed with
Text pushes) — the exact iteration-16 shape, ASan-clean.
Verified: woc-test 540/0; oop-e2e 88/0; log-watcher 7/0; employee 8/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes json's two documented fidelity limits (iteration 5 strictness):
- field_class gains WOB_FIELD_BOOL (a plain `Bool` field) and
WOB_FIELD_NIL_BOOL (a `?Bool`: WO_NIL_SCALAR nil + bool encoding) — the
kind byte alone cannot tell a Bool slot from an Int slot, so the metadata
carries it. Emitter writes them (field_class_meta); loader whitelists
them; json.c encodes `true`/`false` (and `null` for a ?Bool nil), decode's
null/omitted-key pre-write covers NIL_BOOL.
- A JSON number with a fraction or exponent is MALFORMED for an Int field:
the checked decode (`json.decode(t) as T`) yields nil for the whole
document instead of silently truncating 3.7 to 3 — the language has no
float, and corrupting data quietly was the one thing a "checked decode"
must never do. Floats stay representable through a raw `json.Value` field.
- corpus: run/json-bool-fidelity pins the round-trip (true/false both ways,
?Bool null both ways, fraction AND exponent rejected).
- Board's two known-gap entries struck; format doc's field_class marker list
extended.
Verified: oop-e2e 87/0; runtime test + test-iso OK; woc-test 540/0;
log-watcher 7/0; employee 8/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The verdict table's reject half is enforced: a Haxe habit fails loudly at
its own position with the doctrine reason, instead of a generic syntax
error — or, worst, compiling clean: `return super.f()` used to exit 0 (the
unresolved ident placeholder swallowed it).
- parser.ml: doctrine_reject_reason maps each rejected word to its spec
reason (inheritance quartet -> principle 4; cast; Dynamic/untyped ->
principle 13; macro; extern -> principle 10; operator). Fired at three
chokepoints: `class B extends A` (with skip-to-brace recovery so the body
still parses), an expression head (`super`, `cast 3`, `untyped x`), and a
top-level declaration head (`macro fn`, `extern fn`).
- types.ml: `Dynamic`/`untyped` as a TYPE name keep their WO-E225 site but
carry the doctrine message.
- corpus: compile-fail/{reject-inheritance,reject-cast,reject-dynamic}.
- catalog WO-E105 row; plan 8 Task 8 reject half ticked (#if still open);
board updated.
Verified: woc-test 540/0 + test_diag 14/0; oop-e2e 86/0; log-watcher 7/0;
employee 8/0; legit identifiers (`extended`) untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hybrid-boundary inversion is closed: a statically provable interface
violation now fails at COMPILE time instead of reaching wovm as an ICALL
that traps WO_T_BOUNDS at runtime.
- types.ml class_satisfies: the same rule emit.ml's `satisfies` builds
vtable rows from (instance method with matching name + parameter count
for every interface method; `static fn` never satisfies) — one rule, two
consumers, so the check and the vtable can never disagree.
- check_iface_boundary fires wherever a confidently class-typed value flows
into an interface-typed slot: call arguments against the callee's declared
parameters (free fns, methods off confident receivers, interface-method
sigs, statics — resolved exactly as confident_typ resolves returns),
annotated `let`s, and `return`s. Silent when underivable.
- The same per-argument pass extends the ?T boundary to CALL ARGUMENTS
(the previous slice covered stores/returns/operands): nil into a
non-nullable parameter is WO-E212, an unnarrowed ?T argument is WO-E211.
- tests/corpus/trap/unsatisfied-interface -> compile-fail/ with
fixture.code WO-E205, per the fixture's own standing instruction; its
header comment rewritten to the wired reality.
- The new arg checks caught a real mistyped signature in the sample:
log-watcher's rpc_error/rpc_result/call_tool declared `id: json.Value`
while every caller legitimately passes nil (JSON-RPC id-absent) — now
`?json.Value`; dispatch/call_tool/cron-row sites moved to the
bind-then-narrow idiom (including an `or`-guard narrowing:
`if spath == nil or spat == nil { return }`).
- Catalog: E205 gains its main-table row; the "owed gap" section is
rewritten as closed. Board known-gap struck through.
Verified: woc-test 540/0 + test_diag 14/0; oop-e2e 83/0 (fixture now
compile-fail, satisfying-class negative probe compiles clean); oop-accept
ALL MET; log-watcher 7/0; employee 8/0; gc-cycle clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The type system now keeps its nullability promise: a `?T` value cannot be
used, stored, or dereferenced as a plain `T` without narrowing. The canonical
evidence probe (return b.v where v: ?Int, fn -> Int) that compiled clean for
months now fails with WO-E211.
- WO-E211 (un-narrowed use): arithmetic and </<=/>/>= operands, and/or
operands (?Bool), interpolation segments, for-iterables, and returns whose
declared type is not nullable.
- WO-E212 (boundary): nil or ?T stored into a non-nullable slot — annotated
let, assignment to a confidently-typed local (cenv, never the placeholder
env — a placeholder target must stay silent) or a resolvable class field.
- WO-E213 (deref): field/index access through a possibly-nil base.
- Narrowing (locals only — a field place can be re-assigned between check
and use, so chains bind to a local first): `if x != nil { }` narrows the
branch; a DIVERGING then-branch (`if x == nil { return }`) narrows after
the if; `x != nil and x.n > 3` narrows and/or right operands
(short-circuit); `while x != nil` narrows the body. The narrow is
un-applied when an else-less then-env leaks out un-diverged (the existing
env-leak convention must not leak the narrow).
- No false positives by construction: env/cenv types are declared or
confidently inferred; the placeholder fallbacks are plain scalars, never
?T. The whole golden suite passed untouched (540/0).
- Samples updated to the bind-then-narrow idiom (log-watcher config decode +
supervisor lock/next_fire, gc-cycle ring print) — 22 genuine unnarrowed-nil
sites; employee needed zero changes. All acceptances green.
- Corpus: compile-fail/{nullable-unnarrowed-use,nullable-nil-into-plain,
nullable-deref-unchecked} + run/nullable-narrowing (all four forms) — 83/0.
- Catalog: E211/E212/E213 move from "Reserved, not yet emitted" to the main
table; nullable-types-implementation.md status flipped to ENFORCED
(historical record kept); plan 8 Task 6 ticked (boxed scalar cells
superseded by WO_NIL_SCALAR); board updated.
Verified: woc-test 540/0 + test_diag 14/0; oop-e2e 83/0; oop-accept ALL MET;
log-watcher 7/0; employee 8/0; gc-cycle ring prints + reclaims.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`@gc` is no longer part of the language: a developer never writes or mentions
it. GC-ness is decided entirely by inference (structural cycles + demand
promotion), which the earlier 7b commits made complete and precise.
- parser: `@gc` on a class is now WO-E104 ("GC-ness is inferred; run
`woc --dump-gc`. Remove it."). `is_gc` stays false; the class classifies by
inference. No `.wo` in the repo carries `@gc` anymore.
- types.ml: retired the WO-W201 machinery (suggest_gc_annotation,
has_recursive_structure(_type), has_unique_field, gc_suggestion_code) — it
suggested `@gc`, now obsolete since inference traces exactly those classes.
- runner.ml: deleted the 8 WO-W201 gc-suggestion test blocks; the @gc-exemption
test's `Cache` is made self-referential so inference classifies it gc without
an annotation.
- fixtures: dropped `@gc` from rc.wo (Cache demand-promotes via its escape),
elision.wo (Cache given a self-ref to stay structurally gc for the rc-elision
dump), pricing-demo.wo (PriceCache doesn't escape -> now owned), and the
abandoned-cycle/budget-steps corpus (Node is structurally gc). rc.wo keeps a
placeholder comment line so its line-indexed rc assertions hold. Goldens
re-blessed.
- docs: error catalog gains WO-E104 and marks WO-W201 retired; gc-cycle README
records the keyword removal.
Verified: woc-test 553/0 (was 566 minus the 13 retired WO-W201 checks),
test_diag 14/0, oop-e2e 79/0, employee 8/0, log-watcher 7/0. `git grep '@gc'`
finds only comments — success criterion 1 met.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Structural inference (2a) covers cyclic classes; this adds the DEMAND half for
the acyclic-but-aliased case, so @gc is now redundant everywhere.
- owner.ml: a `promote` sink on ctx. In collect mode, a class value that would
raise WO-E304 (escape) records its class instead of erroring — because a
class that MUST escape cannot be owned (second-class borrows can't be stored
or returned), so it must be traced. `class_of_ty` extracts the class from the
escaping local's type. analyze/analyze_fn take ?promote.
- main.ml typecheck_all: after structural injection, a fixpoint runs ownership
in collect mode over every file, unioning promotions into syms.traced (and
every module table), before owner/emit see it. Terminates (promotions only
grow, bounded by class count).
- gcinfer.render_final: --dump-gc now reads the authoritative is_gc_class
(structural + demand + the remaining @gc bridge), with the reason.
Effect: a class that escapes is inferred `gc` with no annotation — e.g. `Cache`
(rc.wo sans @gc) shows `gc (alias escape (demand))`; `Box` returned out of
`leak` is promoted and the program is valid. No false positives: employee's
Department/Employee stay owned; the 566 goldens + 14 test_diag unchanged.
Corpus: compile-fail/borrow-escape-return reclassified to run/ (prints 1) —
returning a borrowed class is now legal under demand promotion; the fixture
encoded pre-7b behavior.
Verified: woc-test 566/0 + test_diag 14/0; oop-e2e 79/0; employee 8/0;
log-watcher 7/0.
NOT in this slice: removing the `@gc` KEYWORD (parser rejection + rewriting the
RC/@gc golden + test_diag assertions + moving the inference injection into the
library so unit tests see it) — coupled to Phase 3, which deletes the RC
machinery those tests cover. The ring still needs Phase 3 to RUN (nullable
`?Node` gcref path).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 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>
- `e.field = v` where e is a table row lowers to DB_UPDATE_FIELD
(class, id, field, value) — the row's indexes maintained at the choke
point; heap-object field assignment still emits SETF unchanged
- `delete <row>` expression: DB_DELETE(class, id), yields the id so it
composes in `try delete x catch (e) nil` (restrict/trap surfaces
catchably); Delete AST node threaded through type/owner/dump/emit
- disassembly-caught bug fixed: in tail position dst == the builtin
window's first reg, so moving the id into dst clobbered the class id
— reserve dst past the window (the emit_ctor guard)
- run/db-update-delete fixture; oop-e2e up, woc-test 566/0,
log-watcher 7/0
- employee seed/list/staff/raise/drop now compile+run; only `report`
(group-by aggregation + projection record) remains
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- `order by <field> [desc]` on whole-row queries: a selection sort over
the result multi, re-reading the key per element via the range var
(DB_GET_FIELD); O(n^2), KISS, no cost planner — the result sets are
small by design
- Text order keys use a new WO_B_STR_LT builtin (content compare, reusing
the WO_B_SORT elem_cmp); scalar keys use the LT opcode. The bug this
fixes: op_lt on two Text pointers compares ADDRESSES
- `take N`: clamp to count, slice [0,N). `take` is the KwTake keyword,
not an Ident — matched as the token
- two bugs found + fixed while testing: multi-line query clauses (skip
the separating newlines) and the key-kind read (must bind the range
var BEFORE ty_of_expr of the order key, or a Text key silently uses
op_lt); Index typechecks to the container's element type (`ds[0]`)
- fixture run/db-query-order; oop-e2e 75/0, woc-test 566/0, 15 runtime
suites, log-watcher 7/0
- employee `seed`/`list`/`staff` modes now compile and run; report
(group-by+projection), raise (update), drop (delete) remain
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- `backlink C.f` field type: parsed, typed as `multi C`, and VIRTUAL —
filtered out of the stored row layout (no column, omittable in
ctor/insert), collected in clsrec.cr_backlinks
- reading a backlink (`d.staff`) lowers to DB_PROBE on the source
class's index for the backing column (backlink_target resolves the
(source class, index number); a backlink with no backing index has
no efficient read)
- `ref C` navigation (`e.dept.name`) chains: a ref value is the target
row's id, so a `Ref C` base navigates into C's fields exactly like a
table-class value, routing to DB_GET_FIELD both in typecheck and emit
- query navigation source `from s in d.staff`: emit_query evaluates the
nav expr to get its id-list instead of DB_SCAN; QNav typechecks with
the range var bound to the navigation's element class
- fixture run/db-query-relations proves both directions; oop-e2e 75/0,
woc-test 566/0, log-watcher 7/0
- still ahead for employee: order/take, group-by aggregates, projection
records, delete + update-through-row
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Ast.Query node + parser: `from <v> in <src> where* [group..into] [order
by] [take] select <e>`, positional `from` trigger so it stays a usable
identifier; select stays grammar-owned (no DbStub conflict)
- typecheck: range var bound to the source table class; a table-class
value is its row id at runtime but TYPES as the class, so `e.field`
checks against the class fields; result is `multi <select-type>`;
group/order/take/navigation diagnosed WO-E250 not-yet (honest edge)
- emit: `from/where/select` lowers to a bytecode LOOP over DB_SCAN's
materialized id list — DB_GET_FIELD per column read, where-guards skip
the push, select projects, result is a fresh multi; no plan tree, no
SQL text (disassembly-provable)
- field access on a @table-class value routes to DB_GET_FIELD instead of
GETF (clsrec.cr_is_table + is_table_class); non-table classes unchanged
so log-watcher is unaffected
- the ASan-caught bug kept in a comment: a table-class query element is a
SCALAR id, not an OWNED pointer — tagging the result multi OWNED dropped
an id as a pointer (SEGV in wo_drop_obj)
- fixture run/db-query-scan (where-filter + select-whole + select-field);
oop-e2e 74/0, woc-test 566/0, log-watcher 7/0
- SLICE scope: group-by aggregation, order/take, and ref/backlink
navigation are the next chunk (employee report/staff/raise need them)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- .wob v3: class records carry an index tail (flags bit0 = unique,
col_cnt, columns) -- @table(index:[a,b]) entries plus one unique
single-column entry per @unique field; loader validates columns in
range and scalar/Text-kinded; emitter validates the declarations
(unknown column, un-indexable kind => diagnostic)
- engine: db_index hash multimap per table, built from the class
table at first touch, maintained ONLY inside wo_row_insert/
wo_row_remove; unique checks re-compare actual column values (a
hash is a hint); replay re-indexes via wo_row_raw_commit AFTER
slots are filled, so recovered tables carry their indexes
- WO_T_UNIQUE = 10; a violating insert is un-applied whole (bitmap,
hash, count, and the never-observable id reclaimed) and traps
catchably -- the employee SEED-DUP pattern
- wo_row_insert gains err_kind so db.c maps UNIQUE/OOM/other to the
right trap; test images and the runner's loader mirror speak v3
- fixtures: trap/db-unique-violation (code 10 exact) and
run/db-unique-catch (catchable dup, composite index accepts
duplicates, next id dense after a refusal)
- gates: oop-e2e 73/0, all 15 runtime suites, woc-test green,
log-watcher 7/0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- compiler: `insert Class { ... }` is a typed Ast.Insert in statement
AND expression position, sharing the ctor literal's field grammar;
typechecked with the ctor's omittable rule; result = the row id (Int)
- owner pass: the engine copies at the row API, so an insert BORROWS
its field values -- no transfer, no E304; node is trap-capable and
carries a live-mask drop entry like DbStub did
- emit: builtin 61 window = class-id const + one slot per DECLARED
field in declaration order; omitted defaults emitted, omitted ?scalar
gets WO_NIL_SCALAR, other omitted optionals the zero word; fresh
argument values reaped after (the push/set copy semantics)
- runtime: database/src/db.c executes via the choke-point row API;
rt.db/rt.wal opaque handles on wo_rt; WO_DATA=<dir> = replay
<dir>/shard-0.wal at boot + commit-before-ack per statement (the
builtin's return IS the ack until iteration 8 ticks); failed commit
un-applies the row and traps WO_T_IO; loader validates the class-id
slot (variable window documented in wob.h + format doc)
- the promised diff: trap/pricing-set-price-db-stub is now
run/pricing-set-price-insert printing engine-allocated ids;
durability smoke prints 1,2 then 3,4 across two WO_DATA runs
- old "bare insert is an Ident" unit test rewritten to the new
contract; runner's loader mirror accepts id 61; goldens re-blessed
- gates: oop-accept ALL CRITERIA MET, oop-e2e 71/0, woc-test 566/0,
wovm-test green, log-watcher 7/0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by driving the compiled log-watcher's third path — the MCP server. It now
answers real JSON-RPC over HTTP: initialize returns protocolVersion/serverInfo,
tools/list returns the full tool list (1049 bytes of generated JSON), and an
unauthorized request gets 401 {"error":"unauthorized"}. corpus 71/0, woc 565/0,
wovm gates green.
- lexer: `\r` and `\0` escapes. Without `\r` a program cannot write CRLF at
all — the server's `index_of(buf, "\r\n\r\n")` was searching for a literal
backslash-r, so it never found a header terminator and hung on every request
- parser: an interpolated sub-expression now mints node ids from the OUTER id
space. A fresh sub-parser started at 1, so `${...}` nodes collided with the
file's own nodes — and every side table (drops, moves, rc, masks, f_decl) is
keyed by node id. Surfaced as WO-E404 "ownership table names `headers`,
which has no register"; silent misattribution otherwise
- types.ml: `net.Conn` is a reserved SCALAR type (a file descriptor). It was
falling through as "some user class", i.e. WO_K_OWNED, so the frame would
DROP an integer at scope end
- types.ml: confident_typ knows `..` yields Text. Interpolation desugars to a
Concat chain, so without it every interpolated value looked underivable —
which is why the `+`-on-Text check missed two live sites in the workload
- `m[k]` on a map is now the OPTIONAL read (nil for a missing key), while
`get(m, k)` stays the asserting one that traps KEY. That is what makes
`let v = m[k]; if v != nil` — the workload's header lookup — work.
trap/missing-map-key now pins `get(...)`, and the surface doc records the
split
- json.encode of a `json.Value` emits it verbatim (kind 255): an echoed id was
coming back as "1" instead of 1
- disasm: TRY/ENDTRY render instead of ?OP32/?OP33
- status board: the push-of-a-borrowed-Text gap is now recorded with the
concrete failure it produces (tools/call tail_log), plus the leaked
temporary-record shell found in the same disassembly
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
log-watcher diagnostics 129 -> 55 (json is what is left: 13 encode sites,
3 `as` parses and their cascade). corpus 71/0, woc 565/0, wovm gates green.
- runtime/src/sysio.c (new): 17 builtins behind the reserved module names —
fs.exists/list/stat/read_all/read_at/append, time.sleep/local/iso,
env.get/stopping, net.listen/accept/read/write/close, proc.run. Thin
blocking libc calls; a failed syscall traps the new WO_T_IO with errno's
own message, which `try ... catch` is how a program handles
- record-returning members (fs.stat, time.local, proc.run) take their
result record's CLASS ID as the last argument, so the VM allocates what
it fills without knowing any source type name (the err_fill pattern)
- absence is the zero word: a missing path from fs.stat and an unset
env.get are nil, not traps
- env.stopping installs SIGTERM/SIGINT handlers on first use only
- types.ml: predeclared Stat/TimeParts/Proc records (field order is the
contract with sysio.c) + the stdlib member table (arity, builtin id,
return type, result record) + stdlib return types in confident_typ
- emit.ml: stdlib member calls lower to their builtin with the record class
id appended; WO-E406 now means "no such member", not "not linked";
predeclared records enter the class table only when a program needs them
- emit.ml: fstate carries the method's declared return type, so a tail
`return []` / `return {}` gets its element kinds; a non-empty list literal
falls back to its own element type when there is no declared destination
- types.ml: confident_typ chases a container read (`c[i]`), which is what
makes a switch over a value pulled out of a map resolve; a void `try` arm
no longer demands its catch arm agree
- corpus: lang-use-stdlib-not-linked now pins WO-E406 for an unknown MEMBER
(fs.slurp) — the "not linked" premise is gone now that fs is linked
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 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.
- `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.