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