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>
Verified first: grant_types_supported advertises client_credentials in
discovery.go, but token.go only ever accepted authorization_code -- no
service-token issuance path exists at all. Building one from scratch is
materially bigger than extending the existing flow; explicitly not
attempted here, left open in the workplan rather than declared done.
What shipped for the human Authorization Code + PKCE flow:
- domain.User.Tenant (new, omitempty) + token.go's effectiveTenant():
falls back to tenant:coulomb (this workstation's actual tenant, ADR-0006)
when unset -- never an empty tenant claim, never a silent reassignment.
- principal_type: "human", unconditional.
- groups/roles promoted from scope-gated to unconditional core claims,
always [] not null when empty. One pre-existing test asserted the old
scope-gated groups behavior -- updated to match the new intentional
behavior, not left failing or reverted.
- assurance built from PKCESession.MFAVerified (new field, threaded
through completeAuthorization's two call sites in authorize.go) --
whether MFA was actually verified in this session, not static enrollment
state. aal2 only when required-and-passed this time, aal1 otherwise.
go build/vet clean, go test ./... green repo-wide. Two new authorize_test.go
cases assert MFAVerified on both paths. tests/profile/profile_test.go's
TestCompleteTokenFlow (the repo's own full HTTP integration test) extended
with real value assertions for all five claims, not just presence checks.
Python conformance tool not run against a live instance (needs the full
Authelia+LLDAP+privacyIDEA stack); TestCompleteTokenFlow's real HTTP round
trip covers the equivalent claim checks instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Discovered while planning tenant-engine's flex-auth WriteAuthorizer
(TEN-WP-0003): src/internal/server/oidc/token.go emits none of
iam-profile_v0.3.md's Core Claims yet -- not tenant, principal_type,
groups, roles, or assurance. tenant_roles isn't a small addition on top of
existing tenant support; it's the last, optional claim on top of a
baseline that doesn't exist yet.
Registered, not implemented -- security-sensitive Go token-issuance code
deserves dedicated context before changing, not a rushed edit from a first
read of the file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bernd's decision: role words (customer/vendor) collide with the separate
capability-role dimension a tenant can hold (PLTF/IAM/VEN/CUS, per
net-kingdom/docs/princedom-isolation-exploration.md's unratified model --
Binky itself is both CUS and, later, VEN). Tenants are grouped instead by
onboarding-risk/entity-shape, orthogonal to role: trial, friendly, single,
small, medium, large, enterprise, consumer, family, community, association,
agentic.
Binky maps to friendly -> tenant:friendly:binky, replacing the earlier
tenant:customer:binky throughout this workplan.
Two open questions flagged, not resolved here: where tenant:coulomb (the
platform's own internal/reference tenant) fits this external-shaped grouping
list, and whether net-kingdom should ratify this list into
iam-profile_v0.2.md's Tenant Claim section, since it currently still
documents the old customer/sandbox suggested identifiers and NetKingdom
owns that canon per ADR-0011.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Provisions the first real NetKingdom tenant (tenant:customer:binky, matching
the slug already live in production OpenBao paths) with a human tenant-admin
user and a workload-identity OIDC client for qonto-assistant's MCP callers,
routed through secrets-engine for secret custody and ops-warden for
discoverable, minimal-touch credential access. Closes Gap #1 flagged in
qonto-assistant's QONTO-WP-0003 closure (no OIDC issuer existed in the fleet).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sync AGENTS.md, CLAUDE.md, and .claude/rules from updated project_rules
templates: workplan-first session protocol, legacy terminology footnote,
and GET /workplans/ examples.
Honest first-pass maturity vector grounded in README/docs/tests present
in this repo; no invented evidence. Flagged for human review before
publish. See reuse-surface history/2026-07-06-coverage-classification.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates