docs(porch-static): brainstorm story 8 (static + lifecycle) to ready
- whole porch track (2-8) now brainstormed and locked (all ready) - four decisions: three hooks (on-listen/on-shutdown/on-route-registered); healthcheck ships BOTH /livez + /readyz; directory listing off-by-default, documented; Last-Modified via a new small time.utc(ms)->TimeParts builtin - language enhancement: YES, one small builtin -- time.utc, a gmtime sibling of time.local (time.local is local-tz, time.iso is UTC-but-ISO); IMS by string-equality, no date parser. The track's third + smallest language touch - byte ranges/large files via fs.read_at + iteration 6 writer; not lang-41-exposed - track language bill now explicit: random_bytes (2), deflate+crc32 (7), time.utc (8) -- each a builtin with a named consumer, none decoration - validated against .dev/reference/fiber. Board: whole track marked ready (cherry picked from commit 9801fceade799e25718606177f09e4306a579e98)
This commit is contained in:
parent
4d3e4261e1
commit
b932e0cb87
2 changed files with 120 additions and 86 deletions
|
|
@ -1195,9 +1195,11 @@ the language arc as v1 history.
|
||||||
### ▸ porch — the web framework track
|
### ▸ porch — the web framework track
|
||||||
|
|
||||||
New 2026-08-26, from [the Fiber v3.5.0 parity study](../plan/exploration/fiber/00-fiber-parity.md).
|
New 2026-08-26, from [the Fiber v3.5.0 parity study](../plan/exploration/fiber/00-fiber-parity.md).
|
||||||
Supersedes language iteration 39, now a pointer. **Stories 2–7 are `ready`
|
Supersedes language iteration 39, now a pointer. **The whole track (2–8) is
|
||||||
(brainstormed 2026-09-06, forks locked, validated against
|
`ready`** (brainstormed 2026-09-06, forks locked, validated against
|
||||||
`.dev/reference/fiber`); 8 remains `refine`.** Ordered by dependency; the
|
`.dev/reference/fiber`). The three language touches the track needs are now
|
||||||
|
explicit and small, each a builtin with a named consumer: `random_bytes` (2),
|
||||||
|
`deflate`/`crc32` (7), `time.utc` (8). Ordered by dependency; the
|
||||||
first slice is deliberately the cheapest so the store pattern and gate shape are
|
first slice is deliberately the cheapest so the store pattern and gate shape are
|
||||||
proven before the runtime and `Resp` are touched.
|
proven before the runtime and `Resp` are touched.
|
||||||
|
|
||||||
|
|
@ -1210,7 +1212,7 @@ proven before the runtime and `Resp` are touched.
|
||||||
| 5 | [Routing + response ergonomics](porch/05-routing-response-ergonomics.md) | ✅ **`ready` 2026-09-06** — **independent, any time; NO upstream dependency (not even iteration 2)**. Five decisions: `head` auto-registers with an opt-out (+ `patch`/`options`/`all`); request ids mirror the limiter's trust model with a **non-crypto** source (so no CSPRNG dependency); per-route `body_limit` is a **second check after routing** (global `BODY_MAX` stays the pre-routing ceiling, over-limit = 413); adding `name`/`body_limit` to `Route` is corpus-free; `Vary` accumulates by comma-join. Plus named routes + runtime-checked URL building, q-value ranking (retires the 🔶) |
|
| 5 | [Routing + response ergonomics](porch/05-routing-response-ergonomics.md) | ✅ **`ready` 2026-09-06** — **independent, any time; NO upstream dependency (not even iteration 2)**. Five decisions: `head` auto-registers with an opt-out (+ `patch`/`options`/`all`); request ids mirror the limiter's trust model with a **non-crypto** source (so no CSPRNG dependency); per-route `body_limit` is a **second check after routing** (global `BODY_MAX` stays the pre-routing ceiling, over-limit = 413); adding `name`/`body_limit` to `Route` is corpus-free; `Vary` accumulates by comma-join. Plus named routes + runtime-checked URL building, q-value ranking (retires the 🔶) |
|
||||||
| 6 | [Streaming core](porch/06-streaming-core.md) | ✅ **`ready` 2026-09-06** — riskiest/highest-leverage; **re-scoped to outbound only**. Three decisions: separate `StreamHandler`/`BodyProducer` parallel path (the `Resp` path untouched → existing responses byte-identical); streaming routes **opt out** of the after-chain, framework **refuses at registration** to combine with header-mutating middleware (loud, never silent), handlers stamp headers via a `security_headers()` helper; chunked **REQUEST** bodies **split into their own future iteration** (parse.wo refusal stays). No language enhancement; rides the fiber loop, not the actor pool (not lang-41-exposed) |
|
| 6 | [Streaming core](porch/06-streaming-core.md) | ✅ **`ready` 2026-09-06** — riskiest/highest-leverage; **re-scoped to outbound only**. Three decisions: separate `StreamHandler`/`BodyProducer` parallel path (the `Resp` path untouched → existing responses byte-identical); streaming routes **opt out** of the after-chain, framework **refuses at registration** to combine with header-mutating middleware (loud, never silent), handlers stamp headers via a `security_headers()` helper; chunked **REQUEST** bodies **split into their own future iteration** (parse.wo refusal stays). No language enhancement; rides the fiber loop, not the actor pool (not lang-41-exposed) |
|
||||||
| 7 | [SSE + compression](porch/07-sse-and-compression.md) | ✅ **`ready` 2026-09-06** — after 6 (+ 5 for q-ranking/comma-join Vary; NOT 2). Five decisions: refuse an incoherent heartbeat/`idle_ms` pair at construction; **codec = two C builtins `deflate`+`crc32`** (perf over pure-`.wo`; hand-rolled, no zlib dep; gzip framing in `.wo`) — the track's **second language dependency** after iteration 2; ETag over uncompressed bytes + `Vary`; `Last-Event-ID` explicitly unsupported (not silently ignored); Vary via comma-join. Codec is pure compute — not lang-41-exposed |
|
| 7 | [SSE + compression](porch/07-sse-and-compression.md) | ✅ **`ready` 2026-09-06** — after 6 (+ 5 for q-ranking/comma-join Vary; NOT 2). Five decisions: refuse an incoherent heartbeat/`idle_ms` pair at construction; **codec = two C builtins `deflate`+`crc32`** (perf over pure-`.wo`; hand-rolled, no zlib dep; gzip framing in `.wo`) — the track's **second language dependency** after iteration 2; ETag over uncompressed bytes + `Vary`; `Last-Event-ID` explicitly unsupported (not silently ignored); Vary via comma-join. Codec is pure compute — not lang-41-exposed |
|
||||||
| 8 | [Static files + lifecycle](porch/08-static-and-lifecycle.md) | ⬜ static half after 6. Byte ranges, `Last-Modified`/`Cache-Control`, index resolution, listing off-by-default, shutdown hooks (the ledger's "no user teardown hooks yet"), plus healthcheck/favicon/redirect/rewrite/skip |
|
| 8 | [Static files + lifecycle](porch/08-static-and-lifecycle.md) | ✅ **`ready` 2026-09-06** — static half after 6 (+ 5's Download helper). Four decisions: three hooks (on-listen/on-shutdown/on-route-registered); healthcheck ships **both** `/livez`+`/readyz`; directory listing **off by default**, documented; **`Last-Modified` needs a small `time.utc(ms)->TimeParts` builtin** (gmtime sibling of time.local — the track's third, smallest language touch; time.local is local-tz, time.iso is UTC-but-ISO), IMS by string-equality (no parser). Byte ranges via `fs.read_at`+iteration 6 writer. Not lang-41-exposed |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,89 +2,126 @@
|
||||||
track: porch
|
track: porch
|
||||||
iteration: "8"
|
iteration: "8"
|
||||||
status: pending
|
status: pending
|
||||||
readiness: refine
|
readiness: ready
|
||||||
---
|
---
|
||||||
|
|
||||||
# porch 8 — static files, lifecycle hooks, and the small middleware everyone ships
|
# porch 8 — static files, lifecycle hooks, and the small middleware everyone ships
|
||||||
|
|
||||||
> Part of [Story — `porch`, the writeonce web framework](00-story.md).
|
> Part of [Story — `porch`, the writeonce web framework](00-story.md).
|
||||||
> Source: [the Fiber parity study](../../plan/exploration/fiber/00-fiber-parity.md) §3, §5.
|
> Source: [the Fiber parity study](../../plan/exploration/fiber/00-fiber-parity.md) §3, §5,
|
||||||
> The static half needs [iteration 6](06-streaming-core.md); the rest does not.
|
> re-checked 2026-09-06 against `.dev/reference/fiber` (v3, `3ca9a9d`) and the
|
||||||
|
> existing `http/files.wo`.
|
||||||
|
> The static half needs [iteration 6](06-streaming-core.md) and iteration
|
||||||
|
> [5](05-routing-response-ergonomics.md)'s `Download` helper; the `Last-Modified`
|
||||||
|
> half needs **one small language-track builtin** (`time.utc`, decision 4). The
|
||||||
|
> rest is pure `.wo`. Not exposed to the lang-41 hang.
|
||||||
>
|
>
|
||||||
> The clean-up iteration. Individually every item is small; together they are
|
> The clean-up iteration, and the last in the track. Individually every item is
|
||||||
> most of what makes a framework feel finished rather than adequate.
|
> small; together they are most of what makes a framework feel finished rather
|
||||||
|
> than adequate.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- **Static files that can serve something large.** `StaticFiles` exists and is
|
- **Static files that can serve something large.** `StaticFiles` exists and is
|
||||||
already careful — traversal is refused rather than normalised, `max_bytes` is a
|
already careful — traversal is refused, `max_bytes` is a hard ceiling, and the
|
||||||
hard ceiling, and the site's `/dl` downloads run through it. What it cannot do
|
site's `/dl` downloads run through it — but it reads the whole file with
|
||||||
is serve a file it cannot hold in memory, resume a partial download, or let a
|
`fs.read_all` and so cannot serve a file it cannot hold in memory, resume a
|
||||||
browser cache correctly. Fiber's static ships `ByteRange`, `MaxAge`,
|
partial download, or let a browser cache correctly. Byte ranges are what make
|
||||||
`CacheDuration`, `IndexNames`, `Browse` and `Download`; the range support is
|
video and large downloads work at all.
|
||||||
what makes video and large downloads work at all.
|
|
||||||
- **Cache headers that let a client skip the request.** `etag_for`/`with_etag`
|
- **Cache headers that let a client skip the request.** `etag_for`/`with_etag`
|
||||||
give conditional GETs; `Cache-Control`, `Last-Modified` and `If-Modified-Since`
|
give conditional GETs already; `Cache-Control`, `Last-Modified` and
|
||||||
are the other half, and `fs.stat` already returns the mtime they need.
|
`If-Modified-Since` are the other half, and `fs.stat` already returns the mtime
|
||||||
- **Lifecycle hooks.** The ledger records "no user teardown hooks yet" as a known
|
they need.
|
||||||
gap. Fiber has eleven hook families; porch needs a small handful — on-listen,
|
- **Lifecycle hooks.** The ledger records "no user teardown hooks yet". porch
|
||||||
on-shutdown, and on-route-registered — and the shutdown one is the one that
|
needs a small handful; the shutdown one is the one that matters, because an app
|
||||||
matters, because an app with its own resources currently has nowhere to close
|
with its own resources currently has nowhere to close them.
|
||||||
them.
|
|
||||||
- **The small middleware every framework ships**: healthcheck, favicon,
|
- **The small middleware every framework ships**: healthcheck, favicon,
|
||||||
redirect, rewrite, and a `skip` combinator. Each is a handful of lines and
|
redirect, rewrite, and a `skip` combinator — each a handful of lines whose
|
||||||
their absence is felt immediately by anyone starting a new app.
|
absence is felt immediately.
|
||||||
|
|
||||||
|
## Decisions locked (brainstorm 2026-09-06)
|
||||||
|
|
||||||
|
1. **Three lifecycle hooks: on-listen, on-shutdown, on-route-registered.** Hooks
|
||||||
|
are classes, like everything else. on-shutdown is the essential one, wired
|
||||||
|
into `env.stopping()`, and runs exactly once even on a trapping path.
|
||||||
|
on-listen runs app code after the port binds (post-bind warmup, a readiness
|
||||||
|
signal). on-route-registered fires at registration time for plugins and
|
||||||
|
logging — it composes with iteration 5's route introspection rather than
|
||||||
|
duplicating it (a fire-at-registration hook versus a query of the table, two
|
||||||
|
different timings).
|
||||||
|
2. **The healthcheck ships both liveness and readiness as distinct endpoints.**
|
||||||
|
`/livez` is a static 200 (the process is up, do not kill me); `/readyz`
|
||||||
|
consults an app-supplied readiness predicate (DB reachable, warmup done) and
|
||||||
|
answers 503 until ready (do not send traffic yet). Conflating them is why
|
||||||
|
deployments flap; shipping one endpoint that is silently only one of the two
|
||||||
|
is the trap.
|
||||||
|
3. **Directory listing ships, off by default, documented.** Index resolution is
|
||||||
|
always on (a directory resolves to `index.html` and friends). A listing is
|
||||||
|
available behind a config flag defaulting off, with the doc stating plainly
|
||||||
|
what it exposes — the alternative is every app hand-rolling a worse, leakier
|
||||||
|
one. Traversal refusal is re-proven against both the index-resolution and
|
||||||
|
listing paths, which is exactly where traversal creeps back in.
|
||||||
|
4. **`Last-Modified` uses a new small `time.utc(ms) -> TimeParts` builtin.**
|
||||||
|
`time.local` is localtime (wrong zone for an HTTP-date) and `time.iso` is UTC
|
||||||
|
but ISO format (wrong shape). `time.utc` is the gmtime sibling of `time.local`
|
||||||
|
— the same `TimeParts` record (including `dow`) but in UTC — a tiny,
|
||||||
|
broadly-reusable builtin that fills a genuine gap (any GMT timestamp or log
|
||||||
|
line wants it). HTTP-date formatting is then pure `.wo` (static day/month name
|
||||||
|
tables plus the record fields). `If-Modified-Since` is handled by
|
||||||
|
string-equality against the formatted `Last-Modified` — the echo-back flow
|
||||||
|
clients actually use — so no HTTP-date *parser* is needed. This is the track's
|
||||||
|
third and smallest language touch, after iteration 2's `random_bytes` and
|
||||||
|
iteration 7's `deflate`/`crc32`.
|
||||||
|
|
||||||
## Phases
|
## Phases
|
||||||
|
|
||||||
### Phase A — ranges and cache headers
|
### Phase A — ranges, cache headers, and the `time.utc` builtin
|
||||||
|
|
||||||
- `Range` request parsing (single range first; multi-range is a multipart
|
- Add `time.utc(ms) -> TimeParts` (decision 4): a module builtin at the next free
|
||||||
response and can wait), `206 Partial Content`, `Content-Range`, and
|
`wob.h` id (re-check — the runtime-v2 track has been consuming them),
|
||||||
`Accept-Ranges`. An unsatisfiable range is `416`, not a truncated `200`.
|
`gmtime_r`-based, mirroring `time.local`'s implementation. Document it in the
|
||||||
- `Last-Modified` from `fs.stat`'s mtime, `If-Modified-Since` handling, and
|
builtin-surface contract in the same change.
|
||||||
`Cache-Control` with a configurable max-age.
|
- `Range` request parsing (single range first; multi-range can wait),
|
||||||
- Serve the body through iteration 6's writer so file size stops bounding what
|
`206 Partial Content`, `Content-Range`, and `Accept-Ranges`. An unsatisfiable
|
||||||
can be served.
|
range is `416`, not a truncated `200`.
|
||||||
|
- `Last-Modified` from `fs.stat`'s mtime formatted with `time.utc`,
|
||||||
|
`If-Modified-Since` by string-equality, and `Cache-Control` with a configurable
|
||||||
|
max-age.
|
||||||
|
- Serve the body through iteration 6's writer with `fs.read_at`, so file size
|
||||||
|
stops bounding what can be served (retiring the `fs.read_all` whole-file read).
|
||||||
- Verify: a ranged request returns exactly the requested bytes; a file much
|
- 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.
|
larger than the arena serves; a conditional request returns 304 with no body.
|
||||||
|
|
||||||
### Phase B — directory behaviour
|
### Phase B — directory behaviour
|
||||||
|
|
||||||
- Index-file resolution (`index.html` and friends) and an optional directory
|
- Index-file resolution and an optional directory listing defaulting off
|
||||||
listing, defaulting **off** — a listing that is on by default is an
|
(decision 3).
|
||||||
information leak the first time someone points it at the wrong directory.
|
- `Download`/`Attachment` disposition, reusing the helper iteration 5 added.
|
||||||
- `Download`/`Attachment` disposition, reusing the helper iteration
|
- Re-verify the traversal refusal against every new path.
|
||||||
[5](05-routing-response-ergonomics.md) 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
|
- Verify: index resolution works; listing is off unless asked for; traversal is
|
||||||
still refused on all new paths.
|
still refused on all new paths.
|
||||||
|
|
||||||
### Phase C — lifecycle hooks
|
### Phase C — lifecycle hooks
|
||||||
|
|
||||||
- Decide the minimal set (fork 1) and the interface — hooks are classes, like
|
- The three hook interfaces (decision 1). Wire the shutdown hook into the
|
||||||
everything else here.
|
existing `env.stopping()` path so an app can flush and close before the process
|
||||||
- Wire the shutdown hook into the existing `env.stopping()` path so an app can
|
exits, guaranteed to run exactly once even on a trapping path.
|
||||||
flush and close before the process exits, and guarantee it runs exactly once
|
- Verify: the shutdown hook fires on SIGTERM before the listener closes, once; a
|
||||||
even on a trapping path.
|
trapping hook does not prevent shutdown; on-listen fires after bind; the
|
||||||
- Verify: the shutdown hook fires on SIGTERM before the listener closes, once;
|
on-route-registered hook fires per registration.
|
||||||
a trapping hook does not prevent shutdown.
|
|
||||||
|
|
||||||
### Phase D — the small middleware set
|
### Phase D — the small middleware set
|
||||||
|
|
||||||
- Healthcheck (liveness and readiness are different questions — say which),
|
- Healthcheck (both endpoints, decision 2), favicon, redirect (permanent and
|
||||||
favicon, redirect (permanent and temporary), rewrite (internal, no round
|
temporary), rewrite (internal, no round trip), and `skip` wrapping another
|
||||||
trip), and `skip` wrapping another middleware with a predicate.
|
middleware with a predicate.
|
||||||
- Verify: each behaves; `skip` composes with the existing chain in registration
|
- Verify: each behaves; `skip` composes with the existing chain in registration
|
||||||
order.
|
order; `/livez` and `/readyz` answer independently.
|
||||||
|
|
||||||
### Phase E — the gate and the ledger
|
### Phase E — the gate and the ledger
|
||||||
|
|
||||||
- Both serving gates. The site is the natural subject: it already serves
|
- Both serving gates. The site is the natural subject: it already serves
|
||||||
`/favicon.svg`, `/health`, and `/dl` downloads through `StaticFiles`, so these
|
`/favicon.svg`, `/health`, and `/dl` downloads through `StaticFiles`.
|
||||||
features have a real consumer rather than a synthetic one.
|
|
||||||
- Close out the porch track's ledger rows and record what the whole track
|
- Close out the porch track's ledger rows and record what the whole track
|
||||||
actually landed versus what the Fiber study predicted.
|
actually landed versus what the Fiber study predicted.
|
||||||
- Verify: `just web-app`, `just site`, `just linkcheck` green.
|
- Verify: `just web-app`, `just site`, `just linkcheck` green.
|
||||||
|
|
@ -97,58 +134,53 @@ readiness: refine
|
||||||
is `416`, never a truncated `200`.
|
is `416`, never a truncated `200`.
|
||||||
- **Given** a file larger than the heap, **when** it is requested, **then** it
|
- **Given** a file larger than the heap, **when** it is requested, **then** it
|
||||||
serves completely and peak memory does not track file size.
|
serves completely and peak memory does not track file size.
|
||||||
- **Given** `If-Modified-Since` matching the file's mtime, **when** the request
|
- **Given** `If-Modified-Since` equal to the file's formatted `Last-Modified`,
|
||||||
arrives, **then** the response is `304` with no body.
|
**when** the request arrives, **then** the response is `304` with no body.
|
||||||
- **Given** a directory with an index file, **when** the directory is requested,
|
- **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
|
**then** the index is served; **and** with no index and listing disabled, the
|
||||||
response is `404`, not a listing.
|
response is `404`, not a listing.
|
||||||
- **Given** a traversal attempt through the index-resolution and listing paths,
|
- **Given** a traversal attempt through the index-resolution and listing paths,
|
||||||
**when** it is served, **then** it is refused — the existing guarantee, re-proven
|
**when** it is served, **then** it is refused.
|
||||||
against the new code paths.
|
|
||||||
- **Given** a registered shutdown hook, **when** the process receives SIGTERM,
|
- **Given** a registered shutdown hook, **when** the process receives SIGTERM,
|
||||||
**then** the hook runs exactly once before the listener closes, and in-flight
|
**then** the hook runs exactly once before the listener closes, and in-flight
|
||||||
requests still complete.
|
requests still complete.
|
||||||
- **Given** a hook that traps, **when** shutdown runs, **then** shutdown still
|
- **Given** a hook that traps, **when** shutdown runs, **then** shutdown still
|
||||||
completes.
|
completes.
|
||||||
- **Given** `skip` wrapping a middleware with a predicate, **when** the
|
- **Given** `/livez` and `/readyz`, **when** the app is up but not ready,
|
||||||
predicate matches, **then** the wrapped middleware does not run and the chain
|
**then** `/livez` is 200 and `/readyz` is 503.
|
||||||
continues in order.
|
- **Given** `skip` wrapping 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
|
## Out Of Scope
|
||||||
|
|
||||||
- **Multi-range requests.** A multipart byte-range response is a separate
|
- **Multi-range requests.** A multipart byte-range response is a separate format;
|
||||||
format; single ranges cover downloads and media seeking, which is what the
|
single ranges cover downloads and media seeking.
|
||||||
workload needs.
|
|
||||||
- **Precompressed asset serving** (`file.gz` beside `file`). Composes with
|
- **Precompressed asset serving** (`file.gz` beside `file`). Composes with
|
||||||
iteration [7](07-sse-and-compression.md); worth doing once, later, when both
|
iteration [7](07-sse-and-compression.md); worth doing once, later, when both
|
||||||
exist.
|
exist.
|
||||||
- **A file-watching or hot-reload story.** Assets are read from disk per
|
- **A file-watching or hot-reload story.** Caching with invalidation is language
|
||||||
request; a cache with invalidation is a different feature and language
|
iteration [18](../language-runtime-database/18-memory-db-features.md).
|
||||||
iteration [18](../language-runtime-database/18-memory-db-features.md) owns
|
- **`pprof`, `expvar`, metrics endpoints.** Language iteration 30. A healthcheck
|
||||||
caching.
|
is one bit, not observability.
|
||||||
- **`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;
|
- **Fiber's fork, mount and prefork hooks.** The shard runtime owns placement;
|
||||||
there is no worker-pool to hook.
|
there is no worker-pool to hook.
|
||||||
- **`SendFile` with kernel `sendfile(2)`.** No such builtin exists and no
|
- **`SendFile` with kernel `sendfile(2)`.** No such builtin exists and no
|
||||||
iteration owns adding one; the streaming writer is the portable answer here.
|
iteration owns adding one; the streaming writer is the portable answer.
|
||||||
|
- **An HTTP-date *parser*.** `If-Modified-Since` is matched by string-equality
|
||||||
|
against the formatted `Last-Modified` (decision 4); parsing arbitrary
|
||||||
|
HTTP-date shapes is not needed for the echo-back flow.
|
||||||
|
|
||||||
## Info
|
## Info
|
||||||
|
|
||||||
Forks the spec must settle:
|
The one language dependency is small: `time.utc`, a `gmtime_r` sibling of the
|
||||||
|
existing `time.local`. Everything else is pure `.wo` — `fs.read_at` (id 44)
|
||||||
|
plus iteration 6's writer for ranges and large files, `fs.stat` (id 42) for
|
||||||
|
mtime, `env.stopping()` (id 50) for the shutdown hook, and the small middleware.
|
||||||
|
None of it touches actors or the per-key pool, so, like the rest of the track
|
||||||
|
since iteration 1, it is not exposed to the lang-41 hang.
|
||||||
|
|
||||||
1. **Which hooks, exactly?** Fiber has eleven families and porch needs the
|
With this iteration ready, the whole porch track (2–8) is brainstormed and
|
||||||
fewest that are load-bearing. On-shutdown is clearly one — an app with open
|
locked. The three language touches the track needs are now explicit and small:
|
||||||
resources has nowhere to close them today. On-listen is convenient for a
|
`random_bytes` (2), `deflate`/`crc32` (7), and `time.utc` (8) — each a builtin
|
||||||
startup banner. On-route-registered is only useful for introspection, which
|
with a named consumer, none of them a primitive shipped as decoration.
|
||||||
iteration [5](05-routing-response-ergonomics.md) may already cover. Fewer is
|
|
||||||
better; each hook is a contract forever.
|
|
||||||
2. **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 `/health` should say which it is, and probably ships both.
|
|
||||||
3. **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.
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue