cad and sa-token-poc both pin git.d-ma.be/mathias/mcp-chassis v0.5.0, which resolves OIDC discovery eagerly at construction. If their IdP is not serving when they start, their JWT validator is never built, nil reads as "JWT auth not configured", and every valid JWT is string-compared against the static token and 401'd — permanently, with the pod reporting healthy.
That is mcp-chassis#4 / gitea-mcp#57, fixed in v0.6.0 and shipped in gitea-mcp v0.14.0. It occurred three times on gitea-mcp (2026-07-17, 2026-09-07, 2026-09-14), the last running six days undetected.
cad git.d-ma.be/mathias/mcp-chassis v0.5.0
sa-token-poc git.d-ma.be/mathias/mcp-chassis v0.5.0 (with a local `replace`)
gitea-mcp git.d-ma.be/mathias/mcp-chassis v0.6.0 ✓
What bumping gets, and what it does not
go get git.d-ma.be/mathias/mcp-chassis@v0.6.0 is non-breaking — NewJWTValidator and NewMultiJWTValidator gained a variadic ...Option, so existing call sites compile unchanged.
But the bump alone only gets half the fix. The retry happens when something calls Validate or Ready. Without a readiness probe driving Ready, a server that boots against a down IdP recovers only on its first inbound JWT — which is better than never, but still means the first caller after an outage eats a 503.
The full shape, as gitea-mcp now does it:
go get ...@v0.6.0
Drop any "retry without issuer X" fallback — a single unreachable issuer no longer fails the whole constructor, so that code is dead.
Treat a constructor error as a config error (exit non-zero), not a degradation to warn about and continue.
Expose /readyz returning 503 when Ready(ctx) is false; point the k8s readinessProbe at it. Keep livenessProbe on /healthz, which stays 200 — restarting does not make the IdP return sooner, and pointing liveness at /readyz turns an upstream outage into a CrashLoopBackOff.
If the server has a /healthz that reports JWT state, make sure it evaluates per request. gitea-mcp's marshalled its body once at startup and reported degraded for six days after recovery — fixing the validator would not have fixed that.
gitea-mcp commits 09bde6d (app) and infra f0eaf5d (probe + alert) are the worked example.
Per-repo notes
sa-token-poc carries replace git.d-ma.be/mathias/mcp-chassis => ../mcp-chassis, so it already builds against working-tree HEAD locally and the pinned version only matters for a clean clone. Worth deciding whether that replace should stay at all — it means CI and a developer's machine can compile different code.
cad is the one with real exposure: it is a deployed service, and this is the failure mode that cost three outages next door.
Done when
both repos require v0.6.0
each either exposes readiness wired to Ready(ctx), or records why it does not need to
sa-token-poc's replace directive is kept deliberately or removed
Refs mcp-chassis#4, gitea-mcp#57, infra GiteaMcpUnavailable.
`cad` and `sa-token-poc` both pin `git.d-ma.be/mathias/mcp-chassis v0.5.0`, which resolves OIDC discovery eagerly at construction. If their IdP is not serving when they start, their JWT validator is never built, `nil` reads as "JWT auth not configured", and every valid JWT is string-compared against the static token and 401'd — permanently, with the pod reporting healthy.
That is mcp-chassis#4 / gitea-mcp#57, fixed in **v0.6.0** and shipped in `gitea-mcp v0.14.0`. It occurred three times on `gitea-mcp` (2026-07-17, 2026-09-07, 2026-09-14), the last running six days undetected.
```
cad git.d-ma.be/mathias/mcp-chassis v0.5.0
sa-token-poc git.d-ma.be/mathias/mcp-chassis v0.5.0 (with a local `replace`)
gitea-mcp git.d-ma.be/mathias/mcp-chassis v0.6.0 ✓
```
## What bumping gets, and what it does not
`go get git.d-ma.be/mathias/mcp-chassis@v0.6.0` is non-breaking — `NewJWTValidator` and `NewMultiJWTValidator` gained a variadic `...Option`, so existing call sites compile unchanged.
**But the bump alone only gets half the fix.** The retry happens when something calls `Validate` or `Ready`. Without a readiness probe driving `Ready`, a server that boots against a down IdP recovers only on its first inbound JWT — which is better than never, but still means the first caller after an outage eats a 503.
The full shape, as `gitea-mcp` now does it:
1. `go get ...@v0.6.0`
2. Drop any "retry without issuer X" fallback — a single unreachable issuer no longer fails the whole constructor, so that code is dead.
3. Treat a constructor error as a **config** error (exit non-zero), not a degradation to warn about and continue.
4. Expose `/readyz` returning 503 when `Ready(ctx)` is false; point the k8s `readinessProbe` at it. Keep `livenessProbe` on `/healthz`, which stays 200 — restarting does not make the IdP return sooner, and pointing liveness at `/readyz` turns an upstream outage into a CrashLoopBackOff.
5. If the server has a `/healthz` that reports JWT state, make sure it evaluates **per request**. `gitea-mcp`'s marshalled its body once at startup and reported `degraded` for six days after recovery — fixing the validator would not have fixed that.
`gitea-mcp` commits `09bde6d` (app) and infra `f0eaf5d` (probe + alert) are the worked example.
## Per-repo notes
**`sa-token-poc`** carries `replace git.d-ma.be/mathias/mcp-chassis => ../mcp-chassis`, so it already builds against working-tree HEAD locally and the pinned version only matters for a clean clone. Worth deciding whether that `replace` should stay at all — it means CI and a developer's machine can compile different code.
**`cad`** is the one with real exposure: it is a deployed service, and this is the failure mode that cost three outages next door.
## Done when
- [ ] both repos require `v0.6.0`
- [ ] each either exposes readiness wired to `Ready(ctx)`, or records why it does not need to
- [ ] `sa-token-poc`'s `replace` directive is kept deliberately or removed
Refs mcp-chassis#4, gitea-mcp#57, infra `GiteaMcpUnavailable`.
---
_Created via git-mcp on behalf of @mathias_
cad done and deployed. sa-token-poc is blocked on something this issue did not anticipate.
cad — v0.11.0, live
go.mod → mcp-chassis v0.7.1
/readyz added, wired to validator.Ready
infra v1.37.0 points cad-mcp's readinessProbe at it
Verified live: both containers ready on localhost:5000/cad:6c1083b, probe path /readyz, and the startup log is clean —
INFO jwt issuer resolved issuer=https://auth.d-ma.be/application/o/claude-ai/ jwks_refresh_interval=1h0m0s
cad mcp: listening on :8081
cad's symptom differed from gitea-mcp's, and the test proved it before the bump.buildMCPHandler returned the constructor's error, so an unreachable Authentik meant cad did not start at all — a CrashLoopBackOff rather than a silent 401 storm. The failing test's output was the bug verbatim:
Louder than gitea-mcp's failure and easier to notice, but still an outage ended only by a human or a lucky backoff.
Two ordering details worth recording:
Only cad-mcp moved to /readyz.cad-serve is a different mux (the EXP-002 run server) with no such route; repointing it would have taken the run server out of its Service for a 404.
The image had to exist first. Repointing readiness while the old image ran would 404 and take cad down — an outage caused by the fix. Confirmed cad:6c1083b was in the registry before touching the probe, so image and probe landed together.
sa-token-poc — the work is done and has nowhere to go
sa-token-poc carries replace … => ../mcp-chassis, so it already builds against working-tree HEAD locally and the pinned version only matters for a clean clone.
That framing was too generous. AGENTS/sa-token-pocis not a git repository at all. It sits inside the local-dev working tree, where .gitignore:13 ignores AGENTS/. So it is an untracked, unversioned directory — which is the actual reason it sat on v0.5.0: nothing tracks it, so nothing could have told anyone.
I found this by committing to the wrong repository. git add -A from inside that directory stages from the repository root, so it staged none of my changes (all ignored) and instead swept in 600 lines of unrelated untracked work. Reverted, and every file restored byte-identical.
The changes are complete on disk and uncommitted, because this issue's third checkbox turns out to be a repo question rather than a go.mod one:
require mcp-chassis v0.7.1
replacekept, with the reasoning written into go.mod: this binary exists to prove a chassis change against the live cluster, so building the working tree is the point. The cost — a clean clone resolving a different version — is stated there, along with "remove this if the repo ever gains CI or gets deployed".
A real bug the bump introduces, fixed. The negative check asserts a wrong-audience validator rejects a token. It asserted only that Validate returned some error. On v0.5.0 that was accidentally safe, because an unreachable issuer failed at construction. On v0.7.1 it does not, so Validate returns ErrUnavailable and the PoC would print
PASS: wrong-audience validator rejected the token (replay guard holds)
having proven nothing. rejectedOnTheMerits() separates a verdict from a non-answer, with a test covering all three outcomes. This repo had no tests before.
That is the sharpest thing in this whole issue: a dependency upgrade silently converted a real assertion into a vacuous one, in the one binary whose entire job is to prove something.
DECISION NEEDED
Blocks: landing the sa-token-poc half.
sa-token-poc needs somewhere to live. Options:
Its own repo (mathias/sa-token-poc) — matches every other component, makes the version visible, and lets CI catch the next stale pin. Cost: another repo, and this issue's sibling ADR-0024 is about reducing fan-out.
Fold it into mcp-chassis as examples/ or an integration test — it exists solely to prove the chassis against a live cluster, which is arguably chassis test surface, and it would then move in lockstep with the library instead of drifting from it.
Accept it as scratch — leave it unversioned and expect it to be stale whenever it is next reached for.
Recommendation: 2. It removes the replace question entirely (same module, no cross-repo pin to drift), it puts the live-cluster proof next to the code it proves, and it is the option that would have prevented this issue existing. Creating a repo needs your go, and folding it is a move I should not make unasked either.
## `cad` done and deployed. `sa-token-poc` is blocked on something this issue did not anticipate.
### cad — `v0.11.0`, live
- `go.mod` → `mcp-chassis v0.7.1`
- `/readyz` added, wired to `validator.Ready`
- infra `v1.37.0` points `cad-mcp`'s readinessProbe at it
Verified live: both containers ready on `localhost:5000/cad:6c1083b`, probe path `/readyz`, and the startup log is clean —
```
INFO jwt issuer resolved issuer=https://auth.d-ma.be/application/o/claude-ai/ jwks_refresh_interval=1h0m0s
cad mcp: listening on :8081
```
**cad's symptom differed from gitea-mcp's, and the test proved it before the bump.** `buildMCPHandler` returned the constructor's error, so an unreachable Authentik meant cad **did not start at all** — a CrashLoopBackOff rather than a silent 401 storm. The failing test's output was the bug verbatim:
```
mcp: build jwt validator: issuer http://127.0.0.1:1/nope:
fetch oidc discovery: dial tcp 127.0.0.1:1: connect: connection refused
```
Louder than gitea-mcp's failure and easier to notice, but still an outage ended only by a human or a lucky backoff.
Two ordering details worth recording:
**Only `cad-mcp` moved to `/readyz`.** `cad-serve` is a different mux (the EXP-002 run server) with no such route; repointing it would have taken the run server out of its Service for a 404.
**The image had to exist first.** Repointing readiness while the old image ran would 404 and take cad down — an outage caused by the fix. Confirmed `cad:6c1083b` was in the registry before touching the probe, so image and probe landed together.
### sa-token-poc — the work is done and has nowhere to go
> **`sa-token-poc`** carries `replace … => ../mcp-chassis`, so it already builds against working-tree HEAD locally and the pinned version only matters for a clean clone.
That framing was too generous. `AGENTS/sa-token-poc` **is not a git repository at all**. It sits inside the `local-dev` working tree, where `.gitignore:13` ignores `AGENTS/`. So it is an untracked, unversioned directory — which is the actual reason it sat on `v0.5.0`: nothing tracks it, so nothing could have told anyone.
I found this by committing to the wrong repository. `git add -A` from inside that directory stages from the **repository root**, so it staged none of my changes (all ignored) and instead swept in 600 lines of unrelated untracked work. Reverted, and every file restored byte-identical.
**The changes are complete on disk and uncommitted**, because this issue's third checkbox turns out to be a repo question rather than a `go.mod` one:
- `require mcp-chassis v0.7.1`
- `replace` **kept**, with the reasoning written into `go.mod`: this binary exists to prove a chassis change against the live cluster, so building the working tree is the point. The cost — a clean clone resolving a different version — is stated there, along with "remove this if the repo ever gains CI or gets deployed".
- **A real bug the bump introduces, fixed.** The negative check asserts a wrong-audience validator rejects a token. It asserted only that `Validate` returned *some* error. On v0.5.0 that was accidentally safe, because an unreachable issuer failed at construction. On v0.7.1 it does not, so `Validate` returns `ErrUnavailable` and the PoC would print
```
PASS: wrong-audience validator rejected the token (replay guard holds)
```
having proven nothing. `rejectedOnTheMerits()` separates a verdict from a non-answer, with a test covering all three outcomes. This repo had no tests before.
That is the sharpest thing in this whole issue: **a dependency upgrade silently converted a real assertion into a vacuous one**, in the one binary whose entire job is to prove something.
### DECISION NEEDED
Blocks: landing the sa-token-poc half.
`sa-token-poc` needs somewhere to live. Options:
1. **Its own repo** (`mathias/sa-token-poc`) — matches every other component, makes the version visible, and lets CI catch the next stale pin. Cost: another repo, and this issue's sibling ADR-0024 is about *reducing* fan-out.
2. **Fold it into `mcp-chassis`** as `examples/` or an integration test — it exists solely to prove the chassis against a live cluster, which is arguably chassis test surface, and it would then move in lockstep with the library instead of drifting from it.
3. **Accept it as scratch** — leave it unversioned and expect it to be stale whenever it is next reached for.
**Recommendation: 2.** It removes the `replace` question entirely (same module, no cross-repo pin to drift), it puts the live-cluster proof next to the code it proves, and it is the option that would have prevented this issue existing. Creating a repo needs your go, and folding it is a move I should not make unasked either.
Leaving open on that half.
---
_Created via git-mcp on behalf of @mathias_
Closed. cad bumped and deployed; sa-token-poc retired rather than bumped.
cad — done
v0.11.0 on mcp-chassis v0.7.1, /readyz wired to validator.Ready, infra v1.37.0 repoints cad-mcp's readinessProbe. Live and verified: both containers ready, both issuers resolved at startup.
sa-token-poc — retired
Both go.mod lines in this issue's table are now moot, because the second repo no longer exists.
It was not a PoC. It was a scratch drawer named after one of its eight artifacts, last genuinely touched 2026-07-09, untracked and gitignored inside local-dev — which is the real reason it sat on v0.5.0. Nothing tracked it, so nothing could have told anyone.
Retired on evidence, not tidiness:
claim
evidence
its thesis shipped
gitea-mcp runs K8S_ISSUER_URL + K8S_MCP_AUDIENCE in production; both issuers resolved at startup today
the property is now unit-tested
TestNewMultiJWTValidator_UsesPerIssuerHTTPClientForAuthedFetch covers the authenticated-JWKS-fetch path, strengthened in v0.7.1 — it runs in CI on every push; the PoC ran never
it could not be run anyway
no deploy manifest, no projected-token volume, no Job — executing it meant rebuilding its harness
nothing referenced it
zero mentions across infra's docs/, tests/, scripts/ for all eight artifacts
the one dependent issue cites something else
infra#333 says "mirroring gitea-mcp's K8S_ISSUER_URL/K8S_MCP_AUDIENCE wiring" — gitea-mcp, not this
Also observed while checking: of 109 accepted requests to gitea-mcp since restart, all 109 took the passthrough path and none took jwt. The SA-token path is configured and unexercised. That is only visible at all because gitea-mcp#60's accepted-path logging landed a few hours earlier.
Two files were not scratch, and were rescued first
egress-probe.sh and litellm-key-gate-proof.py are the executable evidence behind two of ADR-0013's four pre-ship gates — the ones infra#186's closing comment cites as proven live — and they existed nowhere else. Now infra/scripts/adr-0013-egress-gate.sh and adr-0013-key-gate.py (infra v1.38.0), documented in scripts/README.md.
Two defects fixed on the way in. The egress script hardcoded an absolute path into a developer's home directory for the restore, and had no trap — an interrupt between the delete and the apply left docmark with no egress policy, silently. The restore now runs from an EXIT trap and reads the manifest relative to the script.
Not placed in tests/: that directory is offline by construction, and one of these execs into a live pod and deletes a NetworkPolicy while the other mints real gateway keys.
No verify: authored for ADR-0013. ADR-0020 applies from 0020 onward, and authoring an assertion for an earlier record is the deciders' call — the same rule that left infra#397 open. Both script headers say where that field should point if it is ever written.
Discarded, deliberately
My rejectedOnTheMerits() fix went with the drawer. It was a real find — the v0.7.1 bump silently converted the PoC's replay-guard check from a genuine assertion into a vacuous one, since an unreachable issuer's ErrUnavailable is also a non-nil error. Fixing a vacuous assertion inside a binary nobody runs is effort spent on something being retired, so it is gone rather than preserved. Recording it here because the pattern outlives the code: a dependency upgrade can turn a real assertion into a vacuous one by widening what "returned an error" means.
One correction
I committed to the wrong repository twice today, both times because git add -A stages from the repository root, not the working directory. Once into local-dev, sweeping in 600 lines of unrelated untracked work; once in infra, sweeping in an in-progress cobalt-dingo/overview-job.yaml change that was not mine. The first was reverted and every file restored byte-identical; the second was split out before pushing and left uncommitted exactly as found.
## Closed. `cad` bumped and deployed; `sa-token-poc` retired rather than bumped.
### cad — done
`v0.11.0` on `mcp-chassis v0.7.1`, `/readyz` wired to `validator.Ready`, infra `v1.37.0` repoints `cad-mcp`'s readinessProbe. Live and verified: both containers ready, both issuers resolved at startup.
### sa-token-poc — retired
Both `go.mod` lines in this issue's table are now moot, because the second repo no longer exists.
It was not a PoC. It was a **scratch drawer named after one of its eight artifacts**, last genuinely touched 2026-07-09, untracked and gitignored inside `local-dev` — which is the real reason it sat on `v0.5.0`. Nothing tracked it, so nothing could have told anyone.
Retired on evidence, not tidiness:
| claim | evidence |
|---|---|
| its thesis shipped | `gitea-mcp` runs `K8S_ISSUER_URL` + `K8S_MCP_AUDIENCE` in production; both issuers resolved at startup today |
| the property is now unit-tested | `TestNewMultiJWTValidator_UsesPerIssuerHTTPClientForAuthedFetch` covers the authenticated-JWKS-fetch path, strengthened in v0.7.1 — it runs in CI on every push; the PoC ran never |
| it could not be run anyway | no deploy manifest, no projected-token volume, no Job — executing it meant rebuilding its harness |
| nothing referenced it | zero mentions across infra's `docs/`, `tests/`, `scripts/` for all eight artifacts |
| the one dependent issue cites something else | infra#333 says *"mirroring `gitea-mcp`'s `K8S_ISSUER_URL`/`K8S_MCP_AUDIENCE` wiring"* — gitea-mcp, not this |
Also observed while checking: of 109 accepted requests to gitea-mcp since restart, **all 109 took the `passthrough` path and none took `jwt`**. The SA-token path is configured and unexercised. That is only visible at all because gitea-mcp#60's accepted-path logging landed a few hours earlier.
### Two files were not scratch, and were rescued first
`egress-probe.sh` and `litellm-key-gate-proof.py` are the executable evidence behind two of ADR-0013's four pre-ship gates — the ones infra#186's closing comment cites as *proven live* — and they existed nowhere else. Now `infra/scripts/adr-0013-egress-gate.sh` and `adr-0013-key-gate.py` (infra `v1.38.0`), documented in `scripts/README.md`.
Two defects fixed on the way in. The egress script hardcoded an absolute path into a developer's home directory for the restore, and had **no trap** — an interrupt between the delete and the apply left docmark with no egress policy, silently. The restore now runs from an `EXIT` trap and reads the manifest relative to the script.
Not placed in `tests/`: that directory is offline by construction, and one of these execs into a live pod and deletes a NetworkPolicy while the other mints real gateway keys.
No `verify:` authored for ADR-0013. ADR-0020 applies from 0020 onward, and authoring an assertion for an earlier record is the deciders' call — the same rule that left infra#397 open. Both script headers say where that field should point if it is ever written.
### Discarded, deliberately
My `rejectedOnTheMerits()` fix went with the drawer. It was a real find — the v0.7.1 bump silently converted the PoC's replay-guard check from a genuine assertion into a vacuous one, since an unreachable issuer's `ErrUnavailable` is also a non-nil error. Fixing a vacuous assertion inside a binary nobody runs is effort spent on something being retired, so it is gone rather than preserved. Recording it here because the *pattern* outlives the code: **a dependency upgrade can turn a real assertion into a vacuous one by widening what "returned an error" means.**
### One correction
I committed to the wrong repository twice today, both times because `git add -A` stages from the **repository root**, not the working directory. Once into `local-dev`, sweeping in 600 lines of unrelated untracked work; once in `infra`, sweeping in an in-progress `cobalt-dingo/overview-job.yaml` change that was not mine. The first was reverted and every file restored byte-identical; the second was split out before pushing and left uncommitted exactly as found.
Closing.
---
_Created via git-mcp on behalf of @mathias_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
cadandsa-token-pocboth pingit.d-ma.be/mathias/mcp-chassis v0.5.0, which resolves OIDC discovery eagerly at construction. If their IdP is not serving when they start, their JWT validator is never built,nilreads as "JWT auth not configured", and every valid JWT is string-compared against the static token and 401'd — permanently, with the pod reporting healthy.That is mcp-chassis#4 / gitea-mcp#57, fixed in v0.6.0 and shipped in
gitea-mcp v0.14.0. It occurred three times ongitea-mcp(2026-07-17, 2026-09-07, 2026-09-14), the last running six days undetected.What bumping gets, and what it does not
go get git.d-ma.be/mathias/mcp-chassis@v0.6.0is non-breaking —NewJWTValidatorandNewMultiJWTValidatorgained a variadic...Option, so existing call sites compile unchanged.But the bump alone only gets half the fix. The retry happens when something calls
ValidateorReady. Without a readiness probe drivingReady, a server that boots against a down IdP recovers only on its first inbound JWT — which is better than never, but still means the first caller after an outage eats a 503.The full shape, as
gitea-mcpnow does it:go get ...@v0.6.0/readyzreturning 503 whenReady(ctx)is false; point the k8sreadinessProbeat it. KeeplivenessProbeon/healthz, which stays 200 — restarting does not make the IdP return sooner, and pointing liveness at/readyzturns an upstream outage into a CrashLoopBackOff./healthzthat reports JWT state, make sure it evaluates per request.gitea-mcp's marshalled its body once at startup and reporteddegradedfor six days after recovery — fixing the validator would not have fixed that.gitea-mcpcommits09bde6d(app) and infraf0eaf5d(probe + alert) are the worked example.Per-repo notes
sa-token-poccarriesreplace git.d-ma.be/mathias/mcp-chassis => ../mcp-chassis, so it already builds against working-tree HEAD locally and the pinned version only matters for a clean clone. Worth deciding whether thatreplaceshould stay at all — it means CI and a developer's machine can compile different code.cadis the one with real exposure: it is a deployed service, and this is the failure mode that cost three outages next door.Done when
v0.6.0Ready(ctx), or records why it does not need tosa-token-poc'sreplacedirective is kept deliberately or removedRefs mcp-chassis#4, gitea-mcp#57, infra
GiteaMcpUnavailable.Created via git-mcp on behalf of @mathias
caddone and deployed.sa-token-pocis blocked on something this issue did not anticipate.cad —
v0.11.0, livego.mod→mcp-chassis v0.7.1/readyzadded, wired tovalidator.Readyv1.37.0pointscad-mcp's readinessProbe at itVerified live: both containers ready on
localhost:5000/cad:6c1083b, probe path/readyz, and the startup log is clean —cad's symptom differed from gitea-mcp's, and the test proved it before the bump.
buildMCPHandlerreturned the constructor's error, so an unreachable Authentik meant cad did not start at all — a CrashLoopBackOff rather than a silent 401 storm. The failing test's output was the bug verbatim:Louder than gitea-mcp's failure and easier to notice, but still an outage ended only by a human or a lucky backoff.
Two ordering details worth recording:
Only
cad-mcpmoved to/readyz.cad-serveis a different mux (the EXP-002 run server) with no such route; repointing it would have taken the run server out of its Service for a 404.The image had to exist first. Repointing readiness while the old image ran would 404 and take cad down — an outage caused by the fix. Confirmed
cad:6c1083bwas in the registry before touching the probe, so image and probe landed together.sa-token-poc — the work is done and has nowhere to go
That framing was too generous.
AGENTS/sa-token-pocis not a git repository at all. It sits inside thelocal-devworking tree, where.gitignore:13ignoresAGENTS/. So it is an untracked, unversioned directory — which is the actual reason it sat onv0.5.0: nothing tracks it, so nothing could have told anyone.I found this by committing to the wrong repository.
git add -Afrom inside that directory stages from the repository root, so it staged none of my changes (all ignored) and instead swept in 600 lines of unrelated untracked work. Reverted, and every file restored byte-identical.The changes are complete on disk and uncommitted, because this issue's third checkbox turns out to be a repo question rather than a
go.modone:require mcp-chassis v0.7.1replacekept, with the reasoning written intogo.mod: this binary exists to prove a chassis change against the live cluster, so building the working tree is the point. The cost — a clean clone resolving a different version — is stated there, along with "remove this if the repo ever gains CI or gets deployed".A real bug the bump introduces, fixed. The negative check asserts a wrong-audience validator rejects a token. It asserted only that
Validatereturned some error. On v0.5.0 that was accidentally safe, because an unreachable issuer failed at construction. On v0.7.1 it does not, soValidatereturnsErrUnavailableand the PoC would printhaving proven nothing.
rejectedOnTheMerits()separates a verdict from a non-answer, with a test covering all three outcomes. This repo had no tests before.That is the sharpest thing in this whole issue: a dependency upgrade silently converted a real assertion into a vacuous one, in the one binary whose entire job is to prove something.
DECISION NEEDED
Blocks: landing the sa-token-poc half.
sa-token-pocneeds somewhere to live. Options:mathias/sa-token-poc) — matches every other component, makes the version visible, and lets CI catch the next stale pin. Cost: another repo, and this issue's sibling ADR-0024 is about reducing fan-out.mcp-chassisasexamples/or an integration test — it exists solely to prove the chassis against a live cluster, which is arguably chassis test surface, and it would then move in lockstep with the library instead of drifting from it.Recommendation: 2. It removes the
replacequestion entirely (same module, no cross-repo pin to drift), it puts the live-cluster proof next to the code it proves, and it is the option that would have prevented this issue existing. Creating a repo needs your go, and folding it is a move I should not make unasked either.Leaving open on that half.
Created via git-mcp on behalf of @mathias
Closed.
cadbumped and deployed;sa-token-pocretired rather than bumped.cad — done
v0.11.0onmcp-chassis v0.7.1,/readyzwired tovalidator.Ready, infrav1.37.0repointscad-mcp's readinessProbe. Live and verified: both containers ready, both issuers resolved at startup.sa-token-poc — retired
Both
go.modlines in this issue's table are now moot, because the second repo no longer exists.It was not a PoC. It was a scratch drawer named after one of its eight artifacts, last genuinely touched 2026-07-09, untracked and gitignored inside
local-dev— which is the real reason it sat onv0.5.0. Nothing tracked it, so nothing could have told anyone.Retired on evidence, not tidiness:
gitea-mcprunsK8S_ISSUER_URL+K8S_MCP_AUDIENCEin production; both issuers resolved at startup todayTestNewMultiJWTValidator_UsesPerIssuerHTTPClientForAuthedFetchcovers the authenticated-JWKS-fetch path, strengthened in v0.7.1 — it runs in CI on every push; the PoC ran neverdocs/,tests/,scripts/for all eight artifactsgitea-mcp'sK8S_ISSUER_URL/K8S_MCP_AUDIENCEwiring" — gitea-mcp, not thisAlso observed while checking: of 109 accepted requests to gitea-mcp since restart, all 109 took the
passthroughpath and none tookjwt. The SA-token path is configured and unexercised. That is only visible at all because gitea-mcp#60's accepted-path logging landed a few hours earlier.Two files were not scratch, and were rescued first
egress-probe.shandlitellm-key-gate-proof.pyare the executable evidence behind two of ADR-0013's four pre-ship gates — the ones infra#186's closing comment cites as proven live — and they existed nowhere else. Nowinfra/scripts/adr-0013-egress-gate.shandadr-0013-key-gate.py(infrav1.38.0), documented inscripts/README.md.Two defects fixed on the way in. The egress script hardcoded an absolute path into a developer's home directory for the restore, and had no trap — an interrupt between the delete and the apply left docmark with no egress policy, silently. The restore now runs from an
EXITtrap and reads the manifest relative to the script.Not placed in
tests/: that directory is offline by construction, and one of these execs into a live pod and deletes a NetworkPolicy while the other mints real gateway keys.No
verify:authored for ADR-0013. ADR-0020 applies from 0020 onward, and authoring an assertion for an earlier record is the deciders' call — the same rule that left infra#397 open. Both script headers say where that field should point if it is ever written.Discarded, deliberately
My
rejectedOnTheMerits()fix went with the drawer. It was a real find — the v0.7.1 bump silently converted the PoC's replay-guard check from a genuine assertion into a vacuous one, since an unreachable issuer'sErrUnavailableis also a non-nil error. Fixing a vacuous assertion inside a binary nobody runs is effort spent on something being retired, so it is gone rather than preserved. Recording it here because the pattern outlives the code: a dependency upgrade can turn a real assertion into a vacuous one by widening what "returned an error" means.One correction
I committed to the wrong repository twice today, both times because
git add -Astages from the repository root, not the working directory. Once intolocal-dev, sweeping in 600 lines of unrelated untracked work; once ininfra, sweeping in an in-progresscobalt-dingo/overview-job.yamlchange that was not mine. The first was reverted and every file restored byte-identical; the second was split out before pushing and left uncommitted exactly as found.Closing.
Created via git-mcp on behalf of @mathias