- developer directive: pending iteration IDs now ARE the priority order; LANDED iterations keep historical numbers (code comments and commit history cite them — records, not a queue); 8/11 (the half-landed arc), 17 (parked, artifacts on a branch), 18 (next, artifacts named) also frozen - mapping (recorded in 00-story): 19<-20 Float+Bytes, 20<-9c attach, 21<-9d keypair, 22<-9e benchmarks, 23<-9f io_uring WAL, 24<-19 chat, 25<-10 services, 26<-12 blue-green, 27<-9g query corpus, 28<-14 skillhost, 29<-13 metaprogramming - 11 story files renamed; every doc reference re-numbered (word-boundary sweep for the lettered 9x ids, phrase-level for numeric ones); the iterations table rewritten with Seq == priority and "(was N)" notes; story-scoped link check: zero broken - merge-recovery folded in: the partial master merge had dropped the chat story, the fibers exploration note, the arc spec+plan, the framework-v2 plan, and the iteration-17 spec+plan — all restored from their branches and renumbered consistently Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7 KiB
Iteration 16 — the web framework: a .wo library, HTTP/1.1 behind a proxy
Format: fiberloom
product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.Inserted 2026-08-18. LANDED 2026-08-19 (branch
web-framework):just web-app14/0 — deps chain, auth middleware, CRUD with @unique 409 / FK-restrict 409 / checked-decode 400, :name captures, 404, pipelined keep-alive, SIGTERM, WAL restart persistence. Two notable as-built facts: (1) connection policy is pipelined-keep-alive/close-when-idle — a probe showed a parked keep-alive connection starves accept on a single-threaded server; (2) the chain exposed and fixed two compiler gaps — owned values stored in containers now MOVE (run/container-owned-move), and a dep's internalusepaths resolve dep-relatively.v1 polish LANDED 2026-08-20 (branch
framework-v1, developer directive: ship routing/middleware/req-resp as a polished micro-framework, nothing MVC-scale): registration helpersget/post/put/delete_(the take-Handler shape, probe-proven — deviation 1 of the 16 plan retired), 405 +Allowon wrong-method path hits, HEAD served as GET with the body suppressed (RFC 9110 §9.3.2), a request-lineLoggingmiddleware,set_header;just web-app16/0. It exposed and fixed a third compiler gap: a Text single-segment interpolation of a place crossedlet/assignment boundaries uncopied —copy_place_textnow sees throughInterp(run/interp-borrowed-field).Auth-in-core LANDED 2026-08-20 (same branch):
http/auth.wo— the MECHANISM per the core doctrine:Authorizationparsing, pure-.wobase64 (RFC 4648), constant-timect_eq,req.principalas the blessed "who is this" slot (Middleware.before now takesmut reqto write it),BearerAuth+BasicAuth(WWW-Authenticate challenge) middlewares. Policy stays app-side. Probe matrix 26/26 incl. RFC vectors, ASan-clean; web-app dogfoodsBearerAuth(its hand-rolled Auth deleted);just web-app17/0. The framework README now carries the core CHECKLIST: what is ✅ (parsing, routing, middleware, types, auth, JSON), what is a candidate slice (form/multipart, error-mapping hook, config record), and what parks behind 8/11 by design (streaming, backpressure, per-request cancellation).Form-encoded bodies LANDED 2026-08-20 (same branch):
media_type(req)(content-type lowercased, parameters stripped) andform_values(req)(nil unless the content-type says form; '+' and %XX decoded through the existing query decoder). Probe 7/7 release + ASan; web-app's CreateProduct now accepts form OR JSON into one insert path;just web-app19/0.Multipart LANDED 2026-08-20 (same branch):
http/multipart.wo— RFC 7578 parts (fields + file parts with filename/mime), quoted or bare boundary, strict malformed-is-nil (missing closing marker, missing content-disposition),part_namedaccessor; whole-body within BODY_MAX, streaming parks behind 8/11. Probe 13/13 + reuse loop, release + ASan; CreateProduct accepts multipart/form/JSON;just web-app21/0. It surfaced the RETURN flavor of the interp-of-borrowed-place emitter bug (return "${p.content}"— corruption two requests after the handler); fixed in emit_return, pinned in the same fixture.The framework is written IN writeonce and imported like any dependency (iteration 15 is the prerequisite). TLS terminates at a reverse proxy — browsers get TLS+ALPN+h2 from nginx/caddy while the framework speaks HTTP/1.1 keep-alive behind it, so no TLS exists anywhere in the toolchain. h2c is the parked successor (after iterations 8/23/11, when multiplexing has a scheduler to pay off on).
Spec exists:
2026-08-18-web-framework-design.mdsections B (normative) and C (the parked h2c successor). Plan:2026-08-19-web-framework.md(6 tasks: types/builders; HTTP/1.1 parse+serve; router+interfaces+App; the web-app storefront; thejust web-appgate; docs closeout). Two enabling risks retired before planning: interface-field dispatch (probe) and the route-table owned-move double-free (fixed, pinned byrun/container-owned-move).
Goals
- The framework, incubated at
docs/examples/writeonce-framework/: an HTTP/1.1 keep-alive server core (extracted from the soak-proven log-watchermcppattern), a method+path router with:paramcaptures,Req/Resprecords with builder helpers, and the no-function-values handler model —Handler/Middlewarestructural interfaces dispatched by ICALL, with WO-E205 making a non-conforming handler a compile error and?Respmiddleware short-circuiting on the shipped?Tnarrowing. - The consuming app,
docs/examples/web-app/: a small storefront (Product/Orderas@tableclasses, list/show/create routes, one auth middleware, JSON responses) that imports the framework through[deps]— the sample exercises the whole chain: fetch → lock → build → serve → durable data across restart. - Honest limits stated where users read them: single-threaded blocking (concurrency arrives underneath via iterations 8/11), no chunked encoding, no WebSockets, JSON-first (no templates — the removed UI track stays removed).
- Relationship to iteration 25 recorded in both:
serviceblocks later lower onto this library — compiler sugar over the same router, never a rival stack.
Acceptance Criteria
- Given the web-app sample, when
just web-appruns, then deps fetch, the app builds, serves list/show/create with@tablepersistence across a process restart, answers 404/400 correctly, a deliberately trapping handler returns 500 while the server survives, and SIGTERM stops it cleanly — fd- and resident-flat under the opt-in soak. - Given a handler class missing
handle, when the app compiles, then WO-E205 names the class and interface. - Given nginx in front (documented config), when a browser hits it, then the browser negotiates h2 with the proxy while the backend link is HTTP/1.1 — proving the TLS/h2 story without TLS in writeonce.
- Given the framework extracted to its own repository, when the app's
[deps]URL is updated, then nothing else changes (success criterion 4 of the spec).
Out Of Scope
TLS in the toolchain (proxy-terminated by decision); HTTP/2 + the bytes/buffer type (parked to the h2c successor, after 8/23/11); chunked transfer encoding; WebSockets/SSE; templates/SSR; multipart uploads; performance work beyond the soak's flatness gate (benchmarks belong to 22's measurement backbone).
Proposed Solution
Framework modules http/, router/, app.wo as spec §B lays out; the
web-app acceptance script is the gate (curl matrix + restart + stop + soak);
just web-app wires fetch-build-serve-verify into one command. Plan follows
after this iteration is approved on the board.