Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
9.9 KiB
| id | type | title | domain | status | version | created | updated | scope | adr | related | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| netkingdom-tenant-engine-boundary-contract | standard | NetKingdom Tenant Engine Boundary Contract v0.1 | netkingdom | accepted | 0.1 | 2026-07-23 | 2026-08-22 | tenant-domain-boundaries |
|
|
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, current grouping, name/slug) | tenant-engine |
Canonical owner | Current grouping must be one of ADR-0013's taxonomy, or the reserved ungrouped platform/coulomb classifications; the identifier segment is historical and must not be used as current grouping |
| 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; the identifier is immutable, including its historical grouping segment; 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 |
The tenant identifier and current grouping are intentionally independent after
creation. tenant-engine MAY change the record's grouping through an audited
mutation without renaming tenant_id. Reads and domain events expose current
grouping explicitly. No API, guardrail, pricing, or authorization consumer may
recover current grouping by parsing tenant_id.
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 intenant-engine; manual grants are the exception path and must always recordgranted_by. trial-grouped tenants may hold any role without a plan requirement —grant_reason: platform_defaultis valid for trial-eligible roles with noplan_id(ADR-0014).- Every grant or revocation emits a domain event. After a revocation,
key-cape's previously-cachedtenant_rolesclaim 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-capecalls at token-issuance time to source the cachedtenant_rolesclaim; - a live-lookup API
flex-authcalls 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-authitself, for grant/ revoke/plan-assignment mutations —tenant-enginedoes not self-authorize its own writes.
Performance model:
- the live-lookup API must be fast and highly available, since
flex-authis required to call it synchronously on the request path for privileged actions; slow or unavailabletenant-enginelookups fail closed (deny), never fail open. The adapter existing without a production caller does not satisfy this contract and does not permit either service to claim I3; - the cached-claim read API used by
key-capemay be slower, since it only runs at token issuance, not per-request; - request-scoped memoization within a single
flex-authdecision 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
trialtenant holdingVENis still bound bytrialguardrails) are all open design questions fortenant-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-enginedoes 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-enginedoes not define pricing models, tiers, or terms —adaptive-pricingdoes.tenant-enginedoes not store user accounts, profiles, or memberships —user-enginedoes.tenant-enginedoes not make authorization decisions —flex-authdoes.