- `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.8 KiB
7.8 KiB
| track | iteration | status | readiness |
|---|---|---|---|
| porch | 8 | pending | refine |
porch 8 — static files, lifecycle hooks, and the small middleware everyone ships
Part of Story —
porch, the writeonce web framework. Source: the Fiber parity study §3, §5. The static half needs iteration 6; the rest does not.The clean-up iteration. Individually every item is small; together they are most of what makes a framework feel finished rather than adequate.
Goals
- Static files that can serve something large.
StaticFilesexists and is already careful — traversal is refused rather than normalised,max_bytesis a hard ceiling, and the site's/dldownloads run through it. What it cannot do is serve a file it cannot hold in memory, resume a partial download, or let a browser cache correctly. Fiber's static shipsByteRange,MaxAge,CacheDuration,IndexNames,BrowseandDownload; the range support is what makes video and large downloads work at all. - Cache headers that let a client skip the request.
etag_for/with_etaggive conditional GETs;Cache-Control,Last-ModifiedandIf-Modified-Sinceare the other half, andfs.statalready returns the mtime they need. - Lifecycle hooks. The ledger records "no user teardown hooks yet" as a known gap. Fiber has eleven hook families; porch needs a small handful — on-listen, on-shutdown, and on-route-registered — and the shutdown one is the one that matters, because an app with its own resources currently has nowhere to close them.
- The small middleware every framework ships: healthcheck, favicon,
redirect, rewrite, and a
skipcombinator. Each is a handful of lines and their absence is felt immediately by anyone starting a new app.
Phases
Phase A — ranges and cache headers
Rangerequest parsing (single range first; multi-range is a multipart response and can wait),206 Partial Content,Content-Range, andAccept-Ranges. An unsatisfiable range is416, not a truncated200.Last-Modifiedfromfs.stat's mtime,If-Modified-Sincehandling, andCache-Controlwith a configurable max-age.- Serve the body through iteration 6's writer so file size stops bounding what can be served.
- Verify: a ranged request returns exactly the requested bytes; a file much larger than the arena serves; a conditional request returns 304 with no body.
Phase B — directory behaviour
- Index-file resolution (
index.htmland friends) and an optional directory listing, defaulting off — a listing that is on by default is an information leak the first time someone points it at the wrong directory. Download/Attachmentdisposition, reusing the helper iteration 5 added.- Re-verify the traversal refusal against every new path (index resolution and listing both construct paths, which is exactly where traversal creeps back in).
- Verify: index resolution works; listing is off unless asked for; traversal is still refused on all new paths.
Phase C — lifecycle hooks
- Decide the minimal set (fork 1) and the interface — hooks are classes, like everything else here.
- Wire the shutdown hook into the existing
env.stopping()path so an app can flush and close before the process exits, and guarantee it runs exactly once even on a trapping path. - Verify: the shutdown hook fires on SIGTERM before the listener closes, once; a trapping hook does not prevent shutdown.
Phase D — the small middleware set
- Healthcheck (liveness and readiness are different questions — say which),
favicon, redirect (permanent and temporary), rewrite (internal, no round
trip), and
skipwrapping another middleware with a predicate. - Verify: each behaves;
skipcomposes with the existing chain in registration order.
Phase E — the gate and the ledger
- Both serving gates. The site is the natural subject: it already serves
/favicon.svg,/health, and/dldownloads throughStaticFiles, so these features have a real consumer rather than a synthetic one. - Close out the porch track's ledger rows and record what the whole track actually landed versus what the Fiber study predicted.
- Verify:
just web-app,just site,just linkcheckgreen.
Acceptance Criteria
- Given a
Range: bytes=a-brequest, when it is served, then the response is206with exactly those bytes and a correctContent-Range. - Given an unsatisfiable range, when it is served, then the response
is
416, never a truncated200. - Given a file larger than the heap, when it is requested, then it serves completely and peak memory does not track file size.
- Given
If-Modified-Sincematching the file's mtime, when the request arrives, then the response is304with no body. - Given a directory with an index file, when the directory is requested,
then the index is served; and with no index and listing disabled, the
response is
404, not a listing. - Given a traversal attempt through the index-resolution and listing paths, when it is served, then it is refused — the existing guarantee, re-proven against the new code paths.
- Given a registered shutdown hook, when the process receives SIGTERM, then the hook runs exactly once before the listener closes, and in-flight requests still complete.
- Given a hook that traps, when shutdown runs, then shutdown still completes.
- Given
skipwrapping a middleware with a predicate, when the predicate matches, then the wrapped middleware does not run and the chain continues in order.
Out Of Scope
- Multi-range requests. A multipart byte-range response is a separate format; single ranges cover downloads and media seeking, which is what the workload needs.
- Precompressed asset serving (
file.gzbesidefile). Composes with iteration 7; worth doing once, later, when both exist. - A file-watching or hot-reload story. Assets are read from disk per request; a cache with invalidation is a different feature and language iteration 18 owns caching.
pprof,expvar, metrics endpoints. Language iteration 30 (no story file yet). A healthcheck is not observability — it is one bit.- Fiber's fork, mount and prefork hooks. The shard runtime owns placement; there is no worker-pool to hook.
SendFilewith kernelsendfile(2). No such builtin exists and no iteration owns adding one; the streaming writer is the portable answer here.
Info
Forks the spec must settle:
- Which hooks, exactly? Fiber has eleven families and porch needs the fewest that are load-bearing. On-shutdown is clearly one — an app with open resources has nowhere to close them today. On-listen is convenient for a startup banner. On-route-registered is only useful for introspection, which iteration 5 may already cover. Fewer is better; each hook is a contract forever.
- Liveness or readiness for healthcheck? They answer different questions
and conflating them is why deployments flap: liveness says "do not kill me",
readiness says "do not send me traffic yet". A framework shipping one
endpoint called
/healthshould say which it is, and probably ships both. - Is directory listing available at all? Off-by-default is not the same as present-but-off. Shipping it at all means it will eventually be switched on somewhere it should not be. Leaning: ship it, off, with the doc saying plainly what it exposes — the alternative is every app hand-rolling a worse one.