ADR-0014 + tenant-engine boundary contract + IAM Profile v0.3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 5s

Ratifies the tenant capability-role model (PLTF/IAM/VEN/CUS, non-exclusive,
independent of ADR-0013's grouping axis), a hybrid carrying mechanism
(tenant-engine authoritative, key-cape caches a tenant_roles claim at
issuance, flex-auth re-validates live for aal2-class decisions), and
tenant-engine as a new, separate service owning tenant existence, grouping,
capability roles, and plan/subscription assignment -- not a module inside
user-engine, whose own boundary contract already scopes it to consuming
tenant identifiers, not owning them.

canon/standards/tenant-engine-boundary-contract_v0.1.md defines that
ownership boundary before the repo exists, mirroring how
user-engine-boundary-contract_v0.1.md was sequenced.

canon/standards/iam-profile_v0.3.md (minor version per ADR-0011's own
governance -- optional claim addition, no breaking change) adds the
tenant_roles claim, folds in ADR-0013's tenant-identifier vocabulary, and
documents the live-revalidation requirement. docs/platform-identity-
security-architecture.md's Tenant Model section and SCOPE.md's canonical
spec pointer updated to match; other historical citations of v0.2 left as
version-pinned references, not bulk-updated.

Records Bernd's trial-tenant policy: trial-grouped tenants may hold any
capability role (showcase/test/explore), with safety enforced through
tenant-engine-owned resource guardrails (spend limits, entity/action
counts) rather than role gating -- guardrail design is reserved, explicitly
not specified by this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-23 21:45:37 +02:00
parent a18f83c4af
commit 9026d7f904
5 changed files with 838 additions and 7 deletions

View file

@ -0,0 +1,171 @@
# ADR-0014 - Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership
**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."
2. **`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`.
3. **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 (530 minutes for service/agent tokens, per the profile's
Token Lifecycle table) while guaranteeing freshness exactly where it
matters most.
4. **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.
5. **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.
6. **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.

View file

@ -1,7 +1,7 @@
# Platform Identity and Security Architecture
Status: implemented architecture baseline for NetKingdom/Railiance/Coulomb
Date: 2026-05-24
Date: 2026-05-24 (Tenant Model updated 2026-07-23, ADR-0013/ADR-0014)
## Purpose
@ -296,15 +296,39 @@ Examples of platform-root actions:
Every protected resource should belong to a tenant or to the platform
control plane.
Suggested identifiers:
Tenant identifiers are `tenant:<grouping>:<name>`, where `<grouping>` is an
onboarding-risk / entity-shape classification ratified by ADR-0013
(`trial`, `friendly`, `single`, `small`, `medium`, `large`, `enterprise`,
`consumer`, `family`, `community`, `association`, `agentic`):
```text
tenant:platform # platform control plane resources
tenant:coulomb # first internal/reference tenant
tenant:sandbox:<name> # sandbox tenants
tenant:customer:<name> # future customer tenants
tenant:platform # platform control plane — reserved, ungrouped
tenant:coulomb # first internal/reference tenant — reserved, ungrouped
tenant:friendly:binky # first tenant onboarded under this taxonomy
tenant:<grouping>:<name> # general shape for tenants onboarded going forward
```
`tenant:platform` and `tenant:coulomb` predate the taxonomy and stay
reserved outside it — see `canon/standards/iam-profile_v0.3.md`'s Tenant
Claim section for the full rationale.
A tenant separately holds **capability roles**`PLTF`, `IAM`, `VEN`, `CUS`
(ADR-0014), non-exclusive — describing what it *does* on the platform,
independent of its grouping (what kind of entity it *is*). A `trial`-grouped
tenant may hold any capability role, since `trial` exists to showcase every
role; safety instead comes from tenant-level resource guardrails (spend
limits, entity/action counts — reserved, not yet designed).
`tenant-engine` (new service, `canon/standards/tenant-engine-boundary-contract_v0.1.md`)
is the canonical owner of both tenant grouping and capability-role facts,
plus plan/subscription assignment. `key-cape` caches a tenant's roles on
issued tokens (`tenant_roles` claim) for ordinary decisions; `flex-auth`
re-validates live against `tenant-engine` before authorizing privileged,
destructive, platform-root, or otherwise `aal2`-class actions — the same
threshold the assurance model below already uses. `user-engine` continues
to own users and memberships *scoped by* a tenant, not the tenant record
itself.
Tenant membership and platform membership are distinct. A subject may be
an administrator in `tenant:coulomb` without being a platform operator.