key-cape/workplans/KEY-WP-0014-native-credential-lane-handoff.md
tegwick 471465df22 Record the two ops-warden catalog corrections
Accepted ownership of lane key-cape-oidc-login, which their catalog has carried
as asked-and-waiting since 2026-08-28 -- they were blocked on us. Reported the
rapp-qonto-keycape-client blocker as stale against its own source-read date, and
asked for it to be narrowed to the two custody steps rather than cleared, since
the exchange and the step-3 verification now exist but successor generation and
the CAS write deliberately do not.

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
2026-09-08 14:49:01 +02:00

7.6 KiB

id type title domain repo status owner topic_slug created updated state_hub_workstream_id
KEY-WP-0014 workplan Review native login and client credential lane handoffs infotech key-cape blocked codex native-credential-lane-handoff 2026-09-05 2026-09-08 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

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

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

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

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.