Files
kong-workforce-api/README.md
T
Stéphane Tailland 2880e21ef1 Build workforce-api REST + MCP layers (Layer 3), full build in one pass
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.
2026-08-23 17:13:45 +02:00

52 lines
2.9 KiB
Markdown

# 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`.