build / build-push (push) Failing after 19s
internal/api is the admin REST + SSE surface and had no test file at all, so nothing pinned the auth modes, the X-Forwarded-For trust boundary, the rate limiter or the fact that a read route must not mutate anything. This adds a local harness (a Server over fake hooks on an httptest listener, with a real config file on disk) and three test files. What it pins: * Routing. A table over every route in Server.routes: each one answers 200 and reaches the hook it is supposed to, so a new route without a case here is a route nothing tests. Plus method-not-allowed / 404 / outside the base path, a base path other than /api, and a read-route sweep that asserts no mutating hook was reached, the config generation did not move and the file on disk is byte-identical afterwards. A panicking component becomes a 500 and the server keeps serving, because the admin API is the only way to fix a broken instance. * Auth. All three modes. apikey: the right key always works, and a prefix, a suffix, a case-folded key, a wrong basic password, an unknown bearer token, an unsupported scheme and a malformed Authorization header never do; curl -u any:KEY is accepted because that is what clients send. basic: bad username and bad password refused, a successful login issues a bearer token, the token works for the whole API, basic auth also works per request, an API key is not a credential in this mode, logout revokes the token, and a token past its session_ttl stops working. none: nothing is required and the caller runs as a writable loopback principal. trust _loopback: a local client with no credentials is let through, and a forwarded non-loopback client is not. Every mutating route refuses a read-only key with 403 without reaching its hook or touching the file (and the same request with a read-write key succeeds, so the 403 is about the key and not the payload), and allow_config_write: false refuses the settings editor with a valid key while the other mutations still work. The hashing is pinned on the state, not on timing: the authenticator's maps are keyed on the SHA-256 of the credential and hold no plaintext key or password, so a copied server struct cannot leak one. * Product code is untouched; the suite is `package api` with its own setup.