- loader's resident:keys refusal said "rows are still fully resident" and "until tasks 5c/5d land". Both false since f606fc9. Corrected to name the real blocker: UPDATE needs read-modify-append - databasev2 00-story: the sequence graph drew 2->3->4, which reads as 3 needing 2 and 4 needing 3. Both backwards, and it still drew the 2->5->6 path the 2026-08-27 amendment retired. Redrawn stating only real dependencies, with 4 and 3 shown as composing rather than ordered, and the execution order that actually happened - databasev2 03: the hazard and its Outstanding entry both claimed nothing fails "because iteration 2's storage half is unimplemented". Marked discharged, and recorded that the hazard named only half the danger — the bitmap walk would have dropped keys rows outright - databasev2 06: pending -> hold (largely superseded, revisit only on a measurement); dated its 5c/5d references - porch 01: rewritten to the settled shape. readiness ready, status in-progress, phases B and C marked superseded with why - porch 01 claimed time.after "is still a reserved builtin id". False — builtin 90, implemented. That claim is what made the iteration look cheaper than it is - porch README gains honest ledger rows for both features (partial, being rebuilt), not shipped - skill-catalog README pointed at a story path that moved tracks; linkcheck now 0 broken Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit b3d8c403e1d19ac27ec966de85cb293e0765795c)
4.9 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 | 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.