The security layer model is accepted at v0.7. INTENT.md now declares Engine / PIP in this repository's own voice (TEN-DEC-2026-001), SCOPE.md is brought current with shipped APIs, and the intent/scope/implementation review is history/2026-08-29-security-layer-intent-scope-review.md. TEN-IN-0003 is absorbed by the declaration. Approvals stay with approval-engine. TEN-IN-0001 and TEN-IN-0002 promote into TEN-WP-0011. Assistant: grok Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
260 lines
13 KiB
Markdown
260 lines
13 KiB
Markdown
---
|
|
repo: tenant-engine
|
|
updated: "2026-08-29"
|
|
layer: Engine
|
|
role: PIP
|
|
standard: netkingdom-security-layer-model
|
|
standard_version: "0.7"
|
|
companion: net-kingdom/SECURITY-COMPANION.md
|
|
---
|
|
|
|
# INTENT
|
|
|
|
> This file captures **why this repository exists**, the **direction it is
|
|
> moving toward**, and the **kind of system it is meant to become**.
|
|
> It is intentionally **aspirational and stable**, not a description of
|
|
> current implementation. See `SCOPE.md` for what is implemented today.
|
|
|
|
> **Layer: Engine. Role: PIP.** This is the declaration required by
|
|
> NetKingdom Security Layer Model v0.7 §11 and the working companion v0.2.
|
|
> It is this repository's own voice, not a catalog row or a review note
|
|
> written about us. The statute is
|
|
> `net-kingdom/canon/standards/security-layer-model_v0.7.md`; the companion
|
|
> is `net-kingdom/SECURITY-COMPANION.md`. On disagreement the statute
|
|
> governs, and a disagreement is a finding for `gate-house`.
|
|
>
|
|
> **The test we pass, and the role we hold.** The same authoritative tenant
|
|
> state always yields the same result. That is why we are an Engine, and why
|
|
> we are a **PIP**: we supply tenant-as-an-entity facts as claims a decision
|
|
> consumes. We are not a PDP. `access-engine` (`flex-auth` until the governed
|
|
> rename) is the only policy decision point in the estate. A new engine is a
|
|
> PIP unless the statute is amended; we do not become a second one by
|
|
> compiling, caching, or resolving anything that determines an outcome.
|
|
>
|
|
> **The binding rule, read from this side.** Staff never touches Tooling
|
|
> directly. It acts only through Engine APIs. This repository *is* one of
|
|
> those APIs. We do not hold a client for a catalogued Tooling-layer system
|
|
> (`key-cape`, OpenBao). Persistence we own (PostgreSQL in production, SQLite
|
|
> in development) is uncatalogued infrastructure, listed so the check is
|
|
> total, not so it can be mistaken for a Tooling contact.
|
|
>
|
|
> **Writes are PEP-shaped; reads are PIP.** Mutating a tenant, a role grant,
|
|
> a plan assignment, a grouping, or a guardrail is a protected side effect.
|
|
> It proceeds only with a decision from `access-engine` identifying the
|
|
> request, or with a recorded application of a published unreachable-engine
|
|
> stance — and that stance, for this engine, is fail-closed. Returning a
|
|
> tenant fact is not a side effect; it is the PIP job. We never cache an
|
|
> authorization decision, and we never treat a compiled ceiling or a cached
|
|
> `tenant_roles` claim as one.
|
|
>
|
|
> **Approvals are not ours.** The reserved guardrail/quota concern was
|
|
> briefly considered as a home for organizational approval lifecycle before
|
|
> `approval-engine` was seeded. We contest that placement. A guardrail is a
|
|
> safety ceiling — a PIP fact `access-engine` joins with observed
|
|
> consumption. An approval is a durable, authenticated, consumable object
|
|
> owned by `approval-engine` (§4, §9.4) and consumed as a claim. Tenant
|
|
> governance answers *what this tenant is and how far it may go*. It does
|
|
> not mint, store, or evaluate approvals.
|
|
|
|
---
|
|
|
|
## One-liner
|
|
|
|
**Canonical PIP for tenant-as-an-entity facts: existence, onboarding
|
|
grouping, capability roles, plan assignment, and guardrail ceilings —
|
|
consumed as claims, never as decisions.**
|
|
|
|
---
|
|
|
|
## Purpose
|
|
|
|
`tenant-engine` exists to be the single, canonical source of truth for what a
|
|
**tenant** is on the NetKingdom platform — its existence, its onboarding
|
|
grouping, its capability roles, its plan/subscription assignment, and the
|
|
safety ceilings that bind it — separate from who its *users* are
|
|
(`user-engine`), who *authenticates* it (`key-cape`/Keycloak), who
|
|
*authorizes* actions against it (`access-engine` / `flex-auth`), what it
|
|
*pays* for a given plan (`adaptive-pricing`), and whether an act has been
|
|
*approved* (`approval-engine`).
|
|
|
|
Full ownership boundary, formally:
|
|
`net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`. Where
|
|
that contract and this file disagree after the layer-model recut, the
|
|
statute wins, this file records the intended state, and the contract is
|
|
amended — not the other way around.
|
|
|
|
## Why This Exists
|
|
|
|
No service in the fleet owns tenant-as-an-entity facts as a queryable
|
|
resource. `user-engine`'s own boundary contract explicitly scopes it to
|
|
*consuming* tenant identifiers and *storing records scoped by* a tenant —
|
|
not owning the tenant record itself. The IAM Profile defines the *shape* of
|
|
the `tenant` claim, not a live directory of which tenants exist, what they
|
|
are allowed to do, or what plan they are on.
|
|
|
|
That gap became concrete during Binky Hedgehog GmbH's onboarding as the
|
|
platform's first tenant outside `tenant:coulomb`
|
|
(`key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md`): a
|
|
tenant's **capability roles** (`PLTF`/`IAM`/`VEN`/`CUS`, non-exclusive — a
|
|
tenant can hold several at once, and its roles can change independently of
|
|
how it was onboarded) needed a real owner, a real carrying mechanism, and a
|
|
real governance model (role grants are usually a consequence of a paid
|
|
plan). See `net-kingdom/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md`
|
|
and `ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md`.
|
|
|
|
The layer model now names the same gap more sharply. `access-engine` must
|
|
consume tenant facts as claims. A stale `VEN` grant surviving a plan
|
|
cancellation is not an acceptable input to a money-movement or
|
|
credential-vending decision. That is why this engine exists as a live PIP,
|
|
not only as a token-issuance cache.
|
|
|
|
## Primary Utility
|
|
|
|
`tenant-engine` provides:
|
|
|
|
- **Tenant records**: existence, identifier, lifecycle, and current
|
|
onboarding grouping (`trial`/`friendly`/`single`/`small`/`medium`/
|
|
`large`/`enterprise`/`consumer`/`family`/`community`/`association`/
|
|
`agentic`, per ADR-0013). The identifier's grouping segment is historical;
|
|
current grouping is a field, not a parse.
|
|
- **Capability role grants**: `PLTF`/`IAM`/`VEN`/`CUS`, with an audited
|
|
grant/revoke trail linking most grants to a plan-assignment change.
|
|
- **Plan/subscription assignment**: which `adaptive-pricing` plan a tenant
|
|
currently holds, referenced by id — never a duplicated copy of plan terms.
|
|
- **Guardrail/quota policy**: spend, entity-count, and action-count
|
|
ceilings. A guardrail is a safety ceiling, not an entitlement, not a
|
|
meter, and not a decision. `trial`-grouped tenants default to a **zero**
|
|
spend budget.
|
|
- **A cache-read API** `key-cape` calls at token-issuance time to source the
|
|
optional `tenant_roles` token claim (IAM Profile v0.3). The claim is a
|
|
point-in-time cache with the token's lifetime; it is never the live fact.
|
|
- **A live-lookup API** `access-engine` calls synchronously for privileged,
|
|
high-stakes decisions — the case where a cached claim is not trustworthy
|
|
enough. Unavailability on this path fails closed at the consumer, never
|
|
open.
|
|
- **A write API** for grant/revoke/plan/lifecycle/grouping/guardrail
|
|
mutations. Writes are PEP-shaped: authorization-gated by `access-engine`,
|
|
never self-authorized, never performed without a decision record or a
|
|
recorded fail-closed stance.
|
|
|
|
## Intended Users
|
|
|
|
- `key-cape` / Keycloak, at token issuance time (cache-read API).
|
|
- `access-engine` (`flex-auth`), at authorization-decision time (live-lookup
|
|
API), and as the authorizer of every protected write.
|
|
- Operators and tenant administrators, indirectly, through whatever admin
|
|
surface consumes these APIs (`user-engine`'s operator UI is the first).
|
|
- `adaptive-pricing`, as the source `tenant-engine` references for plan
|
|
*definitions* (not the other way around).
|
|
- Staff repositories and agents, only through these Engine APIs — never by
|
|
reaching the store, and never by treating a callable tool as permission.
|
|
|
|
## Strategic Role
|
|
|
|
`tenant-engine` sits **beside** `user-engine`, not inside it — a deliberate
|
|
bounded-context split, not an oversight. `user-engine`'s aggregate root is
|
|
the *person*; `tenant-engine`'s is the *tenant*. Consumers that only need a
|
|
fast, security-critical tenant-role lookup (`access-engine` on every
|
|
privileged decision, `key-cape` on every token issuance) should never need
|
|
to depend on `user-engine`'s much larger surface (registration flows, factor
|
|
models, family dataspace onboarding) to get it.
|
|
|
|
In the layer model this is the PIP half of that split. Tenancy context is an
|
|
**input to the decision, not a decision**. Compiling an effective guardrail,
|
|
resolving current grouping, or listing active roles does not decide whether
|
|
an actor may act. `access-engine` does that, joining these claims with
|
|
identity, zone, approval, maturity, and policy. Request-scoped memoization
|
|
inside a single decision is allowed; cross-request caching of live-lookup
|
|
results is not — that would recreate the staleness the live path exists to
|
|
avoid, and it would be compiled data determining an outcome (§6.1).
|
|
|
|
## Responsibility Boundary
|
|
|
|
`tenant-engine` owns tenant-as-an-entity facts. It never re-derives or
|
|
duplicates facts another system already owns, and it never renders a
|
|
decision another engine is responsible for.
|
|
|
|
### tenant-engine Owns
|
|
|
|
- Tenant existence, identifier, lifecycle, and current onboarding grouping.
|
|
- Capability role grants and their audit trail.
|
|
- Plan/subscription assignment (by reference, not by copying plan terms).
|
|
- Guardrail/quota policy: the ceiling, not the meter, not the bill, not the
|
|
allow.
|
|
|
|
### Other Systems Own
|
|
|
|
| Concern | Owner |
|
|
| --- | --- |
|
|
| OIDC discovery, login, token issuance | `key-cape`, Keycloak |
|
|
| Tenant identifier format and role vocabulary | NetKingdom (IAM Profile, ADR-0013, ADR-0014) |
|
|
| Users, profiles, memberships | `user-engine` |
|
|
| Authorization decisions | `access-engine` (`flex-auth`) — the only PDP |
|
|
| Approval objects | `approval-engine` |
|
|
| Pricing-model / plan definitions | `adaptive-pricing` |
|
|
| Runtime secrets | OpenBao / `secrets-engine` |
|
|
| Audit event custody | `audit-core` |
|
|
| Payment processing | Not yet identified — `tenant-engine` records plan assignment, not payment execution |
|
|
| Containment / actuation | Unowned and held at zero estate-wide (§9.2). This engine does not acquire it. |
|
|
|
|
## Non-Goals
|
|
|
|
- Not an identity provider. Never issues, verifies, or refreshes tokens.
|
|
- Not a policy decision point. Never makes an authorization decision itself
|
|
— it answers queries `access-engine` issues, and it mutates only when
|
|
`access-engine` has allowed the mutation.
|
|
- Not an approval engine. Never stores, supersedes, or consumes an approval
|
|
object.
|
|
- Not a billing processor. Records plan assignment, not payment execution.
|
|
- Not a pricing-model engine. References `adaptive-pricing` plan ids.
|
|
- Not the user/membership domain. Never stores user accounts or profiles.
|
|
- Not a meter. Never observes spend or counts actions.
|
|
- Not Tooling. Persistence it owns is infrastructure for its own facts, not
|
|
a Tooling-layer service other layers may call.
|
|
- Not a place to plan around production observation or automatic
|
|
containment. Companion §10: nothing is observed in production, and
|
|
nothing can be contained automatically. Neither fact is this engine's to
|
|
close.
|
|
|
|
## Evidence Bound
|
|
|
|
Every mutation emits a domain event. That trail is **attributive** unless a
|
|
named control starts branching on an event's presence, in which case that
|
|
class becomes **load-bearing** and must emit atomically, locally, and with
|
|
reconciliation or a heartbeat (§9.6).
|
|
|
|
The archive — wherever it lives — proves that the records it holds were not
|
|
altered or truncated after arrival. It does not prove that an event
|
|
happened, and absence of a record is not evidence of non-occurrence. Local
|
|
events that share a database and runtime credential with the facts they
|
|
attest to cannot be described as independent custody. Independent custody
|
|
belongs to `audit-core`.
|
|
|
|
## Governing Principle
|
|
|
|
This repository should stay focused on the purpose above. Work that changes
|
|
its authority, ownership boundaries, layer, or operational promises should
|
|
be captured in a workplan before implementation, and should stay consistent
|
|
with the security layer model and
|
|
`net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`. If
|
|
the three drift: the statute wins, then the contract, then this file.
|
|
|
|
## Related
|
|
|
|
- `SCOPE.md` — what is implemented today, and what is not
|
|
- `history/2026-08-29-security-layer-intent-scope-review.md` — scope vs
|
|
intent vs implementation after v0.7 acceptance
|
|
- `workplans/TEN-WP-0011-security-layer-conformance.md` — the evolution
|
|
this declaration requires
|
|
- `decisions/decisions.md` `TEN-DEC-2026-001` — layer declaration and the
|
|
approvals contest
|
|
- `docs/flex-auth-integration.md` — write-API `WriteAuthorizer` seam
|
|
- `docs/tenant-guardrail-policy.md` — ceiling contract
|
|
- `docs/tenant-lifecycle-api.md` — update, retire, reactivate
|
|
- `net-kingdom/SECURITY-COMPANION.md` — start here for the layer rules
|
|
- `net-kingdom/canon/standards/security-layer-model_v0.7.md` — the statute
|
|
- `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`
|
|
- `net-kingdom/canon/standards/iam-profile_v0.3.md` — the `tenant_roles`
|
|
claim and carrying mechanism this service implements
|
|
- `net-kingdom/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md`
|
|
- `net-kingdom/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md`
|