- parse.wo: ANY Transfer-Encoding header is 400-and-close (RFC 9112
§6.1) — silently treating chunked as body-less was the smuggling
door the dup-CL fix left open
- net.listen/listen_unix backlog 64 -> 1024: the soak's connect bursts
overflowed the kernel accept queue and BLACK-HOLED clients (three-way
handshake done, server never sees the conn — 35-70 stuck per run,
fully reproduced then gone at 1024; kernel clamps via somaxconn)
- web-app gate grows to 46 checks: TE-reject; the 1k soak — 500 real
conns all served + 500 idle conns all evicted, server fds home
(45 -> 45), RSS 24MB, healthy after
- battery green (site restart + fibers-TSan legs flaked under parallel
battery load, both clean serially — the standing flake pair)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- runtime ids 91-95: net.read_dl/accept_dl/write_dl (per-call deadline,
nil/false = the EXPECTED timeout; ms<=0 = old behavior bit for bit),
net.listen_unix (unlink-before-bind, O_NONBLOCK on the listener —
probe-found: accept4's flag covers accepted sockets only), net.peer
- plane: one-op-per-park stays law — deadlines ride one per-shard
TIMEOUT tick (sentinel user_data) + post-CQE expiry sweep +
POLL_REMOVE tombstone; epoll's deadline scan grew the fd-park case;
fibers POOL instead of freeing mid-run (stale-CQE UAF); plain parks
zero park_deadline (no stale sleep deadlines)
- probe: all five seams verified on BOTH WO_IO backends (timeout
timing exact, peer round-trip, unix rebind)
- framework: parse_request grows first_ms/read_ms; serve_conn — the
keep-alive loop with deadlines where parked idle conns are LEGAL
(close-when-idle RETIRED); App.handle_conn exposes it; plain serve()
unchanged for simple apps
- web-app: app-owned accept_dl loop + ConnWorker actor per connection
(each builds its own App; cross-shard placement rides the DB actor);
WA_IDLE_MS knob; gate grows to 41 checks — two slow requests served
in PARALLEL, stalled client evicted at the idle deadline, slow-loris
torn at the read deadline (400)
- docs: story 35 -> done with banner; SQE/CQE design spec LANDED (was
the review doc); ledger rows (timeouts/unix/keep-alive/peer), graph
(NETSEAM cleared, KEEPAL done), builtin-surface rows, runtime
CODE-LOGIC section, board entry
- battery 13/13 fresh-built
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- park.c/park.h: one event loop per shard. io_uring PRIMARY (raw
io_uring_setup/io_uring_enter, uapi structs mirrored, 5.4-floor ops:
POLL_ADD for fd readiness, TIMEOUT for sleeps, user_data = the fiber);
epoll+deadline-scan FALLBACK behind the startup probe; WO_IO=
uring|epoll forces either so CI proves both on one kernel
- park protocol: a blocking builtin fills cur->park_* and returns
WO_SYS_PARKED; resume either RE-EXECUTES it (fd readiness: accept/
read/write retry) or continues PAST it (sleep: result preset,
park_done=1 — re-executing would restart the full duration)
- sysio: listener + accepted fds nonblocking (accept4 SOCK_NONBLOCK);
accept/read park on EAGAIN; write parks on EAGAIN with its partial
progress carried across the retry in park_wr_at; sleep parks on a
deadline — with ONE fiber the plane's wait IS the blocking call,
program mode is the degenerate case, not a special one
- scheduler: NEXT_RUNNABLE waits on the plane when the queue empties;
a stop interrupting the wait reaps EVERY fiber (queued and parked)
and returns the clean-stop status; parked fibers are GC roots and
fib_reap_all drains them
- proof: full battery green on the uring path (oop-e2e 92/0,
log-watcher 7/0 incl. the mcp accept/read/write loop, employee 8/0,
web-app 21/0, deps 8/0), WO_IO=epoll battery green (log-watcher 7/0,
web-app 21/0), WO_IO=uring forced green, LW_SOAK=8 10/0 (fd + RSS
flatness holds over parked I/O)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 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>
Measured on the workload's supervisor mode, eight seconds, clean SIGTERM exit:
run 1 051 040 B in 24 allocations -> 2 112 B in 19; watch 128 B in 2 -> 64 B in
1. corpus 71/0, woc runtest 565/0, wovm unit gates green, just log-watcher 6/0.
- owner.ml: `oclass_of` called `Text` a builtin scalar, so it was Copy and NO
Text local was ever dropped — that, not the missing stdlib table, was the
leak. Text is now Owned, which forces an answer for what it does at an
ownership boundary, and the answer is uniform: it is COPIED. Into a
container (push/set/`m[i] = v`, already true), into a field (SETF), out of a
function (return), into a binding (`let s = other`), and into a loop cursor.
The source keeps its value; a freshly built Text stays the caller's and is
dropped at the site
- owner.ml: resolve_callee answers for three shapes it never knew — reserved
stdlib members, builtins, and a class's `static` members — so their results
get a type, an owner and a drop
- vm/builtin: WO_B_TEXT_COPY, the one new builtin the rule needs; SETF copies a
TEXT field in; emit copies a Text read out of a container, bound from a
place, returned from a place, or loaded into a cursor, and drops a freshly
built one after a copying store
- sysio.c: fs.read_all/net.read allocated their cap then relabelled the buffer
with the short length — but wo_str_free sizes a block by its len (no size
headers, obj.h), so a 1 MiB buffer wearing a 30-byte length went onto a
32-byte free list and never came back. They copy out at the true size now
- two regressions the corpus caught, fixed in the same pass: a @gc value read
out of a container is a plain borrow, not an rc-counted alias; and push's @gc
escape is keyed on "push is not a user-declared fn" rather than "the callee
did not resolve", which stopped being true once builtins resolved
- docs: Task 1 closed in the executable plan with its before/after numbers, and
the status board's item 1 records the deeper root cause
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>