- rv2 9 story -> status: done. §G G3 landed; ladder A–G complete, live-gated
both directions (just tls 5/0, just tls-server 4/0). review_pending +
phase rows + G sub-phases updated
- doctrine retired where the story named it: language 34 ("TLS permanently
the proxy's job"), language 38 ("proxy-terminated ... no HTTPS clients"),
porch 00-story ("TLS ... proxy-terminated") — each corrected to point at
in-process TLS (net.connect_tls / net.accept_tls)
- status board: rv2 9 row DONE + a top summary; NEXT PLAN = porch then
jarvis (sequencing set: jarvis follows porch)
- jarvis 00-story: sequencing note (no longer runtime-blocked; porch first)
- CODE-LOGIC: the inbound-server section (net.accept_tls, signing, slot
refactor, RST-drain, gate)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit f3a3c962e5f288c25e505851edef4e0a5df9a85f)
5.3 KiB
Story — porch, the writeonce web framework
The second track. Where
language-runtime-database/ grows
the language, this track grows the one library written in it:
porch, consumed by every serving sample
through wo.toml [deps].
Numbering restarts at 1 and is local to this track. Frontmatter carries
track: porch so a query over docs/stories/ can tell a porch iteration 3 from
a language iteration 3. Status rules are the repo's, unchanged: status: in
frontmatter is the only place state lives, no directory encodes it.
Why a separate track
Three reasons, all practical:
- Different substrate, different gates. porch is
.wosource. Its iterations are proven byjust web-appandjust site, never by the conformance corpus oroop-accept. Mixing them into the language track's sequence made both harder to read. - Different cadence. A porch slice is days; a language slice that touches
wob.hand the VM is longer and riskier. Interleaving them in one numbering forced false ordering decisions. - The framework is now the product surface.
writeonce.deis served by porch. Its gaps are what a visitor hits first, so they deserve a roadmap that is not buried behind runtime work.
The language track stays upstream: when a porch iteration needs a new builtin, that half is called out explicitly and the language track owns it.
Where the sequence came from
The Fiber v3.5.0 parity study
— gofiber/fiber read end to end against porch's actual .wo source: its routing
surface, Req/Res API, binder, lifecycle hooks and the Config of all 32 of
its middleware/ packages. Nine of those 32 already have a working porch
counterpart, so this is a breadth roadmap, not a rescue.
That study replaced language-track iteration 39, which is now a pointer here.
The sequence
Ordered by dependency, not by importance — and the first slice is deliberately the cheapest, so the store pattern and the gate shape are proven before the risky work starts.
| # | Iteration | Delivers | Needs |
|---|---|---|---|
| 1 | Store-backed middleware | rate limiting + idempotency over a @table store, serialized through a sharded actor pool |
🔄 in progress; needs no new primitive (call/send/monitor/time.after all landed) |
| 2 | Randomness and cookies | a random_bytes runtime builtin, repeated response headers, Cookie: parsing, signed cookies |
a language-track builtin (phase A) |
| 3 | Sessions | server-side sessions, idle + absolute timeout, revocation | 2 |
| 4 | CSRF | token mint/verify, trusted origins, single-use tokens | 2, 3 |
| 5 | Routing and response ergonomics | the remaining method helpers, named routes, per-route body limit, request ids, the missing response helpers | nothing — parallel to 2–4 |
| 6 | Streaming core | incremental response writes and chunked framing — the seam three iterations wait on | nothing new, but it changes Resp |
| 7 | SSE and compression | server-sent events, gzip/deflate | 6 |
| 8 | Static files and lifecycle | byte ranges, cache headers, directory listing, lifecycle hooks, the small middleware everyone ships | 6 |
1 ─ independent, start here
5 ─ independent, any time
2 ──▶ 3 ──▶ 4
6 ──▶ 7
└──▶ 8
What this track does NOT own
| Not porch's | Owner |
|---|---|
| typed binding of query/params/form into a class | language: @derive — reflection is forbidden by principle 13 |
TTL cache, transaction { }, durable job queue |
language: iteration 18 |
a proxy middleware |
language: iteration 38 — needs net.connect, which does not exist |
| metrics, profiling, per-change CI, fuzzing | runtime-v2 7 — observability (was language iteration 30; metrics/profiling/trace-on-trap; CI + fuzz are tooling, split out) |
| TLS | ✅ runtime-v2 9 — in-process TLS 1.3 both directions (2026-09-09); porch can terminate inbound TLS with net.accept_tls, no front proxy required. The proxy-termination doctrine is retired |
| HTTP/2 | nobody yet — a separate protocol slice; TLS is its prerequisite, now met |
| a runtime template engine | nobody — rejected; markup is a compile-time literal (writeonce-view) |
| a radix-tree router | nobody yet — waiting on a measurement, not a decision |
Review protocol
Same as the language track: the developer reads one iteration, approves or
amends; the next starts only after approval. Each iteration is an unsplittable
value slice with phases, per-phase tasks, Given/When/Then acceptance criteria,
and an out-of-scope list. Every phase ends with both serving gates green —
just web-app and just site — because porch has two consumers and a change
that only satisfies one is not done.