Stéphane Tailland f975394682 feat(mcp): cap read volume per caller identity
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>
2026-08-29 18:05:54 +02:00

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 key officecode) — full CRUD + an employees sub-resource.
  • Employee (single key employeenumber) — full CRUD + reports (direct reports, self-referential FK) and customers (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.

S
Description
No description provided
Readme 69 KiB