writeonce/docs/stories/language-runtime-database/16-web-framework.md
shoney.arickathil 1fe808b7a4 docs(stories): add readiness, retire status: refine, sweep all 47 iterations
- `readiness: ready | refine` is a SECOND axis, orthogonal to status.
  `ready` = the brainstorm is complete and the decisions are LOCKED (a spec
  approved, or the forks explicitly confirmed). `refine` = open forks remain
  and it cannot be planned yet
- `status: refine` RETIRED because it carried both meanings at once, so a held
  iteration with an approved spec (language 18, 26) was indistinguishable from
  one nobody had thought about. status is now purely where the WORK is:
  done | in-progress | pending | hold — `pending` was already the board's own
  rendering word, so nothing new was invented
- all 47 iterations classified from EVIDENCE in their own text, not by guess:
  "the four forks are SETTLED" / "spec + plan approved" / "Approved spec:" for
  ready; "Forks the spec must settle" / "no spec exists yet" for refine. Every
  shipped iteration is ready by definition. 19 done, 5 in-progress, 15
  pending, 8 hold; 27 ready, 20 refine
- two iterations moved refine -> in-progress rather than -> pending: language
  31 and 34 are absorbed into 24 and work on them is literally happening, which
  the board already showed as 🔄 while their frontmatter said otherwise. That
  disagreement is now gone
- board legend, board-views' frontmatter contract, and two new Dataview
  queries updated — the useful one being `readiness: ready AND status:
  pending`, the startable set

WHAT THE NEW AXIS IMMEDIATELY SURFACED: of 15 pending iterations, exactly ONE
is startable — databasev2 4, io_uring group-commit, whose forks were confirmed
settled 2026-08-20. Everything else pending needs a brainstorm first. That was
invisible while one key carried both meanings, and it is now on the board.

Also caught by the sweep, unrelated to readiness but found by cross-checking
frontmatter against the board: SIX duplicate rows. Every iteration moved into
databasev2 was still listed in the LANGUAGE pending table under its retired id
(23, 32, 33, 20, 21, 27) as well as its new one. Stale copies removed. And two
databasev2 rows made claims the sweep contradicts — iteration 1 was billed
"startable today" while its forks are open, and 6 still called itself the
ceiling-raiser after 2 took that role.

Docs only. linkcheck 0 broken / 0 anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 16:54:45 +02:00

7.1 KiB

iteration status readiness
16 done ready

Iteration 16 — the web framework: a .wo library, HTTP/1.1 behind a proxy

Format: 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-app 14/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 internal use paths 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 helpers get/post/put/delete_ (the take-Handler shape, probe-proven — deviation 1 of the 16 plan retired), 405 + Allow on wrong-method path hits, HEAD served as GET with the body suppressed (RFC 9110 §9.3.2), a request-line Logging middleware, set_header; just web-app 16/0. It exposed and fixed a third compiler gap: a Text single-segment interpolation of a place crossed let/assignment boundaries uncopied — copy_place_text now sees through Interp (run/interp-borrowed-field).

Auth-in-core LANDED 2026-08-20 (same branch): http/auth.wo — the MECHANISM per the core doctrine: Authorization parsing, pure-.wo base64 (RFC 4648), constant-time ct_eq, req.principal as the blessed "who is this" slot (Middleware.before now takes mut req to write it), BearerAuth + BasicAuth (WWW-Authenticate challenge) middlewares. Policy stays app-side. Probe matrix 26/26 incl. RFC vectors, ASan-clean; web-app dogfoods BearerAuth (its hand-rolled Auth deleted); just web-app 17/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) and form_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-app 19/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_named accessor; whole-body within BODY_MAX, streaming parks behind 8/11. Probe 13/13 + reuse loop, release + ASan; CreateProduct accepts multipart/form/JSON; just web-app 21/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.md sections 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; the just web-app gate; docs closeout). Two enabling risks retired before planning: interface-field dispatch (probe) and the route-table owned-move double-free (fixed, pinned by run/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-watcher mcp pattern), a method+path router with :param captures, Req/Resp records with builder helpers, and the no-function-values handler model — Handler / Middleware structural interfaces dispatched by ICALL, with WO-E205 making a non-conforming handler a compile error and ?Resp middleware short-circuiting on the shipped ?T narrowing.
  • The consuming app, docs/examples/web-app/: a small storefront (Product/Order as @table classes, 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: service blocks 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-app runs, then deps fetch, the app builds, serves list/show/create with @table persistence 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.