- docs/stories/porch/ — a TRACK folder, not a status folder: status still
lives only in frontmatter. Adds `track: porch` so a query over
docs/stories/ can tell a porch 3 from a language 3
- 00-story.md carries the sequence, the dependency graph, and a table of
what the track explicitly does NOT own (binding -> 29, cache -> 18,
proxy -> 38, metrics -> 30, TLS/templates -> doctrine)
- eight iterations, each with phases, per-phase tasks, Given/When/Then
criteria, out-of-scope and the forks a spec must settle:
1 store-backed middleware (limiter + idempotency — needs nothing new,
first on purpose so the store pattern is proven cheaply)
2 randomness + cookies (phase A is language-track: a CSPRNG builtin;
`Resp.headers` being a map cannot emit two Set-Cookie lines)
3 sessions 4 CSRF 5 routing/response ergonomics (independent)
6 streaming core (the seam 7 and 8 wait on; chunked-request refusal
must survive) 7 SSE + compression 8 static + lifecycle hooks
- language iteration 39 -> status: hold, retitled superseded, with a row
mapping each of its goals to the porch iteration that took it. Kept, not
deleted: the Fiber study cites it and its randomness argument is what
this track is built on
- board gains a porch section; board-views gains porch and both-track
Dataview queries; porch README and the Fiber study §7 point at the track
- no code blocks in any story (plans carry concept and actions in words);
linkcheck 0 broken / 0 anchors
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4.8 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 |
nothing new — starts today |
| 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 | language iteration 30 (no story file yet) |
| TLS, HTTP/2 | nobody — proxy-terminated by doctrine |
| 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.