docs: narrow qonto blocker, record key-cape lane ownership

key-cape corrected two blockers that had stopped being true after our
2026-08-28 source-read:

- rapp-qonto-keycape-client: `keycape service-token` (2026-09-05) is the
  native exchange the blocker recorded as absent, and `keycape verify-client`
  (2026-09-08) is rotation step 3 as one command. Narrowed to steps 1-2 --
  successor generation and the CAS write -- rather than cleared, as they
  asked. rotation.automatable -> false so a future executable driver is not
  told a lane with no admitted custody transport is drivable; the per-step
  truth moves into the steps.
- key-cape-oidc-login: ownership ACCEPTED by key-cape, so verified moves from
  asked-and-waiting to owner-confirmed. Lane stays interim -- acceptance
  covers the identity half, while the fetch_command yields an OpenBao token
  whose mount, role mapping and enforcement are not key-cape's.

Two tests pinned `key-cape-oidc-login` to sitting `asked-and-waiting`, so
answering the question broke them -- they failed on good news. Both now
assert the property instead: an unverified blocker is stale regardless of
date, over whatever lanes are in that state.

Verifying the routing answer against our own front door turned up a defect:
`warden plan` returns `autonomous` for a custody *write* and answers it with
read transports, because it has no read-versus-mutate intent. Recorded as
WARDEN-WP-0038 (proposed) -- the WP-0033-T06 shape, as a class this time.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1276224@bnt-lap001
Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3
This commit is contained in:
tegwick 2026-09-08 14:55:02 +02:00
parent 96dbb22aab
commit c1330049d9
5 changed files with 290 additions and 24 deletions

View file

@ -0,0 +1,172 @@
---
id: WARDEN-WP-ADHOC-2026-09-08
type: workplan
title: "Ad Hoc Tasks — 2026-09-08"
domain: infotech
repo: ops-warden
status: finished
owner: claude
topic_slug: netkingdom
created: "2026-09-08"
updated: "2026-09-08"
---
# Ad Hoc Tasks — 2026-09-08
Four key-cape messages, all follow-on from `WARDEN-WP-0033`. Two carry answers
ops-warden's catalog was waiting for; both make a recorded blocker less true than
it reads. This is the T05 mechanism paying out — and this time the owner ran it
against us rather than us running it against them.
### T01 — Narrow the `rapp-qonto-keycape-client` blocker (msg `08d42f47`)
```task
id: WARDEN-WP-ADHOC-2026-09-08-T01
status: done
priority: high
```
The blocker recorded "still no key-cape-native exchange/rotation command",
`verified: source-read` against key-cape source on 2026-08-28. Accurate then;
two things landed after that read:
- `keycape service-token` (2026-09-05, KEY-WP-0014-T03) — the client_secret_basic
exchange the blocker records as absent.
- `keycape verify-client` (2026-09-08) — rotation step 3 as one command,
including predecessor rejection and identical-secret detection.
**Narrowed, not cleared**, as key-cape asked. What remains genuinely blocked is
steps 1-2: successor generation and the CAS write to both custodians. Those are
custody and deployment acts, key-cape deliberately ships no command for them, and
no admitted ops-warden lane authorizes them either (T03).
`verified` moved to `owner-confirmed` — the correction came from the owner.
`rotation.automatable` flipped `true``false`, which key-cape explicitly asked
us not to do "either way" without precision. Done with the precision they wanted,
in the only place the schema has room for it: the flag is a single hint consumed
by a future executable driver (`WARDEN-WP-0027` Strand B), and a driver told
`true` would attempt custody acts with no admitted transport — the dangerous
direction. So the flag fails safe and the *steps* now carry the per-step truth,
each naming whether it is automatable and why. Offered them a schema change if a
per-step field would serve better; not built on speculation.
### T02 — Record key-cape's acceptance of `key-cape-oidc-login` (msg `12f1bdfa`)
```task
id: WARDEN-WP-ADHOC-2026-09-08-T02
status: done
priority: high
```
key-cape **accepted** ownership: interactive login, OIDC identity-token issuance
and MFA. This confirms the 2026-08-28 correction of `intended_owner` from
secrets-engine to key-cape, and confirms secrets-engine's 2026-08-21 refusal was
right. `verified: asked-and-waiting``owner-confirmed`.
**The lane stays `interim`.** Acceptance covers the identity half only. The
`fetch_command` yields an *OpenBao* token — the `netkingdom` mount, the
`role=<domain>` mapping, the token store and its enforcement are OpenBao's, with
role and policy definition railiance-platform's. `keycape login` yields a
key-cape issuer JWT to a private file: different credential type, different
verifier, different enforcement owner. Accepting an owner is not the same as the
owner fronting the lane, and `ADR-0003` says the interim cover is retired when
the front door exists, not when ownership is agreed. Retirement condition is now
recorded on the entry.
Their reading of the risk is right and we confirmed it: `warden_executes: false`
with a `wiki_ref` pointer, so per `ADR-0001` this is a pointer layer, not a code
path with programmatic consumers. `risk: standard` and
`workload_ref: not-applicable` are correct — a human identity act with no KV
path, consistent with how `ADR-0008` treats the other non-KV lanes.
### T03 — Answer "who executes Qonto rotation steps 1-2" (msg `0a6c9d88`)
```task
id: WARDEN-WP-ADHOC-2026-09-08-T03
status: done
priority: high
```
Answer: **nobody, admittedly, today** — and ops-warden must not invent one.
Steps 1-2 mutate custody on `platform/workloads/rapp-qonto/keycape-client` and
`sso/keycape-rapp-qonto-client`. Custody of platform workload paths is
railiance-platform's; the admitted execution front door for provisioned
secret-exec is `secrets-engine exec`. ops-warden owns neither and absorbing it
would be `ADR-0002` and `ADR-0005` violated in one act. Routed to
railiance-platform, with secrets-engine named as the transport owner — and
secrets-engine independently confirmed the same boundary three days earlier
(msg `15f0c0ca`): they will not rewrite a production lane's custody state from a
coordination message without owner confirmation.
key-cape was right not to treat a generic `warden route` match as authorization.
See T04 for what our own tool did when asked.
### T04 — `warden plan` returned `autonomous` for a custody *write* (defect)
```task
id: WARDEN-WP-ADHOC-2026-09-08-T04
status: done
priority: high
```
Verifying T03 against our own front door:
```
warden plan "generate a successor client secret ... and CAS-write it to
OpenBao platform/workloads/... and sso/..." --json
verdict: autonomous
lane_id: rapp-qonto-keycape-client
commands: warden access ... --exec / --out / --wrap
```
A need that says *generate* and *CAS-write* resolved to `autonomous` and was
answered with three **read** transports. This is the `WARDEN-WP-0033-T06` shape
recurring — a generic keyword match producing a confident wrong act — except T06
fixed one route by adding an exact lane, and the defect is in the classifier.
`warden plan` has no notion of read versus mutate intent, so every write need
matches the lane that *reads* the same path and comes back `autonomous`.
Not fixed here: a classifier is design work with an approval-semantics blast
radius, which the convention says is a workplan, not an adhoc.
Promoted to **`WARDEN-WP-0038`** (`proposed`).
Worth stating plainly: key-cape distrusted this output on principle and was right
to. Had they trusted it, our tool would have told them to proceed autonomously on
an unadmitted custody mutation.
### T05 — Acknowledge the KEY-WP-0009 handoff (msg `5bc58847`)
```task
id: WARDEN-WP-ADHOC-2026-09-08-T05
status: done
priority: low
```
Informational; no route retired or changed. Their routing note matches ours:
"login / OIDC / MFA" → key-cape means **issuance only**, and secret *values*
route to OpenBao custody. Nothing to change in `wiki/CredentialRouting.md`.
Also records that the KEY-WP-0009 reply ops-warden reported never arriving indeed
never existed (KEY-WP-0014-T02, audited 2026-09-05). Our 2026-08-28 finding was
right, and the correction is theirs, recorded.
### T06 — Unpin two tests from a transient catalog state
```task
id: WARDEN-WP-ADHOC-2026-09-08-T06
status: done
priority: medium
```
`test_stale_gaps_flags_unverified_even_when_the_date_is_today` and
`test_cli_route_gaps_fail_on_stale_exits_3` both asserted that
`key-cape-oidc-login` specifically sat `asked-and-waiting`. T02 answering that
question broke them — **the tests failed on good news**, not on a defect, and a
test that fails when a counterparty replies will get suppressed rather than read.
Both now assert the property they were always about: any lane whose blocker was
never re-established is stale regardless of its date, over whatever lanes are in
that state. The named-lane version of the claim already exists as a unit test on
`Delegation.is_verified`, which is where a fixture belongs.

View file

@ -0,0 +1,71 @@
---
id: WARDEN-WP-0038
type: workplan
title: "warden plan must distinguish reading a secret from mutating one"
domain: infotech
repo: ops-warden
status: proposed
owner: ops-warden
topic_slug: netkingdom
planning_priority: P1
depends_on_workplans:
- WARDEN-WP-0029
created: "2026-09-08"
updated: "2026-09-08"
---
# WARDEN-WP-0038 — Read versus mutate intent in the policy front door
## The defect
`warden plan` scores a need against catalog lanes by keyword overlap and returns
a verdict. It has no notion of what the caller wants to *do*. So a need that
says *generate a successor secret and CAS-write it to two custodians* matches the
lane that **reads** that path, scores well on the shared nouns, and returns:
```
verdict: autonomous
commands: warden access <lane> --exec / --out / --wrap
```
Three read transports offered as the answer to a write, under the verdict that
means "proceed without the founder". Reproduced 2026-09-08 against
`rapp-qonto-keycape-client` (`ADHOC-2026-09-08-T04`).
## Why this is worse than a bad suggestion
`warden plan` is the documented first step before drafting any founder credential
step (`.claude/rules/credential-routing.md`), and `autonomous` is consumed as an
authorization signal by agents that have no other check. `WARDEN-WP-0029` shipped
the verdict *because* instinct was not auditable; a verdict that is confidently
wrong in the permissive direction is worse than no verdict.
key-cape hit this and refused to rely on it — "we are not treating a generic
match as authorization" — which is the only reason it surfaced as a design
question rather than as an unadmitted custody mutation. The control that caught
it was a counterparty's caution. That is not a control.
`WARDEN-WP-0033-T06` is the same shape (rapp-postgres, generic
`openbao-api-key` match → `paste_once_provision` for an act that could not work)
and was fixed by adding one exact lane plus one regression. That fixed the
instance. This is the class.
## Shape of the fix (to be designed, not assumed)
- Classify need intent — read / mutate / establish — before scoring, and never
answer a mutate need with a read lane's transports.
- A mutate need against a lane ops-warden does not own is `unroutable` with a CCR
stub naming the custody owner, not `autonomous`. `ADR-0003`: cover gaps, never
silently own them; a permissive verdict on someone else's custody act is a
silent claim of ownership.
- Consider whether `rotation.steps` should be reachable from `plan` at all, given
they are advisory prose and several are explicitly not automatable.
- Regression from the reproduced case, in the manner of T06.
## Open question for design
Whether intent belongs in the matcher or in the lane. A lane could declare which
acts it admits (`read`, `rotate`, `provision`) and `plan` could refuse any act a
lane does not declare — stricter, and it fails closed on lanes that say nothing,
which is the majority today. That is a catalog schema change and needs its own
argument.