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>
workforce-api
Kong routing/policy layer for the workforce slice of
classic-models-api (see /Users/stephanetailland/git/classic-models-api),
split out per the DDD decomposition in ../kong-konnect-deployment-strategy.md.
The backend stays a single monolith — only Kong does the domain split.
Structure mirrors ../sales-api/../payments-api — single MCP route from
the start, not ../catalog-api's original two-route pattern (see
../STATUS.md section 18).
Two resources, both with a REST surface and MCP tools:
Office(single keyofficecode) — full CRUD + anemployeessub-resource.Employee(single keyemployeenumber) — full CRUD +reports(direct reports, self-referential FK) andcustomers(assigned sales rep's customers) sub-resources.
| Route | Method | Backend path |
|---|---|---|
/workforce/v1/offices/ |
GET, POST | /classic-models/api/v1/offices/ |
/workforce/v1/offices/{officecode}/ |
GET, PUT, PATCH, DELETE | /classic-models/api/v1/offices/{officecode}/ |
/workforce/v1/offices/{officecode}/employees/ |
GET | /classic-models/api/v1/offices/{officecode}/employees/ |
/workforce/v1/employees/ |
GET, POST | /classic-models/api/v1/employees/ |
/workforce/v1/employees/{employeenumber}/ |
GET, PUT, PATCH, DELETE | /classic-models/api/v1/employees/{employeenumber}/ |
/workforce/v1/employees/{employeenumber}/reports/ |
GET | /classic-models/api/v1/employees/{employeenumber}/reports/ |
/workforce/v1/employees/{employeenumber}/customers/ |
GET | /classic-models/api/v1/employees/{employeenumber}/customers/ |
/workforce-mcp |
GET, POST, DELETE | MCP streamable-http |
Auth & MCP tool ACLs — the one domain where customer gets zero access
Both viewsets use ReferenceDataPermission
(authentication/permissions.py): read_only/product_manager/support
get read access, writes are admin-only, and — unlike every other
domain built this session — customer is not in the allow-list at
all, not even for reads. Verified directly against the permission
class, not assumed: this is genuinely internal staff/organizational data,
and the backend enforces that explicitly. All 7 read tools
(list-offices, get-office, get-office-employees, list-employees,
get-employee, get-employee-reports, get-employee-customers) have ACL
[admin, read_only, product_manager, support] — no customer. Both write
tools (create-office, create-employee) are admin only.
openid-connect on both surfaces (workforce-api REST audience,
mcp-workforce MCP audience), RFC 8693 exchange chain
MCP → workforce-api → classic-models-api. IdP: local Keycloak.
Structure
Same dev/ + kong/rest/ + kong/mcp/ layout as sales-api. Deploy key
and broker secret reused/mirrored from catalog-api and the live broker
client, same pattern as every domain since payments-api.