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
This commit is contained in:
parent
d9bacfa953
commit
76677bffc5
7 changed files with 780 additions and 86 deletions
228
INTENT.md
228
INTENT.md
|
|
@ -1,44 +1,96 @@
|
|||
---
|
||||
repo: tenant-engine
|
||||
updated: "2026-07-23"
|
||||
updated: "2026-08-29"
|
||||
layer: Engine
|
||||
role: PIP
|
||||
standard: netkingdom-security-layer-model
|
||||
standard_version: "0.7"
|
||||
companion: net-kingdom/SECURITY-COMPANION.md
|
||||
---
|
||||
|
||||
# INTENT
|
||||
|
||||
> **NetKingdom layering review — 2026-08-28.** This repository's role was reviewed
|
||||
> against the NetKingdom IT-security layer model: **Taxonomy → Tooling → Engines →
|
||||
> Staff**, layered by determinism and by the kind of artifact each layer produces.
|
||||
> Findings and the argument behind them:
|
||||
> `gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md`.
|
||||
> The model is `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed),
|
||||
> ratified by `gate-house/decisions/decisions.md` GH-DEC-2026-001.
|
||||
> 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 layer rule that binds every repository: **Staff never touches tooling
|
||||
> directly. It acts only through engine APIs.**
|
||||
> **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.
|
||||
>
|
||||
> **This repository is Engine — deterministic API for a modeled concept.** Add the layer label. The existing boundary contract (`net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`) holds unchanged. Same clarification as user-engine: tenancy context is an input to the decision, not a decision.
|
||||
> **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.
|
||||
>
|
||||
> *This note records what should change. The body below is not yet adapted.*
|
||||
> **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, and its plan/subscription assignment —
|
||||
separate from who its *users* are (`user-engine`), who *authenticates* it
|
||||
(`key-cape`/Keycloak), who *authorizes* actions against it (`flex-auth`), and
|
||||
what it *pays* for a given plan (`adaptive-pricing`).
|
||||
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`.
|
||||
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 currently 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're allowed to do, or what plan they're on.
|
||||
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`
|
||||
|
|
@ -50,60 +102,85 @@ 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, and onboarding grouping
|
||||
(`trial`/`friendly`/`single`/`small`/`medium`/`large`/`enterprise`/
|
||||
`consumer`/`family`/`community`/`association`/`agentic`, per ADR-0013).
|
||||
- **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).
|
||||
- **A live-lookup API** `flex-auth` calls synchronously for privileged,
|
||||
high-stakes decisions — the case where a cached claim isn't trustworthy
|
||||
enough (a stale `VEN` grant surviving a plan cancellation is not an
|
||||
acceptable risk for money-movement or credential-vending actions).
|
||||
- Reserved, not yet implemented: **guardrail/quota policy** (spend limits,
|
||||
entity/action counts) — `trial`-grouped tenants default to a **zero**
|
||||
spend budget; design is future work.
|
||||
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).
|
||||
- `flex-auth`, at authorization-decision time (live-lookup API, and the
|
||||
target of write-authorization for grant/revoke/plan mutations).
|
||||
- `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 eventually consumes `tenant-engine`'s APIs (not yet built).
|
||||
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 (`flex-auth` 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. This matches the fleet's existing
|
||||
convention of many small, single-purpose `-engine`/`-core` services rather
|
||||
than one large one.
|
||||
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.
|
||||
duplicates facts another system already owns, and it never renders a
|
||||
decision another engine is responsible for.
|
||||
|
||||
### tenant-engine Owns
|
||||
|
||||
- Tenant existence, identifier, and onboarding grouping.
|
||||
- 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).
|
||||
- Reserved: guardrail/quota policy, once designed.
|
||||
- Guardrail/quota policy: the ceiling, not the meter, not the bill, not the
|
||||
allow.
|
||||
|
||||
### Other Systems Own
|
||||
|
||||
|
|
@ -112,37 +189,72 @@ duplicates facts another system already owns.
|
|||
| 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 | `flex-auth` |
|
||||
| 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 `flex-auth` issues.
|
||||
— 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, or operational promises should be
|
||||
captured in a workplan before implementation, and should stay consistent
|
||||
with `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md` —
|
||||
if the two drift, the canon contract wins and this file should be corrected.
|
||||
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
|
||||
|
||||
- `docs/flex-auth-integration.md` — how the write API's `WriteAuthorizer`
|
||||
seam is implemented against a real `flex-auth`
|
||||
- `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md` — the
|
||||
formal ownership contract
|
||||
- `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`
|
||||
- `key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md` — the
|
||||
first tenant this service will record
|
||||
|
|
|
|||
110
SCOPE.md
110
SCOPE.md
|
|
@ -2,65 +2,133 @@
|
|||
|
||||
> 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 owner of tenant-as-an-entity facts for NetKingdom: existence,
|
||||
onboarding grouping, capability roles, and plan/subscription assignment.
|
||||
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 can it do, and what plan
|
||||
is it on" for two consumers: `key-cape` (cached, at token issuance) and
|
||||
`flex-auth` (live, for high-stakes decisions). It never stores users,
|
||||
issues tokens, or makes authorization decisions.
|
||||
`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, onboarding grouping (ADR-0013).
|
||||
- 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).
|
||||
- Live-lookup API for `flex-auth` (high-stakes, `aal2`-class decisions).
|
||||
- Write API for grant/revoke/plan mutations, authorization-gated by
|
||||
`flex-auth`.
|
||||
- Reserved, not yet implemented: guardrail/quota policy (spend limits,
|
||||
entity/action counts).
|
||||
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 — `flex-auth` is the PDP; `tenant-engine` is a
|
||||
data source it queries, never the other way around.
|
||||
- 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
|
||||
|
||||
- Status: bootstrapping. No implementation yet — see
|
||||
`workplans/TEN-WP-0001-statehub-bootstrap.md` and the first real
|
||||
implementation workplan it seeds.
|
||||
- Ownership contract (`tenant-engine-boundary-contract_v0.1.md`) and the two
|
||||
governing ADRs (0013, 0014) are ratified in `net-kingdom` and predate this
|
||||
repo's code, by design.
|
||||
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/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 | **no** | `TEN-WP-0008` |
|
||||
| Machine-readable `layer.yaml` | **no** | Frontmatter declaration is in `INTENT.md` |
|
||||
| Persisted `authorization_decision_id` | **no** | Boundary contract requires it; `FlexAuthCheckClient` drops the envelope |
|
||||
| Published unreachable-engine stance map | **no** | Behaviour is fail-closed in code; companion §5 requires it published and tested equal to shipped behaviour |
|
||||
| Independent audit-core emission | **no** | Local `events` table only (`TEN-IN-0001`) |
|
||||
| Bounded event-read interface | **no** | In-process `TenantStore.events()` is unfiltered (`TEN-IN-0002`) |
|
||||
| Claim freshness / input-class lifetime | **no** | Token lifetime covers the cache-read path by profile convention; live-lookup has no declared deadline |
|
||||
|
||||
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/`
|
||||
|
|
|
|||
75
decisions/decisions.md
Normal file
75
decisions/decisions.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Decision records
|
||||
|
||||
## TEN-DEC-2026-001 — Declare Engine / PIP; contest approval lifecycle here
|
||||
|
||||
```yaml
|
||||
id: TEN-DEC-2026-001
|
||||
kind: decision
|
||||
title: "Declare Engine / PIP; contest approval lifecycle here"
|
||||
status: resolved
|
||||
disposition: approved
|
||||
origin: cross-repo
|
||||
origin_ref: TEN-IN-0003
|
||||
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
|
||||
companion: net-kingdom/SECURITY-COMPANION.md
|
||||
owner: tenant-engine
|
||||
affects:
|
||||
- tenant-engine
|
||||
- gate-house
|
||||
- net-kingdom
|
||||
- access-engine
|
||||
- approval-engine
|
||||
created: "2026-08-29"
|
||||
updated: "2026-08-29"
|
||||
decided_by: tenant-engine (reviewing side)
|
||||
decided_at: "2026-08-29"
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
gate-house asked this repository, via `TEN-IN-0003`, to declare its layer in
|
||||
`INTENT.md` in its own voice, or contest it. Proposed layer: Engine —
|
||||
tenant-as-an-entity facts. The existing boundary contract was to hold
|
||||
unchanged. One live question: the reserved guardrail/quota policy was
|
||||
briefly considered as a home for organizational approval lifecycle before
|
||||
`approval-engine` was seeded; if approvals belong nearer tenant governance,
|
||||
now is the time to say.
|
||||
|
||||
The NetKingdom Security Layer Model was accepted at v0.7 on 2026-08-29. The
|
||||
working companion is `net-kingdom/SECURITY-COMPANION.md` v0.2. Statute §4
|
||||
already catalogues this repository as Engine / PIP. Statute §11 says a layer
|
||||
stated *about* a repository is not a declaration; only this file, in this
|
||||
repository's voice, conforms. The previous `INTENT.md` carried a gate-house
|
||||
review note naming Engine above a line that admitted the body was unadapted.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Declare Engine, role PIP.** Same authoritative tenant state yields the
|
||||
same result. This repository supplies tenant-as-an-entity facts as claims
|
||||
a decision consumes. It does not render or cache an authorization
|
||||
decision. `access-engine` (`flex-auth` until the governed rename) remains
|
||||
the only PDP. The declaration lives in `INTENT.md` frontmatter
|
||||
(`layer: Engine`, `role: PIP`) and in the body's own-voice note.
|
||||
2. **Contest placing approval lifecycle here.** A guardrail is a safety
|
||||
ceiling — a PIP fact. An approval is a durable object owned by
|
||||
`approval-engine` (statute §4, §9.4) and consumed as a claim by
|
||||
`access-engine`. Tenant governance will not mint, store, or evaluate
|
||||
approvals. If a tenant mutation should require one, the write stays
|
||||
PEP-shaped: this engine mutates only after `access-engine` has allowed
|
||||
it, and that decision may rest on an `approval-engine` claim.
|
||||
3. **Name the write path PEP-shaped without becoming a PDP.** Protected
|
||||
side effects (create, grant, revoke, plan, lifecycle, grouping,
|
||||
guardrail mutations) proceed only with a decision record or a recorded
|
||||
fail-closed unreachable-engine stance. That stance is already the
|
||||
shipped default; publishing it and persisting decision ids is
|
||||
implementation, recorded as `TEN-WP-0011`, not a change of layer.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `TEN-IN-0003` is closed, absorbed by this decision.
|
||||
- Implementation gaps against v0.7 are not hidden by the declaration.
|
||||
They are `TEN-WP-0011`.
|
||||
- A boundary-contract amendment in `net-kingdom` is requested from that
|
||||
workplan: guardrails are no longer reserved, and "not a policy
|
||||
enforcement point" must be restated so it cannot be read as denying the
|
||||
PEP *shape* of our writes.
|
||||
|
|
@ -12,7 +12,9 @@ id: TEN-IN-0001
|
|||
kind: intake
|
||||
title: "Externalize tenant-engine audit evidence to audit-core"
|
||||
lane: yellow
|
||||
status: open
|
||||
status: closed
|
||||
outcome: promoted
|
||||
promoted_to: TEN-WP-0011-T04
|
||||
priority: high
|
||||
owner: tenant-engine
|
||||
repo: tenant-engine
|
||||
|
|
@ -39,6 +41,11 @@ description: |
|
|||
Promote this intake to a normal workplan before implementation because the
|
||||
delivery semantics and cross-service dependency require design and review.
|
||||
state_hub_intake_id: "01a02176-8ca7-725d-b2c2-4353eb4fd3f5"
|
||||
promoted_at: "2026-08-29"
|
||||
notes:
|
||||
- content: Promoted into TEN-WP-0011-T04 after security-layer-model v0.7 made the evidence bound (statute §9.6) explicit for this PIP.
|
||||
author: tenant-engine
|
||||
created: "2026-08-29"
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -50,7 +57,9 @@ id: TEN-IN-0002
|
|||
kind: intake
|
||||
title: "Remove or authorize the tenant-engine unfiltered event-read interface"
|
||||
lane: red
|
||||
status: open
|
||||
status: closed
|
||||
outcome: promoted
|
||||
promoted_to: TEN-WP-0011-T05
|
||||
priority: high
|
||||
owner: tenant-engine
|
||||
repo: tenant-engine
|
||||
|
|
@ -67,4 +76,9 @@ description: |
|
|||
interface. Add cross-tenant negative tests and give risk-nexus the final
|
||||
accessibility and payload reading.
|
||||
state_hub_intake_id: "01a02176-c121-7952-8d52-2cbe282fd883"
|
||||
promoted_at: "2026-08-29"
|
||||
notes:
|
||||
- content: Promoted into TEN-WP-0011-T05. Unfiltered production-protocol event reads remain a reconstructability defect under statute §9.6 even without an HTTP route.
|
||||
author: tenant-engine
|
||||
created: "2026-08-29"
|
||||
```
|
||||
|
|
|
|||
189
history/2026-08-29-security-layer-intent-scope-review.md
Normal file
189
history/2026-08-29-security-layer-intent-scope-review.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# Review: Security Layer Model v0.7 — INTENT, SCOPE, and implementation
|
||||
|
||||
**Date:** 2026-08-29
|
||||
**Author:** grok
|
||||
**Reviewed:**
|
||||
- `net-kingdom/SECURITY-COMPANION.md` v0.2 (accepted; start here)
|
||||
- `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted 2026-08-29)
|
||||
- `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`
|
||||
- this repo's `INTENT.md` / `SCOPE.md` (before and after this session)
|
||||
- shipped code under `src/tenant_engine/` and finished workplans `TEN-WP-0001`–`0007`, `0009`, `0010`
|
||||
**Trigger:** gate-house broadcast that v0.7 is accepted; intake `TEN-IN-0003` asking this repository to declare its layer in its own voice, or contest it. Proposed layer: Engine.
|
||||
**Outcome:** declared Engine / PIP; contested placing approval lifecycle here; remaining implementation gaps captured as `TEN-WP-0011`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
`tenant-engine` is the catalogued Engine / PIP for tenant-as-an-entity facts. That placement is correct, and this session declares it in `INTENT.md` frontmatter and prose. The gate-house review note that sat above an unadapted body is replaced.
|
||||
|
||||
`SCOPE.md` was the larger documentation failure. It still said the repo was bootstrapping with no implementation, while production already serves tenant records, role grants, plan assignment, lifecycle, guardrails, and a PostgreSQL store, gated on writes (and now reads) through `flex-auth`.
|
||||
|
||||
The implementation is a working PIP with a fail-closed write path. It is **not** yet a conforming PEP-shaped consumer, and it is **not** yet a conforming evidence source. Those are the evolution requirements.
|
||||
|
||||
| Question | Answer |
|
||||
| --- | --- |
|
||||
| Is Engine / PIP the right layer? | **Yes.** Same tenant state, same result. Facts as claims, never a decision. |
|
||||
| Do we contest the proposed layer? | **No.** We contest only the live question about approvals. |
|
||||
| Do approvals belong here? | **No.** Guardrails are ceilings. Approvals are `approval-engine` objects. |
|
||||
| Does SCOPE match INTENT? | **After this session, yes** at the documentation layer. |
|
||||
| Does implementation match INTENT? | **Partial.** PIP reads and fail-closed writes exist. Decision-record persistence, published stance, independent evidence, and claim-lifetime declaration do not. |
|
||||
|
||||
---
|
||||
|
||||
## 2. What the standard now requires of this repository
|
||||
|
||||
Companion v0.2 is the operative form. The statute governs on disagreement.
|
||||
|
||||
### 2.1 Declare (companion §2, statute §11)
|
||||
|
||||
Machine-readable `layer` in `INTENT.md` frontmatter, plus prose in our own voice. A layer stated *about* us is not a declaration. Engines also carry `role`. Catalog row: Engine / PIP, tenant-as-an-entity facts.
|
||||
|
||||
**Before this session:** a gate-house review note named Engine and admitted the body was unadapted. That is exactly the defect §11 now forbids.
|
||||
|
||||
**After this session:** `layer: Engine`, `role: PIP` in frontmatter; body rewritten. `TEN-IN-0003` closed by `TEN-DEC-2026-001`.
|
||||
|
||||
### 2.2 PIP rules that bind a fact engine
|
||||
|
||||
- One decision point: `access-engine` only. We do not render or cache a decision.
|
||||
- Compiled data that determines an outcome is still deciding. An effective guardrail, a grouping, or a role set is a **claim**. Joining it with consumption to allow or deny is `access-engine`'s.
|
||||
- Doctrine and tenancy context arrive as claims, never as a side channel.
|
||||
- Live-lookup unavailability is input degradation at the consumer: fail to reduced authority is `access-engine`'s when it is reachable; our own read path already returns `503` rather than empty roles.
|
||||
|
||||
### 2.3 PEP-shaped writes (companion §5, statute §6.4 / §9.3)
|
||||
|
||||
Mutating tenant facts is a protected side effect. Being PEP-shaped does not change the layer. Four obligations:
|
||||
|
||||
1. No side effect without a decision record naming the request, **or** a recorded application of the published unreachable-engine stance.
|
||||
2. Do not replay a verdict outside its binding and lifetime.
|
||||
3. Publish the unreachable-engine stance map, total, no implicit default, tested equal to shipped behaviour, registered in statute §13.1.
|
||||
4. Reconstructable, bounded by §9.6.
|
||||
|
||||
Shipped behaviour is fail-closed (`DefaultDenyWriteAuthorizer` when the URL is unset; `FlexAuthCheckClient.is_allowed()` returns `False` on any non-allow, including transport failure). That is the right stance for this engine. It is **not published**, and the decision envelope is **discarded**.
|
||||
|
||||
### 2.4 Evidence (companion §6, statute §9.6)
|
||||
|
||||
Local `events` rows share the production database and runtime credential with the facts they attest to (`TEN-IN-0001`). `TenantStore.events()` returns every tenant's event and payload (`TEN-IN-0002`). Grant/revoke *facts* are live PIP input; the *events* are attributive unless a control starts branching on their presence. Completeness must not be claimed. Independent custody is `audit-core`.
|
||||
|
||||
### 2.5 Two things not to plan around (companion §10)
|
||||
|
||||
Nothing is observed in production. Nothing can be contained automatically. This engine does not acquire actuation, and no workplan here should assume kings-guard is watching tenant mutations in operation.
|
||||
|
||||
---
|
||||
|
||||
## 3. INTENT success criteria
|
||||
|
||||
INTENT is aspirational. These are the properties the mature system must hold.
|
||||
|
||||
| # | Criterion | Status | Evidence / gap |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Canonical owner of tenant-as-an-entity facts | **Met** | Domain + store + APIs; boundary contract |
|
||||
| 2 | Deterministic Engine (same state → same result) | **Met** | Pure domain + total guardrail resolution |
|
||||
| 3 | PIP, never PDP | **Met in design** | Reads return facts; writes ask `flex-auth`. Guardrail resolution is a ceiling, not an allow. |
|
||||
| 4 | Cache-read for `key-cape` | **Shipped** | `GET /tenants/{id}/roles` |
|
||||
| 5 | Live-lookup for `access-engine`, fail-closed on store loss | **Shipped** | `GET /tenants/{id}/roles/live` → `503` on `StoreUnavailableError` |
|
||||
| 6 | Writes PEP-shaped, never self-authorized | **Partial** | Authorizer seam exists and fails closed. Decision id is not kept. Stance is unpublished. |
|
||||
| 7 | Guardrails are PIP ceilings, not entitlements or decisions | **Shipped (policy)** | `TEN-WP-0006`; plan-derived layer is empty until `adaptive-pricing` feeds it |
|
||||
| 8 | Grouping current vs identifier historical | **Shipped** | `TEN-WP-0010` |
|
||||
| 9 | Approvals not stored or evaluated here | **Met by absence** | Contested in `TEN-DEC-2026-001` so it stays absent |
|
||||
| 10 | No catalogued Tooling client | **Met** | No OpenBao / `key-cape` client in `src/` |
|
||||
| 11 | Evidence independently custodied, bound claimed honestly | **Not met** | Local events only; `events()` unfiltered |
|
||||
| 12 | Claim freshness / lifetime declared per input class | **Not met** | Relies on token TTL for cache-read; live path has no stated deadline |
|
||||
|
||||
**Score: 8 met, 1 partial, 2 not met, 1 met-by-absence.** The two not-met items and the partial are `TEN-WP-0011`.
|
||||
|
||||
---
|
||||
|
||||
## 4. SCOPE vs INTENT (documentation)
|
||||
|
||||
### Before this session
|
||||
|
||||
| Topic | INTENT (body) | SCOPE | Drift |
|
||||
| --- | --- | --- | --- |
|
||||
| Layer | Review note only; body unadapted | Silent | Non-conforming declaration |
|
||||
| Guardrails | "Reserved, not yet implemented" | Same | Both stale vs `TEN-WP-0006` |
|
||||
| Current state | Purpose written as if pre-code | "bootstrapping. No implementation yet" | False |
|
||||
| Approvals | Unasked | Unasked | Live question from `TEN-IN-0003` unanswered |
|
||||
| Writes | Gated by `flex-auth` | Same | Neither named the PEP shape |
|
||||
|
||||
### After this session
|
||||
|
||||
INTENT states the aspirational Engine / PIP role, the PEP-shaped write path, the evidence bound, and the approvals contest. SCOPE states what is shipped, what is not, and points at `TEN-WP-0011` for the rest. They no longer contradict each other. The remaining documentation debt is the **boundary contract** in `net-kingdom`, which still says guardrails are reserved and that this repo is "not a policy enforcement point" — true of decisions, false of the write-path shape.
|
||||
|
||||
---
|
||||
|
||||
## 5. SCOPE vs implementation (before the rewrite)
|
||||
|
||||
The pre-session `SCOPE.md` was not a conservative boundary. It was wrong about the current state.
|
||||
|
||||
Shipped and previously in SCOPE as "in scope": tenant records, role grants, plan assignment, cache-read, live-lookup, write API.
|
||||
|
||||
Shipped and previously missing from SCOPE, or marked reserved:
|
||||
|
||||
- Guardrail read/write API (`GET/PUT/DELETE /tenants/{id}/guardrails…`)
|
||||
- Lifecycle (`PATCH`, retire, reactivate)
|
||||
- Mutable grouping (`POST /tenants/{id}/grouping`)
|
||||
- PostgreSQL production store
|
||||
- ETag / If-Match / idempotency on lifecycle and guardrail mutations
|
||||
- Authorized reads (`tenant.read`, `tenant.role.read`, `tenant.role.read.live`, `tenant.guardrail.read`)
|
||||
|
||||
Out of SCOPE and correctly absent from the code: identity, PDP, users, pricing terms, payments, secret custody, metering, rate limits, approvals, containment.
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation gaps against the accepted model
|
||||
|
||||
Prioritized. "Necessity" means the statute or companion requires it of an Engine / PIP with a PEP-shaped write path. "Should" is the companion's working-reference shape or a pre-existing intake the model now makes sharper.
|
||||
|
||||
| Prio | Gap | Kind | Why it matters | Track |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| P0 | Layer declared only as a review note | Necessity | §11; `TEN-IN-0003` | **Closed this session** (`INTENT.md`, `TEN-DEC-2026-001`) |
|
||||
| P1 | `FlexAuthCheckClient` discards the decision envelope; no `authorization_decision_id` on events | Necessity | §6.4 obligation 1; boundary-contract Audit Correlation Contract already required this field and the code never stored it | `TEN-WP-0011-T02` |
|
||||
| P1 | Unreachable-engine stance unpublished | Necessity | §6.4 obligation 3 / companion §5. Fail-closed is correct and already coded; a map that lives only in `is_allowed()` / `DefaultDenyWriteAuthorizer` is not published. Must be tested equal to shipped behaviour and offered to the §13.1 register. | `TEN-WP-0011-T02` |
|
||||
| P1 | Local events share the fact store's credential boundary | Necessity (honesty now; custody next) | §9.6. Attributive today. A database-credential compromise forges fact and evidence together. Independent custody is `audit-core`. | `TEN-WP-0011-T04` ← `TEN-IN-0001` |
|
||||
| P1 | `TenantStore.events()` is unfiltered | Necessity | RISK-F-0004 / `TEN-IN-0002`. No HTTP route today, but the production protocol still offers a cross-tenant dump. | `TEN-WP-0011-T05` ← `TEN-IN-0002` |
|
||||
| P2 | No machine-readable `layer.yaml` / conformance test | Should | Companion §2 working reference. Frontmatter satisfies "a `layer:` key". The check that *no new OpenBao client appeared* still wants a file and a test, including the non-Tooling inventory (Postgres, `flex-auth`, State Hub). | `TEN-WP-0011-T01` |
|
||||
| P2 | No declared freshness / lifetime for PIP claims | Necessity (PDP-side, we owe the input class) | §9.7: a PDP states a deadline **per input class**. Cache-read is bounded by token TTL (IAM Profile). Live-lookup currently forbids cross-request caching in prose only. We must publish the input-class lifetime so `access-engine` can name it. | `TEN-WP-0011-T03` |
|
||||
| P2 | Live-lookup re-enters `flex-auth` to authorize the read | Confirm | `GET /roles/live` calls `authorizer.authorize(tenant.role.read.live)` which `POST`s `/v1/check`. If that check itself live-looks up tenant roles, the path is cyclic. Likely saved by a static service-identity rule that does not consume tenant-engine claims — **must be proven**, not assumed. | `TEN-WP-0011-T03` |
|
||||
| P3 | Boundary contract stale on guardrails and "not a PEP" | Docs / canon | Owned by `net-kingdom`. Request an amendment; do not fork the contract here. | `TEN-WP-0011-T06` |
|
||||
| P3 | Staged promotion still open | Ops, not layer-model | `TEN-WP-0008`. Silent pin rollbacks. Orthogonal to v0.7; remains the other ready workplan. | `TEN-WP-0008` |
|
||||
| — | Verdict caching | None found | Each write re-checks. Obligation 2 appears held. T02 should lock it with a test. | `TEN-WP-0011-T02` |
|
||||
| — | Catalogued Tooling contact | None found | No OpenBao / `key-cape` client. Conforming on §5 if the inventory is declared. | `TEN-WP-0011-T01` |
|
||||
| — | Production observation / automatic containment | Do not plan | Companion §10. No task here invents either. | — |
|
||||
|
||||
---
|
||||
|
||||
## 7. Approvals contest (the live question on `TEN-IN-0003`)
|
||||
|
||||
gate-house asked whether organizational approval lifecycle belongs nearer tenant governance, given that guardrail/quota policy was once a candidate home before `approval-engine` was seeded.
|
||||
|
||||
**Contest: no.**
|
||||
|
||||
A guardrail answers *how far this tenant may go before something must stop it*. That is a PIP fact, already modelled, already served to `flex-auth` as a ceiling. An approval answers *whether this act has been granted a durable, authenticated, atomically supersedable object*. Statute §4 and §9.4 assign that object to `approval-engine`. Putting it in `tenant-engine` would either mint a second approval store or turn a ceiling engine into a workflow engine.
|
||||
|
||||
If a tenant mutation should require an approval, the shape is: `approval-engine` holds the object; `access-engine` consumes it as a claim on `tenant.*` writes; this engine mutates only after that decision. That is the PEP write path we already owe. It is not a reason to own the object.
|
||||
|
||||
Recorded as `TEN-DEC-2026-001`.
|
||||
|
||||
---
|
||||
|
||||
## 8. What this session changed, and what it did not
|
||||
|
||||
Changed:
|
||||
|
||||
- `INTENT.md` — own-voice Engine / PIP declaration; body adapted; approvals contested.
|
||||
- `SCOPE.md` — current state, layer table, guardrails and lifecycle in scope, known gaps named.
|
||||
- `TEN-DEC-2026-001` — declaration + contest.
|
||||
- `TEN-IN-0003` — closed, absorbed.
|
||||
- `TEN-IN-0001` / `TEN-IN-0002` — promoted into `TEN-WP-0011`.
|
||||
- `TEN-WP-0011` — evolution workplan.
|
||||
|
||||
Did not change: runtime code, deploy pin, `tenancy.yaml`, agent-rule files (`architecture.md` still says guardrails are reserved — residual, not this workplan's job unless an agent trips on it). Did not implement `layer.yaml`, decision-id persistence, stance publication, or audit-core emission.
|
||||
|
||||
---
|
||||
|
||||
## 9. Suggested next action
|
||||
|
||||
Execute `TEN-WP-0011` in order: T01 (checkable declaration) → T02 (PEP write path) → T03 (PIP freshness, including the live-lookup re-entry proof) → T04/T05 (evidence) → T06 (contract amendment request). Do not wait on `TEN-WP-0008`; it is a different axis (Railiance staged promotion) and both can run.
|
||||
|
||||
Do not add a containment, observation, or approval-object task to this repo on the strength of v0.7.
|
||||
|
|
@ -7,9 +7,11 @@ id: TEN-IN-0003
|
|||
kind: intake
|
||||
title: 'Declaration requested: state this repository''s layer in INTENT.md (security
|
||||
layer model §11)'
|
||||
status: open
|
||||
status: closed
|
||||
outcome: absorbed
|
||||
promoted_to: TEN-DEC-2026-001
|
||||
origin: cross-repo
|
||||
origin_ref: net-kingdom security-layer-model_v0.4 §11
|
||||
origin_ref: net-kingdom security-layer-model_v0.7 §11
|
||||
priority: low
|
||||
owner: tenant-engine
|
||||
requested_by: gate-house
|
||||
|
|
@ -28,12 +30,17 @@ description: 'A conformance sweep on 2026-08-28 found this repository has no lay
|
|||
nearer to tenant governance, now is the time to say. Contesting costs nothing —
|
||||
the three repositories that reviewed this model each returned a correction, two
|
||||
of which changed the standard. NOTE for your own records, unrelated to the request:
|
||||
the State Hub holds two open intakes for tenant-engine created 2026-08-25 (audit
|
||||
evidence externalization; unfiltered event-read interface) with no intakes/intakes.md
|
||||
the State Hub holds two open tenant-engine intakes with no intakes/intakes.md
|
||||
in this checkout to back them, which is an ADR-001 file-is-truth inconsistency you
|
||||
may want to reconcile. Standard: net-kingdom/canon/standards/security-layer-model_v0.4.md.'
|
||||
created: '2026-08-28T21:03:13.580775Z'
|
||||
updated: '2026-08-29T00:45:57.718728Z'
|
||||
updated: '2026-08-29T12:00:00.000000Z'
|
||||
closed_at: '2026-08-29'
|
||||
resolution: 'Declared Engine / PIP in INTENT.md own voice (frontmatter layer/role
|
||||
plus adapted body). Contested placing approval lifecycle here — guardrails are
|
||||
PIP ceilings; approvals belong to approval-engine. Implementation gaps against
|
||||
v0.7 captured as TEN-WP-0011. The withdrawn ADR-001 note in the original intake
|
||||
remains withdrawn.'
|
||||
notes:
|
||||
- content: CORRECTION from gate-house, 2026-08-29. The closing note in this intake
|
||||
claimed the State Hub holds two open tenant-engine intakes with no backing file,
|
||||
|
|
@ -44,4 +51,9 @@ notes:
|
|||
unchanged.
|
||||
author: gate-house
|
||||
created: '2026-08-29T00:45:57.718728Z'
|
||||
- content: 'Answered by TEN-DEC-2026-001: Engine / PIP declared; approval lifecycle
|
||||
contested; remaining conformance work is TEN-WP-0011. Review:
|
||||
history/2026-08-29-security-layer-intent-scope-review.md.'
|
||||
author: tenant-engine
|
||||
created: '2026-08-29T12:00:00.000000Z'
|
||||
```
|
||||
|
|
|
|||
224
workplans/TEN-WP-0011-security-layer-conformance.md
Normal file
224
workplans/TEN-WP-0011-security-layer-conformance.md
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
---
|
||||
id: TEN-WP-0011
|
||||
type: workplan
|
||||
title: "Align tenant-engine with the accepted security layer model"
|
||||
domain: infotech
|
||||
repo: tenant-engine
|
||||
status: ready
|
||||
owner: grok
|
||||
topic_slug: netkingdom
|
||||
created: "2026-08-29"
|
||||
updated: "2026-08-29"
|
||||
depends_on:
|
||||
- TEN-WP-0003
|
||||
- TEN-WP-0006
|
||||
- TEN-WP-0009
|
||||
unblocks: []
|
||||
origin: residual
|
||||
origin_ref: TEN-IN-0003
|
||||
---
|
||||
|
||||
# TEN-WP-0011 — Align tenant-engine with the accepted security layer model
|
||||
|
||||
The NetKingdom Security Layer Model is accepted at v0.7. This repository is
|
||||
catalogued Engine / PIP for tenant-as-an-entity facts. `INTENT.md` now
|
||||
declares that in its own voice (`TEN-DEC-2026-001`). The remaining work is
|
||||
to make the shipped engine match the obligations that declaration takes on.
|
||||
|
||||
Review of record:
|
||||
`history/2026-08-29-security-layer-intent-scope-review.md`.
|
||||
|
||||
Start here: `net-kingdom/SECURITY-COMPANION.md` v0.2. Statute:
|
||||
`net-kingdom/canon/standards/security-layer-model_v0.7.md`. On disagreement
|
||||
the statute governs.
|
||||
|
||||
## Why this is a workplan
|
||||
|
||||
Declaration is documentation. Conformance is mechanical. Companion §2
|
||||
offers `layer.yaml` plus a check as the working reference. Companion §5
|
||||
and statute §6.4 bind the write path as PEP-shaped. Statute §9.6 and the
|
||||
already-open intakes `TEN-IN-0001` / `TEN-IN-0002` bind evidence. Statute
|
||||
§9.7 requires a published lifetime on PIP input classes.
|
||||
|
||||
This is not a layer change (statute §10). We are declaring the layer we
|
||||
already occupy and closing the gaps between that layer's rules and the
|
||||
code.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Not a second PDP. Guardrail resolution stays a ceiling, never an allow.
|
||||
- Not an approval engine. `TEN-DEC-2026-001` contested that placement.
|
||||
- Not containment or production observation (companion §10).
|
||||
- Not staged promotion — that remains `TEN-WP-0008`.
|
||||
- Not forking the boundary contract. T06 requests an amendment in
|
||||
`net-kingdom`; it does not copy the contract into this repo.
|
||||
|
||||
## T01 — Machine-readable layer declaration and conformance check
|
||||
|
||||
```task
|
||||
id: TEN-WP-0011-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Add `layer.yaml` modelled on `ops-warden`'s reference and `kings-guard`'s
|
||||
no-Tooling adaptation: `layer: engine`, `role: pip`, `standard_version:
|
||||
"0.7"`, empty `tooling_contacts`, and a complete `non_tooling_clients`
|
||||
inventory (PostgreSQL / SQLite store, `flex-auth` `/v1/check`, State Hub
|
||||
work records).
|
||||
|
||||
Add a check script and a test so a new catalogued Tooling client
|
||||
(`key-cape`, OpenBao) fails on the commit that introduces it. Record
|
||||
non-Tooling clients so the check is total (companion §4).
|
||||
|
||||
Done when: `INTENT.md` frontmatter and `layer.yaml` agree on layer and
|
||||
role; `make test` covers the check; no OpenBao / `key-cape` client exists
|
||||
in `src/`.
|
||||
|
||||
## T02 — PEP write-path: decision records and published fail-closed stance
|
||||
|
||||
```task
|
||||
id: TEN-WP-0011-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`FlexAuthCheckClient.is_allowed()` currently returns a bool and drops the
|
||||
decision envelope. The boundary contract's Audit Correlation Contract
|
||||
already required `authorization_decision_id` on every mutation; the code
|
||||
never stored it.
|
||||
|
||||
- Keep the decision record (id, effect, request digest) on the authorizer
|
||||
seam and persist it on every mutation event.
|
||||
- Where `access-engine` is unset or unreachable, record the **application
|
||||
of the stance** in place of a decision (companion §5.1). Do not proceed
|
||||
silently.
|
||||
- Publish the unreachable-engine stance as a file named from `layer.yaml`
|
||||
(fail-closed for every scope this engine serves — this is not
|
||||
ops-warden's per-zone fail-open map). Assert with a test that the
|
||||
published map equals shipped behaviour (`DefaultDenyWriteAuthorizer` and
|
||||
transport-failure deny).
|
||||
- Lock obligation 2: no verdict cache. Re-check every write. Add a test
|
||||
that a previous allow cannot authorize a later, different request.
|
||||
- Offer the map to gate-house for the statute §13.1 register. Do not edit
|
||||
the statute from this repo.
|
||||
|
||||
Done when: a granted role, a denied grant, and an unreachable-engine
|
||||
attempt each leave a reconstructable record; `make test` proves stance
|
||||
file == code; no write path can succeed without one of those two records.
|
||||
|
||||
## T03 — PIP claim freshness, and prove live-lookup is not cyclic
|
||||
|
||||
```task
|
||||
id: TEN-WP-0011-T03
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Statute §9.7: every allow has a lifetime, and a PDP states a deadline per
|
||||
input class. This engine owes the input-class description.
|
||||
|
||||
- Publish, next to the stance map, the freshness contract for
|
||||
`tenant_roles` / grouping / plan / guardrail claims: cache-read is
|
||||
bounded by the IAM Profile token lifetime; live-lookup results MUST NOT
|
||||
be cached across requests; request-scoped memoization inside one
|
||||
`access-engine` decision remains allowed (boundary contract).
|
||||
- Name the consumer-visible failure: store unavailability is `503`, never
|
||||
empty roles. That is already shipped; make it part of the published
|
||||
contract so `access-engine` can treat it as input degradation (§9.3).
|
||||
- **Prove** that `GET /tenants/{id}/roles/live` → `POST /v1/check`
|
||||
(`tenant.role.read.live`) does not re-enter tenant-engine for the claims
|
||||
that check needs. If it does, the live path is cyclic and must be
|
||||
unwound (static service-identity rule, or an authorizer bypass that is
|
||||
itself a declared exception — prefer the former). Record the proof in
|
||||
`docs/flex-auth-integration.md`.
|
||||
|
||||
Done when: the freshness contract is published; a test fails if live-lookup
|
||||
cross-request caching is introduced; the re-entry question is answered with
|
||||
evidence, not a comment.
|
||||
|
||||
## T04 — Externalize mutation evidence to audit-core
|
||||
|
||||
```task
|
||||
id: TEN-WP-0011-T04
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Promoted from `TEN-IN-0001` (origin: residual of `TEN-WP-0009`).
|
||||
|
||||
The local `events` table shares a database and runtime credential with the
|
||||
facts it attests to. Statute §9.6: that trail cannot be described as
|
||||
independent custody. Classify current classes as **attributive** unless a
|
||||
named control branches on their presence.
|
||||
|
||||
Design and implement a governed tenant-engine → `audit-core` emission path:
|
||||
|
||||
1. Agree envelope, sender registration, and credential custody with
|
||||
`audit-core` without putting a secret in Git or a work record. Route
|
||||
credentials through `warden route`, never through a State Hub message.
|
||||
2. Preserve mutation atomicity (local outbox against crash) while defining
|
||||
retry, duplicate, and audit-core-unavailable semantics. Unavailable
|
||||
evidence MUST NOT fail-open a mutation, and SHOULD NOT fail-closed a
|
||||
mutation either unless a class is later reclassified load-bearing —
|
||||
declare the trade if emission stays attributive and non-blocking.
|
||||
3. Prove the external copy cannot be rewritten through tenant-engine's
|
||||
runtime database credential.
|
||||
4. Decide whether pre-cutover event classes need a bounded backfill, and
|
||||
record the decision.
|
||||
5. Do not claim the archive proves events happened, or that absence proves
|
||||
they did not.
|
||||
|
||||
Done when: production mutations emit to `audit-core` under the bound above;
|
||||
the trade (atomic vs non-blocking) is declared; `TEN-IN-0001` can close as
|
||||
promoted and completed.
|
||||
|
||||
## T05 — Remove or authorize the unfiltered event-read interface
|
||||
|
||||
```task
|
||||
id: TEN-WP-0011-T05
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Promoted from `TEN-IN-0002` (origin: RISK-F-0004).
|
||||
|
||||
`TenantStore.events()` returns every tenant's event and payload. There is
|
||||
no HTTP event-read route today, but the production protocol still offers
|
||||
the dump.
|
||||
|
||||
- Remove `events()` from the production protocol, **or** replace it with
|
||||
an authorized, deliberately scoped export/query that cannot return
|
||||
another tenant's payload.
|
||||
- Add cross-tenant negative tests.
|
||||
- Hand risk-nexus the accessibility and payload reading.
|
||||
|
||||
Done when: a production caller cannot list another tenant's events through
|
||||
this repo's store protocol; tests say so; `TEN-IN-0002` can close.
|
||||
|
||||
## T06 — Request the boundary-contract amendment
|
||||
|
||||
```task
|
||||
id: TEN-WP-0011-T06
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md` still
|
||||
says guardrail/quota policy is "reserved, not yet implemented" and that
|
||||
`tenant-engine` is "not a policy enforcement point". Both sentences now
|
||||
mislead: guardrails shipped in `TEN-WP-0006`, and writes are PEP-shaped
|
||||
even though this repo is not a PDP.
|
||||
|
||||
File an intake on `net-kingdom` (do not edit the canon from here) asking
|
||||
to:
|
||||
|
||||
- mark guardrail/quota policy as owned and implemented, pointing at
|
||||
`docs/tenant-guardrail-policy.md`;
|
||||
- restate the authorization contract: data source / PIP for decisions;
|
||||
PEP-shaped for its own protected writes; never a second PDP;
|
||||
- mention Engine / PIP and the v0.7 statute next to the existing IAM
|
||||
Profile / ADR-0013 / ADR-0014 references.
|
||||
|
||||
Done when: the intake exists on the canon side with a named id, and this
|
||||
task records it.
|
||||
Loading…
Add table
Add a link
Reference in a new issue