Ship the live-registration check both blocked tasks depend on
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 45s

KEY-WP-0013-T02 and KEY-WP-0014-T04 stay blocked on custody and on ops-warden,
but each contains a KeyCape-owned piece that had been left as prose. T02 requires
proving "live JWKS verification and denied excess scopes without logging values";
T04 step 4 requires verifying a rotated secret, refusing its predecessor and
refusing excess scope. Both were describable and neither was runnable, so the
proof would have been improvised by hand against production at the moment custody
lands -- the worst possible time for it.

keycape verify-client does it in one command. Per registration it checks
discovery origin, the client_credentials exchange and its RS256 signature against
the deployed JWKS, exact sub/tenant/roles/principal_type, that every -deny-scope
is refused, and that the token carries no scope that was not requested. That last
check is a real gap: the caller commands prove every requested scope was granted,
never that nothing extra came back. -previous-secret-env additionally requires
the predecessor to be refused, and treats an unchanged secret as a rotation that
did not happen.

Nothing is written to disk and no value is printed. Failures name the claim, not
the observed value, so running this against production cannot turn a verification
into a disclosure. Every check runs before it reports, so one failure does not
hide the rest.

The exact invocation for each approval client is recorded in the verification
block of the provisioning packet, so custody admission hands back a command
rather than a description.

Tests cover the passing case, an over-broad registration, a live predecessor, an
identical rotation and output non-disclosure; neutering mustFail makes the suite
fail, so the checks have teeth.

Also recorded in KEY-WP-0014: read from ops-warden's catalog rather than waiting
for a reply, key-cape-oidc-login is asked-and-waiting on us since 2026-08-28 and
is a pointer lane with no programmatic consumers, and the rapp-qonto-keycape-client
blocker citing an absent native exchange command went stale when service-token
shipped on 2026-09-05.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1182213@bnt-lap001
Assistant-Session: 966597b9-ae61-46a4-8b9e-1594ab3ec4ad
This commit is contained in:
tegwick 2026-09-08 14:30:43 +02:00
parent 54b7687903
commit 2a8735173b
8 changed files with 525 additions and 4 deletions

View file

@ -85,6 +85,34 @@ railiance-platform and approval-engine respectively, and live provisioning proof
cannot begin until both land. Still no secret read and no production resource
changed.
2026-09-08, second pass. Custody and the human callback are still owed, but one
part of this task was ours all along and was not built: the task requires proving
"live JWKS verification and denied excess scopes without logging values", and
there was no way to do that except by hand, against production, at the moment
custody lands — which is the worst time to be improvising a check.
Shipped `keycape verify-client` (`src/internal/authclient/verify.go`, documented
in `docs/native-authentication.md`). Per registration it verifies discovery
origin, the `client_credentials` exchange and its RS256 signature against the
deployed JWKS, exact `sub`/`tenant`/`roles`/`principal_type`, that every
`-deny-scope` is refused, and that the token carries no scope that was not
requested — the last being a gap the caller commands do not cover, since they
prove requested scopes were granted and not that nothing extra came back.
Failures name the claim, never the observed value, and nothing is written to
disk, so it is safe to run against production. The exact invocation for each of
the two clients is now recorded in the `verification:` block of
`docs/approval-engine-provisioning-request.yaml`, so admission hands back a
command rather than a description.
Tests: `src/internal/authclient/verify_test.go` covers the passing case, an
over-broad registration, a live predecessor secret, an identical "rotation" and
output non-disclosure. Verified with teeth — neutering `mustFail` makes the suite
fail rather than pass silently.
Task stays `wait`. What is owed from elsewhere is unchanged and unreduced: the
two secret values through an admitted custody path, and the exact human
`client_id` and callback URI. Nothing here provisions anything.
## Reconcile tenant vocabularies across approval layers
```task

View file

@ -105,3 +105,34 @@ The KEY-WP-0009 handoff ops-warden reported missing has also now been delivered
Task remains `wait` — no route was changed and no rotation command ships until
ops-warden answers (1) and an execution/rollback authority is admitted for (2).
2026-09-08, second pass — read ops-warden's own catalog rather than waiting for a
reply, and it changes the shape of both questions.
`registry/routing/catalog.yaml`, lane `key-cape-oidc-login`: `intended_owner` was
already corrected to key-cape, and `verified: asked-and-waiting` since
2026-08-28. **ops-warden is waiting on us, not the other way round.** The lane is
also `warden_executes: false` with a `wiki_ref` into `CredentialRouting.md` — a
pointer, per their ADR-0001, not a code path with programmatic consumers. So the
consumer-contract risk behind question (1) is smaller than assumed: adding
`keycape login` alongside retires nothing, and the acceptance we owe them is a
statement of ownership, not a cutover.
Lane `rapp-qonto-keycape-client`: `rotation.owner: key-cape`,
`automatable: true`, and `blocked_on` reads "still no key-cape-native
exchange/rotation command", `verified: source-read` 2026-08-28. **That blocker is
stale.** `keycape service-token` (T03) shipped 2026-09-05 and is exactly the
native `client_secret_basic` exchange they record as absent. It does not clear
the lane, and should be narrowed rather than closed.
Shipped here: `keycape verify-client`, which is rotation step 4 and the denial
checks step 5 rests on — new exchange verified against deployed JWKS, predecessor
refused, excess scope refused, no value printed or written. See
`docs/native-authentication.md`, "Verifying a live registration". The identical
predecessor case is treated as a failure, because an unchanged secret is a
rotation that did not happen.
Steps 1-3 remain custody's and are deliberately not automated. Task stays `wait`
on exactly two answers: which option ops-warden takes for the login proxy, and
who executes the successor generation and CAS update under what authority. No
route changed.