tenant-engine/INTENT.md
tegwick 76677bffc5 Declare Engine/PIP and open TEN-WP-0011 for v0.7 conformance
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
2026-08-29 11:57:37 +02:00

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`