Adopt security-layer-model v0.7 in INTENT.md in this repository's own voice, restate SCOPE.md from that declaration, and record the scope-versus-implementation assessment. USER-IN-0001 is answered; runtime follow-through is USER-WP-0024. Assistant: grok Assistant-Session: 01a04cea-f0d6-7ab3-9ffd-881eb6bea6cb
317 lines
16 KiB
Markdown
317 lines
16 KiB
Markdown
# Security layer model — scope vs intent vs implementation
|
|
|
|
**Date:** 2026-08-29
|
|
**Repository:** user-engine
|
|
**Audience:** user-engine, gate-house, flex-auth, ops-warden
|
|
**Status:** current-state review, not a decision record
|
|
**Sources:**
|
|
|
|
- `net-kingdom/SECURITY-COMPANION.md` v0.2 (operative form; start here)
|
|
- `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted statute)
|
|
- `net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`
|
|
- `INTENT.md` and `SCOPE.md` as adapted this session
|
|
- `intakes/intakes.md` `USER-IN-0001`
|
|
- gate-house broadcast 2026-08-29 (accepted v0.7; nine undeclared repositories)
|
|
- working references: `ops-warden/layer.yaml`, `ops-warden/pep-stance.yaml`,
|
|
`kings-guard/layer.yaml`
|
|
- implementation: `src/user_engine/service.py`,
|
|
`src/user_engine/adapters/flex_auth.py`,
|
|
`src/user_engine/adapters/local.py`,
|
|
`src/user_engine/domain/models.py`,
|
|
`src/user_engine/runtime.py`,
|
|
`src/user_engine/projections.py`
|
|
|
|
The statute governs where the companion and the statute disagree. No
|
|
disagreement was found that this review needed to work around.
|
|
|
|
---
|
|
|
|
## 1. Executive assessment
|
|
|
|
user-engine is already the product INTENT described before the layer
|
|
model: a headless user-domain service with ports, a PostgreSQL store, a
|
|
portal, and a live flex-auth caller. That slice is finished through
|
|
`USER-WP-0023`.
|
|
|
|
The accepted layer model asks for something the product workplans never
|
|
carried: an own-voice Engine/PIP declaration, a machine-readable
|
|
conformance surface, and the PEP obligations that attach to any runtime
|
|
that causes a protected side effect.
|
|
|
|
Until this session the repository carried only a gate-house review note
|
|
on `INTENT.md` that named a layer and admitted the body was unadapted.
|
|
That is not a declaration (companion §2, statute §11). `USER-IN-0001`
|
|
stayed open.
|
|
|
|
This review records the declaration, the SCOPE restatement, and the
|
|
implementation gaps that must move. It does not contest Engine/PIP.
|
|
|
|
---
|
|
|
|
## 2. What we declare, and what we do not contest
|
|
|
|
| Item | Position |
|
|
| --- | --- |
|
|
| Layer | **Engine** |
|
|
| Role | **PIP** |
|
|
| Catalog owns | users, accounts, memberships |
|
|
| Boundary contract | unchanged |
|
|
| One decision point | `access-engine` / `flex-auth`; we supply claims and enforce results |
|
|
| Contest the proposed layer? | **No.** Same inputs, same user-domain result. Not inference, not Tooling, not the PDP. |
|
|
|
|
**Finding, not a contest.** Statute §4 catalogues user-engine as Engine/PIP
|
|
and does not mark it PEP-shaped. Companion §5 and statute §6.4 say a PEP
|
|
is any runtime that causes a protected side effect; being PEP-shaped does
|
|
not change layer. user-engine's production mutations (account lifecycle,
|
|
memberships, invitations, registration, hat selection, catalog and
|
|
profile writes) are protected side effects gated on flex-auth. We are
|
|
PEP-shaped in fact. §13.1 currently inventories `ops-warden` (published)
|
|
and `ops-mason` (missing). user-engine is a third PEP-shaped consumer
|
|
with no row. One row was already the finding; a silent third is worse.
|
|
|
|
Raised for gate-house to inventory. We do not ask to be recatalogued as
|
|
Staff.
|
|
|
|
---
|
|
|
|
## 3. Intent vs scope after the adaptation
|
|
|
|
INTENT is now the layer declaration plus the previous product aspiration.
|
|
SCOPE is the implemented product plus the layer obligations that are now
|
|
in-scope because INTENT adopted them.
|
|
|
|
| Area | INTENT | SCOPE after this session | Implementation |
|
|
| --- | --- | --- | --- |
|
|
| Engine / PIP declaration | Required, own voice, frontmatter | Declared; points at companion and statute | Frontmatter present. No `layer.yaml`. |
|
|
| Users, accounts, memberships | Own them | Own them | Shipped. |
|
|
| Claims, not decisions | Subject context is a PIP input | Explicit; local double excluded from production | Production asks flex-auth. Hats/facts not yet proven under §6.1. Local port can still decide. |
|
|
| PEP stance map | Published, tested equal to code, inventoried in §13.1 | Named as remaining product scope | Fail-closed in `FlexAuthHTTPAdapter`; unpublished. |
|
|
| Allow lifetime | Every allow has a lifetime | Named as missing | `AuthorizationDecision` has effect, id, reason, obligations. No TTL or binding. |
|
|
| Evidence bound | No completeness claim; cadence for load-bearing | Named as partial | Local outbox is transactional with mutations. No classification, no cadence. |
|
|
| Tooling contacts | No OpenBao or key-cape admin client | Out of scope | Held. Env-injected secrets. Unused `SecretProvider` port. |
|
|
| Observation / actuation | Do not assume either exists | Out of scope | Not implemented. Correct. |
|
|
|
|
SCOPE is not silently claiming INTENT's new layer obligations as done.
|
|
The remaining holes are listed in SCOPE "Current Status" and mapped to
|
|
`USER-WP-0024`.
|
|
|
|
---
|
|
|
|
## 4. Implementation assessment against the companion
|
|
|
|
Companion sections are the working checklist. Statute citations in
|
|
parentheses.
|
|
|
|
### 4.1 Declare your layer (companion §2 / statute §11)
|
|
|
|
**Before this session:** undeclared. Gate-house prose in `INTENT.md` is
|
|
explicitly not a declaration.
|
|
|
|
**After this session:** `INTENT.md` frontmatter `layer: Engine`,
|
|
`role: PIP`, plus own-voice body. That is the form companion §2 asks
|
|
for.
|
|
|
|
**Still missing:** a machine-readable declaration file equivalent to
|
|
`ops-warden/layer.yaml`. Statute §11 allows frontmatter *or* an
|
|
equivalent file; the working references use a file because that is what
|
|
a conformance script can fail a commit against. user-engine has no
|
|
checker, so a new OpenBao import would not fail the build. Necessity:
|
|
add `layer.yaml` and a check adapted from kings-guard (Engine, not Staff;
|
|
expect no Tooling client, record non-Tooling clients).
|
|
|
|
### 4.2 Rules that bind everyone (companion §3 / statute §6, §8, §9.7)
|
|
|
|
| Rule | Shipped behaviour | Gap |
|
|
| --- | --- | --- |
|
|
| One decision point | Production `FlexAuthHTTPAdapter` POSTs `/v1/check`. Service `_authorize` denies on non-allow. | `LocalAuthorizationCheckPort` *is* a local PDP (default ALLOW, reason `"local"`). Runtime assembly uses flex-auth only, but nothing prevents a production constructor from passing the local port. |
|
|
| Compiled data still deciding | Access-control facts and hats are documented as claim templates (`docs/hats-realms-services-assets-access-profiles.md`). Projections cache profile material only (`projections.py`). | No test that a consumer cannot treat `AccessControlFact` or an active hat as an allow. Tenant capability roles are forwarded, not evaluated — held, documented in the architecture blueprint. |
|
|
| Doctrine as a claim | We do not apply zone stance or maturity ourselves. | None for doctrine we author — we author none. |
|
|
| Adaptive systems only tighten | We do not raise authority on missing evidence. | Held. Do not plan around actuation; it is at zero. |
|
|
| Staff never touches Tooling | We are Engine; §5 does not bind this shape the same way. PostgreSQL is the modeled-concept store, not a §4 Tooling row. | Record PostgreSQL, State Hub, and env-injected secrets as non-Tooling / own-store in `layer.yaml` so the check is total. |
|
|
| Every allow has a lifetime | Not modelled. | `AuthorizationDecision` must carry a lifetime (TTL or session/obligation binding). Replay of a stored verdict outside that lifetime is a second decision point. Today we do not cache verdicts, which is the safe subset, but we also cannot state a deadline (statute §9.7.2: a PEP MUST state one). |
|
|
|
|
### 4.3 If you touch Tooling (companion §4 / statute §5)
|
|
|
|
Catalogued Tooling is `key-cape` and `OpenBao`.
|
|
|
|
| Contact | Present? | Shape |
|
|
| --- | --- | --- |
|
|
| OpenBao HTTP/CLI | No | Blocked-clean on a secrets-engine signing/read surface. Runtime secrets come from the environment / projected files (`runtime.py`). |
|
|
| key-cape admin / LDAP | No | Consumes verified IAM Profile claims (OIDC/JWKS). That is Engine input, not a Staff Tooling client. |
|
|
| PostgreSQL | Yes — own store | Modeled-concept persistence for a PIP. Not §4 Tooling. List it in the declaration so the check is total. |
|
|
| State Hub | Agents writing progress, not the service | Not catalogued. List as non-Tooling. Carve-out sunsets; we do not become a second state plane. |
|
|
|
|
No §5.1/§5.2/§5.3 Staff contact exists. Conformance state for Tooling
|
|
contact is **conforming** (Engine with no catalogued Tooling client),
|
|
provided we declare the own-store and non-Tooling clients rather than
|
|
leave them silent.
|
|
|
|
`SecretProvider` in `ports.py` has no implementation. Leave it as a port.
|
|
Do not add an OpenBao adapter to close it.
|
|
|
|
### 4.4 If you cause side effects (companion §5 / statute §6.4)
|
|
|
|
user-engine is PEP-shaped. Four obligations:
|
|
|
|
1. **No side effect without a decision record, or a recorded stance.**
|
|
Mutations go through `_authorize` then `_record_mutation` inside
|
|
`store.transaction()`. On flex-auth transport/parse failure,
|
|
`FlexAuthHTTPAdapter` returns `DENY` with reason `"authorization
|
|
service unavailable"` and a **locally minted** `decision_id`
|
|
(`AuthorizationDecision.decision_id` default factory). `_authorize`
|
|
then writes that id onto the deny audit record. That is a fake
|
|
decision id, not a recorded stance application. Statute v0.7
|
|
obligation 1 is explicit: where the engine is unreachable, record
|
|
the application of the stance *in place of* the decision.
|
|
`decision_id` must be absent unless a decision was rendered.
|
|
|
|
2. **Do not replay a verdict outside its binding and lifetime.** No
|
|
verdict cache exists in the flex-auth adapter. Good. Negative
|
|
caching is not used. Keep it that way unless a cache is declared
|
|
with lifetime. The mechanical replay test (canonical request digest
|
|
+ lifetime) is not implemented because we do not replay; still need
|
|
a lifetime on the decision we *hold for the mutation*.
|
|
|
|
3. **Publish the unreachable-engine stance map.** Shipped behaviour is
|
|
fail-closed for every call — no per-zone map, no published file, no
|
|
test that file equals code. `runtime.py` comment says "Production
|
|
authorization and delivery are fail-closed HTTP boundaries." That is
|
|
a comment, which statute §6.4 forbids as the form of a stance.
|
|
Reference shape: `ops-warden/pep-stance.yaml`. user-engine's map can
|
|
be simpler (one protected-system, fail-closed total, including
|
|
unknown) but it must be total, published, and tested.
|
|
|
|
4. **Stay reconstructable**, bounded by evidence (below). Deny audits
|
|
are written outside the mutation transaction, which is correct
|
|
(there is no mutation). They currently look like engine decisions.
|
|
|
|
### 4.5 Evidence (companion §6 / statute §9.6)
|
|
|
|
| Kind | What we have | Classification |
|
|
| --- | --- | --- |
|
|
| Successful user-domain mutation | `append_audit` + `append_outbox` inside the same `store.transaction()` as the save | Accidental omission of the *local* record is prevented. Drain to `audit-core` is after commit (local outbox pattern). |
|
|
| Authorization denial | `append_audit` in `_authorize`, not transactional with a mutation | Load-bearing if any control treats absence of a deny as "it was allowed". Today nothing else branches on that record — attributive in practice, load-bearing in intent (denials are the class the statute names). |
|
|
| Membership removal / account disable | Mutation path with outbox | Load-bearing for access-review and for PIP consumers who cache facts. |
|
|
|
|
What we may not claim, and currently do not claim in code:
|
|
|
|
- the audit record proves it happened;
|
|
- there is no record, so it did not happen.
|
|
|
|
Docs (`docs/evidence-gap-examples.md`) talk about missing *external*
|
|
review references, not about this bound. Completeness of the local trail
|
|
is easy to over-claim in portal copy and operability docs. Necessity:
|
|
classify events, declare attributive vs load-bearing, declare a cadence
|
|
or heartbeat for denials and revocations (rate monitoring will not work
|
|
at this volume), and never describe the trail as complete.
|
|
|
|
No production observation exists to cite (companion §10). This review
|
|
does not.
|
|
|
|
### 4.6 Agents (companion §7 / statute §3.4)
|
|
|
|
The service is an Engine. Agents *working the repository* are Staff
|
|
principals. Credential routing is already inlined in `AGENTS.md` via
|
|
`warden route`. No standing user-engine credential is vended to agents.
|
|
No change to runtime is required; INTENT now states the four agent
|
|
rules so a future convenience (a long-lived portal token in an agent
|
|
session, treating tool availability as permission) fails the
|
|
declaration.
|
|
|
|
### 4.7 Conformance state (companion §8 / statute §11)
|
|
|
|
| Question | State |
|
|
| --- | --- |
|
|
| Tooling contact | **Conforming** — none to catalogued Tooling, once listed |
|
|
| Capability we refuse to take locally (secrets-engine API, actuation, observation) | **Blocked-clean** — not worse than conforming |
|
|
| PEP stance unpublished; fake decision ids | **Undeclared violation** of §6.4 until the map and recording ship |
|
|
| Layer declaration | **In progress** — own-voice frontmatter done; machine-readable file and checker not done |
|
|
|
|
Blocked-clean on actuation and observation is the correct posture.
|
|
Do not open a kings-guard or OpenBao client to look more complete.
|
|
|
|
### 4.8 Railiance workloads (companion §9)
|
|
|
|
`rapp-user-engine` is the managed package. Decisions come from
|
|
flex-auth; credentials from the platform after deploy-time injection;
|
|
evidence toward `audit-core` via the outbox. Axis-to-layer mapping is
|
|
unsettled (statute §20.3); this review does not invent one.
|
|
|
|
### 4.9 Estate zeros (companion §10)
|
|
|
|
Do not cite "observed in operation." Do not plan automatic containment,
|
|
step-up, or isolation as user-engine work.
|
|
|
|
---
|
|
|
|
## 5. Necessities for the current implementation
|
|
|
|
Ranked by whether the accepted standard is already violated in shipped
|
|
behaviour, versus missing declaration machinery.
|
|
|
|
### Must change (shipped behaviour vs v0.7)
|
|
|
|
1. **Stop minting a local `decision_id` on engine-unavailable DENY.**
|
|
Record stance application (zone or equivalent scope, failure mode,
|
|
outcome). `decision_id` present only when flex-auth rendered one.
|
|
2. **Publish `pep-stance.yaml`** (or equivalent) named from the layer
|
|
declaration. Total, no implicit default. Test: published map equals
|
|
`FlexAuthHTTPAdapter` behaviour. Ask gate-house to add the row to
|
|
§13.1.
|
|
3. **Give `AuthorizationDecision` a lifetime** and refuse to treat an
|
|
allow with no end as a standing grant. Even without a verdict cache,
|
|
the PEP must state one deadline.
|
|
|
|
### Must add (declaration and checkability)
|
|
|
|
4. **`layer.yaml`** plus a conformance script/test adapted from
|
|
kings-guard: Engine/PIP, empty `tooling_contacts`, listed
|
|
non-Tooling clients (Postgres own-store, State Hub, env secrets,
|
|
flex-auth Engine API, key-cape OIDC as claim input).
|
|
5. **Confine `LocalAuthorizationCheckPort`** to tests and explicitly
|
|
standalone fixtures. Production `create_application()` already uses
|
|
flex-auth; make construction of a default-allow adapter in the
|
|
runtime path fail.
|
|
|
|
### Should add (evidence and PIP contract)
|
|
|
|
6. **Classify local audit/outbox events** as load-bearing or
|
|
attributive. Declare a heartbeat or reconciliation for denials and
|
|
membership/account revocations. Fix docs language so the trail is
|
|
never "complete."
|
|
7. **Prove hats / `AccessControlFact` are claims.** A test that
|
|
selecting a hat or exporting facts never returns an allow/deny, and
|
|
a consumer contract sentence in the hats doc and the boundary-facing
|
|
docs: compiling those facts into a local decision is §6.1.
|
|
|
|
### Do not do
|
|
|
|
- Do not add an OpenBao or key-cape admin client.
|
|
- Do not cache flex-auth allows.
|
|
- Do not observe production or build containment.
|
|
- Do not treat `USER-IN-0001` as closed by the old gate-house note;
|
|
it closes when this repository's own files declare, which they now
|
|
do for INTENT, with implementation follow-through on `USER-WP-0024`.
|
|
|
|
---
|
|
|
|
## 6. Mapping to work
|
|
|
|
| Necessity | Workplan task |
|
|
| --- | --- |
|
|
| Own-voice INTENT + SCOPE + this review | `USER-WP-0024-T01` (done this session) |
|
|
| `layer.yaml` + conformance check | `USER-WP-0024-T02` |
|
|
| PEP stance map, stance recording, lifetime, local-port confinement | `USER-WP-0024-T03` |
|
|
| Evidence classification and cadence | `USER-WP-0024-T04` |
|
|
| Access-control facts as claims | `USER-WP-0024-T05` |
|
|
| Intake close and §13.1 registration request | `USER-WP-0024-T06` |
|
|
|
|
---
|
|
|
|
## 7. Adoption note for gate-house
|
|
|
|
user-engine answers `USER-IN-0001`: **Engine, role PIP**, in our own
|
|
voice, in `INTENT.md`. We do not contest the proposed layer. We raise
|
|
that we are PEP-shaped for our own mutations and are absent from
|
|
statute §13.1. Findings against the accepted text remain welcome; this
|
|
review is one.
|