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
48 lines
2.5 KiB
Markdown
48 lines
2.5 KiB
Markdown
# 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`.
|