2880e21ef1
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.
52 lines
2.9 KiB
Markdown
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`.
|