diff --git a/docs/00-code-review.md b/docs/00-code-review.md index e610a6b..79e3239 100644 --- a/docs/00-code-review.md +++ b/docs/00-code-review.md @@ -131,3 +131,28 @@ Two capability gaps this re-run named that the original critique did not, now `fs` has six builtins (ids 40–45) and can create, grow and read a file but never replace, truncate, delete or rename one; and there is no `net.connect` anywhere in `runtime/src/`, so no program can open an outbound connection. + +## Re-verification 2026-09-09 + +Code is the source of truth; the standing critique's production-plumbing row and +the 2026-08-26 re-verification have been overtaken by shipped work. Kept as +written above; corrected here: + +- **"No TLS anywhere (proxy-mandated forever)"** — false since 2026-09-09. + Runtime-v2 9 landed hand-rolled TLS 1.3 in-process, both directions: + `net.connect_tls`/`net.read_tls`/`net.write_tls` (ids 115–117) and + `net.accept_tls` (118), live-gated (`just tls`, `just tls-server`). The + proxy-termination doctrine is retired. +- **"no crypto primitives"** — false. `crypto.c` holds SHA-1/SHA-256/HMAC (iteration + 34), ChaCha20-Poly1305, AES-GCM, HKDF, X25519, RSA-PSS/PKCS1 + ECDSA-P256 verify + *and* constant-time sign (RFC 6979), and an X.509 layer — all RFC/NIST-vector + gated. +- **"there is no `net.connect` anywhere in `runtime/src/`"** — false since + 2026-09-07 (`WO_B_NET_CONNECT` = 110; the id ceiling is now `WO_B_MAX` 118). +- **"send is one-way — no reply/request-response"** — overtaken by iteration 24's + `call`; **"no supervision, links, or actor death"** — overtaken by iteration 24's + monitors/death notices; the cross-shard message double free that shadowed the + actor path (language 41) is fixed (`63065ff`, marshal on the crossing). +- Still true: no HTTP/2, no debugger/LSP, git-rev-only deps, and — precisely — + **no RNG exposed to `.wo`** (the runtime has a `getrandom` source since rv2 9, + unsurfaced until porch 2's `random_bytes`). diff --git a/docs/00-dependency-graph.md b/docs/00-dependency-graph.md index 0208933..dc2f09a 100644 --- a/docs/00-dependency-graph.md +++ b/docs/00-dependency-graph.md @@ -302,8 +302,8 @@ flowchart TD P6["porch 6 streaming core"]:::ready P7["porch 7 SSE + compression"]:::ready P8["porch 8 static files + lifecycle"]:::ready - P9["porch 9 idempotent replay (⏸ hold; readiness: ready)"]:::held - L41["language 41 actor-arena hang (in-progress)"]:::held + P9["porch 9 idempotent replay (ready — unblocked 2026-09-09)"]:::ready + L41["language 41 actor-arena double free ✅ fixed 63065ff (cross-shard marshal)"]:::done RB --> P2 P2 --> P3 @@ -315,7 +315,7 @@ flowchart TD P5 --> P8 DFL --> P7 TU --> P8 - L41 -.blocks.-> P9 + L41 -.fixed 2026-09-09 — no longer blocks.-> P9 P1 -.re-scope 79e6da4: replay-on-retry split out of 1.-> P9 ``` @@ -426,58 +426,78 @@ transport. ## 7. jarvis — the AI-assistant track and everything it waits on The sixth track ([jarvis](stories/jarvis/00-story.md)): an AI assistant built in -writeonce. Only **jarvis 1** (the chat loop) is close to startable; it sits on -two chains — the **outbound HTTPS path** (the real blocker, mostly runtime-v2 9's -TLS ladder) and the **framework path** (porch, all `ready`). Phase-level, because -the TLS ladder is where the waiting actually happens. +writeonce. **The runtime side is done** — the whole outbound HTTPS path landed +2026-09-09 (runtime-v2 9, both directions, live-gated) and language 41 is fixed. +What jarvis 1 waits on now is purely the **framework**: the developer set the +order *porch first, then jarvis*. So this graph is the porch→jarvis chain. ```mermaid flowchart TD classDef done fill:#1a7f37,color:#fff,stroke:none classDef ready fill:#0969da,color:#fff,stroke:none classDef refine fill:#eac54f,color:#000,stroke:none - classDef held fill:#6e7781,color:#fff,stroke:none NC["net.connect (id 110) ✅"]:::done - A["rv2 9 A — AEAD ✅ (= rv2 8 A–C: ChaCha20-Poly1305 + AES-GCM)"]:::done - B["rv2 9 B — HKDF ✅"]:::done - C["rv2 9 C — X25519 ✅"]:::done - D["rv2 9 D — signatures: RSA-PSS/PKCS1 + ECDSA-P256 ✅"]:::done - E["rv2 9 E — ASN.1/DER + X.509 chain + trust store"]:::refine - F["rv2 9 F — record layer + handshake FSM (client), net.connect_tls"]:::refine - G["rv2 9 G — inbound server (porch TLS termination)"]:::refine - - P2["porch 2 randomness+cookies (ready)"]:::ready - P3["porch 3 sessions (ready)"]:::ready - P6["porch 6 streaming (ready)"]:::ready - P7["porch 7 SSE (ready)"]:::ready + TLS["rv2 9 in-process TLS 1.3 ✅ — net.connect_tls / read_tls / write_tls (115–117), net.accept_tls (118)"]:::done + L41["language 41 cross-shard marshal ✅"]:::done WOHTML["wo-html / writeonce-view ✅"]:::done - J1["jarvis 1 — the chat loop (unwritten)"]:::refine - J2["jarvis 2 — tool use / agent loop"]:::refine - J3["jarvis 3 — retrieval (RAG) + embeddings/vector sub-gap"]:::refine + RB["random_bytes builtin (porch 2 phase A / lang 39) — surfaces the runtime's getrandom"]:::ready + P2["porch 2 randomness + cookies (ready)"]:::ready + P3["porch 3 sessions (ready)"]:::ready + P4["porch 4 CSRF (ready)"]:::ready + P5["porch 5 routing + response ergonomics (ready, zero deps)"]:::ready + P6["porch 6 streaming core (ready)"]:::ready + P7["porch 7 SSE + compression (ready)"]:::ready + P8["porch 8 static + lifecycle (ready)"]:::ready + P9["porch 9 idempotent replay (ready — unblocked by L41)"]:::ready - NC --> F - A --> F - B --> F - C --> D - D --> E - E --> F - F --> J1 + J1["jarvis 1 — the chat loop (ready; forks auto-approved, review_pending)"]:::ready + J2["jarvis 2 — tool use / agent loop (refine)"]:::refine + J3["jarvis 3 — retrieval (RAG) (refine)"]:::refine + + NC --> TLS + RB --> P2 P2 --> P3 + P2 --> P4 + P3 --> P4 + P5 --> P7 + P5 --> P8 P6 --> P7 + P6 --> P8 + L41 --> P9 + + TLS --> J1 P2 --> J1 P3 --> J1 + P4 -. CSRF-protects the POST once built .-> J1 + P6 --> J1 P7 --> J1 WOHTML --> J1 J1 --> J2 J1 --> J3 ``` -The outbound path is the critical one: **A/B/C/D landed, E→F remain** (G is -inbound, not needed for jarvis dialling out). The framework path (porch 2/3/6/7) -is entirely `ready` and unblocked — buildable in parallel with the TLS ladder. -jarvis 1 itself is not yet written; jarvis 2/3 follow it. +**jarvis 1's dependency list, from the code and the stories (2026-09-09):** + +| jarvis 1 needs | for | state | +| --- | --- | --- | +| `net.connect_tls` / `net.read_tls` / `net.write_tls` (runtime-v2 9) | dialing the LLM API over HTTPS, streaming its SSE reply | ✅ landed, live-gated | +| `net.connect` (id 110) | the TCP under it | ✅ landed | +| language 41 fix | actors carrying messages across shards without the double free | ✅ landed | +| `@table` | durable `Conversation` / `Message` history | ✅ exists | +| wo-html / writeonce-view | the chat page | ✅ exists | +| **porch 2** randomness + cookies (needs the `random_bytes` builtin first) | session id + signed cookie | ready, **unbuilt** | +| **porch 3** sessions | the session principal history is keyed to | ready, unbuilt (after 2) | +| **porch 6** streaming core | incremental response writes | ready, unbuilt | +| **porch 7** SSE + compression | token streaming to the browser | ready, unbuilt (after 5 + 6) | +| porch 4 CSRF | protecting `POST /message` (bearer-gated until then) | ready, unbuilt (after 2 + 3) | +| porch 5 routing + response ergonomics | the route surface | ready, unbuilt, zero deps | + +**Build order that satisfies it** (the porch critical path to jarvis): the +`random_bytes` builtin → porch 2 → porch 3 → porch 5 → porch 6 → porch 7 (→ porch +4, 8, 9 to complete porch) → **jarvis 1**. Nothing on the runtime side is +outstanding; every remaining edge into jarvis 1 is a porch iteration. ## Maintenance rule diff --git a/docs/examples/porch/README.md b/docs/examples/porch/README.md index 83f8e56..4310ca3 100644 --- a/docs/examples/porch/README.md +++ b/docs/examples/porch/README.md @@ -72,9 +72,13 @@ porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" } cannot live in the library). Parallel requests, stalled-client eviction and parked idle keep-alive are gate-proven. The plain `serve()` stays single-threaded for simple apps. -- **TLS: none, anywhere.** Deploy behind nginx/caddy; the proxy terminates - TLS+ALPN and gives browsers HTTP/2 while this backend speaks HTTP/1.1 - keep-alive. See the web-app sample's README for the nginx sketch. +- **TLS: available in the runtime, not used by this sample yet.** Since + runtime-v2 9 (2026-09-09) the runtime terminates TLS 1.3 itself — + `net.accept_tls(listener, cert, key)` (see `docs/examples/tls-server`) — so a + front proxy is no longer mandatory. This sample still runs plaintext + HTTP/1.1 keep-alive behind nginx/caddy (which also supplies ALPN/HTTP/2); + see the web-app sample's README for the nginx sketch. HTTP/2 itself is a + separate future slice. - `Content-Length` bodies only (no chunked encoding); **WebSockets ARE supported since 2026-08-27** — `ws_accept` (`http/ws.wo`) performs the RFC 6455 handshake and hands back the hijacked `net.Conn`, and `http/wsframe.wo` @@ -211,7 +215,7 @@ needs to know, found in the course of building them ([porch 1](../../stories/por | --- | --- | | Constant-time comparison · Authorization parsing · Basic auth · principal | ✅ `http/auth.wo`, `req.principal` | | CORS | ✅ `Cors { allow_origin }` — preflight 204 (before) + origin stamp on every response (after) — slice 2 | -| Security headers | ✅ `SecurityHeaders` after-middleware (nosniff, DENY, referrer-policy); HSTS stays at the TLS proxy by design — slice 2 | +| Security headers | ✅ `SecurityHeaders` after-middleware (nosniff, DENY, referrer-policy); HSTS is set wherever TLS terminates — the front proxy today, or the runtime itself once a sample adopts `net.accept_tls` (runtime-v2 9) — slice 2 | | Host validation | ✅ `HostAllow { host }` answers 421 before any route — slice 2 | | Strict parsing | ✅ same item as Transport's row: duplicate Content-Length is a 400 | diff --git a/docs/examples/site b/docs/examples/site index 81ef9b9..003073f 160000 --- a/docs/examples/site +++ b/docs/examples/site @@ -1 +1 @@ -Subproject commit 81ef9b9ca00c7941dd37d3d3e3e4de6af849a5b4 +Subproject commit 003073f2df04edd53ac68aae18a06180b41c40fb diff --git a/docs/examples/web-app/README.md b/docs/examples/web-app/README.md index 54d68bb..b40d96a 100644 --- a/docs/examples/web-app/README.md +++ b/docs/examples/web-app/README.md @@ -26,9 +26,12 @@ the token comes from the `WA_TOKEN` env var. ## TLS / HTTP2 -None here, deliberately: deploy behind nginx/caddy — the proxy terminates -TLS+ALPN and speaks h2 to browsers while this backend serves HTTP/1.1 -keep-alive. Sketch: +This sample runs plaintext behind nginx/caddy — the proxy terminates TLS+ALPN +and speaks h2 to browsers while this backend serves HTTP/1.1 keep-alive. The +runtime itself can now terminate TLS 1.3 (`net.accept_tls`, runtime-v2 9, +2026-09-09; see `docs/examples/tls-server`), so the proxy is a deployment +choice here, not a requirement — HTTP/2 is the remaining reason to keep it. +Sketch: server { listen 443 ssl; diff --git a/docs/stories/00-status.md b/docs/stories/00-status.md index e171b0f..9c10c44 100644 --- a/docs/stories/00-status.md +++ b/docs/stories/00-status.md @@ -102,7 +102,8 @@ RSA-PSS + ECDSA-P256 with an RFC 6979 nonce, plus `wo_pkey_parse` Deferred (named follow-ups, none blocking): park-based handshake, `TlsConn` language object, connection pooling, close_notify on shutdown, complete-formula EC ladder. **Next: porch** (then jarvis — the developer set jarvis to follow -porch completion). Separately open: language 41's marshal fix (unblocks porch 9). +porch completion). Language 41's marshal fix has since landed (above), so porch 9 +is unblocked too.
outbound client detail (F-phases) @@ -139,8 +140,9 @@ auto-approved 2026-09-08/09, `review_pending` for a developer second review. The developer set the order: **jarvis is implemented once porch is complete and ready for all future jarvis iterations**. So with rv2 9 (the outbound seam) done, the path is **porch first** — the framework the jarvis chat loop is written on — -then jarvis 1–3. porch's own blocker is language 41's marshal fix (unblocks -[porch 9](porch/09-idempotent-replay.md)); the porch track (2–8) is brainstormed +then jarvis 1–3. porch's former blocker — language 41's marshal fix — landed +2026-09-09, so [porch 9](porch/09-idempotent-replay.md) is buildable; the porch +track (2–8) is brainstormed to `ready`, its language bill three small builtins (`random_bytes`, `deflate`/`crc32`, `time.utc`). @@ -1266,7 +1268,7 @@ that sequences its tasks. Read one, approve, then the next starts. | 33 | [Single-file store](databasev2/07-single-file-db.md) | ⬜ off-chain, small — `WO_DATA=.db` file form; driver-only (story written 2026-08-22) | | 34 | [Crypto builtins](language-runtime-database/34-crypto-builtins.md) | 🔄 **code landed** as 24's T1 (`d14fa9f`): `sha1`/`sha256`/`hmac_sha256`, ids 85–87 in `wob.h`, `runtime/src/crypto.c`, RFC/FIPS vectors 18/0, corpus pin. The 24 gate that once needed it is cleared. Frontmatter keeps `status: refine` only until 24's T10 closeout sets it to `done` | | 38 | [Content platform capabilities](language-runtime-database/38-content-platform-capabilities.md) | ⬜ off-chain, needs a spec — the two capability families no iteration owns, confirmed against `runtime/src/wob.h`: `fs` mutation verbs (six fs builtins, ids 40–45; `append` creates-if-absent, so nothing is ever replaced, truncated, deleted or renamed) and `net.connect` (ids 51–55 + 91–95, no connect, and no `connect()` anywhere in `runtime/src/` — so no OIDC/SMTP/object-store/webhook/federation). Driven by a `docs/examples/vault` content-collaboration workload, in 28's mould. New builtins from 96 (89/90 reserved for 31); no `.wob` bump (`WOB_VERSION 6u`, last moved by 36). Story written 2026-08-26 from the "can it build a Nextcloud?" ask | -| 39 | [Web framework parity](language-runtime-database/39-web-framework-parity.md) | ⬜ off-chain, needs a spec — from [the Fiber v3.5.0 study](../plan/exploration/fiber/00-fiber-parity.md) (all 32 of its middleware read against `porch`; **nine already have a counterpart**). Leads with a **random-bytes builtin**: the framework ledger claimed CSRF/sessions were unblocked by iteration 34's HMAC, but HMAC authenticates a token and cannot mint one — there is no RNG anywhere in the runtime. Then cookies (absent both ways; `Resp.headers` being a map cannot carry two `Set-Cookie` lines), then limiter/idempotency (cheapest wins — `@table` + `time.ticks`, nothing new), sessions, CSRF, and the routing/response sugar. Streaming/SSE/compression, `@derive` binding, TTL cache, `proxy` and metrics all excluded with owners named | +| 39 | [Web framework parity](language-runtime-database/39-web-framework-parity.md) | ⬜ off-chain, needs a spec — from [the Fiber v3.5.0 study](../plan/exploration/fiber/00-fiber-parity.md) (all 32 of its middleware read against `porch`; **nine already have a counterpart**). Leads with a **random-bytes builtin**: the framework ledger claimed CSRF/sessions were unblocked by iteration 34's HMAC, but HMAC authenticates a token and cannot mint one — and while the runtime now has a `getrandom(2)` source internally (runtime-v2 9's TLS ephemeral keys), nothing exposes it to `.wo` yet. Then cookies (absent both ways; `Resp.headers` being a map cannot carry two `Set-Cookie` lines), then limiter/idempotency (cheapest wins — `@table` + `time.ticks`, nothing new), sessions, CSRF, and the routing/response sugar. Streaming/SSE/compression, `@derive` binding, TTL cache, `proxy` and metrics all excluded with owners named | | 40 | [Shutdown drain guarantee](language-runtime-database/40-shutdown-drain-guarantee.md) | ✅ **LANDED 2026-08-27 — chain 3, with 31; split out of 24.** One rule: **a message sent before the stop flag is observed must be delivered and run before the engine stops.** Found by measurement, not review: making the chat gate's drain leg start its OWN (cold) server exposed that **5 of 16** fresh-server SIGTERM drains left a WebSocket client at EOF with no close frame and no diagnostic. Traced to `shard_main` — `NEXT_RUNNABLE()` already stated the contract ("a WORKER on stop keeps DRAINING … close frames!") but the IDLE branch reaped and broke, abandoning its inbox for teardown to free. An actor between messages is exactly that idle case, which is why a WARM soak server hid it for so long. Fix is one branch honouring the primary's drain window, yielding on an empty poll. **20 of 20 clean after**; `just chat` 11 checks 0 failures at the full 1000-client soak (which also settled the fd question: 1000 connections left the count at 44); runtime battery 36 suites 0 fail, compiler 556 checks 0 fail. Ruled out: a bigger spin (a 1 s wall-clock deadline still failed 2 of 12) and spawn-during-shutdown. Outstanding: a pin below the gate — nothing in `runtime/test/` drives the engine start/stop and no corpus fixture can trigger a stop | | 37 | [wo-html components](language-runtime-database/37-wo-html-components.md) | ✅ off-chain — LANDED 2026-08-25. Raw text literal (backtick, margin stripped at lex time, `{{ }}` auto-escapes) + the component layer: `Component`/`render_all`/`Layout` in wo-html, `ok_html` moved into the framework, site and shop both migrated | | 35 | [net runtime seams](language-runtime-database/35-net-runtime-seams.md) | ⬜ off-chain — fd deadlines on the park plane, Unix sockets, peer address; owns the ledger's three 🔧 rows (story written 2026-08-22) | diff --git a/docs/stories/jarvis/00-story.md b/docs/stories/jarvis/00-story.md index 1677ed9..9eff54a 100644 --- a/docs/stories/jarvis/00-story.md +++ b/docs/stories/jarvis/00-story.md @@ -14,13 +14,14 @@ frontmatter is the only place state lives, no directory encodes it. ## The problem, stated once -An assistant's whole job is to reach a model, and the runtime cannot reach -anything outbound. It learned to *listen* — sockets in 8/11/35, unix sockets and -peer address in 35 and runtime-v2 — but it has never learned to *dial*: there is -no `net.connect` (outbound TCP), confirmed against `runtime/src/wob.h` (the net -builtins stop at listen/accept/read/write plus the unix-socket client), and no -outbound TLS client anywhere. An LLM API is HTTPS on a remote host. jarvis -therefore does not begin until two things exist. +An assistant's whole job is to reach a model. When this track was written +(2026-09-07) the runtime could not reach anything outbound — it had learned to +*listen* (sockets in 8/11/35, unix sockets and peer address in 35 and runtime-v2) +but never to *dial*: no `net.connect`, no outbound TLS. **Both gaps are now +closed** (confirmed against `runtime/src/wob.h`): `net.connect` (id 110, TCP) and +the full TLS 1.3 client `net.connect_tls`/`net.read_tls`/`net.write_tls` (ids +115–117, runtime-v2 9, live-gated) — plus inbound `net.accept_tls` (118) for porch. +An LLM API is HTTPS on a remote host, and the runtime can now dial it directly. The design chosen is **direct outbound HTTPS** — jarvis dials the LLM API itself, keeping the pure single-binary story. That gates the whole track on @@ -49,7 +50,7 @@ owns its own connection rather than shipping a second executable. ## Architecture - browser ⇄ jarvis (a porch app) ⇄ net.connect ✅ + TLS (rv2 9, in progress) ⇄ LLM API + browser ⇄ jarvis (a porch app) ⇄ net.connect_tls ✅ (rv2 9, done) ⇄ LLM API Requests arrive at a porch web app; the answer streams the other way, token by token, LLM → jarvis → browser, over porch's SSE. Conversation state is durable @@ -108,7 +109,7 @@ Blockers, which must land before iteration 1 starts: | local, in-process model inference | needs an ML runtime and heavy FFI — against the no-external-dependency doctrine | | the local-gateway companion process | considered and rejected (above) in favour of the single-binary story | | voice / audio in or out | a separate surface with its own capture and codec story; no rung asks for it | -| starting before the blockers land | like [porch 9](../porch/09-idempotent-replay.md) waiting on language 41, jarvis waits on the outbound seam — documented, not worked around | +| starting before porch is complete | the runtime blockers (`net.connect`, TLS, language 41) have all landed; what jarvis now waits on is the **framework** — the developer set jarvis to follow porch completion (see Sequencing) — documented, not worked around | ## Review protocol diff --git a/docs/stories/language-runtime-database/34-crypto-builtins.md b/docs/stories/language-runtime-database/34-crypto-builtins.md index 4983394..a172b87 100644 --- a/docs/stories/language-runtime-database/34-crypto-builtins.md +++ b/docs/stories/language-runtime-database/34-crypto-builtins.md @@ -27,7 +27,9 @@ readiness: ready > from `.wo`. This unblocked chain position 4: the WebSocket handshake needs > SHA-1, and `just chat` verifies the accept-key independently. > -> **The gap it did NOT close:** there is still no RNG in the runtime. HMAC +> **The gap it did NOT close:** no RNG reaches `.wo` (the runtime gained an +> internal `getrandom(2)` source with runtime-v2 9's TLS, but no builtin exposes +> it — porch 2 / iteration 39's `random_bytes` does that). HMAC > authenticates a token and cannot mint one, so CSRF and sessions stay blocked > — which is why [39](39-web-framework-parity.md) leads with a random-bytes > builtin rather than treating them as unblocked. diff --git a/docs/stories/porch/00-story.md b/docs/stories/porch/00-story.md index a9dc0eb..3ef6473 100644 --- a/docs/stories/porch/00-story.md +++ b/docs/stories/porch/00-story.md @@ -72,7 +72,7 @@ risky work starts. | --- | --- | | typed binding of query/params/form into a class | language: [`@derive`](../language-runtime-database/29-compile-time-metaprogramming.md) — reflection is forbidden by principle 13 | | TTL cache, `transaction { }`, durable job queue | language: [iteration 18](../language-runtime-database/18-memory-db-features.md) | -| a `proxy` middleware | language: [iteration 38](../language-runtime-database/38-content-platform-capabilities.md) — needs `net.connect`, which does not exist | +| a `proxy` middleware | language: [iteration 38](../language-runtime-database/38-content-platform-capabilities.md) — needs `net.connect`, ✅ landed 2026-09-07 (id 110; plus `net.connect_tls` for an HTTPS upstream, runtime-v2 9). Buildable now | | metrics, profiling, per-change CI, fuzzing | [runtime-v2 7](../runtime-v2/07-observability.md) — observability (was language iteration 30; metrics/profiling/trace-on-trap; CI + fuzz are tooling, split out) | | TLS | ✅ [runtime-v2 9](../runtime-v2/09-in-process-tls.md) — in-process TLS 1.3 both directions (2026-09-09); porch can terminate inbound TLS with `net.accept_tls`, no front proxy required. The proxy-termination doctrine is retired | | HTTP/2 | nobody yet — a separate protocol slice; TLS is its prerequisite, now met | diff --git a/docs/stories/porch/01-store-backed-middleware.md b/docs/stories/porch/01-store-backed-middleware.md index 75665f4..d5990bb 100644 --- a/docs/stories/porch/01-store-backed-middleware.md +++ b/docs/stories/porch/01-store-backed-middleware.md @@ -5,12 +5,22 @@ status: done readiness: ready --- -# porch 1 — store-backed middleware: rate limiting and idempotency +# porch 1 — store-backed middleware: rate limiting > Part of [Story — `porch`, the writeonce web framework](00-story.md). > Source: [the Fiber parity study](../../plan/exploration/fiber/00-fiber-parity.md) §2. > Spec: [`2026-08-29-porch-store-backed-middleware-design.md`](../../superpowers/specs/2026-08-29-porch-store-backed-middleware-design.md). > +> **Re-scoped 2026-08-30 — this iteration is now RATE LIMITING ONLY.** +> Idempotency was built, reviewed and reverted; it moved to +> [porch 9](09-idempotent-replay.md); the C-runtime crash that blocked it +> ([language 41](../language-runtime-database/41-actor-arena-crash.md)) was +> fixed 2026-09-09, so porch 9 is now buildable. +> The split was made because the limiter is provably stable (five consecutive +> gate runs, 56 checks, 0 failures) while idempotency's gate flaked on a +> runtime defect — and a feature whose test passes some of the time is not +> shipped. Detail in History. +> > **Rewritten 2026-08-29** after the brainstorm settled every fork. The > iteration's premise changed: it was scoped as the cheapest slice because it > needed "only a `@table` and `time.ticks`", and it now serializes through an @@ -63,14 +73,15 @@ globally. | Phase | State | | --- | --- | -| A — store convention | ✅ `519d411` two purpose-shaped tables; `3a9bddc` added the digest column the refusal criterion needs | -| B — rate limiter | ✅ `676e651` the shared key-pool actor (also C's foundation), `153fd29` its `reset_at` unit fix; `a653dd0` the limiter delegates all counting to the pool, `831e9d8` `trust_proxy`'s absent-XFF fallback fix | -| C — idempotency | ✅ `eae1b06` rebuilt on the same pool actor (the actor runs the route's `Handler` itself); three fix rounds: `e61015f` never replay a transient 5xx, `464147a` close the ephemeral-row race, `9ad5947` delete the ephemeral row after one read | -| D — gate and ledger | ✅ + this commit — the pool-saturation gate leg (§19, `scripts/web-app-accept.sh`), README ledger rows to ✅, pool-size capacity docs, `make_pool`'s mod-by-zero guard | +| A — store convention | ✅ `519d411`, `3a9bddc` — two purpose-shaped tables plus the digest column. `IdempotencyKey` is retained but unused; the file says why | +| B — rate limiter | ✅ `676e651`, `153fd29` (the pool), `a653dd0`, `831e9d8` (the limiter). Exact counting under 30 parallel clients, restart-durable, `trust_proxy` off by default | +| C — idempotency | ⏸ **moved to [porch 9](09-idempotent-replay.md).** Built and reviewed (`eae1b06`…`9ad5947`, plus the final wave), then reverted — see History | +| D — gate and ledger | ✅ `21934b1`, `2ac1b8b`, and the re-scope commit. 56 checks, 0 failures, stable across five consecutive runs | -Superseded pre-rewrite commits (`5b1e82a`, `aee7926`, `5c3544d`) are kept in -History below rather than deleted — the reasoning for the rebuild survives -there. +**What the split bought.** Before it the suite reported anywhere from 0 to 6 +failures run to run; after it, five consecutive runs at 56/0. The limiter was +finished either way — it was being held hostage by a defect in code it does not +call. ## Phases diff --git a/docs/stories/porch/02-randomness-and-cookies.md b/docs/stories/porch/02-randomness-and-cookies.md index 45ac560..7a5d3d7 100644 --- a/docs/stories/porch/02-randomness-and-cookies.md +++ b/docs/stories/porch/02-randomness-and-cookies.md @@ -30,8 +30,11 @@ randomness and cookies maps onto primitives writeonce already has. - **Randomness is the one gap.** fiber's `SecureToken` is `base64.RawURLEncoding` over 32 `crypto/rand` bytes; `UUIDv4` is the same entropy behind a format; `encryptcookie.GenerateKey` is a raw `rand.Read`. - All three **panic** if the source fails. writeonce has no RNG at all. This is - phase A, and it is the whole of the language work. + All three **panic** if the source fails. writeonce exposes no RNG to `.wo` + yet — the runtime does have a `getrandom(2)` source internally (runtime-v2 9's + TLS uses it for ephemeral keys), so phase A is surfacing that as a + `random_bytes` builtin, not inventing entropy. It is the whole of the language + work. - **Repeated `Set-Cookie` is not language work.** fiber gets multiple lines from fasthttp appending them; porch expresses the same with a `multi SetCookie` field, and `multi ` is an existing language feature. diff --git a/docs/stories/runtime-v2/07-observability.md b/docs/stories/runtime-v2/07-observability.md index 45133d5..79563e7 100644 --- a/docs/stories/runtime-v2/07-observability.md +++ b/docs/stories/runtime-v2/07-observability.md @@ -63,9 +63,10 @@ GC pauses) live in the C runtime, not in `.wo`. an `expvar`-style JSON blob, or both. The format decides who can consume it without a translator. 3. **Pull endpoint or push.** A mounted `/metrics` endpoint (pull) fits the - proxy-fronted, single-binary model; a push to a collector needs - `net.connect`, which does not exist (iteration 38) — so pull is almost - certainly the answer, but say so. + single-binary model; a push to a collector needs `net.connect` — which now + exists (id 110, landed 2026-09-07; `net.connect_tls` for an HTTPS collector, + runtime-v2 9) — so push is possible, but pull is still the simpler default; + say which. 4. **Is stack-trace-on-trap in this iteration at all?** It is separable, it is the highest debugging value per line, and it touches the trap path rather than the metrics path — a candidate to land first and alone. diff --git a/runtime/src/CODE-LOGIC.md b/runtime/src/CODE-LOGIC.md index b7fb552..6599afb 100644 --- a/runtime/src/CODE-LOGIC.md +++ b/runtime/src/CODE-LOGIC.md @@ -418,9 +418,12 @@ need SHA-256, and that is the whole of the demand so far. Correctness is pinned to the published vectors rather than to itself: RFC 3174 for SHA-1, the FIPS/RFC 6234 vectors for SHA-256, RFC 4231 for HMAC, -in `runtime/test/test_crypto.c` (18 checks). **There is still no RNG anywhere -in the runtime** — HMAC authenticates a token but cannot mint one, which is why -iteration 39 leads with a random-bytes builtin. +in `runtime/test/test_crypto.c` (18 checks). ~~There is still no RNG anywhere in +the runtime~~ — **corrected 2026-09-09:** the runtime now has a `getrandom(2)` +source (`tls_rand` in `sysio.c`, used for TLS ephemeral keys, ClientHello +randomness and RSA-PSS salts), but nothing exposes it to `.wo` yet — HMAC still +cannot mint a token from the language, which is why iteration 39 / porch 2 lead +with a `random_bytes` builtin that surfaces this source. ## Hand-rolled TLS 1.3 client (runtime-v2 9, ids 115–117)