Declare Engine/PIP and open USER-WP-0024 for layer conformance
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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
This commit is contained in:
tegwick 2026-08-29 11:56:06 +02:00
parent 7961518be5
commit 96430299b7
5 changed files with 692 additions and 47 deletions

151
INTENT.md
View file

@ -1,24 +1,94 @@
# INTENT
---
layer: Engine
role: PIP
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
companion: net-kingdom/SECURITY-COMPANION.md
declared: "2026-08-29"
---
> **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.
>
> The layer rule that binds every repository: **Staff never touches tooling
> directly. It acts only through engine APIs.**
>
> **This repository is Engine — deterministic API for a modeled concept.** Add the layer label. The existing boundary contract (`net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`) holds unchanged. Worth making explicit that user-engine supplies subject context as input to the authorization decision and never renders one.
>
> *This note records what should change. The body below is not yet adapted.*
# INTENT
> 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.
## Layer
**Engine, role PIP.** This is user-engine's own declaration under the
NetKingdom Security Layer Model
([`SECURITY-COMPANION.md`](../net-kingdom/SECURITY-COMPANION.md) v0.2;
statute [`security-layer-model_v0.7.md`](../net-kingdom/canon/standards/security-layer-model_v0.7.md),
accepted 2026-08-29). A layer stated about this repository by another
repository is not a declaration; this one is.
The same authoritative user-domain inputs yield the same result. That
determinism is the test. user-engine is not Staff: it is not a judgment
surface. It is not Tooling: it does not package identity infrastructure or
hold runtime secrets. It is not the PDP: `access-engine` (`flex-auth` until
the governed rename) is the only decision point in the estate.
The catalog row we accept: **users, accounts, memberships.** The existing
boundary contract
(`net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`) holds
unchanged. What the model adds, and what this file now states in our own
voice:
- we **supply subject context as claims** a decision consumes;
- we **never render, cache, or compile an authorization decision**;
- a new engine is a PIP unless the statute is amended, and we stay one.
**PEP-shaped, same layer.** Protected mutations in this service — account
lifecycle, memberships, invitations, registration orchestration, hat
selection, catalog and profile writes — proceed only after a decision from
`access-engine`, or under a published unreachable-engine stance whose
application is recorded in place of a decision. Being PEP-shaped does not
move the repository out of Engine/PIP. The stance map belongs at a path
named in the machine-readable layer declaration, registered in statute
§13.1; shipping fail-closed in an adapter is not publication.
**Not contested.** Engine/PIP is the right assignment. What we raise back
is that §4 catalogues us as Engine/PIP and does not mark the PEP shape
that our protected mutations already have. That is a register finding,
not a layer dispute.
### Binding rules this repository keeps
1. **One decision point.** `access-engine` renders authorization decisions.
user-engine asks, enforces, and records. Access-control facts, hats,
memberships, and projections are inputs and read models, never a local
allow.
2. **Compiled data that determines an outcome is still deciding.** A
registry, cache, or access-profile schema that resolved a result before
the engine ran would be an early decision. Provenance of anything we
export as claims must remain reconstructable from the decision record
that consumed it.
3. **Doctrine arrives as a claim.** Nothing we hold — membership, hat,
assurance forwarded from IAM, tenant capability roles — becomes
authority except by reaching `access-engine` as a request claim or a
versioned policy rule.
4. **Adaptive systems may only tighten.** user-engine does not manufacture
authority, and it does not assume the estate can contain anything
automatically. Actuation is unowned and held at zero.
5. **Every allow has a lifetime.** A decision we enforce is used within
its stated binding and lifetime, not as a standing grant.
6. **Evidence proves alteration and truncation, not omission at source.**
Local audit and the durable outbox prove the records they hold. They
do not prove that an event never sent did not happen. Completeness is
not claimed.
7. **Agents working this repository are Staff principals**, not a second
layer. No standing credential, tool use only as a conduit or an Engine
API, memory is not a state plane, every action reconstructable as the
caller's. How to get a credential or a lane is `ops-warden`'s, not
this file's.
**Tooling.** §5 binds Staff. This Engine owns the persistence of the
concept it models (the user-domain store). It consumes verified IAM
Profile claims from `key-cape` / Keycloak / `local-identity`, and runtime
secret *references* injected by Railiance. It holds no OpenBao client and
no Keycloak admin client. A `SecretProvider` port exists so an engine
surface can replace environment injection when `secrets-engine` exposes
one; until then that capability is at zero here, not taken locally.
## Purpose
`user-engine` exists to provide a reusable, headless user-domain and
@ -27,6 +97,11 @@ preference, membership, identity-context, and application-specific user
attribute management without coupling those concerns to a particular identity
provider, authorization engine, security stack, or user interface.
As a PIP it is the estate's source of user-domain facts that authorization
and consuming applications may take as claims: who the subject is in a
domain context, which account and memberships apply, which profile
material may be projected, and which identity-context mappings hold.
## Primary Utility
The project provides a canonical user layer that can start small in a
@ -45,13 +120,16 @@ It manages:
- profile projections for consuming applications;
- lifecycle and profile-change events.
Those records are the PIP surface. They are not a policy package.
## Strategic Direction
`user-engine` is intended to become the NetKingdom identity-domain integration
layer: a headless service that exposes canonical identity, user, account,
layer: a headless Engine that exposes canonical identity, user, account,
membership, tenant, team, profile, lifecycle, and evidence-facing concepts to
applications without requiring those applications to know the technical IAM,
security, authorization, audit, or secret-management implementation details.
applications, and that supplies those facts as claims to `access-engine`,
without requiring those applications to know the technical IAM, security,
authorization, audit, or secret-management implementation details.
NetKingdom infrastructure remains the source of truth for authentication,
credential assurance, token issuance, policy decisions, security controls,
@ -70,6 +148,9 @@ applications and agents to answer:
claims-enrichment contexts;
- which lifecycle, access-review, evidence, or integration gaps need attention.
It should never answer whether an actor may act. That answer is
`access-engine`'s, including when the resource is a user-engine record.
## Strategic Role
`user-engine` separates user-domain management from authentication,
@ -85,6 +166,11 @@ identity-context mappings. It should reference, map to, or consume adjacent
canon entities when their source of truth belongs to NetKingdom infrastructure,
security, access-control, governance, or organization systems.
Hats, realms, services, assets, and access profiles stay user-domain facts
and claim templates. They exist so `access-engine` and consuming runtimes
can see the same subject context; they do not become a second decision
point by compilation or by local evaluation.
## Intended Users
- application developers adding user/account functionality to a service;
@ -96,7 +182,9 @@ security, access-control, governance, or organization systems.
context;
- domain services that need identity-canon aligned user, actor, principal,
subject, tenant, team, membership, and evidence references without depending
on technical IAM implementation details.
on technical IAM implementation details;
- `access-engine`, as a PIP consumer of subject, membership, and
access-control facts.
## Product Boundaries
@ -107,15 +195,19 @@ It does not aim to be:
- a full identity provider;
- a password, passkey, session, or MFA system;
- a fine-grained authorization engine;
- the policy decision point for protected resources;
- the policy decision point for protected resources, including its own;
- the owner of NetKingdom security controls or runtime secrets;
- the full organization, HR, or directory authority;
- a directory server;
- a UI application.
- a UI application;
- an observer of production security events;
- an actuation surface for containment, step-up, or authority reduction.
It provides the user-domain and identity-domain APIs, mapping records, catalog
metadata, projections, events, evidence references, and audit records that those
surrounding systems can consume.
metadata, projections, events, evidence references, and local audit records
that those surrounding systems can consume. Platform audit custody stays
with `audit-core`. Optional UI in this repository is a surface over the
same APIs, not a product identity.
## Design Principles
@ -124,14 +216,17 @@ surrounding systems can consume.
- standalone-friendly;
- enterprise-integratable;
- identity-provider agnostic;
- authorization-engine agnostic;
- authorization-engine agnostic — and never a substitute for one;
- NetKingdom-integrated without hiding source-of-truth boundaries;
- canon-aligned through explicit entity and relationship mappings;
- catalog-driven customization;
- explicit ownership, visibility, mutability, and sensitivity of attributes;
- layered profiles instead of one global metadata blob;
- deterministic and inspectable effective profile resolution;
- evidence and lifecycle awareness for identity-domain changes;
- evidence and lifecycle awareness for identity-domain changes, bounded by
what an archive can actually prove;
- fail closed on unreachable `access-engine` for protected mutations, with
that stance published rather than implied by adapter code;
- concrete user-domain focus with a possible future extraction path toward a
generic profile engine.
@ -147,3 +242,9 @@ intent: applications should be able to consume user, account, identity-link,
actor, principal, subject, tenant, team, membership, profile, lifecycle, and
evidence context through stable APIs and mappings without taking a direct
dependency on the technical IAM, authorization, security, or audit substrate.
It succeeds as a PIP when `access-engine` can consume those facts as claims
and reconstruct their application from the decision record, and as a
PEP-shaped engine when every protected mutation it performs is bound to a
decision or to a recorded stance, with a lifetime, and without a second
decision point hiding in a cache, a hat, or a local adapter.

108
SCOPE.md
View file

@ -1,11 +1,25 @@
# SCOPE
## Layer
**Engine, role PIP** — deterministic API for users, accounts, and
memberships. Subject context is an input to the authorization decision and
never a decision. Protected mutations are PEP-shaped: they consume
`access-engine` (`flex-auth`) and do not substitute for it.
Declaration: `INTENT.md` frontmatter. Statute:
`net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted).
Working companion: `net-kingdom/SECURITY-COMPANION.md`. Boundary contract
unchanged:
`net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`.
## One-Liner
Headless user-domain and identity-domain service for accounts, identity
links, memberships, catalogs, projections, audit, and events, with an
Headless user-domain and identity-domain Engine (PIP) for accounts, identity
links, memberships, catalogs, projections, local audit, and events, with an
optional in-repo portal. It consumes NetKingdom IAM, authorization, tenant
authority, provisioning, and delivery; it does not own them.
authority, provisioning, and delivery; it does not own them and it does not
decide them.
## In Scope
@ -17,8 +31,10 @@ authority, provisioning, and delivery; it does not own them.
preferences;
- tenant, application, team, and scope memberships;
- hats, realms, services, assets, access profiles, and active access
context as user-domain facts;
context as user-domain facts and claim templates;
- identity-context read models for domain consumers;
- PIP exports: subject, membership, and access-control facts for
`access-engine` and other engines to consume as claims;
- canon interface cards, entity and relationship mappings, and explicit
gap records;
- application registry for profile consumers;
@ -35,17 +51,22 @@ authority, provisioning, and delivery; it does not own them.
(create, read, update, retire, reactivate, recover);
- optional CSRF-protected portal over the same APIs (self-service,
onboarding, tenant admin, platform operator);
- standalone/local fixtures and a production PostgreSQL store;
- standalone/local fixtures and a production PostgreSQL store of the
modeled concept;
- integration ports for claims, flex-auth decisions (including a rotating
caller token), provisioning, registration verification, tenant
management, outbox delivery, and runtime secrets.
management, outbox delivery, and runtime secrets;
- PEP enforcement of `access-engine` decisions on user-engine-owned
mutations, including a published unreachable-engine stance.
## Out Of Scope
- login, OIDC/SAML token issuance, passwords, passkeys, sessions, and MFA
lifecycle — `key-cape`, Keycloak, or `local-identity`;
- final authorization policy decisions and the protected-system registry —
`flex-auth`;
`flex-auth` / `access-engine`;
- caching or compiling an authorization decision, including from hats,
access profiles, or memberships;
- durable authorization grants beyond user-engine-owned memberships;
- tenant identifier authority, grouping reclassification, and capability
role grants — `tenant-engine`;
@ -53,16 +74,22 @@ authority, provisioning, and delivery; it does not own them.
consuming apps (e.g. coulomb-social) and KeyCape client policy;
- policy, control, access-review, exception, and organization
source-of-truth ownership;
- runtime secret custody — OpenBao / Railiance;
- runtime secret custody — OpenBao / Railiance; no OpenBao client here;
- a Keycloak or key-cape admin client;
- platform audit store and transactional SMTP — `audit-core` and
`email-connect`;
- full SCIM server, enterprise directory replacement, or inbound
SAML/OIDC federation (demand-triggered; Keycloak expanded mode is the
published path);
- a generic extracted profile engine.
- a generic extracted profile engine;
- observing production security events — `kings-guard`, and currently
unstaffed;
- actuation: reduce authority, require step-up, isolate a workload — an
Engine concept held at zero estate-wide.
The in-repo portal is an optional surface, not a UI product. Password and
MFA screens stay on the identity provider.
MFA screens stay on the identity provider. `LocalAuthorizationCheckPort`
is a test and standalone double, not a production decision point.
## Boundary Rule
@ -73,6 +100,13 @@ deployment, event transport, durable audit, secrets, organization records,
or additional UI — and they integrate through explicit adapters. They must
not become hidden sources of profile or identity-domain truth.
user-engine must not become a hidden source of authorization truth.
Memberships, hats, and access-control facts are claims. `access-engine`
renders the decision. When `access-engine` is unreachable, shipped
behaviour is fail-closed for protected mutations; that stance still has
to be published, tested equal to the code, and recorded as stance
application rather than as a minted local decision id.
Governing published contracts:
- IAM Profile v0.3 — https://policy.coulomb.social/standards/iam-profile/v0.3/
@ -80,15 +114,23 @@ Governing published contracts:
- NetKingdom architecture — https://policy.coulomb.social/architecture/net-kingdom/v0.1/
- User-engine boundary contract (accepted, not yet published) —
`~/net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`
- NetKingdom Security Layer Model v0.7 (accepted) —
`~/net-kingdom/canon/standards/security-layer-model_v0.7.md`
- Working companion —
`~/net-kingdom/SECURITY-COMPANION.md`
## Current Status
Workplans `USER-WP-0001` through `USER-WP-0023` are finished. There is no
active workplan. The isolated MVP, multi-tenancy, catalogs, canon
alignment, durable PostgreSQL store, self-service and admin portal,
public-registration orchestration, and flex-auth caller identity (live A2
on `flex-auth-user-engine`) are in the repo and, where applicable, on
Railiance.
Workplans `USER-WP-0001` through `USER-WP-0023` are finished. The isolated
MVP, multi-tenancy, catalogs, canon alignment, durable PostgreSQL store,
self-service and admin portal, public-registration orchestration, and
flex-auth caller identity (live A2 on `flex-auth-user-engine`) are in the
repo and, where applicable, on Railiance.
`USER-WP-0024` is the active workplan: layer declaration is in `INTENT.md`;
machine-readable conformance, the published PEP stance map, decision
lifetime, evidence cadence, and the access-control-fact claim contract
are not yet shipped.
Still operator-owned, not remaining product scope:
@ -100,10 +142,25 @@ Still operator-owned, not remaining product scope:
- `policy.enabled` and tenant-engine caller `enforce` belong to flex-auth
/ tenant-engine.
Layer-model residue that is remaining product scope, tracked in
`USER-WP-0024` and assessed in
`history/2026-08-29-security-layer-scope-intent-assessment.md`:
- no `layer.yaml` and no conformance check;
- unreachable-engine stance is fail-closed in
`FlexAuthHTTPAdapter` but unpublished, untested against a map, and
recorded as a locally minted `decision_id`;
- `AuthorizationDecision` carries no lifetime;
- denials have no declared emission cadence or heartbeat;
- `LocalAuthorizationCheckPort` defaults to allow and is a second
decision point if a production runtime ever constructed it.
## Against INTENT.md
INTENT is the stable aspiration. Against it, the repo now does the job it
set out to do.
INTENT is the stable aspiration. Against the pre-layer product aims, the
repo still does the job it set out to do. Against the layer declaration
now in INTENT, SCOPE is narrower than INTENT on conformance artifacts
and PEP obligations.
| INTENT aim | Status |
| --- | --- |
@ -112,10 +169,17 @@ set out to do.
| Users, links, memberships, catalogs, projections, events | Met. |
| NetKingdom identity-domain integration layer | Met for the owned slice. Consumes KeyCape, flex-auth, tenant-engine, identity-provisioner, audit-core, email-connect. |
| Applications answer who / which scopes / what to project | Met via `/me`, identity context, catalogs, and projections. |
| Engine / PIP declaration in own voice | Met in `INTENT.md` as of 2026-08-29. Closes the declaration half of `USER-IN-0001`. |
| Subject context is a claim, never a decision | Held in production path (`FlexAuthHTTPAdapter`). Not yet proven for hats/access-control facts under §6.1, and the local double can still decide. |
| PEP-shaped: no side effect without a decision or recorded stance | Partial. Production asks flex-auth and fail-closes on errors. Stance is unpublished; unavailable DENY mints a local `decision_id`. |
| Published unreachable-engine stance map, tested equal to shipped behaviour | Missing. |
| Every allow has a lifetime | Missing on `AuthorizationDecision`. |
| Evidence bound: no completeness claim; cadence for load-bearing events | Partial. Mutations enqueue audit+outbox in the same store transaction. Completeness is not claimed in code; docs do not classify load-bearing vs attributive or declare a cadence. |
| Not an IdP, PDP, secret store, directory, or org authority | Held. |
| Optional UI, not UI-driven | Held, with a narrower reading: an optional portal now lives *in this repo* over the same APIs. INTENT's "not a UI application" still applies to product identity. |
| Canon-aligned mappings without taking IAM as SoT | Met (`USER-WP-0007`, interface card). Access-review, policy, and control remain references, not owned records. |
| Path from local setup to governed NetKingdom deploy | Met. |
| No OpenBao / key-cape admin client | Held. `SecretProvider` is an unused port; runtime reads env. |
Still aspirational, and deliberately not started here:
@ -123,6 +187,10 @@ Still aspirational, and deliberately not started here:
demand, targeting published Keycloak expanded mode;
- first-class access-review and governance records;
- a dedicated agent consumption product (projections exist);
- extracting a generic profile engine.
- extracting a generic profile engine;
- observing production, or actuating containment — estate-wide zeros,
not this repo's gaps to close.
Those remain INTENT, not a hole in SCOPE.
Those remain INTENT, not a hole in SCOPE, except the layer-conformance
rows above, which are now SCOPE because INTENT has adopted the accepted
standard.

View file

@ -0,0 +1,317 @@
# 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.

View file

@ -7,7 +7,7 @@ id: USER-IN-0001
kind: intake
title: 'Declaration requested: state this repository''s layer in INTENT.md (security
layer model §11)'
status: open
status: answered
origin: cross-repo
origin_ref: net-kingdom security-layer-model_v0.4 §11
priority: low
@ -32,5 +32,12 @@ description: 'A conformance sweep on 2026-08-28 found this repository has no lay
for what this repository actually does, that is more useful to us than a label added
to close a checkbox. Standard: net-kingdom/canon/standards/security-layer-model_v0.4.md.'
created: '2026-08-28T21:02:12.760565Z'
updated: '2026-08-28T21:02:12.760565Z'
updated: '2026-08-29'
answered: '2026-08-29'
answer: >-
Own-voice declaration in INTENT.md: layer Engine, role PIP. Subject
context is a claim, never a decision. Protected mutations are PEP-shaped
without changing layer. Not contested. Runtime follow-through is
USER-WP-0024. Assessment:
history/2026-08-29-security-layer-scope-intent-assessment.md.
```

View file

@ -0,0 +1,152 @@
---
id: USER-WP-0024
type: workplan
title: "Security layer model conformance for Engine/PIP"
domain: communication
repo: user-engine
status: active
owner: grok
topic_slug: netkingdom
created: "2026-08-29"
updated: "2026-08-29"
depends_on:
- USER-WP-0023
---
# USER-WP-0024 — security layer model conformance
Adopt the accepted NetKingdom Security Layer Model
(`net-kingdom/canon/standards/security-layer-model_v0.7.md`, companion
`net-kingdom/SECURITY-COMPANION.md`) as this repository's own declaration
and close the implementation gaps that leave a PEP-shaped Engine short of
the standard.
Assessment:
`history/2026-08-29-security-layer-scope-intent-assessment.md`.
Working references: `ops-warden/layer.yaml` and
`kings-guard/layer.yaml` (no Tooling contacts). Credential and lane
questions go to `ops-warden`, not this workplan.
## T01 — Declare Engine/PIP in own voice
```task
id: USER-WP-0024-T01
status: done
priority: high
```
Replace the gate-house review note in `INTENT.md` with this repository's
own declaration: frontmatter `layer: Engine`, `role: PIP`, plus prose that
subject context is a claim, never a decision, and that protected
mutations are PEP-shaped without changing layer. Restate `SCOPE.md` from
that INTENT. Record the scope-vs-intent-vs-implementation assessment
under `history/`.
Done 2026-08-29. Closes the declaration half of `USER-IN-0001`. Does not
by itself make the runtime conforming.
## T02 — Machine-readable layer declaration and check
```task
id: USER-WP-0024-T02
status: todo
priority: high
```
Add `layer.yaml` (or equivalent) naming Engine/PIP, empty catalogued
Tooling contacts, non-Tooling / own-store clients (PostgreSQL modeled
concept, State Hub, env-injected secrets, flex-auth Engine API, OIDC
claim input), and the path of the PEP stance map. Adapt
`kings-guard/scripts/check_layer_conformance.py` and a unit test so an
undeclared OpenBao, Vault, LDAP, or cluster client fails the commit that
introduces it.
Do not add a Tooling client to have something to declare.
**Done when:** `layer.yaml` is the machine-readable declaration, the
checker exits 0 on the current tree, and a test covers the empty
Tooling-contact claim.
## T03 — PEP obligations on the production path
```task
id: USER-WP-0024-T03
status: todo
priority: high
```
Ship the four §6.4 obligations for this PEP-shaped Engine:
1. Publish `pep-stance.yaml` (total, no implicit default). Production
remains fail-closed, including unknown. A test asserts the file equals
`FlexAuthHTTPAdapter` behaviour.
2. On engine-unavailable DENY, record stance application (scope, failure
mode, outcome). `decision_id` is present only when flex-auth rendered
a decision. Stop minting a local id via `AuthorizationDecision`'s
default factory on that path.
3. Give `AuthorizationDecision` an explicit lifetime (TTL or
session/obligation binding). Do not add a verdict cache.
4. Confine `LocalAuthorizationCheckPort` to tests and standalone
fixtures. Production `create_application()` must be unable to
construct a default-allow adapter.
**Done when:** the published map matches shipped fail-closed behaviour,
unavailable DENY audits carry no engine decision id, allows carry a
lifetime, and the local port cannot enter the production runtime
assembly.
## T04 — Evidence bound and cadence
```task
id: USER-WP-0024-T04
status: todo
priority: medium
```
Classify local audit and outbox events as load-bearing or attributive.
Keep mutation+audit+outbox atomic in the store transaction (already
true). For denials and membership/account revocations — low-volume
load-bearing classes — declare a heartbeat or reconciliation, not a
rate. Update `docs/evidence-gap-examples.md` and operability copy so the
trail is never described as complete, and so absence of a record is not
read as non-occurrence.
**Done when:** the classification is in-repo, the heartbeat or
reconciliation form is named, and docs use the sound evidence sentences.
## T05 — Access-control facts remain claims
```task
id: USER-WP-0024-T05
status: todo
priority: medium
```
Prove under statute §6.1 that hats, access profiles, and
`AccessControlFact` exports cannot be mistaken for an authorization
decision. Add a test that selection and export never return allow/deny.
Document the consumer contract: compiling those facts into a local
decision is an early decision and is forbidden.
**Done when:** a test and the hats/boundary docs state the claim
contract, and no user-engine API on that path exposes an effect.
## T06 — Close the intake and request the stance-map row
```task
id: USER-WP-0024-T06
status: done
priority: medium
```
Mark `USER-IN-0001` answered: Engine/PIP, not contested. Message
`gate-house` with the declaration path, the PEP-shaped finding for
statute §13.1 (user-engine is a third PEP-shaped consumer; the register
currently has one published row), and a pointer at this workplan for the
runtime follow-through.
Done 2026-08-29. Intake marked answered in `intakes/intakes.md`. Message
`c014d12a-fe7a-4a90-ba73-03a5ac3e7f73` to `gate-house` carries the
declaration, the non-contest, and the §13.1 request (row to be added
when `pep-stance.yaml` ships in T03).