Status: Accepted Date: 2026-07-23 Deciders: Bernd Worsch, Codex

## Context

ADR-0013 introduced the tenant onboarding grouping taxonomy (`trial`/`friendly`/`single`/.../`agentic`), deliberately orthogonal to a separate, unratified capability-role model sketched in `docs/princedom-isolation-exploration.md`: `PLTF` (operates the platform), `IAM` (organizes its own users/auth/secrets), `VEN` (provides apps/services to others), `CUS` (consumes apps/services from `PLTF` or `VEN` tenants) — non-exclusive, a tenant may hold several at once.

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 `roles` claim — which is a per-subject claim ("coarse identity roles" for the human/service/agent holding the token), a different concept from a per-tenant capability fact. Conflating the two would be a category error: `roles: ["VEN"]` on a token would ambiguously mean "this subject has vendor-role" vs. "this subject's tenant is a vendor."

No existing service owns tenant-as-an-entity facts (existence, grouping, capability roles, plan/subscription state) as a queryable resource. `user-engine`'s own boundary contract (`canon/standards/user-engine-boundary-contract_v0.1.md`) explicitly scopes `user-engine` to consuming tenant identifiers and storing tenant-scoped records, not owning tenant identity or capability facts.

Bernd's direction (2026-07-23):

- 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.
- Role grants are usually tied to a payment plan — most concretely, `IAM` means the tenant has its own dedicated key-cape/Keycloak instance for isolation, scale, and performance, which is itself a paid capability.
- `trial`-grouped tenants may hold any 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.
- A new service, `tenant-engine`, will be built (Bernd) as the owner of this domain, organized beside `user-engine` rather than inside it — smaller, single-purpose services are easier to reason about and drift less, matching the fleet's existing convention (`activity-core`, `audit-core`, `user-engine`, and others).

## Decision

1. Capability-role vocabulary ratified as core NetKingdom vocabulary: `PLTF`, `IAM`, `VEN`, `CUS`, non-exclusive. `IAM` 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."

1. `tenant-engine` is the canonical owner 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 `user-engine`. Its ownership boundary is defined in the companion contract, `canon/standards/tenant-engine-boundary-contract_v0.1.md`.

1. Carrying mechanism: hybrid cache + live re-validation. `tenant-engine` is the single source of truth. `key-cape` stamps a cached, optional `tenant_roles` claim onto issued tokens at issuance time, sourced from `tenant-engine` (added to the IAM Profile as a new optional claim — `canon/standards/iam-profile_v0.3.md`). Consumers may trust the cached claim for ordinary decisions. `flex-auth` MUST re-validate live against `tenant-engine` — never trust the cached claim alone — before authorizing privileged or high-stakes actions, using the same threshold class the profile already defines for `assurance.level >= aal2` (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.

1. Role governance is plan-linked. Granting a role is normally a consequence of a tenant's payment-plan state in `tenant-engine`, not a separate manual workflow — starting with `IAM`. `tenant-engine` records which plan grants which role(s); `adaptive-pricing` remains the source of plan/pricing-model definitions, `tenant-engine` owns the tenant's current plan/subscription assignment, referenced by id, never duplicated locally. Whether `VEN` needs an approval gate beyond payment (reselling access carries legal/compliance exposure a payment alone doesn't cover) is not resolved by this ADR — left to `tenant-engine`'s own workplan.

1. Trial tenants may hold any capability role, unrestricted. The `trial` grouping's purpose (showcase, test, explore) requires demonstrating every role. Safety is enforced through resource guardrails instead: trial tenants default to a spend budget of zero, 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 `tenant-engine`'s to own once designed — not specified by this ADR.

1. Grouping and capability role are independent axes recorded on the same tenant record in `tenant-engine`. Neither constrains the other except where a future guardrail policy explicitly says so.

## Consequences

- `canon/standards/iam-profile_v0.3.md` adds the optional `tenant_roles` 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 `iam-profile_v0.2.md`.
- `tenant-engine` becomes a new repository with its own workplans (Bernd). `canon/standards/tenant-engine-boundary-contract_v0.1.md` defines its ownership boundary now, before code exists — the same sequencing `user-engine`'s contract followed.
- `flex-auth` policy packages gating high-stakes actions must add a `tenant-engine` live-lookup step; they cannot trust `tenant_roles` alone for those decisions.
- `key-cape` needs a `tenant-engine` integration at token-issuance time to source the cached claim — tracked in `key-cape`'s own workplans, not here.
- `docs/platform-identity-security-architecture.md`'s Tenant Model section is updated to reflect the grouping + role split and `tenant-engine`'s role (companion change alongside this ADR).
- Guardrail/quota policy is named as required near-term work and given an owner (`tenant-engine`), but is explicitly not designed by this ADR.

## Alternatives Considered

### Token-claim-only, no live re-validation

Rejected: staleness would be unbounded within a token's lifetime for genuinely high-stakes actions. A stale `VEN` 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.

### Registry-only, no cached claim

Rejected: every ordinary request would pay a `tenant-engine` round-trip even for non-critical checks, adding latency and a hard runtime dependency for every consumer, not just the ones handling privileged actions.

### Restrict role eligibility by tenant grouping (e.g., `trial` cannot hold `VEN`)

Rejected per Bernd's direction: `trial` tenants exist specifically to showcase every role. Resource guardrails are the intended safety mechanism instead, keeping the two axes (grouping, role) independent.

### Put tenant-role/plan storage inside `user-engine`

Rejected. `user-engine`'s own boundary contract scopes it to consuming tenant identifiers and storing tenant-scoped user records, not owning tenant-as-an-entity facts. A dedicated service avoids coupling a security-critical, high-frequency lookup (used by `flex-auth` on every privileged decision, and by `key-cape` on every token issuance) to `user-engine`'s much larger surface (registration flows, factor models, family dataspace onboarding) that has nothing to do with tenant capability state.

## Follow-Up

- `tenant-engine` repository creation and its own workplan (Bernd).
- `key-cape` integration: source `tenant_roles` from `tenant-engine` at token issuance.
- `flex-auth` policy package updates: live `tenant-engine` re-validation gate for privileged actions.
- Guardrail/quota policy design for `trial` (and eventually all) tenants: spend limits, entity/action count limits, enforcement point, override process.
- Resolve whether `VEN` needs an approval gate beyond payment-plan state.