approval-engine/docs/caller-authentication.md
tegwick 31da1af5e4 Record the verified principal type on approver entries (schema v4)
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
2026-09-09 14:09:54 +02:00

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-engine with the scopes above;
  • the secrets-engine PEP service client with approval:read and approval: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.