NK-ADR-0014 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership

Source: net-kingdom · docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

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.