<p>ADR-0013 introduced the tenant onboarding grouping taxonomy (<code>trial</code>/<code>friendly</code>/<code>single</code>/.../<code>agentic</code>), deliberately orthogonal to a separate, unratified <strong>capability-role</strong> model sketched in <code>docs/princedom-isolation-exploration.md</code>: <code>PLTF</code> (operates the platform), <code>IAM</code> (organizes its own users/auth/secrets), <code>VEN</code> (provides apps/services to others), <code>CUS</code> (consumes apps/services from <code>PLTF</code> or <code>VEN</code> tenants) — non-exclusive, a tenant may hold several at once.</p>
<p>That exploration left open where capability roles actually live (a per-token claim vs. a registry), who owns them, how they're granted or revoked, and how this interacts with the IAM Profile's existing <code>roles</code> claim — which is a <em>per-subject</em> claim ("coarse identity roles" for the human/service/agent holding the token), a different concept from a <em>per-tenant</em> capability fact. Conflating the two would be a category error: <code>roles: ["VEN"]</code> on a token would ambiguously mean "this subject has vendor-role" vs. "this subject's tenant is a vendor."</p>
<p>No existing service owns tenant-as-an-entity facts (existence, grouping, capability roles, plan/subscription state) as a queryable resource. <code>user-engine</code>'s own boundary contract (<code>canon/standards/user-engine-boundary-contract_v0.1.md</code>) explicitly scopes <code>user-engine</code> to <em>consuming</em> tenant identifiers and <em>storing tenant-scoped records</em>, not owning tenant identity or capability facts.</p>
<p>Bernd's direction (2026-07-23):</p>
<ul><li>Implement the previously-discussed hybrid carrying approach: cache a tenant's capability roles on the token for ordinary decisions, but require a live check for critical/high-stakes actions.</li><li>Role grants are usually tied to a payment plan — most concretely, <code>IAM</code> means the tenant has its own dedicated key-cape/Keycloak instance for isolation, scale, and performance, which is itself a paid capability.</li><li><code>trial</code>-grouped tenants may hold <strong>any</strong> capability role without restriction, specifically so the platform can showcase, test, and explore every role. Safety for trial tenants comes from resource guardrails (spend limits defaulting to zero budget, entity/action count limits), not from role gating — guardrail design itself is future work, not this ADR.</li><li>A new service, <code>tenant-engine</code>, will be built (Bernd) as the owner of this domain, organized beside <code>user-engine</code> rather than inside it — smaller, single-purpose services are easier to reason about and drift less, matching the fleet's existing convention (<code>activity-core</code>, <code>audit-core</code>, <code>user-engine</code>, and others).</li></ul>
</section>
<sectionid="decision"><h2>Decision</h2>
<ol><li><strong>Capability-role vocabulary ratified as core NetKingdom vocabulary</strong>: <code>PLTF</code>, <code>IAM</code>, <code>VEN</code>, <code>CUS</code>, non-exclusive. <code>IAM</code> specifically means: the tenant operates its own dedicated IAM implementation instance (lightweight key-cape or expanded Keycloak) rather than sharing the platform's, for isolation/scale/performance — not "any tenant that happens to have users."</li></ol>
<ol><li><strong><code>tenant-engine</code> is the canonical owner</strong> of tenant-domain facts: tenant existence, grouping (ADR-0013), capability roles (this ADR), plan/ subscription assignment, and — reserved for future design, not built now — guardrail/quota policy. It is a new, separate service, not a module inside <code>user-engine</code>. Its ownership boundary is defined in the companion contract, <code>canon/standards/tenant-engine-boundary-contract_v0.1.md</code>.</li></ol>
<ol><li><strong>Carrying mechanism: hybrid cache + live re-validation.</strong><code>tenant-engine</code> is the single source of truth. <code>key-cape</code> stamps a cached, optional <code>tenant_roles</code> claim onto issued tokens at issuance time, sourced from <code>tenant-engine</code> (added to the IAM Profile as a new optional claim — <code>canon/standards/iam-profile_v0.3.md</code>). Consumers may trust the cached claim for ordinary decisions. <code>flex-auth</code><strong>MUST</strong> re-validate live against <code>tenant-engine</code> — never trust the cached claim alone — before authorizing privileged or high-stakes actions, using the same threshold class the profile already defines for <code>assurance.level >= aal2</code> (privileged, destructive, platform-root, secret, credential-vending flows). This bounds staleness risk for ordinary actions to a token's short lifetime (5–30 minutes for service/agent tokens, per the profile's Token Lifecycle table) while guaranteeing freshness exactly where it matters most.</li></ol>
<ol><li><strong>Role governance is plan-linked.</strong> Granting a role is normally a consequence of a tenant's payment-plan state in <code>tenant-engine</code>, not a separate manual workflow — starting with <code>IAM</code>. <code>tenant-engine</code> records which plan grants which role(s); <code>adaptive-pricing</code> remains the source of plan/pricing-model <em>definitions</em>, <code>tenant-engine</code> owns the tenant's <em>current</em> plan/subscription assignment, referenced by id, never duplicated locally. Whether <code>VEN</code> needs an approval gate beyond payment (reselling access carries legal/compliance exposure a payment alone doesn't cover) is <strong>not resolved by this ADR</strong> — left to <code>tenant-engine</code>'s own workplan.</li></ol>
<ol><li><strong>Trial tenants may hold any capability role, unrestricted.</strong> The <code>trial</code> grouping's purpose (showcase, test, explore) requires demonstrating every role. Safety is enforced through resource guardrails instead: trial tenants default to a <strong>spend budget of zero</strong>, with entity and action count limits to follow. Guardrail policy design (exact limits, enforcement point, override process) is real, near-term future work, reserved as <code>tenant-engine</code>'s to own once designed — not specified by this ADR.</li></ol>
<ol><li><strong>Grouping and capability role are independent axes</strong> recorded on the same tenant record in <code>tenant-engine</code>. Neither constrains the other except where a future guardrail policy explicitly says so.</li></ol>
</section>
<sectionid="consequences"><h2>Consequences</h2>
<ul><li><code>canon/standards/iam-profile_v0.3.md</code> adds the optional <code>tenant_roles</code> claim and folds in ADR-0013's tenant-identifier vocabulary update (both non-breaking per ADR-0011's own minor-version rule — no existing implementation is invalidated by either change). Supersedes <code>iam-profile_v0.2.md</code>.</li><li><code>tenant-engine</code> becomes a new repository with its own workplans (Bernd). <code>canon/standards/tenant-engine-boundary-contract_v0.1.md</code> defines its ownership boundary now, before code exists — the same sequencing <code>user-engine</code>'s contract followed.</li><li><code>flex-auth</code> policy packages gating high-stakes actions must add a <code>tenant-engine</code> live-lookup step; they cannot trust <code>tenant_roles</code> alone for those decisions.</li><li><code>key-cape</code> needs a <code>tenant-engine</code> integration at token-issuance time to source the cached claim — tracked in <code>key-cape</code>'s own workplans, not here.</li><li><code>docs/platform-identity-security-architecture.md</code>'s Tenant Model section is updated to reflect the grouping + role split and <code>tenant-engine</code>'s role (companion change alongside this ADR).</li><li>Guardrail/quota policy is named as required near-term work and given an owner (<code>tenant-engine</code>), but is explicitly not designed by this ADR.</li></ul>
<p>Rejected: staleness would be unbounded within a token's lifetime for genuinely high-stakes actions. A stale <code>VEN</code> grant surviving a plan downgrade or cancellation is not an acceptable risk for money-movement or credential-vending flows — exactly the class the profile already treats as requiring the strongest assurance.</p>
<h3>Registry-only, no cached claim</h3>
<p>Rejected: every ordinary request would pay a <code>tenant-engine</code> round-trip even for non-critical checks, adding latency and a hard runtime dependency for every consumer, not just the ones handling privileged actions.</p>
<h3>Restrict role eligibility by tenant grouping (e.g., <code>trial</code> cannot hold <code>VEN</code>)</h3>
<p>Rejected per Bernd's direction: <code>trial</code> tenants exist specifically to showcase every role. Resource guardrails are the intended safety mechanism instead, keeping the two axes (grouping, role) independent.</p>
<p>Rejected. <code>user-engine</code>'s own boundary contract scopes it to consuming tenant identifiers and storing tenant-<em>scoped</em> user records, not owning tenant-as-an-entity facts. A dedicated service avoids coupling a security-critical, high-frequency lookup (used by <code>flex-auth</code> on every privileged decision, and by <code>key-cape</code> on every token issuance) to <code>user-engine</code>'s much larger surface (registration flows, factor models, family dataspace onboarding) that has nothing to do with tenant capability state.</p>
</section>
<sectionid="follow-up"><h2>Follow-Up</h2>
<ul><li><code>tenant-engine</code> repository creation and its own workplan (Bernd).</li><li><code>key-cape</code> integration: source <code>tenant_roles</code> from <code>tenant-engine</code> at token issuance.</li><li><code>flex-auth</code> policy package updates: live <code>tenant-engine</code> re-validation gate for privileged actions.</li><li>Guardrail/quota policy design for <code>trial</code> (and eventually all) tenants: spend limits, entity/action count limits, enforcement point, override process.</li><li>Resolve whether <code>VEN</code> needs an approval gate beyond payment-plan state.</li></ul>