An entry stored subject_id, assurance and evidence_ref but nothing about what kind of principal bound the approval, and subject_id is a naming convention rather than a verified claim. /entries is not restricted by principal type — only /consume is — and the approval-engine-operator client holds approval:approve, so a service can supply approver evidence today. Whether it may is gate-house doctrine; that it is legible is ours. Add entries.principal_type, populate it from the verified token, surface it on the object and the audit evidence path (not the claim, which stays least-disclosure), and migrate v3 stores leaving legacy rows null rather than back-filling a claim nobody made. Also corrects two statements in the requirements issued to informed-decision: agent tokens are not barred from approval:approve, and an empty assurance object is accepted rather than refused. 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
2.5 KiB
Caller authentication
Production accepts only RS256 JWTs verified against KeyCape JWKS with the exact
configured issuer and approval-engine audience. exp, iat, sub,
principal_type, tenant, roles, scope, and assurance are mandatory.
Missing or unverifiable credentials fail closed. The development static-token
mode is explicit, file-backed, and refused with --production.
The verified tenant must exactly match the service's configured store tenant;
cross-tenant reads and mutations are rejected before object lookup.
| Route | Required scope |
|---|---|
| create approval | approval:create |
| get approval or claim | approval:read |
| add approval entry | approval:approve |
| revoke | approval:revoke |
| supersede | approval:supersede |
| consume | approval:consume and service/agent principal |
| cadence, outbox, storage | approval:observe |
| explicit heartbeat | approval:emit |
Create additionally requires binding.actor == sub. Approval-entry subject,
assurance, evidence reference, and principal type are derived from the
verified JWT, never the request body.
principal_type is recorded on the entry (schema v4) because subject_id
alone cannot answer what kind of principal bound the approval — user:alice is
a naming convention, not a verified claim. This engine does not restrict
/entries to human principals: whether a service or agent may supply approver
evidence is approval doctrine and belongs to gate-house, and the
approval-engine-operator registration holds approval:approve today. What
this engine owes is that the evidence chain says which it was. Entries written
before v4 stay null rather than being back-filled into a claim nobody made. KeyCape owns client registration and scope grants; approval-engine
only verifies and enforces them. Requested registrations are:
- audience/resource server
approval-enginewith the scopes above; - the secrets-engine PEP service client with
approval:readandapproval:consume; - separately governed lifecycle/operator clients with only their needed mutation or observation scopes.
Client credentials belong in OpenBao/operator custody and must not be placed in manifests, logs, State Hub, or this repository.
KeyCape's OpenBao service-auth contract currently emits aud as the OAuth
clientId. That pattern must not be reused here. Tokens presented to this API
MUST have resource-server audience approval-engine. Requested non-secret
client fragments are in docs/keycape-service-registrations.md.