Answer who presents approval-engine-operator: nobody

railiance-platform asked and declined to guess, which was right — this
registration carries the widest scope set of the pair. Nothing in this
repo obtains an OAuth token; it only verifies them. Scope by scope the
bundle describes no single actor: approve now belongs to
informed-decision's human client, emit is redundant against the server's
own heartbeat, and no requester identity was ever settled for create.

Cancel CCR-2026-0020 as an owner decision rather than leave it in_flight,
keep verifier custody CCR-2026-0018, and record that a future presenter
gets a narrowed registration — approve must not travel with the
operational scopes.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1275879@bnt-lap001
Assistant-Session: eb464208-f821-41b2-bc5a-a6c33d92a8ad
This commit is contained in:
tegwick 2026-09-09 23:17:43 +02:00
parent fec4eaeb1b
commit 849c75bb09
2 changed files with 68 additions and 1 deletions

View file

@ -116,7 +116,54 @@ clients:
Human approvers use the existing KeyCape human flow with `approval:approve`
only, still with `aud=approval-engine`. They must not receive
`approval:consume`.
`approval:consume`. As of 2026-09-09 that flow is `informed-decision`'s
(`INFD-WP-0001`); see `docs/approver-surface-requirements.md`.
## `approval-engine-operator` — requested, unpresented, do not provision
Answering `railiance-platform` (`RPF-WP-0035-T06`, `CCR-2026-0020`) 2026-09-09.
Their question was the right one and we could not answer it from this source
either, because the answer is *nobody*.
**Nothing in this repository presents this client, and nothing is deployed that
could.** This engine only *verifies* bearer tokens; it never obtains one. The
sole outbound credential it holds is the mounted `audit-token` used for
audit-core delivery (`approval_engine/audit.py`), which is not an OAuth client.
`approval_engine/pep.py` is a consumer-side helper for PEPs such as
`secrets-engine`, which present their own tokens. There is no client-credentials
grant anywhere in the package, and `deploy/approval-engine.yaml` is unapplied.
Scope by scope, the bundle we originally requested does not describe one actor:
| Scope | Who would present it |
| --- | --- |
| `approval:create` | whoever *requests* an approval. Not named anywhere in the estate today; `gate-house` doctrine says which acts need one, but no requester identity has been settled |
| `approval:approve` | `informed-decision`'s **human** client, per its own registration. A service principal holding this is exactly the question `gate-house` has not ruled on — see the `principal_type` note below |
| `approval:read` | anything rendering or inspecting an approval |
| `approval:revoke`, `approval:supersede` | attended lifecycle operations |
| `approval:observe` | monitoring and operations |
| `approval:emit` | **redundant in normal operation** — the running server emits its own heartbeat on `--heartbeat-seconds` with no token; the route exists only to force one |
So this was a convenience bundle written when this repo assumed a single
"lifecycle operator" would exist, and it aged badly: it now carries the widest
scope set in the pair, including `approval:approve`, for an identity nobody has
named.
**Decision, option (b): cancel `CCR-2026-0020`.** No client-side reader is
wanted, because there is no presenter. This is an owner decision and not a
deferral — recorded here so the request does not rot `in_flight`. Verifier
custody (`CCR-2026-0018`) is unaffected and remains correct: KeyCape must be
able to *authenticate* this client if it is ever presented.
When a presenter is actually named, the registration should be re-requested
**narrowed to that presenter's scopes**, not reinstated as this bundle. In
particular `approval:approve` should not travel with the operational scopes:
schema v4 records `entries.principal_type` so a service bind is legible, but
legibility is not permission, and whether a non-human may supply approver
evidence at all is `gate-house`'s to rule on.
`secrets-engine-approval` above is unaffected — it has a real presenter, a
narrow scope pair, and is the client that unblocks `T05`.
## Human approver client — not supplied (2026-09-08)

View file

@ -184,6 +184,26 @@ is accepted rather than refused. T01 remains `progress`: what is left is
key-cape owning the registrations, credential custody from railiance-platform,
and the open scope question (A) above — none of them reachable from here.
2026-09-09 (third): `railiance-platform` (`RPF-WP-0035-T06`) asked who presents
`approval-engine-operator` and refused to guess — correctly, since it carries
the widest scope set of the pair. The answer is that nobody does. Nothing in
this repository obtains an OAuth token: the engine only verifies them, its one
outbound credential is the mounted `audit-token` for audit-core delivery, and
`pep.py` is a consumer-side helper for PEPs that present their own. Scope by
scope the bundle does not describe a single actor — `approval:approve` now
belongs to `informed-decision`'s human client, `approval:emit` is redundant
because the server emits its own heartbeat with no token, and no requester
identity has ever been settled for `approval:create`. It was a convenience
bundle written when this repo assumed a lifecycle operator would exist.
Answered (b): cancel `CCR-2026-0020` as an owner decision rather than leave it
`in_flight`; verifier custody `CCR-2026-0018` is unaffected and stays correct.
Recorded in `docs/keycape-service-registrations.md`, with the rule that a future
presenter gets a registration narrowed to its own scopes and that
`approval:approve` must not travel with the operational scopes — schema v4 makes
a service bind legible, but legibility is not permission, and the should-it
question remains gate-house's. `secrets-engine-approval` is unaffected: it has a
real presenter and is the client that unblocks T05. T01 stays `progress`.
## Harden durable storage and migrations
```task