key-cape/history/2026-09-09-scope-reassessment.md
tegwick 66ca76cb9c Name the third state SCOPE had no vocabulary for
Folds in two points from the custody owner and a peer session that arrived after
the reassessment landed.

Implemented and unproven is a distinct state from both missing and done, and
SCOPE had no words for it. verify-client's predecessor rejection is written and
unit-tested and has never run against a genuinely distinct predecessor, so this
repository holds a receipt that reads like rotation evidence and is not. Both
owners state that limit independently, which is why it belongs in SCOPE rather
than only in a workplan. "There is a test" and "it has been exercised against the
real thing" now read as separate claims wherever SCOPE makes one.

Also records that the two approval clients' live verification was independently
confirmed by the custody owner, and corrects a conflation this repository made:
client-side retrieval of those secrets is unadmitted and stays so, but the
attended operator path is not a client-side read and never required one. That
conflation is what left T02 recorded as waiting on a run that had already
happened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-09 20:09:01 +02:00

5.7 KiB

KeyCape scope reassessment — 2026-09-09

Source baseline: 5ae80d3. Supersedes the 2026-09-05 assessment against b989de4, which remains the record of the ten-gap backlog and its closures.

Why now

SCOPE.md still declared itself reviewed against b989de4, 63 commits behind. Its limits section had been kept honest by appending to bullets, which works until the header asserts a review date that no longer describes what was reviewed. That is the drift this document exists to prevent, in the file whose only job is to say accurately what this repository does.

Method

Every capability claim was checked against source at this revision rather than carried forward from the previous text: routes from the mux registrations in cmd/keycape/main.go, subcommands from the dispatch in main.go and internal/authclient/cli.go, flags from the flag sets, claim behaviour from token.go and userinfo.go. Live claims were checked against docs/evidence/. Where the existing prose and the code disagreed, the code won.

That method was chosen because the opposite failed repeatedly over the preceding two days: four times a statement about current state was written from a workplan paragraph, a partial git status, or an inference about what was admitted, without reading the authoritative artifact. Every one was wrong and every one was cheap to check.

What changed since 2026-09-05

Gaps G01 through G09 are closed; G10 is external and remains open. The capabilities that reached SCOPE only piecemeal, now stated:

  • the authorization-code grant binds the redirect URI, refuses clients not registered for it, authenticates confidential clients and consumes codes atomically; UserInfo checks algorithm, issuer and token purpose (G01);
  • upstream Authelia ID tokens are verified — signature, issuer, audience, validity window — failing closed and independently of transport, on an explicit operator decision not to police the transport instead (G01);
  • /readyz probes LLDAP by bind, plus Authelia and privacyIDEA, distinct from /healthz; SIGTERM drains for 15s (G08);
  • keycape verify-client checks a live registration's subject, tenant, roles, lifetime and scope refusals without emitting values;
  • a human tenant may be supplied by a client registration where the directory has placed the user nowhere, and issuance is refused when the two conflict; assurance.at is the authentication time rather than the mint time;
  • internal/jose is the single RS256/JWKS verifier behind both the caller path and upstream verification;
  • the container packages all five binaries, and the dev stack is bootstrappable from a clean checkout.

Three corrections to claims SCOPE previously made: caller commands omitted verify-client; the container was said to package only keycape, untrue since KEY-WP-0026; and the issuer API row omitted /readyz.

The distinction this revision adds

SCOPE now states plainly that it describes source, and that source behaviour and deployed behaviour are different claims. The evidence for each is different too: 390 Go tests across 23 packages and the named harness runs for the first; committed receipts in docs/evidence/ for the second.

What is actually proven live is narrower than what the source does, and worth stating exactly:

  • the upstream issuer was probed and pinned in sso/keycape-config. The pre-pin state did not match, so the fail-closed login outage that risk described was real and was averted rather than hypothetical;
  • both approval service clients were verified in an attended owner process: JWKS signature verification, exact claims, excess-scope and wrong-secret denial at a 900s lifetime, no credential values emitted.

Those receipts also decline what they did not test — no real predecessor rotation, no observed wall-clock expiry — and this document declines it with them. They also force a distinction SCOPE had no vocabulary for: implemented and unproven, a third state between missing and done. verify-client's predecessor rejection is written and unit-tested and has never run against a genuinely distinct predecessor, so the repository holds a receipt that reads like rotation evidence and is not. Both owners state that limit independently, which is why it belongs in SCOPE rather than only in a workplan.

One framing correction worth propagating, from the custody owner: client-side retrieval of those secrets is unadmitted and remains so, but the attended operator path is not a client-side read and never required one. Conflating the two is what left T02 recorded here as waiting on a run that had already happened.

The deployed image predates this revision, so the authorization-code bindings, upstream verification, readiness, graceful shutdown and both tenant-related changes are running nowhere yet. Two of them fail closed at startup, which is why they are listed in docs/operations.md under "Unreleased fail-closed startup changes" rather than only in a hub message.

What remains

G10 is owner work: KEY-WP-0013-T05 waits on informed-decision for the approver client ID and deployed callback, and KEY-WP-0014-T04 has no admitted execution authority for a real predecessor rotation. Neither can be closed from this repository.

From G04, relying-party behaviour and MFA against a migrated realm are still unexercised, and credential and MFA migration is not supplied at all, so no harness can establish it. Complete profile conformance and drop-in Keycloak interchangeability remain unestablished — and the conformance suite found a concrete reason they are not the same question: stock Keycloak advertises the implicit and password grants this profile excludes, as server capabilities no realm configuration removes.