Engine/PIP declaration is now checkable (layer.yaml plus a Tooling-client scan). Writes persist a decision record or the published fail-closed stance, live-lookup freshness is published, events_for is tenant-scoped, and mutation evidence drains to audit-core from a local outbox without blocking the mutation. Sender registration is requested as AUDIT-IN-0002. Boundary-contract amendment is requested as NET-IN-0002. Assistant: grok Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
9.5 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | depends_on | unblocks | origin | origin_ref | state_hub_workstream_id | |||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| TEN-WP-0011 | workplan | Align tenant-engine with the accepted security layer model | infotech | tenant-engine | finished | grok | netkingdom | 2026-08-29 | 2026-08-29 |
|
residual | TEN-IN-0003 | 43ad25c6-7149-53d1-8012-817a061ace92 |
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-001contested 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
id: TEN-WP-0011-T01
status: done
priority: high
state_hub_task_id: "f46944d8-e638-5f64-a4ad-a713102dfa72"
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
id: TEN-WP-0011-T02
status: done
priority: high
state_hub_task_id: "62659305-8283-594e-8670-f9335f5c74a0"
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-engineis 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 (DefaultDenyWriteAuthorizerand 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
id: TEN-WP-0011-T03
status: done
priority: high
state_hub_task_id: "7815548c-36aa-518c-812e-8562be3a5111"
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 oneaccess-enginedecision 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 soaccess-enginecan 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 indocs/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
id: TEN-WP-0011-T04
status: done
priority: high
state_hub_task_id: "cfab6837-5f62-5beb-b99f-943d39b5b94d"
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:
- Agree envelope, sender registration, and credential custody with
audit-corewithout putting a secret in Git or a work record. Route credentials throughwarden route, never through a State Hub message. - 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.
- Prove the external copy cannot be rewritten through tenant-engine's runtime database credential.
- Decide whether pre-cutover event classes need a bounded backfill, and record the decision.
- 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.
Done 2026-08-29: local outbox is in the mutation transaction; drain
POSTs /v1/events and never fails the mutation; trade declared in
docs/evidence-emission.md; no pre-cutover backfill. Production landing
waits on sender registration, requested as AUDIT-IN-0002.
T05 — Remove or authorize the unfiltered event-read interface
id: TEN-WP-0011-T05
status: done
priority: high
state_hub_task_id: "7540049b-9b76-5d16-9ce4-57395a53ecbc"
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.
Done 2026-08-29: events() removed from the production protocol;
events_for(tenant_id) is the only read. Cross-tenant negative test in
tests/test_events_scoped.py. Reading handed to risk-nexus.
T06 — Request the boundary-contract amendment
id: TEN-WP-0011-T06
status: done
priority: medium
state_hub_task_id: "ffd2cc53-bde2-5e3c-a144-ab565bc12776"
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.
Done 2026-08-29: filed NET-IN-0002 on net-kingdom
(net-kingdom/intakes/intakes.md).