feat(api): admission, the header limit, and the router that mounts two routes
Dimensions 5.2 and 5.3, plus the router they hang from. Coverage holds at 100.00% for afd_api (608 regions). ## The ceiling is a semaphore, not a counter protocol `dispatchApi` writes the protocol out by hand — `fetchAdd`, compare, `fetchSub` in a `defer`, and a second store to keep a gauge in step. It is correct because four statements agree; an arm that forgets the `defer` leaks a slot and the instance walks down to serving nothing. `Semaphore::try_acquire_owned` refuses instantly and hands back a permit that releases on drop. No arithmetic, no release path to forget, and cancellation comes free: a caller that hangs up drops the future, which drops the permit. Zig's `defer` cannot express that — there is no future to drop. A test proves it by aborting a live request and watching the slot come back. The route CLASS is resolved when the route is MOUNTED rather than per request: nothing about it can change between requests, and an unmetered route now reaches its handler without a branch. `is_metered` is an exhaustive match, so a new class fails the build — `route_admission.zig` had traded its exhaustive switch for an `else` arm and a runtime walk over two hand-kept name lists. `tower::limit::ConcurrencyLimit` + `load_shed` was the off-the-shelf shape and was rejected: it sheds through an error type that carries no ceiling, so `X-RateLimit-Limit` could not be filled from it, and axum needs `Infallible`. ## The request id is minted, not borrowed The first draft read it from a `tower_http`-stamped request extension, so the id existed only if a layer nobody had written was mounted, and its absence degraded silently — making `req_unknown` mean three different things. The Zig shed mints its own id inline, and so does this one. `req_unknown` means one thing again: entropy failed. A shed is the WORST case for identification — no principal, no workspace, no route params, nothing has been read yet — so the id is the entire identity of that record, and it is minted once and used in both the log line and the envelope. The Zig shed logs no id at all, leaving its two records uncorrelated. `afd_crypto` grows a public `Entropy` so this does not become a second `getrandom` call site. ## The same 16 KiB, for the opposite reason httpz defaults to 4 KiB and Zig had to RAISE it — a session bearer plus proxy headers passes 4 KiB routinely. hyper defaults to ~400 KB, so nothing is ever refused and a client can hold that much buffer per connection. Same number, inverted argument: a ceiling here where it was a floor there. Proven over a real socket, because hyper enforces it while PARSING — there is no request value for a `Service` to see. The Zig test accepts either a 431 or a transport close; this one requires the 431, because 17 KiB fits in any socket send buffer and a test that also accepted a dead socket would keep passing if the 431 path regressed entirely. ## HEAD, and what the router does not claim `method_routing.rs` tries the `head` route and then falls through to `get`, so every GET handler answers HEAD unless something stops it. In Zig that trap was dormant — the request never arrives — and the scope table would have resolved HEAD to the WRITE rung. Refused once for the whole daemon rather than eighty-one times, and with `route_layer` so an unserved path is still a 404. `handler_for` is total over the ten families: a family whose handlers are not ported says so in an arm rather than by being missing from a list. All 81 routes are tabled and two are served, which a test asserts rather than describes. No admission layer is wired yet, deliberately: `Ops` is the only class mounted and it is never metered, so the metered branch would be an arm no request could reach. ## Logging trace on both probes and on admit (per-request, and what predicts a shed), debug on mounts and the refused HEAD, info once when the router is built, warn when readiness goes red, error when entropy fails. Every warn and error carries an `error_code`, per the logging standard's rule for those two levels. Every call-bearing log field is hoisted to a `let`: `tracing`'s `log` feature compiles a second copy of the expression and llvm-cov scores the dead one. Three of these cost the crate its 100% until they were hoisted.
K
Kishore Kumar committed
6f70680cb89107073c62f12b1782d85316fb3c09
Parent: 5e99406