key-cape/workplans/KEY-WP-0014-native-credential-lane-handoff.md

363 lines
20 KiB
Markdown
Raw Normal View History

---
id: KEY-WP-0014
type: workplan
title: "Review native login and client credential lane handoffs"
domain: infotech
repo: key-cape
Close T04 and pin the single route by which a principal becomes human KEY-WP-0014-T04 sat `wait` with nothing outstanding, which is a contradiction. Its title is admit rotation and verify consumer handoff, and both are delivered: the semantics are published, the executor, authority and transport are named, the CCR will be prepared on request, and step 4 shipped as keycape verify-client; ops-warden took option (a) for the reason given, with both lanes owner-confirmed and no route changed. Executing a rotation was never its deliverable, so treating the owner's deferral as an open item would have kept the task open against work it was not scoped to do. The deferral stands separately, revisited on evidence. GH-DEC-2026-016 §5 applies A-16 to what makes a principal human. Verified against source that `human` has exactly one route here -- a literal on the authorization-code path after an upstream login resolved to a directory user, with no configuration able to assert it -- so A-16 does not yet bite and no provenance claim is warranted. Adding one would encode a distinction that does not exist. Asserting the behaviour would not protect that: a test checking a human token says human passes just as happily when the value starts coming from a registration. The guard parses the package and requires every principal_type assignment to be a string literal, the set being exactly human and service. It covers both shapes -- the browser path assigns into a map, the service path uses a key-value pair in a map literal -- and checking only assignments found one of two routes and passed, which the pinned literal set caught. Verified by mutation. 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-10 23:05:16 +02:00
status: finished
owner: codex
topic_slug: native-credential-lane-handoff
created: "2026-09-05"
Close T04 and pin the single route by which a principal becomes human KEY-WP-0014-T04 sat `wait` with nothing outstanding, which is a contradiction. Its title is admit rotation and verify consumer handoff, and both are delivered: the semantics are published, the executor, authority and transport are named, the CCR will be prepared on request, and step 4 shipped as keycape verify-client; ops-warden took option (a) for the reason given, with both lanes owner-confirmed and no route changed. Executing a rotation was never its deliverable, so treating the owner's deferral as an open item would have kept the task open against work it was not scoped to do. The deferral stands separately, revisited on evidence. GH-DEC-2026-016 §5 applies A-16 to what makes a principal human. Verified against source that `human` has exactly one route here -- a literal on the authorization-code path after an upstream login resolved to a directory user, with no configuration able to assert it -- so A-16 does not yet bite and no provenance claim is warranted. Adding one would encode a distinction that does not exist. Asserting the behaviour would not protect that: a test checking a human token says human passes just as happily when the value starts coming from a registration. The guard parses the package and requires every principal_type assignment to be a string literal, the set being exactly human and service. It covers both shapes -- the browser path assigns into a map, the service path uses a key-value pair in a map literal -- and checking only assignments found one of two routes and passed, which the pinned literal set caught. Verified by mutation. 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-10 23:05:16 +02:00
updated: "2026-09-10"
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.
2026-09-10. The authority question is answered and the blocker is narrowed to a
schedulable act. railiance-platform named the executor (platform operator,
attended founder session; explicitly not ops-warden, not secrets-engine
autonomously, and no unattended agent), the transport (the governed
`openbao-platform-admin-login` lane via `warden access ... --exec`), and the
authority (founder_required attended OIDC, role=platform-admin). What is missing
is a rotation CCR, which they will prepare *if KeyCape asks* — a two-custodian
CAS rotation with a service restart being a distinct version-guarded operation
rather than an implementation detail of an existing lane.
**Not asked, deliberately.** Requesting a production credential rotation with a
restart window is the operator's decision, not this repository's, so it was put
to him rather than answered here (ops-warden msg 10fcc8af).
Established before recommending anything either way: **this rotation has no
incident driver.** KEY-WP-0011 recovered a real exposure on 2026-08-23, and the
coordinated rotation that followed replaced the RS256 signing key, the LLDAP bind
credential, the Authelia client credential and the privacyIDEA admin token. The
`rapp-qonto` client secret was not in that payload — it is an `env:` secretRef
resolved from a separate Kubernetes Secret, never inline in `config.yaml`, which
config validation enforces. So this is lifecycle hygiene, not remediation, and
nobody should be carrying it as leftover incident work. Hygiene can wait for a
chosen window; remediation could not.
Also recorded: `warden route`/`warden plan` previously returned a generic match
that read as authorization, and offered read transports against a write need.
This repository declined to act on it; railiance-platform independently called it
a defect; fixed under WARDEN-WP-0038.
## Admit rotation and verify consumer handoff
```task
id: KEY-WP-0014-T04
Close T04 and pin the single route by which a principal becomes human KEY-WP-0014-T04 sat `wait` with nothing outstanding, which is a contradiction. Its title is admit rotation and verify consumer handoff, and both are delivered: the semantics are published, the executor, authority and transport are named, the CCR will be prepared on request, and step 4 shipped as keycape verify-client; ops-warden took option (a) for the reason given, with both lanes owner-confirmed and no route changed. Executing a rotation was never its deliverable, so treating the owner's deferral as an open item would have kept the task open against work it was not scoped to do. The deferral stands separately, revisited on evidence. GH-DEC-2026-016 §5 applies A-16 to what makes a principal human. Verified against source that `human` has exactly one route here -- a literal on the authorization-code path after an upstream login resolved to a directory user, with no configuration able to assert it -- so A-16 does not yet bite and no provenance claim is warranted. Adding one would encode a distinction that does not exist. Asserting the behaviour would not protect that: a test checking a human token says human passes just as happily when the value starts coming from a registration. The guard parses the package and requires every principal_type assignment to be a string literal, the set being exactly human and service. It covers both shapes -- the browser path assigns into a map, the service path uses a key-value pair in a map literal -- and checking only assignments found one of two routes and passed, which the pinned literal set caught. Verified by mutation. 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-10 23:05:16 +02:00
status: done
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.
Close the KEY-WP-0009 handoff gap and deliver the two blocked admissions KEY-WP-0009-T04 is done. Re-ran verification on main with an explicit Go 1.23.0 toolchain and a writable build cache: contract validator PASS, build, vet and the full test suite pass, git diff --check clean. gofmt had drifted on internal/server/telemetry/events.go and tests/migration/scenario_c_test.go (comment alignment only, no semantic change); both reformatted so the task's formatting claim holds. The four handoffs to netkingdom, secrets-engine, ops-warden and railiance-platform were then actually sent -- the 2026-09-05 audit had shown the earlier claimed delivery had no receipts -- and each receipt id is recorded in the workplan and readable back from the hub. KEY-WP-0013-T02 and KEY-WP-0014-T04 stay in wait. Their remaining work is not ours to do, so what was owed was the request, and it is now delivered: custody admission for the two approval clients to railiance-platform, the exact human client id and callback URI to approval-engine, and the OpenBao-token vs issuer-JWT split plus Qonto rotation execution authority to ops-warden. Each message quotes the existing non-secret packet or reviewed sequence and states what is explicitly not being treated as authorization. No secret was read, no production resource changed, no route retired. SCOPE.md updated to match. 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 10:22:52 +02:00
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).
Ship the live-registration check both blocked tasks depend on 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
2026-09-08 14:30:43 +02:00
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.
Answer the approver-client questions, and fix what checking them turned up 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
2026-09-09 14:25:38 +02:00
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.
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
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.
**2026-09-10: the authority is answered. What remains is an artifact and a
decision.** railiance-platform answered the routed question (`7c7228ac`, relayed
by ops-warden `b14655b3`):
- **Who:** the platform operator, attended, as a founder-attended session. Not
ops-warden, not secrets-engine autonomously, and no unattended agent —
railiance-platform named themselves in that exclusion too.
- **Transport:** the governed `openbao-platform-admin-login` lane, invoked only
through `warden access openbao-platform-admin-login --exec -- <command>` with a
unique metadata receipt path. The owner command is never run directly.
- **Authority:** `founder_required` attended OIDC via netkingdom
`role=platform-admin`. Nothing else in that repo carries write authority
against `platform/workloads/`.
**Missing: a rotation CCR.** They will not raise one speculatively, and their
reason is sound — a two-custodian CAS rotation with a service restart is a
distinct version-guarded operation, not an implementation detail of an existing
lane. It must name the CAS precondition and expected version on *both*
custodians, sibling-field preservation on the KV write, the restart window,
bounded predecessor retention for rollback and denial checks, and the
reconcile-to-same-version step on failure. That last is step 5 of the sequence in
`docs/native-authentication.md`, so the reviewed sequence is what the CCR encodes.
**Blocked on a decision that is not KeyCape's to take unilaterally.** Their offer:
"if key-cape wants the rotation scheduled, say so and I will prepare it under
RPF-WP-0035 with both custodians in one window." ops-warden deliberately did not
ask on our behalf — correctly, per their ADR-0003, since implying an owner request
from a routing errand is quiet ownership. So T04 now waits on the repository owner
deciding whether the Qonto rotation should happen, and when. Nothing indicates
compromise; this is hygiene plus the first real exercise of rotation step 4, which
is why it is a judgement call rather than an obvious yes.
**Precedent offered, and it is the right one.** The identical two-custodian shape
was exercised 2026-09-09 for the approval clients: attended founder session,
OpenBao KV custody plus a Kubernetes-delivered consumer copy, compatible pinned
rollout, protected recovery retained, and rollback that restored configuration and
image and detached delivery before a clean resume. Their
`scripts/keycape_approval_custody.py` (activate / resume-activate / verify) is the
shape a rapp-qonto rotation should follow rather than a bare `bao kv patch` — for
exactly the provider/consumer consistency reason this task gave when it declined
to ship a wrapper around a raw KV write. They endorsed that refusal unprompted.
**And the `warden plan` defect is fixed** (WARDEN-WP-0038, 2026-09-10). The Qonto
need now returns `founder_required` naming the write owner, and read transports
are no longer offered against a write need. ops-warden's closing line is worth
keeping: "Your caution was load-bearing and it should not have had to be."
If the rotation is scheduled, it is also the first opportunity to close the
`real_predecessor_rotation_tested: false` gap recorded above — `verify-client`
with `-previous-secret-env` is exactly step 4, and the predecessor would finally
be genuinely distinct.
**Decision, 2026-09-10: not yet.** The repository owner declined to schedule the
rotation now. Recorded with the reasoning so it is revisited on evidence rather
than re-litigated from scratch: nothing indicates compromise, the offer stands
open, and the estate already has one founder-attended window pending for the two
fail-closed startup changes. Rotating for hygiene alone, in a second attended
session, against a credential with no incident attached, is work the owner chose
not to spend now.
This is a deferral, not a refusal, and the thing that should reopen it is a
change in evidence rather than the passage of time: an actual exposure or
suspected one (see KEY-WP-0011), the Qonto secret's age becoming a stated concern,
a consumer requiring proof of rotation, or a decision to prove rotation step 4
before relying on it elsewhere. Any of those makes the answer different; a
calendar date does not.
Close T04 and pin the single route by which a principal becomes human KEY-WP-0014-T04 sat `wait` with nothing outstanding, which is a contradiction. Its title is admit rotation and verify consumer handoff, and both are delivered: the semantics are published, the executor, authority and transport are named, the CCR will be prepared on request, and step 4 shipped as keycape verify-client; ops-warden took option (a) for the reason given, with both lanes owner-confirmed and no route changed. Executing a rotation was never its deliverable, so treating the owner's deferral as an open item would have kept the task open against work it was not scoped to do. The deferral stands separately, revisited on evidence. GH-DEC-2026-016 §5 applies A-16 to what makes a principal human. Verified against source that `human` has exactly one route here -- a literal on the authorization-code path after an upstream login resolved to a directory user, with no configuration able to assert it -- so A-16 does not yet bite and no provenance claim is warranted. Adding one would encode a distinction that does not exist. Asserting the behaviour would not protect that: a test checking a human token says human passes just as happily when the value starts coming from a registration. The guard parses the package and requires every principal_type assignment to be a string literal, the set being exactly human and service. It covers both shapes -- the browser path assigns into a map, the service path uses a key-value pair in a map literal -- and checking only assignments found one of two routes and passed, which the pinned literal set caught. Verified by mutation. 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-10 23:05:16 +02:00
**Closed 2026-09-10.** The paragraph above left T04 `wait` with nothing
outstanding, which is a contradiction: `wait` means waiting or blocked, and this
was neither. Re-reading the task against its own title settles it — *admit
rotation and verify consumer handoff* — and both halves are delivered:
- **Rotation admitted.** The semantics were reviewed and published
(`docs/native-authentication.md`), the executor and authority are named
(platform operator, attended founder session, governed
`openbao-platform-admin-login` lane, `founder_required` attended OIDC via
`role=platform-admin`), the transport is admitted, and the CCR will be prepared
on request. Step 4 shipped here as `keycape verify-client`, treating an
identical predecessor as a failure because an unchanged secret is a rotation
that did not happen. Steps 13 are custody's and are deliberately not
automated by this repository.
- **Consumer handoff verified.** ops-warden took option (a) for the reason we
gave rather than out of caution — the proxy yields an OpenBao token and
`keycape login` an issuer JWT, so they were never one thing to cut over
between. Lane `key-cape-oidc-login` is `owner-confirmed` with our boundary
written in verbatim, and `rapp-qonto-keycape-client` was narrowed to steps 12
rather than cleared. No route was changed.
*Executing* a rotation was never this task's deliverable, and treating the
owner's deferral as an open item would have kept a task open against work it was
never scoped to do. The deferral stands on its own above, revisited on evidence.
Two residuals are recorded rather than carried here, because they belong to
whoever next opens a window: `real_predecessor_rotation_tested: false` and
`observed_wall_clock_expiry: false`. `verify-client -previous-secret-env` is
ready to close the first the moment a genuinely distinct predecessor exists.
Also standing, from ops-warden and not to be lost: until WARDEN-WP-0038 lands,
**treat a `warden plan` verdict on any need containing a write, rotate or
provision act as unreliable** — it inherits the verdict of the lane that reads
the same path, and `autonomous` is documented as the signal to proceed without
the founder. That applies to this repository whenever it plans a rotation.