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>
163 lines
9.3 KiB
Markdown
163 lines
9.3 KiB
Markdown
---
|
|
id: netkingdom-tenant-engine-boundary-contract
|
|
type: standard
|
|
title: "NetKingdom Tenant Engine Boundary Contract v0.1"
|
|
domain: netkingdom
|
|
status: accepted
|
|
version: "0.1"
|
|
created: "2026-07-23"
|
|
updated: "2026-07-23"
|
|
scope: tenant-domain-boundaries
|
|
adr:
|
|
- docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md
|
|
- docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md
|
|
- docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md
|
|
related:
|
|
- canon/standards/iam-profile_v0.3.md
|
|
- canon/standards/user-engine-boundary-contract_v0.1.md
|
|
- canon/standards/credential-management_v0.2.md
|
|
- docs/platform-identity-security-architecture.md
|
|
---
|
|
|
|
# NetKingdom Tenant Engine Boundary Contract v0.1
|
|
|
|
## Purpose
|
|
|
|
This contract defines how `tenant-engine` integrates into the NetKingdom
|
|
landscape without duplicating identity, authorization, user/membership,
|
|
pricing, deployment, or secret-custody responsibilities that other services
|
|
already own.
|
|
|
|
`tenant-engine` owns **tenant-as-an-entity** facts: existence, identifier
|
|
and grouping (ADR-0013), capability roles (ADR-0014), plan/subscription
|
|
assignment, and — reserved, not yet implemented — guardrail/quota policy.
|
|
It is not an identity provider, not a policy decision point, not a billing
|
|
processor, not a pricing-model engine, and not the user/membership domain.
|
|
NetKingdom owns this cross-repo boundary contract; `tenant-engine` and its
|
|
adapters must preserve the ownership rules below even in local/standalone
|
|
deployments.
|
|
|
|
## Ownership Model
|
|
|
|
| Concern | Owner | tenant-engine responsibility |
|
|
| --- | --- | --- |
|
|
| OIDC discovery, login, token issuance | `key-cape`, Keycloak | Supply `tenant_roles` via a read API at issuance time; never issues tokens itself |
|
|
| Tenant identifier format and grouping vocabulary | NetKingdom (`iam-profile_v0.3.md`, ADR-0013) | Store and validate tenant records against the ratified vocabulary; does not redefine it |
|
|
| Tenant capability-role vocabulary | NetKingdom (ADR-0014) | Store role grants against the ratified vocabulary (`PLTF`/`IAM`/`VEN`/`CUS`); does not redefine it |
|
|
| Users, profiles, memberships | `user-engine` | References `tenant_id` as an external key only; never stores user or membership records |
|
|
| Fine-grained authorization decisions | `flex-auth` | Serves as an authoritative data source `flex-auth` queries live for high-stakes checks; makes no authorization decisions itself |
|
|
| Pricing-model / plan definitions | `adaptive-pricing` | References plan definitions by id; never forks or duplicates plan terms locally |
|
|
| Runtime secrets | OpenBao / `secrets-engine` | Tenant-scoped OpenBao paths (e.g. `tenants/<name>/...`) reference `tenant-engine`'s tenant registry as the naming authority; `tenant-engine` never stores secret material |
|
|
| Deployment mechanics | Railiance | Publishes readiness signals only |
|
|
| Credential routing/discovery | `ops-warden` | `tenant-engine`'s own service credentials (if any) are routed the same way as any other lane; `tenant-engine` is not a routing authority |
|
|
|
|
## Source-Of-Truth Matrix
|
|
|
|
| Resource kind | Source of truth | tenant-engine relation | Boundary rule |
|
|
| --- | --- | --- | --- |
|
|
| Tenant record (existence, grouping, name/slug) | `tenant-engine` | Canonical owner | Grouping value must be one of ADR-0013's taxonomy, or the reserved ungrouped `platform`/`coulomb` identifiers |
|
|
| Tenant capability roles | `tenant-engine` | Canonical owner | Non-exclusive; grant/revoke are audited mutations (see Grant Contract below), never free-form field updates |
|
|
| Plan/subscription assignment | `tenant-engine` | Canonical owner | References an `adaptive-pricing` plan id; does not define plan terms |
|
|
| Pricing-model / plan definitions | `adaptive-pricing` | Consume by reference only | `tenant-engine` must not cache plan terms beyond what's needed to resolve which roles a plan currently grants |
|
|
| Guardrail/quota policy (spend limits, entity/action counts) | `tenant-engine` (reserved) | Canonical owner once designed | Not implemented by this contract version — reserved namespace only, see Guardrail Policy section |
|
|
| `tenant_roles` token claim | `tenant-engine` (live) / `key-cape` (cached copy) | `tenant-engine` is authoritative; the token claim is a point-in-time cache | Privileged/high-stakes decisions MUST re-query `tenant-engine` live — see IAM Profile v0.3, "Tenant Roles" section — never trust the cached claim alone |
|
|
| Tenant identifier claim shape (`tenant`) | NetKingdom / IAM Profile contract | `tenant-engine` validates against it; does not mint the claim format | The wire format stays owned by the profile; `tenant-engine` owns which concrete tenant values currently exist |
|
|
| User/membership records scoped by a tenant | `user-engine` | No relation | `tenant_id` is the only key shared between the two services; `tenant-engine` never stores or reads user data |
|
|
| Authorization decisions | `flex-auth` | Data source only | `tenant-engine` never enforces access itself; it answers queries `flex-auth`'s policy packages issue |
|
|
|
|
## Tenant Role & Plan Grant Contract
|
|
|
|
Every role grant or revocation is an audited mutation, not a direct field
|
|
write, and must carry:
|
|
|
|
| Field | Requirement |
|
|
| --- | --- |
|
|
| `tenant_id` | Stable tenant identifier |
|
|
| `role` | One of `PLTF`, `IAM`, `VEN`, `CUS` |
|
|
| `grant_reason` | `plan_assignment`, `manual_grant`, or `platform_default` |
|
|
| `plan_id` | `adaptive-pricing` plan id; required when `grant_reason: plan_assignment`, otherwise null |
|
|
| `granted_by` | Subject or system that performed the grant |
|
|
| `granted_at` | Timestamp |
|
|
| `revoked_at` | Nullable timestamp |
|
|
| `correlation_id` | Request/import correlation id |
|
|
|
|
Rules:
|
|
|
|
- Plan-linked roles (starting with `IAM`) are granted and revoked
|
|
automatically as a consequence of plan-assignment changes in
|
|
`tenant-engine`; manual grants are the exception path and must always
|
|
record `granted_by`.
|
|
- `trial`-grouped tenants may hold any role without a plan requirement —
|
|
`grant_reason: platform_default` is valid for trial-eligible roles with no
|
|
`plan_id` (ADR-0014).
|
|
- Every grant or revocation emits a domain event. After a revocation,
|
|
`key-cape`'s previously-cached `tenant_roles` claim is considered stale
|
|
until the token naturally expires — v0.1 relies on the profile's short
|
|
token lifetimes for bounded staleness, not push invalidation.
|
|
- Cross-tenant grants are rejected; a grant always targets exactly one
|
|
`tenant_id`.
|
|
|
|
## Authorization Contract
|
|
|
|
`tenant-engine` is a data source, not a policy enforcement point.
|
|
`flex-auth` remains the policy decision point for authorization outcomes;
|
|
`key-cape` remains the token issuer. `tenant-engine` exposes:
|
|
|
|
- a **read API** `key-cape` calls at token-issuance time to source the
|
|
cached `tenant_roles` claim;
|
|
- a **live-lookup API** `flex-auth` calls for high-stakes decisions
|
|
(`assurance.level >= aal2`-class actions, per IAM Profile v0.3), returning
|
|
current roles, grouping, and plan state;
|
|
- a **write API**, authorization-gated by `flex-auth` itself, for grant/
|
|
revoke/plan-assignment mutations — `tenant-engine` does not self-authorize
|
|
its own writes.
|
|
|
|
Performance model:
|
|
|
|
- the live-lookup API must be fast and highly available, since `flex-auth`
|
|
calls it synchronously on the request path for privileged actions; slow
|
|
or unavailable `tenant-engine` lookups fail closed (deny), never fail open;
|
|
- the cached-claim read API used by `key-cape` may be slower, since it only
|
|
runs at token issuance, not per-request;
|
|
- request-scoped memoization within a single `flex-auth` decision is
|
|
allowed; cross-request caching of live-lookup results is not, since it
|
|
would recreate the staleness risk the live path exists to avoid.
|
|
|
|
## Guardrail Policy (Reserved, Not Implemented)
|
|
|
|
Named here so the shape exists before it's designed, matching ADR-0014:
|
|
|
|
- Spend limits — `trial`-grouped tenants default to a **zero** spend budget.
|
|
- Entity count limits — reserved, not yet specified.
|
|
- Action count limits — reserved, not yet specified.
|
|
- Enforcement point, override process, and how guardrails interact with
|
|
capability roles (a `trial` tenant holding `VEN` is still bound by `trial`
|
|
guardrails) are all open design questions for `tenant-engine`'s own
|
|
workplan, not resolved by this contract.
|
|
|
|
## Audit Correlation Contract
|
|
|
|
Every `tenant-engine` mutation (tenant creation, role grant/revoke, plan
|
|
assignment) must create or reference a correlation bundle:
|
|
|
|
| Field | Requirement |
|
|
| --- | --- |
|
|
| `correlation_id` | Stable id propagated across request, decision, audit, and event records |
|
|
| `actor` | IAM Profile actor envelope (who performed the mutation) |
|
|
| `tenant_id` | Tenant affected |
|
|
| `resource` and `action` | e.g. `tenant-engine:role`, `grant` / `revoke` |
|
|
| `authorization_decision_id` | `flex-auth` decision id backing the write |
|
|
| `outbox_event_id` | Event id emitted for the mutation |
|
|
| `change_summary` | Redacted mutation summary |
|
|
|
|
## Non-Goals
|
|
|
|
- `tenant-engine` does not process payments or hold financial instruments —
|
|
it records which plan a tenant is assigned to, sourced from whatever
|
|
system actually processes payment (not yet identified; out of scope for
|
|
this contract).
|
|
- `tenant-engine` does not define pricing models, tiers, or terms —
|
|
`adaptive-pricing` does.
|
|
- `tenant-engine` does not store user accounts, profiles, or memberships —
|
|
`user-engine` does.
|
|
- `tenant-engine` does not make authorization decisions — `flex-auth` does.
|