- Req grows internal conn field (net.Conn, filled by parse) — handlers
touch it only through ws_accept
- ws_upgrade_valid: RFC 6455 §4.2.1 (GET, Upgrade token, Connection
token list, 24-char key, version 13); ws_accept_key pure
(base64(sha1(key+GUID)) — the runtime vector already pins the RFC
worked example); ws_accept writes the 101 and returns the fd;
hijacked() = the status-101 sentinel
- serve.wo: 101 skips serialize AND close — the loop forgets the fd
and returns to accept; plain HTTP byte-identical (web-app 26/26)
- codec + end-to-end proof land with the chat sample's gate; battery
12/12 (fibers TSan leg flaked empty under load, 10/10 on rerun;
web-app restart leg has a pre-existing 0.5s boot race, noted)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
- router/router.wo: Handler (handle(req) -> Resp) and Middleware
(before(req) -> ?Resp; nil = continue) structural interfaces; Route/Mw
record classes built as ctor literals at the registration site — the
ownership shape the corpus pins (run/container-owned-move);
route_match with :param captures over '/'-split segments (empties
dropped, first mismatch wins).
- app.wo: App holds the middleware chain + route table (take-push),
satisfies http's Dispatcher, dispatches middleware-then-first-match,
fills the captures onto the borrowed request in place (Dispatcher takes
`mut req` — the borrow checker rightly refused rebuilding a Req from
borrowed maps; captures collect locally so a failed match never touches
the request), 404 fallback, serve(host, port) delegation.
Verified against a throwaway app (not committed): middleware 401
short-circuit without the token; /things/:id captures 42 into the body;
unknown path 404; a DIV0 handler answers 500 and the next request is
served; SIGTERM clean. Framework image emits at 12161 bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- http/parse.wo: bounded-read buffering to the header terminator, then
exactly Content-Length body bytes; %XX decoding ('+' = space in query
strings only, malformed escapes pass through — parsing stays total);
path/query split with decoded pairs; header names lowercased; the
three-state Parsed record (closed / malformed / request) with keep-alive
carry-over — bytes past this request belong to the next one on the
connection.
- http/serve.wo: Dispatcher interface (the router's seam), status/reason
serialization with computed Content-Length, and the blocking loop:
malformed -> 400 + close; a trapping handler -> 500 AND the loop lives;
fds closed on every path; env.stopping() honored.
- Connection policy discovered by probing, not assumed: a parked keep-alive
connection BLOCKS accept on a single-threaded server (probe: client 1
idles open, client 2 starves). Policy: serve PIPELINED requests on one
connection (carry non-empty), close when the client would idle; a proxy
reconnects. README states it.
Verified against a throwaway echo app (not committed): %20 query decode;
two pipelined requests -> two responses on one connection; DIV0 handler ->
500 and the NEXT connection served; GARBAGE -> 400; SIGTERM stops clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- docs/examples/writeonce-framework: library project (no fn main) — wo.toml,
README (what it is + the honest v1 limits + the proxy TLS/h2 story), and
http/types.wo: pub Req/Resp records + the response builders (ok_text/
ok_json/created/not_found/bad_request/unauthorized/conflict/server_error/
redirect). Typechecks + emits entry-less via woc --emit (1767-byte image).
- docs/examples/web-app: manifest with the real [deps] entry (future GitHub
URL as documentation; the gate substitutes a file:// remote) + README
(routes table, run instructions, nginx h2-in-front sketch). Code lands
with Task 4.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>