flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after
secrets-engine found that a claim-bearing request's request_digest covers the
carried claim, so it can never equal a pdp_digest recorded before that claim
existed. A consumer obeying GH-DEC-2026-008 against request_digest would have
failed closed permanently on every claim rather than on a bad one.
The value recorded at issue was already correct, so no code changes. What was
wrong was this repo's description of the comparison target: a reader would
reach for request_digest and fail closed forever. The schema and
docs/approval-claim.md now name approval_binding_digest as the execute-time
target and state that request_digest is never it, while leaving the issue-time
description as it stood.
Separately, docs/keycape-service-registrations.md now records a live collision
in the deployment inputs: the manifest serves --tenant platform while the
requested registrations issue tenant:coulomb, and ApiApplication.identity
compares them with exact string equality before any object lookup, so those
tokens would be denied 403 on every non-health route. flex-auth's
tenant:platform is a PDP subject this engine never reads and cannot bridge the
two. The values are left as-is on purpose — resolving it needs an owner
statement on whether the two name the same layer, and guessing grants
cross-tenant access to the approval store. The doc's stale issuer is corrected
to the live https://kc.coulomb.social from 06544b0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715850@bnt-lap001
Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
4.3 KiB
Requested KeyCape registrations
Status: requested by APPROVAL-WP-0002-T01. Non-secret. KeyCape owns issuance,
client disablement, and the exact claim contract. This file is a consumer
request, not a live registration.
Tokens presented to approval-engine MUST use resource-server audience
approval-engine. Do not reuse the OpenBao service-auth pattern that sets
aud to the OAuth clientId.
Required claims remain those in docs/caller-authentication.md: iss, sub,
aud, exp, iat, principal_type, tenant, roles, scope, assurance.
principal_type for consume callers must be service or agent.
Resource server
| Field | Value |
|---|---|
| Audience | approval-engine |
| Issuer | the deployed KeyCape issuer (manifest uses https://kc.coulomb.social) |
| JWKS | GET /jwks on the KeyCape service |
| Scopes | approval:create, approval:read, approval:approve, approval:revoke, approval:supersede, approval:consume, approval:observe, approval:emit |
Tenant reconciliation — unresolved, blocks token issuance
This is a live collision, not a naming preference. ApiApplication.identity
compares the verified JWT tenant claim to the engine's configured store tenant
with exact string equality and raises Forbidden before any object lookup
(approval_engine/api.py:66). There is no mapping table, no normalisation, and
no prefix handling anywhere in this engine.
The three values currently in play:
| Value | Where it is set | Current content |
|---|---|---|
| Store tenant | deploy/approval-engine.yaml --tenant (CLI default platform) |
platform |
JWT tenant claim |
the client registrations below | tenant:coulomb |
| CheckRequest tenant | flex-auth policy subject | tenant:platform — never read by this engine |
platform != tenant:coulomb, so tokens issued under the registrations below
would be denied 403 on every non-health route. Spelling similarity between
platform and tenant:platform is not a mapping either; the flex-auth policy
subject is a PDP input this engine never inspects, so it cannot participate in
the comparison at all.
Denial evidence: tests/test_auth.py::test_wrong_tenant_is_forbidden — a
signature-valid token whose tenant differs from the store tenant is refused
without mutation.
Resolution is an owner decision and is deliberately not taken here. Either
KeyCape issues tenant: platform to match the store, or this repo's manifest
sets --tenant tenant:coulomb to match the registration. Which is correct
depends on whether platform and tenant:coulomb name the same layer — a
question this engine cannot answer, and answering it wrongly grants a token
cross-tenant access to the approval store. The values below are left as
requested until that mapping is stated by an owner, so the mismatch stays
visible rather than being silently resolved by whichever document was edited
last.
Tracked against APPROVAL-WP-0002-T01; related KEY-WP-0013-T02,
SECRETS-WP-0009-T03.
Clients
Confidential client secrets stay in OpenBao/operator custody. secretRef
names below are placeholders for that custody path.
clients:
- clientId: secrets-engine-approval
displayName: secrets-engine PEP consume client
audience: approval-engine
allowedScopes: [approval:read, approval:consume]
grantTypes: [client_credentials]
clientType: confidential
secretRef: env:KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET
serviceSubject: service:secrets-engine
principal_type: service
tenant: tenant:coulomb
roles: [secrets-engine]
tokenLifetime: 15m
- clientId: approval-engine-operator
displayName: approval-engine lifecycle operator
audience: approval-engine
allowedScopes:
- approval:create
- approval:read
- approval:approve
- approval:revoke
- approval:supersede
- approval:observe
- approval:emit
grantTypes: [client_credentials]
clientType: confidential
secretRef: env:KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET
serviceSubject: service:approval-engine-operator
principal_type: service
tenant: tenant:coulomb
roles: [approval-operator]
tokenLifetime: 15m
Human approvers use the existing KeyCape human flow with approval:approve
only, still with aud=approval-engine. They must not receive
approval:consume.