ADR-0084 · Networking is a typed effect + handler, gated on user-defined effects (#44); real sockets deferred to the backend
-
Status: Proposed
-
Summary: The web-server northstar demands networking. Q39 frames it as a typed
{Net}effect (listen/accept/read/write) realized by a swappable handler — capability-secured, effect-tracked, mock-for-tests/real-for-prod. VERIFIED FROM CODE: the kernelHandler(Bang/Core/IR.lean) is a CLOSED triple (state/throws/transaction) with operations HARDCODED inhandlesOp/dispatchOn— there is no general handler carrying user op-clauses + a reified continuation. So a genuine{Net}effect (its own ops) REQUIRES user-defined effects (#44) as a hard KERNEL prerequisite (#44 = L, weeks, spine-touching, ~424Handler-match sites, ripples to the calc machine/LR/soundness/backend per invariant #4).{Net}is a small INSTANCE of #44, not a peer. The achievable-now slice is a mock/simulated handler on the pureSource.eval(no syscalls); real sockets need the FFI seam (Q37) + compiled backend (◊5+). Decision: name B (genuine {Net} via #44) as the correct answer; ship A (a "net-shaped" demonstrator over the existingstatehandler — zero kernel change, honestly labelled a demo, ops are get/put) as the cheap near-term slice; gate B on #44; defer D (real sockets) to ◊5+; REJECT C (a bespoke| netkernel constructor — spends #44's full ripple to buy one hardcoded effect, violating invariant #5's "generalize, don't special-case"). -
Depends-on: 0030, 0063, 0070
-
Relates-to: Q39 (net-as-effect), Q37 (FFI-as-effect — the seam D needs), Q40 (compilation strategy), #44 (user-defined effects — the prerequisite), ◊5 (the backend a real server needs)
-
Status: Proposed — scoped by the netscope design spike (2026-07-07); the #44-kernel-gate finding manager-verified against
IR.lean/Dispatch.lean. Not accepted/started — the net prong is a longer chain gated on #44 + the runtime; this records the design + the achievable-now line for when it's taken up. -
Date: 2026-07-07
-
Layer: effects/kernel (B/C touch the kernel via #44; A is a surface library over the existing
statehandler). -
Builds on: ADR-0030 (STM/concurrency — the
listen/acceptmultiplexing substrate), ADR-0063 (escapedCap= the fail-loud "no handler ⟹ can't network"), ADR-0070/0072 (named capabilities — how aNetlabel instance is named at the surface). Reference: Q39 (the network-as-effect thesis), Q37 (FFI-as-effect).
Context
The northstar (docs/PRD.md, the web-server/client project) demands networking. Q39 frames networking not as a
primitive but as a typed {Net} effect (listen/accept/read/write) realized by a swappable handler —
capability-secured (no handler ⟹ can't network), effect-tracked (the row shows a fn touches the net),
mock-for-tests / real-for-prod. The question: can bang express a {Net} effect today, and if not, what is the
smallest thing it CAN run? The kernel is a pure, fuel-bounded interpreter (Source.eval) — it cannot do a
syscall — so the real-net end is clearly deferred; the design question is where the achievable line sits.
The gating fact (verified from the code)
The kernel Handler (Bang/Core/IR.lean) is a CLOSED set of three (state/throws/transaction); operations
are HARDCODED strings in handlesOp/dispatchOn (Bang/Core/Semantics/Dispatch.lean — get/put, raise,
newTVar/readTVar/writeTVar). There is no general handler carrying user operation-clauses + a reified
continuation. Named capabilities (ADR-0070/0072) name an instance of the three built-ins; they do not declare a
fourth effect kind. Therefore a genuine {Net} effect requires user-defined effects (#44) as a hard KERNEL
prerequisite.
Considered options
- A — "net-shaped" demo over the existing
statehandler. A network buffer is state; under a distinctNetlabel (Labelis aNat, freely chosen at the surface),read/writeare library functions overget/puton an in-memory buffer. Zero kernel change; runs onSource.evaltoday. - B — genuine
{Net}effect + mock handler, via #44. A user-declaredeffect Net { read; write; … }with its own named ops, discharged by a pure mock handler. Needs #44 (general handler) first. Runs onSource.eval; still no real socket. - C — bespoke
| netHandlerconstructor. A fourth kernel constructor for networking. REJECTED. - D — real
{Net}+ real sockets. The FFI seam (Q37) + compiled backend (◊5+).
Decision
Name B the correct answer; ship A now as an honest demonstrator; gate B on #44; defer D to ◊5+; reject C (it spends #44's full ~424-site ripple to buy one hardcoded effect instead of the general form — invariant #5 and #44 both say generalize the handler, don't special-case one more effect).
The mock-now / real-later boundary (the load-bearing line)
runs PURE on Source.eval (in-memory simulation) │ needs the RUNTIME (◊5+)
─────────────────────────────────────────────────┼──────────────────────────────
A net-shaped demo over state (now, S) │ D real sockets via FFI (Q37)
B {Net} + mock handler (via #44, M) │ + compiled backend
─────────────────────────────────────────────────┴──────────────────────────────
Everything left of the line is a pure request→response SIMULATION. A real web server is right of the line and does not exist yet. The near-term prong is effect-MODELING, not a running server.
Prerequisite chain
A (S, now) → #44 (L, weeks, spine-touching) → B {Net}+mock (M, pure) → Q37 FFI seam + backend (XL, ◊5+) →
web server.
Consequences
- A ships a real slice of the Q39 thesis (capability-security + handler-swap) with no kernel line touched
(invariant #5 provably held) — but its ops are
get/put, not the{Net}interface, so it must be labelled a demonstrator, not the effect. - The chain is honest: #44 is the real gate, the backend the real server — nobody mistakes A for a running server.
- The genuine
{Net}effect is far (behind #44, the largest post-MVP direction).
Revisit if
#44 (user-defined effects) is taken up → B becomes a small instance (finalize this ADR to Accepted, first cut
read/write request→response, defer listen/accept = concurrency multiplexing to ADR-0030); OR the compiled
backend + FFI seam (Q37, ◊5+) lands → D (real sockets) becomes reachable.