The ACL answers 'may this identity call create-product'. Nothing answered 'may
it call it with these arguments' — write authorises writing, not this
particular record. And the read cap counts calls, so one call asking for a
hundred thousand rows cost the same as one asking for ten.
The OPA plugin sends the decoded JSON-RPC body, so the decision point sees the
tool name and its arguments. The policy lives once in k8s-platform/opa and every
domain asks the same server.
Enforced at the gateway rather than in an agent, because an agent that has been
talked into something is the case this exists for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A scope authorises a read and says nothing about volume. An agent reads a
thousand records in seconds where a person clicks ten, and until now nothing
noticed: the token is valid, the tool is permitted, the ACL passes. That gap
stopped being theoretical the day an autonomous run started reading the whole
catalog on a schedule.
identifier: credential keys the counter on the sub claim — the end user,
whoever is asking on their behalf — so an agent looping under one person's
identity spends that person's budget and cannot spend everyone's. Verified on
catalog: pm-user and ro-user hold separate counters.
Per-route namespace, so a busy domain cannot exhaust another's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 routes: Office (single key, full CRUD + employees sub-resource)
and Employee (single key, full CRUD + reports + customers
sub-resources). MCP layer built with the single-route pattern from
the start.
Only domain where customer role has zero access — verified against
ReferenceDataPermission.check(): read_only/product_manager/support
get reads, writes are admin-only, customer isn't in the allow-list
at all. All 9 MCP tool ACLs reflect this exactly.
Keycloak (local): workforce-api-audience + mcp-workforce-audience
client scopes, resource clients, broker/librechat attachments.
Deploy key and broker secret reused/mirrored, same pattern as every
domain since payments-api.