KEY-WP-0005-T02-T03: cached tenant_roles claim, close workplan
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m21s

New internal/adapters/tenantengine package, mirroring
internal/adapters/{lldap,privacyidea,authelia}'s shape: Client.Roles()
calls tenant-engine's cache-read endpoint. Fails open by construction --
unreachable, non-200, malformed body, or a nil *Client all return
(nil, false), never an error to specially handle. Wired into
TokenHandler.TenantEngine (nil by default, existing tests unaffected);
token.go stamps tenant_roles only when ok.

7 adapter tests plus 3 TokenHandler-level tests proving the actual
required behavior end-to-end: present when reachable, token issuance still
200 with every other core claim intact when unreachable (tenant_roles
simply absent -- the literal done-criteria), absent when not configured.

Real bug found and fixed at the source, not worked around: the first live
cross-process check (real flex-auth, real tenant-engine, this adapter)
returned tenant_not_found for a tenant that existed -- tenant-engine's read
endpoint was keyed by its internal tenant_id, but key-cape only ever has
the tenant's profile identifier. Fixed in tenant-engine
(ADHOC-2026-07-24), re-verified with the same live three-process chain --
roles=[IAM] ok=true.

Workplan closed: T01-T03 done. Explicitly still open: client_credentials /
service-token issuance -- no such flow exists in token.go at all, a
materially larger separate piece of work than either task here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-24 00:18:17 +02:00
parent fb888579dc
commit 44da5f5f99
5 changed files with 375 additions and 4 deletions

View file

@ -4,7 +4,7 @@ type: workplan
title: "IAM Profile core claims: tenant, principal_type, groups, roles, assurance, tenant_roles"
domain: infotech
repo: key-cape
status: ready
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
@ -139,7 +139,7 @@ human included.
```task
id: KEY-WP-0005-T02
status: todo
status: done
priority: high
state_hub_task_id: "1b8f44b4-2763-4d5a-a3eb-1c2b9a25dd15"
```
@ -165,11 +165,44 @@ Done when: token issuance still succeeds with `tenant_roles` omitted when
`tenant-engine` is unreachable (test simulates the outage); present and
correct when reachable, for a tenant with known role grants.
**Done 2026-07-24:** New `internal/adapters/tenantengine` package
(`Client.Roles(ctx, tenantID) ([]string, bool)`), mirroring
`internal/adapters/{lldap,privacyidea,authelia}`'s shape exactly. Fails
open by construction — unreachable, non-200, or malformed body all return
`(nil, false)`, never an error the caller has to specially handle; a `nil
*Client` also fails open safely rather than panicking, so `TokenHandler`
doesn't need a separate "is this configured" branch. Wired into
`TokenHandler.TenantEngine` (nil by default — existing tests and call
sites are unaffected); `token.go` stamps `tenant_roles` only when `ok`.
7 adapter-level tests (success, non-200, malformed body, connection
failure, timeout, nil client, empty base URL). 3 new `token_test.go` cases
prove the actual required behavior end-to-end through `TokenHandler`, not
just at the adapter layer: present and correct when reachable; **token
issuance still returns 200 with every other required core claim intact**
when unreachable, `tenant_roles` simply absent (the literal done-criteria);
absent when `TenantEngine` isn't configured at all. `go build`/`vet`
clean, `go test ./...` green repo-wide.
**A real bug found and fixed along the way, not worked around:** the first
live cross-process check (real `flex-auth`, real `tenant-engine`, this
adapter) returned `tenant_not_found` for a tenant that genuinely existed.
`tenant-engine`'s `GET /tenants/{tenant_id}/roles` was keyed by its
internal `tenant_id`, but `key-cape` (like any external caller) only ever
has the tenant's profile *identifier* (the `tenant` claim value) — it has
no way to know tenant-engine's internal id. Fixed at the source:
`tenant-engine/workplans/ADHOC-2026-07-24.md` (identifier-or-id resolution
added to `InMemoryTenantStore`), re-verified with the exact same live
three-process chain afterward — `roles=[IAM] ok=true`, resolved by
identifier. This is exactly why T02 insisted on a live-server-reachable
test rather than only an `httptest` fake: the fake would have used
whatever id shape the test author happened to pick and never caught this.
## Task: Closure review
```task
id: KEY-WP-0005-T03
status: todo
status: done
priority: low
state_hub_task_id: "34555bf7-0f18-4dac-ade2-6ff3738f353f"
```
@ -180,3 +213,33 @@ producing conformant tokens, and unblocks `flex-auth`'s side of the
`tenant_roles` picture receiving a real claim to reason about (though
`flex-auth`'s live-lookup path doesn't depend on this claim existing — only
the cache-read/performance path does). Run `statehub fix-consistency`.
**Closed 2026-07-24.** T01T02 done. `go build ./...`, `go vet ./...` clean;
`go test ./...` green across the whole repo. Python
`net-kingdom/tools/iam-profile-conformance` not run against a live instance
(needs the full Authelia+LLDAP+privacyIDEA stack, impractical in this
pass) — real coverage instead came from `tests/profile`'s own full HTTP
integration test (`TestCompleteTokenFlow`) with genuine claim-value
assertions, and from three separate live cross-process checks against a
real `tenant-engine` (one of which found and fixed a real bug in
`tenant-engine` itself, `ADHOC-2026-07-24`).
**What this closes:** `key-cape`'s human Authorization Code + PKCE flow now
emits every IAM Profile v0.3 core claim (`tenant`, `principal_type`,
`groups`, `roles`, `assurance`) plus the optional `tenant_roles` cache,
correctly sourced from a real `assurance` signal (MFA actually verified
this session, not static enrollment) and a real `tenant-engine` call
(fail-open, never blocking login). `KEY-WP-0004`'s Binky onboarding can now
produce conformant tokens for human logins.
**What stays explicitly open, not silently dropped:**
- `client_credentials` / service-token issuance — no such flow exists in
`token.go` at all yet (confirmed in T01); needed before any *service*
caller (not a human) can get a token with `principal_type: "service"` and
its own `tenant`/`tenant_roles`. A materially larger, separate piece of
work than either task in this workplan.
- `flex-auth`'s side of `tenant_roles` (`FLEX-WP-0008`, already closed
separately) doesn't depend on this claim existing at all — its
live-lookup path calls `tenant-engine` directly, so this workplan doesn't
block it, but also doesn't complete the full `tenant_roles` picture on
its own until service tokens exist too.