tenant-engine/history/2026-08-29-security-layer-intent-scope-review.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

14 KiB
Raw Blame History

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-00010007, 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/live503 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-T04TEN-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-T05TEN-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 POSTs /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.