- `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>
7.2 KiB
7.2 KiB
| track | iteration | status | readiness |
|---|---|---|---|
| porch | 5 | pending | refine |
porch 5 — routing and response ergonomics: the parity that is merely missing
Part of Story —
porch, the writeonce web framework. Source: the Fiber parity study §5.Independent of iterations 2–4 and of the streaming seam — startable at any time, and a reasonable slice to interleave when the risky work needs a break. Nothing here is hard; all of it is felt.
Goals
- The rest of the method helpers.
Apphasget/post/put/delete_. ARoute { method: "PATCH" }literal already works, so this is registration ergonomics rather than capability — but writing the literal by hand forPATCHwhilegetexists is the kind of asymmetry that makes a framework feel unfinished. Addpatch,options,head, and anall. - Named routes and reverse routing. Fiber has
Name()andGetRouteURL(). porch has neither, so every link in the site is a hand-written string that no compiler checks — and the site is exactly the app where a renamed path breaks a page silently. - A per-route body limit.
BODY_MAX = 1048576is one compile-time constant for the whole server. An upload route and a JSON route want different numbers, and the JSON route wants a much smaller one than the upload route can live with. - Request ids.
req.ctxalready exists to carry one; there is no generator and no middleware. With iteration 2's builtin available this is a few lines, and it is the difference between logs you can correlate and logs you cannot. - The response helpers written by hand today.
Location,Vary,Attachment/Download, and a content-negotiatedformatdispatch on top of the existingaccepts(). Also q-value ranking, which the ledger has carried as a known 🔶 since the negotiation slice landed.
Phases
Phase A — method helpers and route introspection
- The missing registration helpers, including
all, and decide whetherheadauto-registers alongsideget(Fiber hasDisableHeadAutoRegister, which tells you the default is auto and that people want it off). - Route introspection — list the table — because it is nearly free once routes
are already a
multi Route, and it is what makes a startup banner or a route-dump flag possible. - Verify: each method dispatches;
405still carries a correctAllowbuilt from the real table; existing routes unchanged.
Phase B — named routes and URL building
- A name on
Route, a lookup, and a builder that fills:paramcaptures. - Decide the failure mode for a missing or extra parameter. A silently wrong URL
is worse than a trap, and this is a compile-time-checkable shape only once
language iteration 29's
@deriveexists — so for now it is a runtime check and should say so. - Migrate the site's internal links onto it, which is the proof it is usable.
- Verify: every site link resolves through the builder; a wrong parameter set is refused loudly.
Phase C — per-route body limits and request ids
- Move the limit from a module constant to route-level configuration with the current value as the default, so no existing app changes behaviour.
- A request-id middleware writing into
req.ctx, and settle whether an inbound header is trusted (fork 2). - Thread the id into the logging middleware's output, since a request id nothing logs is decoration.
- Verify: an oversized body is refused per-route; the id appears in logs and is stable across a request's lifetime.
Phase D — response helpers and negotiation ranking
Location,Vary,Attachment/Download, and aformat-style dispatch choosing a builder fromaccepts().- Rank q-values properly instead of stripping them, retiring the ledger's 🔶.
- Verify:
Varyaccumulates rather than overwrites (which the iteration-2 repeated-header work makes possible); negotiation picks the highest-q match, not the first.
Phase E — the gate and the ledger
- Both serving gates, the ledger rows, the board entry.
- Verify:
just web-app,just site,just linkcheckgreen.
Acceptance Criteria
- Given a route registered with each new helper, when the matching
method arrives, then it dispatches; and an unmatched method still
yields
405with anAllowlisting exactly the registered methods. - Given
headauto-registration, when aHEADrequest hits aGETroute, then the response is headers-only with theContent-LengthaGETwould have sent — the behaviourserialize()already implements, now reachable by registration. - Given a named route with
:paramcaptures, when a URL is built with the right parameters, then it matches that route's pattern exactly; and a wrong or missing parameter is refused rather than producing a plausible-looking wrong URL. - Given two routes with different body limits, when a body exceeding the smaller arrives at each, then it is refused at the small route and accepted at the large one.
- Given no per-route limit, when a request arrives, then the previous global limit applies unchanged.
- Given a request-id middleware, when a request is handled, then the same id appears in every log line for that request and in the response header.
- Given an
Acceptheader with q-values out of order, when negotiation runs, then the highest-q acceptable type wins — not the first listed. - Given two
Varycontributions from different middleware, when the response leaves, then both appear.
Out Of Scope
- A radix-tree router. Path matching is a linear scan and the ledger marks it 🔶 pending a measurement. Language iteration 22 built the benchmark harness but pointed it at the database. Until someone benches the router, this is an optimisation without evidence.
- Case-insensitive or strict-slash routing. Fiber exposes both as config. porch is case-sensitive and lenient; changing that is a behaviour change for existing apps and wants its own decision.
- Compile-time-checked URL building. The typed version needs language iteration 29. Runtime-checked now, upgraded later.
- Streaming responses,
SendFile, byte ranges — iterations 6 and 8. - Typed binding of params into a class — language iteration 29 again.
Info
Forks the spec must settle:
- Does
headauto-register? Fiber's default is yes with an opt-out. Auto is friendlier; explicit is more predictable and never surprises someone debugging why a route they did not register is answering. - Is an inbound request-id header trusted? Behind the mandated proxy,
trusting it is what makes tracing work across hops. On an open port it lets a
client forge correlation ids and poison logs.
client_ipandnet.peeralready exist for exactly this trust decision — reuse that conclusion. - Where does a route's body limit live? A field on
Routeis the obvious home but widens a record that the conformance corpus pins the ownership shape of. Check that fixture before choosing.