tenant-engine/SCOPE.md
tegwick 6644ad8402
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 50s
Onboard tenant-engine to the staged-promotion contract
TEN-WP-0008. railiance/app.toml declares criticality=high, empty secrets,
isolated canary, and the live PostgreSQL digest as previous_stable.
Manifests render through kustomize (deploy/ and deploy/canary/). Stage 1
passed. Stage 2/3 Helm-only CLI gap requested as RAIL-BS-IN-0001 rather
than a dummy chart.

Assistant: grok
Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
2026-08-29 14:51:27 +02:00

134 lines
6.9 KiB
Markdown

# SCOPE
> Lightweight boundary for agents and contributors. Full contract:
> `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`.
> Aspirational role: `INTENT.md`. Layer declaration: Engine / PIP
> (`INTENT.md` frontmatter; `TEN-DEC-2026-001`).
---
## One-liner
Canonical PIP of tenant-as-an-entity facts for NetKingdom: existence,
lifecycle, onboarding grouping, capability roles, plan/subscription
assignment, and guardrail ceilings.
---
## Core Idea
`tenant-engine` answers "what is this tenant, what roles does it hold, what
plan is it on, and how far may it go" for two consumers: `key-cape`
(cached, at token issuance) and `access-engine` / `flex-auth` (live, for
high-stakes decisions). It never stores users, issues tokens, evaluates
approvals, or makes authorization decisions. Tenancy context is an input to
the decision, not a decision.
---
## Layer
| Key | Value |
| --- | --- |
| Layer | **Engine** |
| Role | **PIP** |
| Statute | `net-kingdom/canon/standards/security-layer-model_v0.7.md` |
| Companion | `net-kingdom/SECURITY-COMPANION.md` |
| Catalog row | tenant-as-an-entity facts (`§4`) |
| Tooling contacts | none (`key-cape` and OpenBao are not clients of this repo) |
| Non-Tooling clients | PostgreSQL / SQLite (own store); `flex-auth` `POST /v1/check` (Engine API on the write and authorized-read path); State Hub (work records, not a runtime dependency) |
| Write path | PEP-shaped: fail-closed when `access-engine` is unset or unreachable |
| Conformance today | Declaration is in our own voice. Mechanical `layer.yaml` check, persisted decision records, published stance map, and independent evidence custody are **not** yet shipped — see the review and `TEN-WP-0011`. |
---
## In Scope
- Tenant records: identifier, lifecycle (`active` / `retired`), current
onboarding grouping (ADR-0013). Grouping is mutable through an audited
reclassification, not by renaming `tenant_id`.
- Capability role grants/revocations: `PLTF`/`IAM`/`VEN`/`CUS` (ADR-0014),
audited, mostly plan-linked.
- Plan/subscription assignment, referenced by `adaptive-pricing` plan id.
- Guardrail/quota *policy*: spend / entity-count / action-count ceilings,
resolved as a total function of grouping, plan, override, and lifecycle.
Seeded key: `spend.monthly`. `trial` defaults to zero.
- Cache-read API for `key-cape` (token-issuance-time `tenant_roles` claim
source): `GET /tenants/{id}/roles`.
- Live-lookup API for `access-engine` (high-stakes, `aal2`-class
decisions): `GET /tenants/{id}/roles/live`. Store unavailability returns
`503`, never `200` plus empty roles.
- Write API for create / grant / revoke / plan / metadata / retire /
reactivate / grouping / guardrail mutations, authorization-gated by
`flex-auth`. Unset or unreachable authorizer denies.
- Authoritative tenant read with ETag / If-Match / idempotency on
lifecycle and guardrail mutations.
## Out of Scope
- Identity, authentication, MFA, token issuance — `key-cape`/Keycloak.
- Authorization decisions — `access-engine` is the PDP; `tenant-engine` is
a data source it queries, never the other way around.
- Approval objects and approval lifecycle — `approval-engine`. Contested
in `TEN-DEC-2026-001`; this repo will not grow one.
- Users, profiles, memberships — `user-engine`. `tenant_id` is the only key
shared between the two services.
- Pricing-model / plan term definitions — `adaptive-pricing`.
- Payment processing — not yet identified as owned by any repo.
- Runtime secret custody — OpenBao / `secrets-engine`.
- Metering / consumption counters — whoever meters the resource writes
them; this service does not observe spend.
- Rate limiting / traffic shaping — gateway concern, not `action_count`.
- Containment / actuation — unowned and held at zero estate-wide.
- Irreversible operational decisions without human approval.
- Direct Tooling-layer clients — Staff and agents reach tenant facts
through this Engine, not past it.
## Current State
Production service, not a bootstrap. Finished workplans `TEN-WP-0001`
through `TEN-WP-0007`, `TEN-WP-0009`, and `TEN-WP-0010` shipped the domain
model, the three boundary-contract APIs, the `flex-auth` write authorizer,
lifecycle, guardrails, PostgreSQL as the production store, and mutable
grouping. `TEN-WP-0008` (staged-promotion onboarding) is still `ready`,
not done.
| Surface | Shipped | Notes |
| --- | --- | --- |
| Domain model + SQLite/in-memory store | yes | `TEN-WP-0002` |
| Cache-read / live-lookup / write APIs | yes | `TEN-WP-0002` |
| `flex-auth` `WriteAuthorizer` | yes | Fail-closed; `is_allowed()` returns a bool and **discards the decision envelope** |
| Production runtime | yes | `TEN-WP-0004`; image pin in `deploy/base/tenant-engine.yaml` |
| Lifecycle (update / retire / reactivate) | yes | `TEN-WP-0005`; no hard-delete |
| Guardrail policy | yes | `TEN-WP-0006` / `0007`; `docs/tenant-guardrail-policy.md` |
| PostgreSQL production store | yes | `TEN-WP-0009`; deploy mounts `TENANT_ENGINE_DATABASE_URL_FILE` |
| Mutable grouping | yes | `TEN-WP-0010`; identifier segment stays historical |
| Staged promotion | yes (declared) | `TEN-WP-0008`; `railiance/app.toml`; live pin recorded as `previous_stable`. Helm CLI apply is a railiance-bootstrap gap (`RAIL-BS-IN-0001`). |
| Machine-readable `layer.yaml` | yes | `TEN-WP-0011-T01`; check in `scripts/check_layer_conformance.py` |
| Persisted `authorization_decision_id` | yes | `authz_records` plus mutation event payload (`TEN-WP-0011-T02`) |
| Published unreachable-engine stance map | yes | `pep-stance.yaml`, fail-closed, tested equal to shipped behaviour |
| Independent audit-core emission | path shipped, sender pending | Local outbox + POST `/v1/events`; `AUDIT-IN-0002` |
| Bounded event-read interface | yes | `events_for(tenant_id)` only (`TEN-WP-0011-T05`) |
| Claim freshness / input-class lifetime | yes | `pip-claims.yaml` (`TEN-WP-0011-T03`) |
The boundary contract still labels guardrail policy "reserved, not
implemented" and still calls this repo "not a policy enforcement point".
Both sentences are stale: guardrails shipped, and the write path is
PEP-shaped even though this repo is not a PDP. Amendment is a
`net-kingdom` change, requested from `TEN-WP-0011`.
## Getting Oriented
- Start with: `INTENT.md`
- Working companion: `net-kingdom/SECURITY-COMPANION.md`
- Statute: `net-kingdom/canon/standards/security-layer-model_v0.7.md`
- Ownership contract: `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`
- Claim/carrying mechanism: `net-kingdom/canon/standards/iam-profile_v0.3.md`
- Layer declaration: `decisions/decisions.md` `TEN-DEC-2026-001`
- Review: `history/2026-08-29-security-layer-intent-scope-review.md`
- Evolution workplan: `workplans/TEN-WP-0011-security-layer-conformance.md`
- flex-auth write-authorization integration: `docs/flex-auth-integration.md`
- Guardrail contract: `docs/tenant-guardrail-policy.md`
- Lifecycle contract: `docs/tenant-lifecycle-api.md`
- Agent instructions: `AGENTS.md`, `CLAUDE.md`
- Workplans: `workplans/`