All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
informed-decision and approval-engine both asked to hear problems with the human
approver registration now rather than at handover. Checking their requested shape
against the source rather than agreeing it on paper turned up three things.
Scope gap, accepted: [openid, approval:approve] cannot render a decision, since
GET /v1/approvals/{id} and /claim both need approval:read -- the surface could
submit an entry it was never able to display. Published
[openid, approval:read, approval:approve]. Reading through a service identity was
the alternative and is worse: it weakens the evidence-of-what-this-person-saw
claim the component exists to make. approval:consume stays excluded.
Assurance shape, published and a defect fixed. Both asked for a documented shape
and KeyCape already emitted one, so it is written down rather than renegotiated.
Writing it down surfaced that `at` was the token mint time rather than the
authentication time. Those differ by hours whenever a browser session is reused,
and approval-engine persists this object verbatim as the only downstream record
that MFA happened -- so a stored approval could have evidenced MFA at a moment
the person proved nothing. PKCESession.AuthTime now carries the original login
instant through session reuse, with mint time as the fallback.
Blocker found before anyone built on it: a human token cannot carry
tenant:platform. effectiveTenant resolves the human tenant from the directory
user, no adapter populates User.Tenant, and the per-client tenant field is read
only on the client_credentials path -- so every human token defaults to
tenant:coulomb, which approval-engine refuses by exact string equality. It would
have presented as a failed approval rather than a registration defect. Two
resolutions sent to the owners and neither implemented here: the choice decides
whether a human's tenant is a property of the person or of the registration, and
that is not KeyCape's alone to make.
Also recorded ops-warden's answers to KEY-WP-0014-T04, including their finding
that `warden plan` returns `autonomous` for a need containing generate and
CAS-write, because it has no read-versus-mutate intent. Their standing
instruction -- treat a warden plan verdict on any write, rotate or provision need
as unreliable until WARDEN-WP-0038 lands -- is recorded in the workplan rather
than left in an inbox.
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
214 lines
11 KiB
Markdown
214 lines
11 KiB
Markdown
---
|
|
id: KEY-WP-0014
|
|
type: workplan
|
|
title: "Review native login and client credential lane handoffs"
|
|
domain: infotech
|
|
repo: key-cape
|
|
status: blocked
|
|
owner: codex
|
|
topic_slug: native-credential-lane-handoff
|
|
created: "2026-09-05"
|
|
updated: "2026-09-08"
|
|
state_hub_workstream_id: "0d003df3-f7d3-5063-8ca0-e1e33f7df74a"
|
|
---
|
|
|
|
Source: ops-warden inbox message 0dd9c7bd-0ecd-42d1-806f-7cc4ba9730ed.
|
|
The native service exchange and public PKCE login commands are now implemented.
|
|
Cross-owner rotation and consumer-specific route cutover remain outstanding.
|
|
|
|
## Design owner command and custody boundaries
|
|
|
|
```task
|
|
id: KEY-WP-0014-T01
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "0c0a0b61-c19e-5631-9cda-8b2dc0f47d8f"
|
|
```
|
|
|
|
Review ops-warden's existing key-cape-oidc-login proxy and
|
|
rapp-qonto-keycape-client route contracts. Specify the native interactive login
|
|
and bounded exchange commands, token delivery, renewal and custody-mediated
|
|
rotation before implementation. Keep secret custody with OpenBao and avoid
|
|
retiring the proxy until replacement commands have equivalent verification.
|
|
|
|
## Verify handoff delivery evidence
|
|
|
|
```task
|
|
id: KEY-WP-0014-T02
|
|
status: done
|
|
priority: low
|
|
state_hub_task_id: "d9a5de97-b7d5-5599-98c2-eaab32f51495"
|
|
```
|
|
|
|
Ops-warden reports KEY-WP-0009-T04's claimed reply did not arrive. Verify prior
|
|
receipts for all four named recipients before claiming successful notification.
|
|
No outbound coordination messages were sent during the 2026-09-05 repo review.
|
|
|
|
|
|
## Implement and verify native caller commands
|
|
|
|
```task
|
|
id: KEY-WP-0014-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "4e46f474-a15e-594f-ae07-ad37a9667d89"
|
|
```
|
|
|
|
Implemented keycape service-token and login with HTTPS discovery, RS256/JWKS
|
|
verification, exact audience bindings, PKCE/state/nonce, private out-of-Git file
|
|
delivery and redacted errors. Service Basic authentication now form-decodes
|
|
credentials. Integration tests exercise the real token/JWKS handlers and browser
|
|
callback, including forged state, tampering, excess scope and unsafe output.
|
|
See docs/native-authentication.md for invocation and ownership boundaries.
|
|
|
|
## Admit rotation and verify consumer handoff
|
|
|
|
```task
|
|
id: KEY-WP-0014-T04
|
|
status: wait
|
|
priority: high
|
|
state_hub_task_id: "e7632c71-9676-5837-a925-7f905e3015c5"
|
|
```
|
|
|
|
Qonto rotation requires admitted custody/provider transaction and rollback
|
|
semantics; see the exact review sequence in docs/native-authentication.md.
|
|
The current login proxy yields an OpenBao token, so it cannot be replaced by a
|
|
JWT-writing command without adapting its consumer contract. Verify that split
|
|
with the platform owner and ops-warden before route changes. No route was changed.
|
|
|
|
2026-09-05 receipt audit: GET /messages/?from_agent=key-cape returned one
|
|
2026-03 image-publication message and no KEY-WP-0009 handoff receipts for
|
|
NetKingdom, secrets-engine, ops-warden or railiance-platform. Corrected the
|
|
unsupported statement in KEY-WP-0009-T04. This proves only that no receipts
|
|
are present in the current Hub response, not that no message ever existed.
|
|
The 2026-09-05 audit's finding stands as recorded; the replacement outbound
|
|
messages were sent on 2026-09-08 (below).
|
|
|
|
2026-09-08: `0a6c9d88-cf50-45d8-acdc-99a40747683e` -> ops-warden, readable back
|
|
via `GET /messages/?from_agent=key-cape`. It puts both open questions to the
|
|
route and custody owner in one place:
|
|
|
|
1. The `key-cape-oidc-login` proxy yields an OpenBao token and `keycape login`
|
|
yields an issuer JWT — different credential types, different verifiers,
|
|
different enforcement owners, so not interchangeable. Two admissible options
|
|
are offered: keep the proxy and run `keycape login` alongside it for JWT
|
|
consumers, or adapt the route's consumer contract to a JWT and then cut over.
|
|
Option (a) is recommended until every consumer of the route is enumerated.
|
|
2. Qonto rotation for `rapp-qonto-keycape-client`, quoting the five-step reviewed
|
|
sequence from `docs/native-authentication.md` and asking who executes the
|
|
successor generation and CAS update, in which transport, under what authority.
|
|
It records that `warden route`/`warden plan` returned no exact admitted lane
|
|
and that a generic match is not being treated as authorization.
|
|
|
|
The KEY-WP-0009 handoff ops-warden reported missing has also now been delivered
|
|
(`5bc58847-eed5-4164-bb0f-a0d8455e75b8`), closing the KEY-WP-0014-T02 correction.
|
|
|
|
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.
|
|
|
|
Both catalog corrections were sent, receipts readable via
|
|
`GET /messages/?from_agent=key-cape`:
|
|
|
|
- `12f1bdfa-b240-414b-aa49-128a4d2acf20` — accepts ownership of lane
|
|
`key-cape-oidc-login`, answering the WARDEN-WP-0033-T04 follow-up they have
|
|
carried as `asked-and-waiting` since 2026-08-28. States the boundary the lane's
|
|
`fetch_command` crosses: key-cape owns browser authentication and the identity
|
|
token; the `netkingdom` auth mount, `role=<domain>` mapping, token store and
|
|
enforcement of the resulting OpenBao token are not ours. Supplies the source
|
|
paths they need to move the lane to `verified: source-read`, and records our
|
|
revised, lower estimate of the consumer risk — a pointer lane under ADR-0001
|
|
retires nothing when `keycape login` runs alongside it — while asking them to
|
|
correct us if the catalog understates its consumers.
|
|
- `08d42f47-8e19-4976-98f5-dd259e7b1225` — reports lane
|
|
`rapp-qonto-keycape-client`'s blocker as stale against its 2026-08-28
|
|
source-read, since `keycape service-token` is the native `client_secret_basic`
|
|
exchange it records as absent and `keycape verify-client` is its rotation step 3
|
|
verbatim. Asks them to **narrow** it to steps 1-2 rather than clear it, with
|
|
proposed replacement text, and notes `rotation.automatable: true` is now
|
|
half-true and should say so precisely rather than be flipped either way.
|
|
|
|
Neither message asks for a route change and neither was one.
|
|
|
|
2026-09-08/09 — ops-warden answered both, and the second answer matters more than
|
|
the question did.
|
|
|
|
**Item 1 answered: option (a).** Keep the proxy as-is and run `keycape login`
|
|
alongside it, "for your reason rather than out of caution -- they are different
|
|
credential types with different verifiers, so this is not a cutover deferred, it
|
|
is two things that were never one." They verified our pointer-layer reading
|
|
rather than accepting it, and added a fact we did not have: `warden access`
|
|
already excludes `is_login` lanes from raw-value streaming
|
|
(WARDEN-WP-0032-T05). Lane `key-cape-oidc-login` moved to `owner-confirmed` with
|
|
our boundary written in verbatim (commit c133004). It stays `interim` on purpose:
|
|
per their ADR-0003 a lane retires when a front door exists, not when ownership is
|
|
agreed, and by our own analysis `keycape login` is not that front door. The entry
|
|
now names the retirement condition instead of implying we owe one.
|
|
|
|
**Item 2 answered: nobody owns it, admittedly.** Steps 1-2 mutate custody on
|
|
platform workload paths, which is railiance-platform's, with `secrets-engine exec`
|
|
as the admitted transport. ops-warden declined to take the act on the grounds
|
|
that fronting the read lane is not authority to mutate — their ADR-0002 and
|
|
ADR-0005 — and routed it themselves rather than describing the route and leaving
|
|
it to us. Lane `rapp-qonto-keycape-client` narrowed exactly as asked and moved
|
|
`source-read` -> `owner-confirmed`, crediting the correction to us rather than to
|
|
a re-read they did not perform. Rotation step 3 now points at
|
|
`keycape verify-client` instead of restating its assertions.
|
|
|
|
**They found a defect in their own front door because of our caution, and told
|
|
us.** Running our need through `warden plan` returns `verdict: autonomous`,
|
|
`lane_id: rapp-qonto-keycape-client`, answered with three read transports — a
|
|
need containing *generate* and *CAS-write* inherits the verdict of the lane that
|
|
reads the same path, because `warden plan` has no read-versus-mutate intent at
|
|
all. `autonomous` is documented as the signal to proceed without the founder.
|
|
Raised as WARDEN-WP-0038. Their stated conclusion, which we should not soften: "A
|
|
counterparty declining to trust our output is not a control, and we are not going
|
|
to record it as one."
|
|
|
|
Standing instruction from them until WP-0038 lands, recorded here so it is not
|
|
lost in an inbox: **treat a `warden plan` verdict on any need containing a write,
|
|
rotate or provision act as unreliable.** That applies to this repository whenever
|
|
it plans a rotation.
|
|
|
|
**On `automatable`, they did not do what we suggested and said so.** We asked
|
|
them not to flip the boolean and to state the half-truth precisely; the schema has
|
|
one boolean whose only consumer is a future executable driver, and a driver told
|
|
`true` would attempt the custody steps. So it is `false`, failing safe, with the
|
|
precision moved into per-step notes. They offered to add a per-step field if we
|
|
would rather. We would not: a lane-level flag that fails safe plus per-step notes
|
|
is the right shape, and asking them to change a schema for our convenience would
|
|
be worse than the imprecision. No reply needed on that point.
|
|
|
|
T04 stays `wait`: the rotation authority now has a named owner
|
|
(railiance-platform, transport `secrets-engine exec`) but does not yet exist, and
|
|
nothing here changed a route.
|