key-cape/workplans/KEY-WP-0014-native-credential-lane-handoff.md
tegwick 7a73352368 Record the custody owner's confirmations and guard a receipt against misreading
railiance-platform replied on all three open threads. Recording what they add
rather than what we already knew.

T02: they reached the same reading of the verifier receipt independently and add
human_client_consume_denied: true for both clients, confirm the verifier ran
twice (activation and post-rollout) at generation 38 with the signing key
unchanged, and state T02 can close against that receipt. They also correct our
framing, rightly: the packet says client-side retrieval is unadmitted, which
stays true, but the attended operator path is not a client-side read and never
required one. This task had been treating those as the same constraint.

T04: the live approval clients mean this repo now holds a committed receipt that
looks like rotation evidence and is not. Both owners independently state the same
two gaps -- no real predecessor rotation, no observed wall-clock expiry. Recorded
in T04 rather than only in T02, because verify-client's predecessor rejection is
now implemented and unproven, which is a different state from missing or done,
and T04 is where that distinction belongs.

Answered their open question on CCR-2026-0020, which has no named presenting
actor. As issuer: the registration carries both approval:create and
approval:approve, so one presenter can create an entry and approve it. That is a
separation-of-duties property of the holder, not a defect in the token -- KeyCape
issues the grants approval-engine asked for. Recorded both shapes KeyCape can
support, and that who holds it is approval-engine's decision and the doctrine
question gate-house's, not ours.

Also narrowed the operations note: the deployed config has not been written since
the activation, so the inspected state is the state that will boot.

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-09 20:05:40 +02:00

12 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.

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.

2026-09-09, guarding against a misreading that is now easy to make. The approval clients went live and docs/evidence/2026-09-09-keycape-verifier-admission.json records a passing verifier run, so there is now a committed receipt in this repo that looks like rotation evidence and is not. It records real_predecessor_rotation_tested: false and observed_wall_clock_expiry: false, and under limits, actual_predecessor_rotation_observed: false and wall_clock_jwt_expiry_observed: false. railiance-platform stated the same two gaps unprompted from their side (ae6ff4bc): "do not cite this receipt as rotation evidence."

So keycape verify-client's predecessor rejection has been exercised only against unit tests and a first provisioning where predecessor and successor were never distinct. Rotation step 4 is implemented and unproven, which is a different state from either "missing" or "done", and T04 is where that distinction lives.