/mcp's bearer was built in main.go and the buildMux tests used stubs, so
dropping it would have kept CI green. buildMux now takes the MCP handler
and wraps it with the same BearerMiddleware and credential as the REST
API. "/mcp" and "GET /metrics" left the public allowlist, so passing
either as a public route is refused. buildMux also registers GET
/metrics and returns the metrics-wrapped handler, so main passes its
result to ListenAndServe unchanged.
Flip-tested: registering /mcp unwrapped turns
TestBuildMuxRefusesMCPWithoutAValidToken red, and dropping the metrics
wrap turns the four metrics tests red.
Comments now claim only what is tested. The ListenAndServe line and the
symlink tripwire wiring in main (#154 comment) are still untested and
tracked in #182.
Closes#154
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1. Rejections are logged. chassis v0.2.0 refused without a log line, so
since #149 an unknown REST caller got silent 401s. v0.7.1 writes
`mcp auth rejected` (reason, remote, token fingerprint, path) and
`mcp auth accepted` per authenticated request. Tested through
buildMux.
2. Fallout: since chassis v0.7 (mcp-chassis#4) NewJWTValidator no longer
fails on OIDC discovery; the issuer resolves lazily and a JWT gets
503-and-retry until it does. #83's boot retry loop therefore never
retried, and its tests failed. buildJWTValidator now builds once and
logs an issuer still unresolved at boot. Behaviour change: a
misconfigured issuer no longer exits the process; JWTs get 503, the
static bearer keeps working. internal/startupretry is now unused.
3. Metrics: authenticated REST requests were already labelled by route
(the REST mux sets Pattern on the same *http.Request the middleware
reads). Requests refused by auth never reached the REST mux and were
labelled path="/". labelRESTPattern now sets the REST route's pattern
before auth; unmatched paths keep "/", so the label set stays bounded.
Closes#155
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The retry landed in 9a232ad but was tested only through startupretry with
fake funcs; nothing exercised chassisauth.NewJWTValidator's real 502 path.
Extracts the main() closure into buildJWTValidator (behaviour unchanged)
and tests it against an httptest issuer: 3x502 then 200 succeeds with 4
discoveries; a persistent 502 stops at exactly the 8-attempt budget with
the last error; cancelling ctx mid-backoff returns context.Canceled.
Flip-verified: Attempts=1 turns all three red.
Lane: risk/medium issue, committed to main because the behaviour change
already shipped in 9a232ad; this is a test plus a pure extraction.
Closes#83
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>