# 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. ```yaml 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`.