--- id: KEY-WP-0024 type: workplan title: "Wire tenant_roles as explicit opt-in configuration" domain: infotech repo: key-cape status: finished owner: claude topic_slug: tenant-roles-opt-in-wiring created: "2026-09-08" updated: "2026-09-08" state_hub_workstream_id: "d75f52f2-df0d-5a56-8394-892feeec4b4d" --- Closes gap G07. The tenant-engine client and `TokenHandler.TenantEngine` were implemented and tested, but `main.go` supplied no client and exposed no configuration, so the stock executable left the field nil and never emitted `tenant_roles`. The capability existed at library level only. Wired rather than documented away as library-only: the claim is cheap, the adapter already fails open, and leaving a tested capability unreachable from the binary invites the same gap being rediscovered later. ## Add opt-in configuration and wire the client ```task id: KEY-WP-0024-T01 status: done priority: medium state_hub_task_id: "0e53485b-7e94-5dd8-8e02-1d18f5413a78" ``` Added a `tenantEngine` config block with `baseURL` and an optional `timeout`. An empty `baseURL` disables the claim, which keeps the stock server's behaviour exactly as it was — enabling it is a deliberate act. Validation treats a configured source as one that must work: the URL must be http/https with a host, and the timeout must parse and fall in (0, 10s], since it sits on the synchronous token-issuance path. A `timeout` set without a `baseURL` is rejected rather than ignored — it means someone expected the claim to be on and it silently would not have been. ## Verify it in the built executable ```task id: KEY-WP-0024-T02 status: done priority: medium state_hub_task_id: "c4279ce0-f6f8-5612-a48a-c1d2fd3da3b6" ``` G07's closure criterion is the built executable, not the wiring, so all three behaviours were checked by running `bin/keycape` and inspecting real tokens rather than by reading the code: - configured against a stub tenant-engine, a `client_credentials` token carries `tenant_roles: [capability:approve, capability:observe]`; - with no `tenantEngine` block, the claim is absent; - configured but with the source down, issuance still succeeds and the claim is absent — the fail-open behaviour the adapter documents, confirmed end to end. Nine validation cases cover the accepted and rejected configurations.