6 tasks, words-only: (1) framework skeleton — Req/Resp records + builders, standalone-typechecking project; (2) HTTP/1.1 parse/serialize + keep-alive serve loop with the try-bounded dispatch seam (400-close on malformed, 500- survive on handler traps, fd/stop clean); (3) router with :param captures + Handler/Middleware interfaces + App.serve; (4) web-app storefront — @table Product/Order, auth middleware, json routes, [deps] manifest carrying the future GitHub URL; (5) scripts/web-app-accept.sh — temp git remote from the framework dir, file:// substitution into a temp app copy, full curl matrix + restart persistence + SIGTERM + opt-in soak, wired as just web-app; (6) docs closeout. Both enabling risks retired pre-plan (interface-field dispatch probe; owned-move container fix pinned by run/container-owned-move). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
73 lines
3.9 KiB
Markdown
73 lines
3.9 KiB
Markdown
# 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](00-story.md).
|
|
>
|
|
> **Inserted 2026-08-18.** 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/9f/11,
|
|
> when multiplexing has a scheduler to pay off on).
|
|
>
|
|
> **Spec exists:** [`2026-08-18-web-framework-design.md`](../../superpowers/specs/2026-08-18-web-framework-design.md)
|
|
> sections B (normative) and C (the parked h2c successor). **Plan:**
|
|
> [`2026-08-19-web-framework.md`](../../superpowers/plans/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 10 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/9f/11); chunked
|
|
transfer encoding; WebSockets/SSE; templates/SSR; multipart uploads;
|
|
performance work beyond the soak's flatness gate (benchmarks belong to 9e'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.
|