tenant-engine/SCOPE.md
tegwick ec41920be6 Refresh stale scope and architecture notes to match shipped state
SCOPE.md still said TEN-WP-0008 was "ready, not done" two paragraphs
above its own table listing staged promotion as shipped, and its list of
finished workplans omitted 0008 and 0011. All twelve workplans are
finished; say so once.

.claude/rules/architecture.md still described `guardrail/` as a reserved,
unimplemented namespace and the production store as TBD. Guardrails
shipped in TEN-WP-0006/0007 and PostgreSQL became the production store in
TEN-WP-0009. Also corrects the live-lookup caller to `access-engine`,
matching SCOPE.md and the boundary contract.

No behaviour change; 287 tests pass unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHwvAEQfmzLHtrFGhXtVjq

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 823014@bnt-lap001
Assistant-Session: 2a0786b1-efea-4c38-959b-6e86a493f259
2026-09-07 00:21:03 +02:00

134 lines
7 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. Workplans `TEN-WP-0001` through
`TEN-WP-0011` are all `finished`: they shipped the domain model, the three
boundary-contract APIs, the `flex-auth` write authorizer, lifecycle,
guardrails, PostgreSQL as the production store, mutable grouping,
staged-promotion onboarding, and the security-layer conformance surfaces.
No workplan in this repo is currently open.
| 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/`