ADR-194: Two Servers, Not One — The Standard One Is The Operator’s And An Application’s Is Its Own

Status: Superseded by ADR-289 — its D1 is D6 there, D4 is D14, D5 is D15, and D2 and D3 are D12 and D13 as narrowed by ADR-200 D1 and D2 Date: September 22, 2026 Target Version: Nikaia 0.0.153 Answers: open-decisions.md’s what does a Nikaia program have to write to be a microservice, and who decides what is exposed? Related: ADR-018 (what a handler is given and gives back), ADR-069 D1 and D2 (http is a package, and the subtraction that put it there), ADR-121 D1 and D4 (the awaitable readiness this rests on), ADR-038 §4.5 and D5 (the server’s order, and what an operating property is), ADR-108 (a path names its root at the call)

1. Context

The ask was the ergonomics of python -m http.server: one command line, no ceremony, functionality reachable in a minute.

The question that page carried forced a choice between the program decides what is exposed and the operator decides, and offered four options to pick from. The owner’s answer is that the choice is false, and it is false for a reason worth writing down: those are not two designs for one product. They are two products, and each is right about itself.

2. Decisions

D1 — The socket is std’s and the protocol is the package’s.

ADR-069 D2’s subtraction, carried one item further. That record itemised what genuinely needs the compiler — a path’s provenance, escaping by type, a body that is bytes the program never allocated — and pushed the protocol and the framework out of std. A socket is neither: it is an operating-system resource of exactly the kind std already owns in fs and io, and the runtime knows about it already — rt::io’s own words are a socket is not a file; epoll answers a socket exactly.

And the http package is Nikaia, which cannot make a syscall. So the line falls where the subtraction already put it: std lends the socket, the package speaks HTTP.

D2 — There are two servers, and they are different products.

Why this is the answer and not a compromise. A standard server has no author present at run time, so the only person who can decide anything is the one starting it. An application has an author who decided already, and a tool that overrode them would be taking away the reason to write the application. The earlier framing — exposure is an operating property, so the operator names the routes — is ADR-038 D5 applied where it belongs, which is the first product, and it was wrong to reach into the second.

D3 — It binds to localhost, and anything wider is asked for.

Both products. python -m http.server binding every interface is half of why it is a footgun, and the fail-closed default costs one flag to leave.

The flag is not silent. ADR-108’s shape is the precedent: a way around a check is recorded per site and listed by nikaia --trust. Binding past localhost is that shape — this record does not build the listing, and it names it as where such a thing belongs.

D4 — An application routes with .route(…), and there is no @route yet.

ADR-018’s own form, and it needs no language change at all: measured at 0.0.152, the whole chain runs across a package boundary — a type constructed through its package, a method chain over it, a function-typed parameter (ADR-102 D1), and the async closure ADR-192 D1 writes for a run parameter.

No word on the declaration, and no every pub fn is a route. The second is refused outright and stays refused: pub is a package word (ADR-047 D2), and reading it as the network may call it would give one word two meanings and make the dangerous one invisible at the declaration — the polarity ADR-010 D1 exists for.

And ADR-018’s example is stale in one token. It writes http::Server::new(); NK1149 answers a type is constructed by its anonymous constructor since ADR-140 D2. The record is not rewritten, and what is written is http::Server().

D5 — The MVP’s protocol is the smallest one a microservice needs, and the parser is Rust until it is not.

GET and POST, bodies by Content-Length, Connection: close, no chunked transfer and no TLS. That is ADR-038 §4.5’s own order read as a scope: a socket layer that keeps registrations, then a minimal HTTP/1.1 server on it, then the parsing moved into Nikaia, then rustls, then HTTP/2.

The parser is written in Rust, in nikaia-std, and moving it into a Nikaia grammar is a later step of that order — self-hosting the protocol is not the MVP, and saying so is the difference between a step deferred and a step forgotten. (That step is taken at 0.0.248: nikaia-std/src/tools/http1.nika is the grammar and the rules, and http1.rs keeps the bytes.)

The limits are the operator’s in the first product and the program’s in the second: a body cap and a connection cap exist from the first commit, because a server without them is a denial of service rather than a default.

3. Consequences

4. What this does not decide

5. What is built

Nothing of the server. What is built is the ground under it, and that is more than the entry it answers suggested:

The order of work is ADR-038 §4.5’s, and #90 carries it.

This site is open source. Improve this page.