writeonce/docs/stories/language-runtime-database/35-net-runtime-seams.md
shoney.arickathil c0b0dbb846 docs: audit all markdown against the code, fix findings, flatten status folders
- README: shipped concurrency/HTTP/WebSockets sat in the roadmap as "not yet
  available"; "no package manager" contradicted [deps]; the deps example
  would not have compiled (the key IS the module name)
- runtime/README: leads with wovm, wo-rt.c demoted to a historical section;
  dropped 2 nonexistent recipes, crates/rt, @gc refcounting, 13 suites -> 18
- employee + log-watcher READMEs claimed "does not compile"; both are gates
- error catalog: +10 emitted codes incl WO-E250, the only diagnostic the
  shipped query surface raises; recorded why the sweep rotted
- language-surface: group-by parses, then the typechecker refuses it
- 00-code-review + 00-link-audit re-run; history kept, not rewritten
- 48 dead Rust-era exploration links de-linked rather than re-pointed (their
  prose names the retired plan by number); successor map -> discarded.md
- 08-project-structure: compiler/plan/ never existed; corpus has 9 dirs, 5 empty
- releasing.md: dropped a --draft step the workflow never had
- new docs/00-doc-audit.md: findings + disposition, incl one row where the
  audit was wrong and the doc it accused was right
- status folders removed: 34 stories flat, status only in frontmatter; 252
  links recomputed from resolved paths; board/board-views/structure retaught
- story 24 -> in-progress, since frontmatter is now the only truth
- new iteration 38: fs mutation verbs + net.connect, the two capability
  families no iteration owned
- new iteration 39: gofiber/fiber v3.5.0 parity study. The ledger called
  CSRF/sessions unblocked by iteration 34's HMAC, but the runtime has no
  source of randomness at all
- linkcheck skips .dev/.superpowers: 0 broken paths, 0 bad anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:20:22 +02:00

5.7 KiB
Raw Blame History

iteration status
35 done

Iteration 35 — net runtime seams: timeouts, Unix sockets, peer address

Format: product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.

LANDED 2026-08-23 (branch framework-v1b, with the serving slice riding it): per-call _dl deadlines (nil/false = the expected timeout; ids 91–93), net.listen_unix (unlink-before-bind, id 94), net.peer (id 95). Plane: shard-tick TIMEOUT + expiry sweep + POLL_REMOVE tombstone on uring, extended deadline scan on epoll, fibers POOLED against stale-CQE UAF — the full design in the review spec. Proof: all five seams probe-verified on BOTH WO_IO backends; serve_conn + web-app's fiber-per-connection pattern gate parallel requests, idle eviction, and slow-loris tearing (web-app 41 checks).

Inserted 2026-08-22 — the framework ledger's three 🔧 rows get one owner: "Read/write/idle timeouts — net has no timeout surface", "Unix socket binding — net.listen is TCP-only", and "Trusted-proxy client IP — needs a peer-address runtime seam". Each is a small builtin-surface addition; the framework knobs built ON them stay framework slices. Off the concurrency chain; no chain item depends on it, but a production-shaped deployment (proxy in front, sockets not ports, slow-client defense) needs all three.

Why this iteration exists

The framework cannot defend against a slow client (no read deadline — one stalled socket parks a fiber forever), cannot sit behind a same-host proxy the idiomatic way (Unix socket beats a loopback port), and cannot TRUST X-Forwarded-For (parsing is expressible in .wo today, but verifying the peer actually IS the proxy needs the peer's address, which no builtin exposes). All three are runtime seams by nature: the information or mechanism lives at the fd level.

Goals

  • Deadlines on parked net I/O. A read/accept/write that would park can carry a deadline; expiry resumes the fiber with a distinguishable timeout result (nil-or-trap decided by the spec, consistent with the stdlib's nil-vs-trap contracts). On serving shards this composes with the existing plane — a park is ALREADY a POLL_ADD or TIMEOUT submission (arc T4); the seam arms both and takes whichever fires. Program mode gets the same surface over blocking syscalls.
  • Unix-domain listeners and connections beside the TCP ones — same accept/read/write/close builtins afterward (an fd is an fd; only the bind/connect shape differs).
  • The peer's address, readable — for an accepted connection, enough to answer "is this my trusted proxy?" (address + family; port where meaningful). The framework's trusted-proxy middleware then becomes a pure-.wo candidate slice.
  • The framework knobs are explicitly NOT here — timeout defaults, proxy allowlists, socket-path config all live in framework slices that consume these seams.

Acceptance Criteria (draft — the spec refines)

  • Given a fiber reading a socket whose peer sends nothing, when the declared deadline expires, then the fiber resumes with the timeout result (never a hang), other fibers having run throughout (TID-verified), and the fd is still usable or cleanly closed per the spec's stated semantics.
  • Given a listener on a Unix socket path, when a client connects and exchanges bytes, then the whole existing net surface works unchanged over it, and the log-watcher/web-app gates stay byte-identical on TCP.
  • Given an accepted connection, when the handler asks for the peer address, then loopback TCP and Unix-socket peers are both identifiable, and the answer round-trips into the trusted-proxy check's comparison.
  • Given the full battery plus a soak with deliberately stalled clients, when it runs, then zero leaked fds and flat RSS — timeouts must CLEAN UP, not merely return.

Out Of Scope

  • Framework policy (default timeout values, proxy allowlist shape, keep-alive idle policy) — framework slices on top.
  • TLS, h2c — unchanged owners (proxy; parked behind 23).
  • Connect-side timeouts for outbound clients beyond what the deadline seam gives free — no workload asks yet.
  • Cancellation as a general mechanism — iteration 31's request/response + timers own actor-level cancellation; this is strictly fd-level deadlines.

Info

Forks the spec must settle:

  1. Timeout result shape: nil result vs a distinguishable trap — must follow the stdlib's existing nil-vs-trap doctrine (07-series contracts; a timeout is an EXPECTED outcome, which argues nil).
  2. Deadline plumbing on the plane: one park may need BOTH a POLL_ADD and a TIMEOUT in flight (io_uring linked ops vs two submissions + first-wins cancel; epoll fallback = the existing deadline scan). The park protocol contract (03-concurrency-coroutines.md) gains the rule.
  3. Surface shape: per-call deadline argument vs per-fd setting (net.set_deadline(fd, ms)); leaning per-call — no hidden fd state, matches the no-coloring doctrine.
  4. Unix-socket path semantics: unlink-before-bind? stale-socket handling on restart (the never-stopping-runtime doctrine says a restart must not need manual cleanup).

Proposed Solution

Brainstorm → small spec settling the four forks → implement in the time.ticks/34 shape (builtin ids + park.c deadline arming + contract rows + fixtures incl. a stalled-client corpus case). Independent of the chain; natural pairing is right before or with iteration 24 (chat wants read deadlines for dead-client eviction even before lifecycle timers).