Declare Engine/Lifecycle against security layer model v0.7
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Replace the gate-house review note with this repository's own declaration:
INTENT.md frontmatter, layer.yaml, and a published PEP stance map. SCOPE.md
and agent boundary docs now match that layer. The review under history/
identifies the implementation remainder; SECRETS-WP-0008 is the follow-on
workplan. SECRETS-IN-0001 is closed.

The layer is not contested. Catalog "custody" is a finding: OpenBao owns
custody, this engine owns the lifecycle API over it. SSH-CA signing is
accepted as a proposed engine API and declined as a Staff lane.

Assistant: grok
Assistant-Session: 01a04cea-cb33-7c63-bad7-c1b0f9f0076b
This commit is contained in:
tegwick 2026-08-29 11:57:47 +02:00
parent b1f0f7b666
commit 1945e16685
14 changed files with 1177 additions and 108 deletions

View file

@ -1,12 +1,15 @@
## Repo boundary
This repo owns the **secrets-engine** workflow/interaction layer only. It does
not own:
This repo owns the **secrets-engine** Lifecycle engine only (NetKingdom
security layer model v0.7: Engine / Lifecycle). It does not own:
- Secret custody, policy, lease, and audit backend → OpenBao / railiance-platform
- SSH certificate issuance → ops-warden (`warden sign`)
- SSH certificate issuance (Staff PEP) → ops-warden (`warden sign`)
- Tunnels and remote transport → ops-bridge
- Authorization decisions → flex-auth
- Authorization decisions → access-engine (`flex-auth`)
- Approval objects → approval-engine
- Evidence custody and integrity → audit-core
- Identity and claim lifecycle → user-engine / key-cape
- Security doctrine and the layer model → gate-house / net-kingdom canon
- Cross-system security boundary doc → net-kingdom/docs/
- Request history and progress index → State Hub (read model)

View file

@ -1,4 +1,4 @@
**Purpose:** secrets-engine is a headless, multi-application, multi-tenant secrets workflow and automation layer that orchestrates approved secret custody, delivery, and lifecycle work across build, test, and production stages, with OpenBao as the initial enforcement backend.
**Purpose:** secrets-engine is the Lifecycle engine for approved credential work: a deterministic API over OpenBao that orchestrates cataloged custody, delivery, and lifecycle actions across build, test, and production. It does not render authorization decisions.
**Domain:** infotech
**Repo slug:** secrets-engine

View file

@ -2,7 +2,7 @@
## Repo Identity
**Purpose:** secrets-engine is a headless, multi-application, multi-tenant secrets workflow and automation layer that orchestrates approved secret custody, delivery, and lifecycle work across build, test, and production stages, with OpenBao as the initial enforcement backend.
**Purpose:** secrets-engine is the Lifecycle engine for approved credential work: a deterministic API over OpenBao that orchestrates cataloged custody, delivery, and lifecycle actions across build, test, and production. It does not render authorization decisions.
**Domain:** infotech
**Repo slug:** secrets-engine
@ -166,17 +166,24 @@ Read `docs/netkingdom-security-infrastructure.md` before changing secret
catalogs, OpenBao policies, auth roles, delivery modes, or ops-warden routing.
Core rules:
- OpenBao is the custody, policy, lease, and audit backend.
- secrets-engine owns workflow, catalog, decision checks, safe delivery, and
non-secret evidence.
- flex-auth decides authorization; user-engine/key-cape own identity and claims.
- Layer: Engine / Lifecycle (`INTENT.md`, `layer.yaml`). PEP stance:
`pep-stance.yaml`.
- OpenBao is the custody, policy, lease, and audit backend (Tooling).
- secrets-engine is the Lifecycle API over that backend: catalog, decision
consumption, safe delivery, and non-secret evidence. It does not render
authorization decisions.
- `access-engine` (`flex-auth`) decides authorization; user-engine/key-cape own
identity and claims; `audit-core` owns evidence custody.
- ops-warden routes non-SSH credential requests here and must not vend secrets.
SSH certificate issuance remains ops-warden's Staff PEP.
- ops-bridge may consume scoped delivery for remote execution but must not store
secret material.
- info-tech-canon is the source for canonical terminology and stage/policy
concepts as they harden.
concepts as they harden. gate-house owns the layer model.
- Never write raw secret values to Git, State Hub, chat, prompts, workplans, or
normal logs.
- Never claim local evidence or a missing record proves occurrence or
non-occurrence.
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->

206
INTENT.md
View file

@ -1,40 +1,78 @@
---
layer: Engine
role: Lifecycle
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
companion: net-kingdom/SECURITY-COMPANION.md
declaration: layer.yaml
pep_stance: pep-stance.yaml
declared_at: "2026-08-29"
---
# 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.
> **Layer: Engine. Role: Lifecycle.** This is this repository's own declaration
> under the accepted NetKingdom Security Layer Model
> (`net-kingdom/canon/standards/security-layer-model_v0.7.md`) and its working
> companion (`net-kingdom/SECURITY-COMPANION.md`). The machine-readable form
> required by §11 is `layer.yaml`. A layer stated about this repository by
> another repository is not a declaration; this file, in this repository's
> voice, is.
>
> The layer rule that binds every repository: **Staff never touches tooling
> directly. It acts only through engine APIs.**
> secrets-engine is a **deterministic API over OpenBao** for one modeled
> concept: the cataloged credential lane and its lifecycle. The same
> authoritative inputs — catalog entry, access-engine decision record, and
> OpenBao state — must yield the same plan, apply, delivery, and lifecycle
> result. That is why this repository is an Engine, not Staff, even though
> operators and agents invoke it at runtime.
>
> **This repository is Engine — deterministic API for a modeled concept.** Add the layer label. Credential custody, materialization, and lifecycle remain secrets-engine's. Two boundaries to make explicit: the decision that authority exists is access-engine's, and the doctrine governing when and how authority may be materialized is gate-house's. secrets-engine implements; it does not author the rules or render the decision.
> **It is not a decision point.** `access-engine` (today `flex-auth`) renders
> whether authority exists. gate-house authors the doctrine that may reach that
> decision only as a request claim or a versioned policy rule. OpenBao is
> Tooling: custody, policy enforcement, leases, and backend audit. This engine
> materializes an already-rendered decision into scoped OpenBao work. A
> credential is an artifact of authority, never its source.
>
> *This note records what should change. The body below is not yet adapted.*
> **It is PEP-shaped.** Apply, provision, exec delivery, handoff, suspend,
> deactivate, and destroy cause protected side effects, so the four PEP
> obligations in §6.4 bind: no side effect without a decision record or a
> recorded stance; no replay of a verdict outside its binding and lifetime;
> a published unreachable-engine stance at `pep-stance.yaml`; reconstructability
> bounded by §9.6. Being PEP-shaped does not change the layer.
>
> **Proposed surfaces, not yet assented as shipped APIs.** ops-warden has
> declared its SSH-CA signing write as an engine gap with intended owner
> secrets-engine. kings-guard has proposed secret-use evidence against this
> engine. This repository **accepts both as proposed Lifecycle surfaces** and
> **declines operational SSH-lane stewardship**: `ops-warden` remains the Staff
> PEP that requests a certificate after a decision. What belongs here is the
> engine API those Staff repositories should call instead of OpenBao. Until
> those surfaces exist, the gaps stay proposed, not silently taken.
>
> **Finding on the §4 catalog cell.** The catalog row currently says this
> repository owns "credential abstraction, custody, lifecycle". Custody belongs
> to OpenBao. This engine owns the lifecycle API *over* that custody. The
> wording should not assign a Tooling property to an Engine.
> 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.
## One-liner
**secrets-engine is the secure interaction and automation layer for approved
secret custody, scoped OpenBao capability handoff, delivery, and lifecycle work
across build, test, and production, with OpenBao as the initial enforcement
backend.**
**secrets-engine is the Lifecycle engine for approved credential work: a
deterministic API that turns an access-engine decision into scoped OpenBao
custody, delivery, and lifecycle actions across build, test, and production.**
## Why This Exists
Secret management is too important to be handled through scattered manual UI
steps, copied commands, chat handoffs, and ad hoc token files.
OpenBao is the right place to enforce custody, policy, lease, and audit. It is
not, by itself, the right daily interaction surface for operators, agents,
workloads, and approval workflows. Direct use of OpenBao UI and CLI surfaces has
already produced avoidable friction:
OpenBao is the right Tooling for custody, policy, lease, and audit. It is not,
by itself, the right daily interaction surface for operators, agents, workloads,
and approval workflows. Direct use of OpenBao UI and CLI surfaces has already
produced avoidable friction:
- approved decisions still require a human to bridge low-level OpenBao details;
- agents and operators hit interface mismatches before reaching the security
@ -42,111 +80,145 @@ already produced avoidable friction:
- build, test, and production need different levels of privilege and ceremony;
- secret values must never move through chat, Git, prompts, State Hub messages,
or workplans;
- ops-warden needs a reliable front door for credential and scoped capability
use, not direct secret custody responsibility.
- Staff repositories such as ops-warden need an Engine API for credential and
scoped-capability work, not a direct OpenBao client and not secret-vending
responsibility of their own.
This repository exists to capture that complexity once, behind a stable and
auditable interface.
This repository exists to capture that complexity once, behind a stable,
deterministic, and auditable interface.
## The Mission
To provide a decision-aware secrets workflow that can:
To provide a decision-aware Lifecycle engine that can:
- translate approved requests into narrowly scoped OpenBao changes or capability
- consume an access-engine decision and refuse to proceed without one, or
record the application of a declared unreachable-engine stance in its place;
- translate that decision into narrowly scoped OpenBao changes or capability
grants;
- operate with distinct build, test, and production privilege layers;
- deliver secrets and scoped capabilities to commands and workloads without
printing or storing raw values in coordination systems;
- record enough non-secret evidence for review, audit, and troubleshooting;
- emit non-secret evidence under the §9.6 bound — the archive proves records
it holds were not altered or truncated after arrival, not that every event
was sent, and not that absence means non-occurrence;
- make routine secure work fast enough that operators and agents actually use
the secure path.
the Engine path instead of touching OpenBao.
## Core Principles
### 1. Decision First
### 1. Decision elsewhere, materialization here
Secret establishment, access, rotation, and deactivation start from an explicit
request and decision. The engine may automate the work after approval; it does
not silently create new secret authority.
Secret establishment, access, rotation, and deactivation start from an
access-engine decision record naming the request it was rendered for.
This engine may automate the work after that decision; it does not render,
cache, or substitute one. Compiled catalog data that would determine an
outcome before the engine runs is still deciding, and is forbidden.
### 2. OpenBao Enforces, secrets-engine Orchestrates
### 2. OpenBao is Tooling; this engine is the API over it
OpenBao remains the vault, policy, lease, and audit backend. secrets-engine owns
the workflow, catalog, validation, delivery, scoped capability handoff, and
operator/agent interface.
OpenBao remains the vault, policy, lease, and audit backend. secrets-engine
owns the catalog, validation, delivery, scoped capability handoff, lifecycle
operations, and the operator/agent/Staff interface to those operations. Staff
must not hold a direct OpenBao client for work this engine is meant to expose.
### 3. Stage-aware Privilege
### 3. Stage-aware privilege
Build, test, and production are separate security contexts. Each has its own
OpenBao role, policy boundary, approval expectation, TTL limits, and delivery
rules.
rules. Stage is the current equivalent scope for the PEP stance map until
security-zone membership is consumed as a claim.
### 4. No Secret Values in Coordination Surfaces
### 4. No secret values in coordination surfaces
Git, State Hub, workplans, chat, prompts, issue comments, and normal logs carry
only non-secret metadata. Raw values are delivered through OpenBao, wrapped
responses, local exec-time injection, or short-lived bootstrap files with strict
permissions.
### 5. Least Friction Without Broad Power
### 5. Least friction without broad power
The common path should be one clear command or review action. That ease must not
require handing platform-root or platform-admin power to routine automation.
The common path should be one clear command or Engine API call. That ease must
not require handing platform-root or platform-admin power to routine
automation, and it must not mint standing credentials for agents. Agent
authority is per task, time-bounded, and attributable to the principal the
agent acts for.
### 6. Bootstrap Honestly, Then Harden
### 6. Bootstrap honestly, then harden
During infrastructure setup it is acceptable to use temporary root-created
OpenBao credentials stored outside repositories with mode 0600 and explicit
revocation. Those bootstrap shortcuts must be tracked as temporary and replaced
by narrower auth roles.
revocation. Those bootstrap shortcuts must be tracked as temporary, must never
become an implicit fallback, and must be replaced by narrower service identity
bound to OpenBao.
### 7. Prefer Exec-time Delivery
### 7. Prefer exec-time delivery
When a workload or operator command needs a secret, the default delivery mode is
process-local injection for the duration of that command. The engine should make
this easier than copying or inspecting a secret value.
process-local injection for the duration of that command. Every such allow has
an explicit lifetime — a TTL, or a binding to a session that ends. The engine
should make this easier than copying or inspecting a secret value.
### 8. Auditable and Reversible
### 8. Evidence is bounded, not omniscient
Every apply, read, delivery, lease, verification, revocation, and deactivation
has non-secret evidence that can be reviewed later.
emits non-secret evidence. Load-bearing events — those a control would be
unsound without — require atomic local emission and a declared cadence or
heartbeat. Attributive events should seek atomicity; where they trade it away,
the trade is declared and completeness is not claimed. Evidence belongs in
`audit-core`. Local JSONL and State Hub notes are not a substitute for that
engine.
### 9. Adaptive systems may only tighten
This engine may shorten a lifetime, refuse a delivery, suspend a lane, or
require a fresh decision. It must not manufacture authority, widen a grant, or
treat silence from `access-engine` as permission.
## What This Is
secrets-engine is:
- a workflow layer for approved secret changes, scoped capability grants, and
access;
- a CLI and service API for operators, agents, and automation;
- the Lifecycle engine for cataloged credential lanes;
- a CLI today and a service API for operators, agents, CI, and Staff
repositories that must not speak OpenBao;
- a typed catalog of KV secret lanes, auth-capability lanes, grants, delivery
modes, and stage policies;
- an OpenBao policy/auth-role applier with strict local validation;
- an exec-time secret delivery and scoped capability handoff helper;
- an audit evidence writer for State Hub and local logs;
- the credential interaction surface that ops-warden can route to.
- a PEP at every protected side effect, with a published unreachable-engine
stance;
- the credential interaction surface that ops-warden routes to after a
decision.
## What This Is Not
secrets-engine is not:
- a replacement for OpenBao;
- a replacement for OpenBao, or the owner of custody;
- an identity provider or MFA system;
- an authorization decision engine;
- an authorization decision engine, an approval store, or a maturity engine;
- an application-specific secret store;
- a place to persist raw secret values outside OpenBao;
- a prompt-time secret injection mechanism for LLMs;
- a bypass around review, approval, or production custody.
- a bypass around review, approval, or production custody;
- the operational owner of the SSH certificate lane — that Staff PEP remains
ops-warden's; this engine may later expose the signing API it should call;
- an observation or containment system — kings-guard proposes, this engine
does not watch production, and actuation is unowned estate-wide.
## System Boundary
| Concern | Primary owner | secrets-engine responsibility |
| --- | --- | --- |
| Secret custody, leases, audit backend | OpenBao / railiance-platform | Use through least-privilege roles and validated paths. |
| Secret custody, leases, backend audit | OpenBao (Tooling) / railiance-platform | Lifecycle API over least-privilege roles and validated paths. |
| Authorization decisions | access-engine (`flex-auth`) | Consume a decision record; never render or cache one. |
| Approval object | approval-engine | Consume an approval as an input claim; never store or mutate it. |
| Security doctrine | gate-house | Apply doctrine only as it arrives on the decision or in a versioned policy package. |
| Human and service identity | key-cape / user-engine | Consume identity claims; do not replace identity lifecycle. |
| Authorization decisions | flex-auth / State Hub decision model | Require and verify decisions before privileged actions. |
| SSH certificate issuance | ops-warden | Provide routed credential/capability access; do not make ops-warden vend secrets. |
| SSH certificate issuance (Staff PEP) | ops-warden | Route and, when the surface exists, expose the OpenBao SSH-CA API; do not take the lane. |
| Evidence custody and integrity | audit-core | Emit non-secret events under the §9.6 bound; do not become the archive. |
| Workload secret consumption | Workload repos / CI / runtime | Provide safe delivery and handoff contracts plus catalog entries. |
| Request history and progress | State Hub | Write non-secret evidence and decision links only. |
| Request history and progress | State Hub | Write non-secret progress pointers only; not a security state plane. |
## Direction of Evolution
@ -161,10 +233,16 @@ The repository should evolve through these phases:
4. **Hardening:** replace bootstrap token files with OIDC/service auth, wrapped
delivery, short leases, dual control for production provisioning, and routine
rotation/deactivation workflows.
5. **Service mode:** expose an API that ops-warden, agents, CI, and future UI
surfaces can use without knowing OpenBao internals.
5. **Layer-model conformance:** published PEP stance equals shipped behaviour;
live side effects hold an access-engine decision record or a recorded stance;
evidence classified and emitted to audit-core; proposed SSH-CA and secret-use
surfaces accepted or declined in this repository's own contract, not only in
other repositories' gap registers.
6. **Service mode:** expose a deterministic API that ops-warden, agents, CI, and
future UI surfaces can use without knowing OpenBao internals.
## Guiding Question
**How can approved secret work become low-friction for humans and agents while
keeping raw values, OpenBao privileges, and production impact tightly bounded?**
keeping raw values, OpenBao privileges, and production impact tightly bounded
to a decision this engine did not render?**

View file

@ -4,15 +4,20 @@ Headless, multi-application, multi-tenant secrets workflow and automation layer
for approved secret custody, delivery, and lifecycle work across build, test,
and production stages.
OpenBao remains the custody and enforcement backend. `secrets-engine` owns the
operator and agent interaction model: catalog, decision checks, plan/apply,
guarded provisioning, verification, delivery, evidence, lifecycle metadata, and
native-access deactivation. Local evidence can be inspected through an
**Layer: Engine / Lifecycle** under the accepted NetKingdom Security Layer
Model (`layer.yaml`). OpenBao remains the custody and enforcement backend.
`secrets-engine` is the deterministic API over it: catalog, decision
consumption, plan/apply, guarded provisioning, verification, delivery,
evidence, lifecycle metadata, and native-access deactivation. It does not
render authorization decisions. Local evidence can be inspected through an
allowlisted per-lane `audit` summary without exposing record detail.
## Start Here
- [INTENT.md](INTENT.md) - why this repository exists.
- [INTENT.md](INTENT.md) - why this repository exists, including the Engine /
Lifecycle declaration.
- [layer.yaml](layer.yaml) - machine-readable layer declaration and proposed
surfaces.
- [ProductRequirementsDocument.md](ProductRequirementsDocument.md) - product
requirements and MVP scope.
- [NetKingdom security infrastructure boundary pointer](docs/netkingdom-security-infrastructure.md)

103
SCOPE.md
View file

@ -3,15 +3,20 @@
> Implemented capability boundary for agents and contributors. Aspirational
> direction belongs in `INTENT.md`; current work and operational gates belong in
> `workplans/`.
>
> **Layer declaration (accepted model v0.7):** Engine / Lifecycle. Machine-readable
> form: `layer.yaml`. PEP stance: `pep-stance.yaml`. This file states what the
> implementation currently does inside that layer; it does not restate doctrine.
## One-liner
secrets-engine is a decision-gated Python CLI that validates non-secret secret
lane metadata and orchestrates narrowly scoped OpenBao policy, AppRole,
provisioning, verification, delivery, routing, handoff, and revocation actions.
secrets-engine is the Lifecycle engine for cataloged credential work: a
decision-gated Python CLI that validates non-secret lane metadata and
orchestrates narrowly scoped OpenBao policy, AppRole, provisioning,
verification, delivery, routing, handoff, and revocation actions.
It is an OpenBao workflow client, not a vault, authorization service, identity
provider, credential broker, or general secrets API.
It is a deterministic API over OpenBao, not a vault, not a policy decision
point, not an identity provider, and not a general secrets API.
## Implemented Capabilities
@ -33,15 +38,19 @@ provider, credential broker, or general secrets API.
- Resolves a catalog lane by catalog id or `approval.decision_ref`.
- Resolves legacy lane decisions from State Hub by id, with tracked local YAML
mirrors retained only for non-production and explicit throwaway demos.
- Fails every production live command closed while State Hub lacks the durable
action-authorization endpoint. A local mirror can unlock a prod-labeled lane
only when an explicit unsafe-demo switch, disabled Hub URL, and loopback
OpenBao target are all present. Plans and `apply --dry-run` remain usable.
mirrors retained only for non-production and explicit throwaway demos. This is
not an access-engine decision record.
- Fails every production live command closed while there is no durable
access-engine / ActionAuthorization record. That fail-closed row is the
published unreachable-engine stance for `prod` in `pep-stance.yaml`. A local
mirror can unlock a prod-labeled lane only when an explicit unsafe-demo
switch, disabled Hub URL, and loopback OpenBao target are all present. Plans
and `apply --dry-run` remain usable.
- Builds and validates the flex-auth `ActionAuthorization` profile, including
exact lane/stage/action/target/actor/purpose matching, bounded validity,
State Hub authority, request digest and decision binding, accepted policy
package/version, and an independently required distinct-approver threshold.
Validation is consume-only; this process does not evaluate policy.
- Renders guarded OpenBao plans for exact consumer ACL policies and AppRoles.
- Applies policy and AppRole metadata idempotently. Existing mounts render a
non-mutating check and are never created by apply.
@ -116,7 +125,8 @@ cannot be recovered through that AppRole.
event on a best-effort basis. Posts carry stable idempotency/source headers.
Each requested State Hub delivery receives an append-only local `delivered`,
`queued`, `failed`, or `skipped-no-topic` companion record; edge-relay queued
receipts retain only the non-secret outbox id.
receipts retain only the non-secret outbox id. This trail is attributive:
completeness is not claimed, and it is not `audit-core`.
- Every live privileged CLI handler records an attempt before lane-approval
resolution and a terminal success, verification failure, rejection,
interruption, or typed backend/input failure. Failure evidence contains the
@ -211,9 +221,11 @@ verification, `approle-login` is auth-capability handoff metadata, and
- First-class rotate, compromise, reactivate, lease-status, or audit report
commands; lifecycle operations currently execute plans without persistent
lane state.
- Resolution of a durable State Hub action authorization and wiring its
validated approval threshold to each production handler.
- Direct flex-auth evaluation, JWT signature verification, or identity
- Resolution of a durable access-engine decision record / State Hub
ActionAuthorization and wiring its validated approval threshold to each
production handler. The consumer validator exists; the serving endpoint does
not, so live production remains fail-closed.
- Direct access-engine evaluation, JWT signature verification, or identity
authentication. KeyCape claims receive only a consumer preflight; OpenBao is
responsible for cryptographic JWT validation.
- Runtime tenancy isolation; `org`, `repo`, consumers, and stages are catalog
@ -221,32 +233,55 @@ verification, `approle-login` is auth-capability handoff metadata, and
- Management or health verification of ESO, Kubernetes Secrets, deployments,
provider accounts, SSH issuance, tunnels, or remote transport.
- Any backend other than the local `bao`/`vault` CLI speaking to OpenBao.
- An SSH-CA signing engine API. ops-warden still signs through its declared
OpenBao gap; this repository has accepted that surface as proposed only.
- A secret-use evidence engine API for kings-guard. `route` and `audit` are
operator summaries over local JSONL, not an observation surface.
- Emission to `audit-core`. Evidence today is local JSONL plus best-effort
State Hub progress notes, classified attributive, completeness not claimed.
- Named stance-application records (stage, failure mode, decision id present
only where rendered). Fail-closed production currently surfaces as a
`DecisionError` on the privileged-evidence path.
- Security-zone membership as a request claim. PEP scope is catalog stage.
## System Boundary
- **OpenBao / railiance-platform** owns custody, policy enforcement, leases, and
audit. secrets-engine invokes it through supplied credentials.
- **flex-auth / State Hub decisions** own authorization. secrets-engine only
resolves and enforces recorded decision status.
- **OpenBao / railiance-platform** (Tooling) owns custody, policy enforcement,
leases, and backend audit. secrets-engine is the Lifecycle API over it.
- **access-engine (`flex-auth`)** owns authorization decisions. secrets-engine
consumes a decision record or applies its published unreachable-engine stance;
it does not evaluate policy.
- **approval-engine** owns the durable approval object. secrets-engine may
consume an approval as an input claim and must not store or mutate one.
- **gate-house** owns security doctrine. Doctrine reaches this engine only as it
already reached the decision, never as a side channel.
- **audit-core** owns evidence custody and integrity. Local JSONL and State Hub
notes are not that archive.
- **user-engine / key-cape** own identity, OIDC, MFA, and claims.
- **ops-warden** issues SSH certificates and routes non-SSH credential needs; it
does not vend their values.
- **ops-warden** issues SSH certificates (Staff PEP) and routes non-SSH
credential needs here; it does not vend their values. The SSH-CA write
surface is a proposed engine API, not a transferred lane.
- **ops-bridge** owns tunnels and remote execution transport and may consume a
scoped delivery path.
- **workload/platform repositories** own ESO/Kubernetes delivery, provider
rotation, and application health.
- **info-tech-canon / net-kingdom** own canonical terminology and the
cross-system security boundary.
cross-system security boundary. **gate-house** owns the layer model.
Canonical boundary:
`net-kingdom/docs/secrets-engine-security-infrastructure-boundary.md`.
Layer model: `net-kingdom/canon/standards/security-layer-model_v0.7.md`.
Working companion: `net-kingdom/SECURITY-COMPANION.md`.
## Security Rules
- Never place raw secret values in Git, State Hub, chat, prompts, workplans,
normal logs, or evidence.
- Never treat catalog admission, a dry-run, or an old workload CCR as approval
for a new production auth surface.
- Never render or cache an authorization decision. Catalog admission, a dry-run,
an old workload CCR, or a local fixture is not an access-engine allow.
- Never treat silence from `access-engine` as permission. Production live
actions fail closed; any unreachable-engine residue must be the published
stance, recorded, never implicit.
- Never mutate an existing shared mount or replace workload delivery by
implication.
- Never add KV destruction back to ordinary `revoke`; irreversible custody
@ -254,7 +289,22 @@ Canonical boundary:
approved action. The live path remains disabled until that approval contract
exists.
- Keep bootstrap and handoff material outside repositories with mode 0600 and
explicit expiry/revocation handling.
explicit expiry/revocation handling. Bootstrap is not an implicit fallback
from service identity.
- Never claim that local evidence or a missing record proves occurrence or
non-occurrence. Completeness is not claimed.
## Layer-model obligations (current vs intended)
| Obligation | Current | Intended |
| --- | --- | --- |
| Layer declaration | `INTENT.md` frontmatter + `layer.yaml` | Keep in this repository's own voice |
| One decision point | Consumer validator for ActionAuthorization; production live fail-closed | Consume an access-engine decision record before every protected side effect |
| PEP stance | `pep-stance.yaml`; prod fail-closed, build/test fail-open relative to access-engine | Published map equals shipped behaviour; stance application recorded by name |
| Evidence bound | Attributive local JSONL + best-effort State Hub | Load-bearing vs attributive classified; load-bearing to `audit-core` with cadence |
| SSH-CA surface | Proposed; not shipped | Engine API for ops-warden's Staff PEP; lane stewardship stays with ops-warden |
| Secret-use evidence | `route` / `audit` over local JSONL | Engine surface of lease/revocation/mount/rotation metadata |
| Agent credential | Bootstrap token file still accepted; KeyCape scaffold unwired | Per-task, time-bounded service identity; no standing engine credential |
## Where Current Work Lives
@ -262,6 +312,7 @@ Canonical boundary:
- `docs/hardening-backlog.md` tracks exit from bootstrap mode.
- `history/` contains dated capability and intent assessments.
- `INTENT.md` remains the stable aspirational direction.
- `layer.yaml` and `pep-stance.yaml` are the layer-model declaration surface.
## Provided Capabilities
@ -298,7 +349,7 @@ title: Non-secret routing and evidence pointers
description: Reports decision/readiness metadata and records scrubbed local and best-effort State Hub
evidence without returning secret values. Append-only delivery receipts expose State Hub failures,
and an allowlisted audit command summarizes lane operations and cleanup. KV verification can attest a
supplied real unrelated identity, but the engine does not own identity selection. This is not a durable
remote audit store.
supplied real unrelated identity, but the engine does not own identity selection. This is attributive
local evidence, not audit-core and not a secret-use observation API.
keywords: [routing, evidence, state-hub, audit, secrets]
```

View file

@ -17,6 +17,14 @@ belongs to NetKingdom because it defines cross-system security infrastructure
responsibilities and boundaries across OpenBao, flex-auth, user-engine/key-cape,
ops-warden, ops-bridge, info-tech-canon, State Hub, and agents.
The accepted layer model and working companion, which this repository now
declares against as Engine / Lifecycle, live at:
```text
net-kingdom/canon/standards/security-layer-model_v0.7.md
net-kingdom/SECURITY-COMPANION.md
```
secrets-engine consumes that boundary and implements the secrets workflow,
catalog, stage policies, OpenBao apply/delivery mechanics, and evidence model
that the canonical document assigns to it.

View file

@ -0,0 +1,163 @@
# Review: Security layer model v0.7 vs INTENT, SCOPE, and implementation
Date: 2026-08-29
Author: grok
Standard: `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted)
Companion: `net-kingdom/SECURITY-COMPANION.md` v0.2
Compared: adapted `INTENT.md`, updated `SCOPE.md`, current CLI implementation
Intake closed by this session: `SECRETS-IN-0001`
## Executive assessment
The accepted layer model confirms what this repository already was in practice
and had not yet declared in its own voice: **Engine, role Lifecycle** — a
deterministic API over OpenBao for cataloged credential lanes. The gate-house
review note at the top of `INTENT.md` was not a declaration. That note is now
replaced by frontmatter, prose, `layer.yaml`, and a published PEP stance map.
The intent remains directionally sound. The layer model does not move the
system boundary (OpenBao stays Tooling; `access-engine` stays the only PDP;
ops-warden stays the SSH Staff PEP). It **tightens the contract** this engine
owes at every protected side effect, and it **names two proposed surfaces**
this repository had not answered: an SSH-CA signing API, and secret-use
evidence for kings-guard.
`SCOPE.md` and the implementation already describe a guarded OpenBao CLI MVP
with one proven native workload pilot. Against the new intent they are
**aligned on layer and ownership, behind on PEP, decision, and evidence
obligations**. Several of those obligations overlap active work in
`SECRETS-WP-0007`; the new work is the layer-model remainder, not a restart of
CLI hardening.
A finding is raised against the §4 catalog cell, not against the proposed
layer: the catalog says this repository owns "custody". Custody is OpenBao's.
This engine owns the lifecycle API over that custody.
## What was declared
| Artifact | Content |
| --- | --- |
| `INTENT.md` frontmatter | `layer: Engine`, `role: Lifecycle` |
| `INTENT.md` body | Own-voice declaration, PEP shape, evidence bound, proposed surfaces |
| `layer.yaml` | Machine-readable declaration; owned OpenBao contact; no decision surface |
| `pep-stance.yaml` | Unreachable-engine stance by catalog stage; prod fail-closed |
| `scripts/check_layer_conformance.py` | Mechanical check of the above |
| `tests/test_layer_conformance.py` | Declaration + published map equals shipped prod fail-closed |
Contest of the proposed **layer** (Engine): none. The determinism test holds.
Contest of **catalog wording**: yes — "custody" should not appear as this
engine's owned property.
Answers to proposed ownerships:
| Proposed surface | Answer |
| --- | --- |
| SSH-CA signing write (`ops-warden` §5.3 gap) | **Accept** as a Lifecycle engine API. **Decline** operational SSH-lane stewardship. ops-warden remains the Staff PEP. |
| Secret-use evidence (`kings-guard`) | **Accept** as a proposed Lifecycle engine surface, bounded by §9.6. Not shipped. |
## INTENT vs SCOPE
No contradiction on layer, owners, or forbidden claims. Drift is maturity:
| Intent claim | SCOPE today | Gap kind |
| --- | --- | --- |
| Engine / Lifecycle declaration | Declared | Closed this session |
| Consume an access-engine decision before every protected side effect | Consumer validator exists; production live fail-closed because the durable object is not served | Implementation (WP-0007-T04 + WP-0008-T02) |
| Published PEP stance equals shipped behaviour, stance application recorded | Map published; prod fail-closed matches; records are `DecisionError`, not named stance fields | Implementation (WP-0008-T01) |
| Evidence to `audit-core` under §9.6 | Local JSONL + best-effort State Hub; attributive; completeness not claimed | Implementation (WP-0008-T03) |
| SSH-CA engine API | Not implemented; ops-warden still writes OpenBao | Proposed surface (WP-0008-T04) |
| Secret-use evidence API | `route` / `audit` summarize local JSONL | Proposed surface (WP-0008-T05) |
| No standing agent credential | Bootstrap token file still accepted; KeyCape scaffold unwired to OpenBao | Hardening (WP-0007-T05 / WP-0008-T06) |
| Service API | CLI only | Deferred until WP-0007 exit criteria |
| Every allow has a lifetime | Delivery sessions self-revoke; decision TTL waits on durable authorization | Partial |
| Doctrine as claims | Catalog/policies are local YAML | Later; do not compile outcomes here |
`SCOPE.md` remains the implemented-capability ledger. Volatile workplan status
stays out of it. The new durable additions are the layer table, the updated
system boundary, and the explicit not-implemented layer-model surfaces.
## INTENT vs implementation
The strongest implemented slice is unchanged:
```text
catalog -> approval-status or fail-closed prod gate -> guarded plan
-> policy/AppRole apply -> bounded presence/capability verification
-> CLI-local exec/handoff -> non-secret route/evidence pointer
```
That slice is a Lifecycle engine **shape**. It is not yet a conforming PEP.
### Scorecard against the accepted model
| Rule | Assessment | Evidence | Material gap |
| --- | --- | --- | --- |
| §3.3 Lifecycle | Aligned in shape | OpenBao isolated in `openbao.py`; catalog/plan/delivery/lifecycle are the API | Service API absent; CLI is the only surface |
| §6 one decision point | Partial, fail-closed | `authorization.py` validates a supplied object and does not evaluate policy. Production live commands refuse without a durable ActionAuthorization. Build/test still accept State Hub status or a local fixture | Local/hub status is not an access-engine decision record. Risk of compiled early decision if fixtures return for production |
| §6.1 compiled data | Watch | Catalog admission and publication-scope policy clamp lanes; they must not become the allow | Keep catalog descriptive; effects stay in the decision |
| §6.4 PEP obligation 1 | Partial | Privileged handlers record attempt/terminal evidence; production refusal is a `DecisionError` | Stance application is not recorded as named fields (stage, failure mode, decision id present only where rendered) |
| §6.4 PEP obligation 2 | Aligned as declared | No verdict cache; `verdict_caching: none` | Must remain none when ActionAuthorization is live |
| §6.4 PEP obligation 3 | Published, not yet loaded | `pep-stance.yaml` is total over build/test/prod/unknown; tests pin prod fail-closed to `_require_lane_approval` | Code still hardcodes the gate rather than loading the map. Build/test fail-open is relative to access-engine, not a manufactured allow — that residue is itself a gap |
| §6.4 PEP obligation 4 | Attributive | Local JSONL + hub receipts; OpenBao remains backend audit | Completeness not claimed (correct). No `audit-core` emit |
| §9.6 load-bearing vs attributive | Undeclared in code | `layer.yaml` classifies today's trail as attributive | Destroy, revoke, and dual-control provision will become load-bearing once they are production-live. They need atomic local outbox + cadence/heartbeat **before** any control branches on them |
| §9.7 lifetime | Partial | AppRole sessions self-revoke; ActionAuthorization has `expires_at` in the consumer schema | Production never reaches the lifetime because live prod is disabled. Bootstrap tokens remain standing credentials |
| §20.2 credentials after a decision | Principle aligned; production blocked | Fail-closed is the honest residue while the PDP object is unserved | Do not reopen production on hub status |
| Staff must not touch OpenBao | External | ops-warden still has the SSH-CA write gap with intended owner here | Engine surface not shipped; do not take the Staff lane |
| Agent rule 1 (no standing credential) | Not met | `--bootstrap-token-file` and `BAO_TOKEN` are the live auth path | KeyCape scaffold exists and is deliberately unwired |
| Observation / actuation | Correctly not claimed | Companion §10: nothing is observed in production; nothing can be contained automatically | Do not plan as if either existed |
### Overlap with active workplans
`SECRETS-WP-0007` already owns CLI hardening that the 2026-08-23 assessment
called P0/P1: merge-safe provision (done), lifecycle split (in progress),
exact-action authorization (in progress), session self-revocation (in
progress), verification/evidence (in progress). Those tasks stay there.
What the layer model **adds**, and what `SECRETS-WP-0008` is for:
1. Bind the published PEP stance to shipped behaviour and record stance
application by name.
2. Treat the durable ActionAuthorization as an **access-engine decision
record** under §6.4, not as a State Hub status check, once WP-0007-T04's
endpoint exists.
3. Classify evidence and emit it to `audit-core`.
4. Answer the two proposed surfaces (SSH-CA API, secret-use evidence) in this
repository's own contract.
5. Retire standing engine credentials as a layer obligation, not only as
bootstrap hygiene.
Do not start service mode, runtime tenancy, or a second custody backend from
this review. Companion §10 still holds: nothing is observed in production, and
nothing can be contained automatically.
## Recommended sequence
1. Keep `SECRETS-WP-0007` as the CLI-hardening track. Do not reopen live
production on coarse hub status.
2. Land `SECRETS-WP-0008-T01` — load `pep-stance.yaml` as the source of the
fail-closed gate and record stance application fields.
3. When the durable authorization object is served, consume it as a decision
record (`WP-0008-T02` / `WP-0007-T04`) with digest+lifetime replay rules.
4. Classify evidence; emit load-bearing classes to `audit-core` with a local
outbox; declare cadence or heartbeat.
5. Publish the SSH-CA engine contract with ops-warden before writing a client
replacement. Keep their Staff PEP.
6. Publish the secret-use evidence contract with kings-guard. `audit` is not
that contract.
7. Wire service identity only after the platform-owned OpenBao JWT role exists.
Bootstrap remains break-glass.
## Conclusion
`INTENT.md` now declares Engine / Lifecycle in this repository's own voice and
accepts the two proposed surfaces without taking Staff work. `SCOPE.md` is
honest about the CLI MVP and the new not-implemented obligations. The
implementation is a Lifecycle engine in shape and a partial PEP in behaviour:
production fail-closed is the correct residue, build/test still run on a
pre-decision-record approval path, and evidence is attributive local notes
rather than `audit-core`.
The repository should not advertise itself as a service API, a second PDP, a
custody backend, an SSH issuer, or an observation system. Follow-on work is
`SECRETS-WP-0008`.

View file

@ -7,13 +7,21 @@ id: SECRETS-IN-0001
kind: intake
title: 'Declaration requested: state this repository''s layer in INTENT.md (security
layer model §11)'
status: open
status: closed
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: secrets-engine
requested_by: gate-house
proposed_layer: Engine
resolution: >-
Declared Engine / Lifecycle in INTENT.md frontmatter and layer.yaml on
2026-08-29 against accepted v0.7. Layer not contested. Catalog cell
"custody" is a finding: OpenBao owns custody; this engine owns the
lifecycle API over it. SSH-CA signing accepted as a proposed engine API
and declined as a Staff lane. Secret-use evidence accepted as proposed.
Review: history/2026-08-29-layer-model-scope-intent-review.md.
Workplan: SECRETS-WP-0008.
description: 'A conformance sweep on 2026-08-28 found this repository has no layer
declaration of its own. It carries a layering review note gate-house wrote into
the top of its INTENT.md on 2026-08-24, and that note names a layer — but the words

144
layer.yaml Normal file
View file

@ -0,0 +1,144 @@
# secrets-engine — NetKingdom security layer declaration
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md
# Companion: net-kingdom/SECURITY-COMPANION.md
# Voice: INTENT.md (this repository's own declaration, §11)
# Validate: python3 scripts/check_layer_conformance.py
#
# §11 requires a machine-readable form because prose cannot distinguish a
# declaration from a transcribed review. ops-warden offered the layer.yaml
# shape estate-wide; this file is that shape adapted for a Lifecycle engine
# rather than a Staff repository.
#
# §5 (Staff never touches Tooling) does not apply to this repository's OpenBao
# client. A Lifecycle engine is the deterministic API over Tooling it fronts.
# OpenBao contact here is owned, not a §5.3 gap.
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.7"
repository: secrets-engine
layer: engine
role: lifecycle
declared_by: INTENT.md
declared_at: "2026-08-29"
# §3.3: a Lifecycle engine is a deterministic API over Tooling it owns.
# OpenBao remains the vault; this engine fronts it.
owned_tooling:
- id: openbao-lifecycle
tooling: OpenBao
modules:
- src/secrets_engine/openbao.py
operations:
- "bao policy/auth/kv subprocess adapter"
- "CAS-aware KV create/patch via JSON input files, never argv values"
note: >-
This is the owned Lifecycle contact, not a Staff §5 shape. A new direct
OpenBao client outside the listed modules is a finding.
# §6: no repository other than access-engine exposes an authorization decision
# surface. secrets-engine consumes and validates a supplied ActionAuthorization
# object; it does not evaluate policy.
decision_surfaces_exposed: none
# §6.4 — apply, provision, exec, handoff, revoke, and lifecycle mutations
# cause protected side effects. Being PEP-shaped does not change the layer.
pep_shaped: true
pep_stance: pep-stance.yaml
protected_actions:
- apply
- provision
- verify
- exec
- handoff
- revoke
- lifecycle-suspend
- lifecycle-deactivate
- lifecycle-destroy
# §13 proposed capabilities. Owner status is proposed, not assented, until
# the surface exists in this repository's own contract.
proposed_capabilities:
- id: ssh-ca-signing-surface
state: unowned-capability
capability: >-
Deterministic engine API for OpenBao SSH-CA signing, so a Staff PEP
(ops-warden) does not hold a direct VaultCA write.
intended_owner: secrets-engine
owner_status: proposed
blocked_on: >-
No engine API exists yet. ops-warden continues to sign through its
declared §5.3 gap. This repository accepts the engine surface and
declines operational ownership of the SSH access lane.
review: "2026-11-28"
consequence: >-
ops-warden's ssh-ca-signing-write gap remains the production path.
- id: secret-use-evidence
state: unowned-capability
capability: >-
Lease, revocation, mount, rotation, and delivery-session metadata
exposed as an engine surface for kings-guard secret-abuse posture.
intended_owner: secrets-engine
owner_status: proposed
blocked_on: >-
Local JSONL plus best-effort State Hub notes exist; they are not an
engine API and they are not audit-core. Completeness is not claimed.
review: "2026-11-28"
consequence: >-
kings-guard secret-abuse posture stays fixture-driven.
# Contacts that are not Tooling, recorded so the check is total.
non_tooling_clients:
- id: state-hub-decisions-and-progress
target: state-hub
layer: not-catalogued
modules:
- src/secrets_engine/decisions.py
- src/secrets_engine/evidence.py
operation: "HTTP GET /decisions/{id} and POST /progress/"
write: true
note: >-
Outside §5 by the scope rule: State Hub is not a §4 Tooling row.
Decisions consumed from it are a gap relative to access-engine, not a
Tooling contact. Progress notes are not a security state plane.
- id: access-engine-action-authorization
target: access-engine (flex-auth)
layer: engine
modules:
- src/secrets_engine/authorization.py
operation: "Local validation of a supplied ActionAuthorization object"
write: false
note: >-
Engine-to-engine consume, not a decision surface. The durable State Hub
endpoint that would serve the object is not yet available; production
live actions therefore fail closed (see pep-stance.yaml).
- id: key-cape-service-auth-scaffold
target: key-cape
layer: tooling
modules:
- src/secrets_engine/service_auth.py
operation: "HTTPS client_credentials exchange; not connected to OpenBao"
write: false
note: >-
key-cape is catalogued Tooling. This contact is an identity-token
preflight only and is deliberately not a write into OpenBao. Wiring it
as OpenBao JWT login remains blocked on the platform-owned role.
# §9.6 — what this repository may claim about evidence it writes today.
evidence_bound:
kind: attributive
proves:
- local JSONL records that were written were not rewritten by this engine
does_not_prove:
- that a record was ever sent to audit-core
- absence of a record as evidence of non-occurrence
- completeness of the trail
destination_today: "local JSONL plus best-effort State Hub progress notes"
intended_destination: audit-core
completeness_claimed: false

85
pep-stance.yaml Normal file
View file

@ -0,0 +1,85 @@
# secrets-engine — PEP unreachable-engine stance map
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md §6.4, §9.3
# Companion: net-kingdom/SECURITY-COMPANION.md §5
# Voice: INTENT.md / layer.yaml
# Validate: pytest tests/test_layer_conformance.py
#
# §6.4 obligation 3 requires a declared unreachable-engine stance that is
# total, scoped per zone or equivalent, carries no implicit default and no
# per-call discretion, and is published rather than held in code comments.
# Catalog stage is the equivalent scope until security-zone membership arrives
# as a claim on the decision.
#
# The property that makes this worth reading: tests/test_layer_conformance.py
# asserts this map against the shipped production fail-closed gate in
# secrets_engine.cli._require_lane_approval. A published map free to drift
# from the code is worse than none.
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.7"
repository: secrets-engine
pep_shape: true
declared_by: INTENT.md
decision_engine: access-engine # flex-auth until the governed rename
scope: catalog-stage # equivalent scope; not security-zones yet
protected_action: >-
OpenBao metadata apply, KV provision, verification, exec delivery,
auth-capability handoff, native revoke, and lifecycle
suspend/deactivate/destroy
# Total by construction over every catalog stage plus unknown.
# No implicit default — an unlisted value is a config error, not a fallback.
# "fail_closed" means no protected side effect without a durable
# access-engine / ActionAuthorization record.
# "fail_open" here means the engine-unreachable residue: proceed only under
# the existing lane-approval check (State Hub or local fixture), which is
# itself a tracked gap relative to access-engine, not a manufactured allow.
stance:
build: fail_open
test: fail_open
prod: fail_closed
unknown: fail_closed
# Explicit, three-factor exception. Not a stance row and not a default.
# All three must hold or the production gate remains closed.
demo_exception:
id: unsafe-local-demo
requires:
- "SECRETS_ENGINE_UNSAFE_DEMO=1"
- "hub URL empty"
- "OpenBao target is loopback"
applies_to: prod
note: >-
Throwaway local demonstration only. Not a production residue and not
available when any one factor is missing.
on_apply:
recorded_fields:
- stage
- action
- approval_status # rejected when the stance refuses
- error_type # DecisionError when fail_closed fires
- decision_id # present only where a decision was resolved
written_to:
- "local JSONL (.evidence/evidence-YYYY-MM-DD.jsonl)"
- "best-effort State Hub progress note"
never_recorded: "any secret material, token, password, or raw value"
gap: >-
Stance application is currently recorded as a DecisionError on the
privileged-evidence path, not as named stance fields (stage, failure
mode, decision id present only where rendered). Closing that gap is
SECRETS-WP-0008-T01.
# §6.4 obligation 2 — the verdict is never cached. A retry of the same
# canonical request digest inside the decision lifetime is the decision
# being used as issued; a different resource is not.
verdict_caching: none
# §6.4 obligation 4 / §9.6 — attributive today. No control branches on the
# presence of a local evidence record. Completeness is not claimed.
reconstructability:
bound: "§9.6 attributive — completeness is not claimed"
declared_at: layer.yaml#evidence_bound

View file

@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Check secrets-engine against the NetKingdom security layer model (§3.3, §6, §11).
Read-only. This is the Engine/Lifecycle adaptation of the ops-warden reference
checker. §5 Staff shapes do not apply to the owned OpenBao contact.
Mechanical checks:
- a machine-readable declaration exists and says Engine / Lifecycle
- INTENT.md frontmatter matches that declaration
- no authorization decision surface is exposed
- the PEP stance map is published at the path named in the declaration
- every OpenBao subprocess adapter lives in a module listed as owned tooling
Exit 0 clean, 1 finding, 2 declaration malformed.
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src" / "secrets_engine"
DECL = ROOT / "layer.yaml"
INTENT = ROOT / "INTENT.md"
BAO_ARGV = re.compile(
r"""\[\s*(?:["']bao["']|bao_bin\b|bao_binary\b|self\.bao_bin)\s*,"""
)
DECISION_SURFACE = re.compile(
r"""\b(evaluate_policy|check_permission|render_decision|pdp_decide)\b"""
)
def load_declaration() -> dict:
if not DECL.exists():
print(f"MISSING: {DECL} — secrets-engine must declare in its own voice (§11)")
raise SystemExit(2)
decl = yaml.safe_load(DECL.read_text())
for key in (
"layer",
"role",
"repository",
"standard_version",
"owned_tooling",
"decision_surfaces_exposed",
"pep_shaped",
"pep_stance",
):
if key not in decl:
print(f"MALFORMED: layer.yaml has no {key!r}")
raise SystemExit(2)
if decl["layer"] != "engine":
print(f"MALFORMED: declared layer is {decl['layer']!r}, expected 'engine'")
raise SystemExit(2)
if decl["role"] != "lifecycle":
print(f"MALFORMED: declared role is {decl['role']!r}, expected 'lifecycle'")
raise SystemExit(2)
if decl["repository"] != "secrets-engine":
print(f"MALFORMED: repository is {decl['repository']!r}")
raise SystemExit(2)
return decl
def intent_frontmatter() -> dict:
text = INTENT.read_text(encoding="utf-8")
if not text.startswith("---"):
print("MALFORMED: INTENT.md has no YAML frontmatter (§11 / companion §2)")
raise SystemExit(2)
end = text.find("\n---", 3)
if end < 0:
print("MALFORMED: INTENT.md frontmatter is unclosed")
raise SystemExit(2)
return yaml.safe_load(text[3:end]) or {}
def owned_modules(decl: dict) -> set[str]:
names: set[str] = set()
for entry in decl.get("owned_tooling", []):
for module in entry.get("modules", []):
names.add(Path(module).name)
return names
def scan_bao_modules() -> dict[str, list[int]]:
found: dict[str, list[int]] = {}
for path in sorted(SRC.rglob("*.py")):
hits: list[int] = []
for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
stripped = line.strip()
if stripped.startswith("#"):
continue
if BAO_ARGV.search(line):
hits.append(n)
if hits:
found[path.name] = hits
return found
def scan_decision_surfaces() -> dict[str, list[int]]:
found: dict[str, list[int]] = {}
for path in sorted(SRC.rglob("*.py")):
hits: list[int] = []
for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if DECISION_SURFACE.search(line):
hits.append(n)
if hits:
found[path.name] = hits
return found
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--report", action="store_true")
args = ap.parse_args()
decl = load_declaration()
front = intent_frontmatter()
ok = True
if str(front.get("layer", "")).lower() != "engine":
ok = False
print("FINDING: INTENT.md frontmatter layer is not Engine")
if str(front.get("role", "")).lower() != "lifecycle":
ok = False
print("FINDING: INTENT.md frontmatter role is not Lifecycle")
if decl["decision_surfaces_exposed"] not in {None, "none"}:
ok = False
print("FINDING: decision_surfaces_exposed is not none — §6 forbids a second PDP")
surfaces = scan_decision_surfaces()
if surfaces:
ok = False
print("FINDING: possible authorization decision surface in source:")
for name, lines in sorted(surfaces.items()):
print(f" src/secrets_engine/{name}: line(s) {', '.join(map(str, lines[:6]))}")
stance = ROOT / str(decl["pep_stance"])
if decl.get("pep_shaped") and not stance.is_file():
ok = False
print(f"FINDING: pep_shaped but {decl['pep_stance']} is missing (§6.4 / §13.1)")
found = scan_bao_modules()
owned = owned_modules(decl)
undeclared = {name: lines for name, lines in found.items() if name not in owned}
if undeclared:
ok = False
print("FINDING: OpenBao adapter outside owned_tooling modules:")
for name, lines in sorted(undeclared.items()):
print(f" src/secrets_engine/{name}: line(s) {', '.join(map(str, lines[:6]))}")
if args.report:
print(
f"{decl['repository']} — layer: {decl['layer']} "
f"role: {decl['role']} (model v{decl['standard_version']})"
)
print(f"declared by {decl['declared_by']}")
print(f"pep stance: {decl['pep_stance']}")
print(f"owned OpenBao modules: {sorted(owned)}")
print(f"OpenBao argv adapters found: {sorted(found)}")
if ok and not args.report:
print(
"PASS — Engine/Lifecycle declaration present, no decision surface, "
f"{len(found)} OpenBao adapter module(s) owned."
)
elif ok:
print("\nPASS — declaration, owned tooling, and PEP stance path hold.")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,117 @@
"""Layer-model conformance (security-layer-model_v0.7 §3.3, §6, §11).
The declaration must be this repository's own, machine-readable, and equal to
the shipped production fail-closed gate. A published stance map that may drift
from the code is worse than none.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
import yaml
from secrets_engine.catalog import validate_entry
from secrets_engine.cli import _require_lane_approval
from secrets_engine.errors import DecisionError
from tests.test_catalog import VALID
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "check_layer_conformance.py"
DECL = ROOT / "layer.yaml"
STANCE = ROOT / "pep-stance.yaml"
INTENT = ROOT / "INTENT.md"
def _decl() -> dict:
return yaml.safe_load(DECL.read_text(encoding="utf-8"))
def _stance() -> dict:
return yaml.safe_load(STANCE.read_text(encoding="utf-8"))
def test_declares_engine_lifecycle_in_its_own_voice():
assert DECL.exists(), "no layer.yaml — §11 requires a machine-readable declaration"
data = _decl()
assert data["repository"] == "secrets-engine"
assert data["layer"] == "engine"
assert data["role"] == "lifecycle"
assert data["declared_by"] == "INTENT.md"
assert data["decision_surfaces_exposed"] == "none"
assert data["pep_shaped"] is True
def test_intent_frontmatter_matches_declaration():
text = INTENT.read_text(encoding="utf-8")
assert text.startswith("---"), "companion §2 requires INTENT.md frontmatter"
end = text.find("\n---", 3)
front = yaml.safe_load(text[3:end])
assert front["layer"] == "Engine"
assert front["role"] == "Lifecycle"
assert front["declaration"] == "layer.yaml"
assert front["pep_stance"] == "pep-stance.yaml"
def test_checker_passes_on_the_real_tree():
result = subprocess.run(
[sys.executable, str(SCRIPT)],
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stdout + result.stderr
def test_proposed_capabilities_carry_gap_record_fields():
for cap in _decl()["proposed_capabilities"]:
for field in ("capability", "intended_owner", "blocked_on", "review", "state"):
assert cap.get(field), f"{cap.get('id')} missing {field}"
assert cap["state"] == "unowned-capability"
assert cap["owner_status"] == "proposed"
def test_stance_map_is_total_over_catalog_stages():
stance = _stance()["stance"]
required = {"build", "test", "prod", "unknown"}
assert required <= set(stance), f"stance not total; missing {required - set(stance)}"
assert set(stance.values()) <= {"fail_open", "fail_closed"}
assert stance["prod"] == "fail_closed"
assert stance["unknown"] == "fail_closed"
assert _stance()["verdict_caching"] == "none"
def test_published_prod_stance_equals_shipped_fail_closed(monkeypatch):
"""pep-stance.yaml prod: fail_closed must equal _require_lane_approval."""
assert _stance()["stance"]["prod"] == "fail_closed"
entry = validate_entry(dict(VALID, stage="prod", approval={"model": "bootstrap-only"}))
cfg = SimpleNamespace(hub_url="http://127.0.0.1:8000", bao_addr="http://127.0.0.1:8200")
monkeypatch.delenv("SECRETS_ENGINE_UNSAFE_DEMO", raising=False)
with pytest.raises(DecisionError, match="live production remains disabled"):
_require_lane_approval(cfg, entry, "apply")
def test_build_stage_is_not_the_production_fail_closed_gate(tmp_path, monkeypatch):
"""build is fail_open relative to access-engine: lane approval still applies."""
assert _stance()["stance"]["build"] == "fail_open"
(tmp_path / ".decisions").mkdir()
(tmp_path / ".decisions" / "x.yaml").write_text(
"id: x\ntitle: approved\nstatus: resolved\nsuperseded_by: null\n"
)
import secrets_engine.cli as cli
monkeypatch.setattr(cli, "repo_root", lambda: tmp_path)
entry = validate_entry(
dict(
VALID,
stage="build",
path="build/team/thing",
approval={"model": "decision", "decision_ref": "x"},
)
)
cfg = SimpleNamespace(hub_url="", bao_addr="http://127.0.0.1:8200")
assert _require_lane_approval(cfg, entry, "apply").id == "x"

View file

@ -0,0 +1,222 @@
---
id: SECRETS-WP-0008
type: workplan
title: "Evolve the Lifecycle engine to the accepted security layer model"
domain: infotech
repo: secrets-engine
status: ready
owner: grok
topic_slug: custodian
created: "2026-08-29"
updated: "2026-08-29"
---
# SECRETS-WP-0008 - Evolve the Lifecycle engine to the accepted security layer model
## Goal
Close the gaps between the 2026-08-29 layer declaration (Engine / Lifecycle)
and the obligations the accepted NetKingdom Security Layer Model v0.7 places on
a PEP-shaped Lifecycle engine.
Source review: `history/2026-08-29-layer-model-scope-intent-review.md`.
Declaration surface already landed this session: `INTENT.md` frontmatter,
`layer.yaml`, `pep-stance.yaml`, and `tests/test_layer_conformance.py`.
This workplan does **not** restart CLI hardening owned by `SECRETS-WP-0007`.
It adds the layer-model remainder: stance binding, decision-record consumption,
`audit-core` evidence, and the two proposed engine surfaces.
## Non-goals
- Taking ops-warden's SSH access lane or becoming a credential broker.
- Rendering or caching authorization decisions.
- Observation in production or automatic containment (companion §10: both are
at zero estate-wide).
- Service mode, runtime tenancy, or a second custody backend.
- Reopening live production on State Hub status or local fixtures.
## Coordination
- `SECRETS-WP-0007-T04` owns the consumer validator and the fail-closed
production gate until a durable ActionAuthorization object is served.
- `SECRETS-WP-0007-T05` owns delivery-session self-revocation and the KeyCape
scaffold; this workplan owns the layer-model "no standing credential" bar.
- railiance-platform owns production OpenBao and the JWT role.
- `access-engine` / State Hub own serving the decision record.
- `audit-core` owns evidence custody.
- ops-warden owns the SSH Staff PEP and must assent any SSH-CA API contract.
- kings-guard owns secret-abuse posture and must assent the evidence surface.
- gate-house owns statute §13.1 inventory of PEP stance maps.
## Tasks
## Publish stance application as shipped behaviour
```task
id: SECRETS-WP-0008-T01
status: todo
priority: high
```
`pep-stance.yaml` is published and tests pin `prod: fail_closed` to
`_require_lane_approval`. The gate is still hardcoded, and a refusal is
recorded as a `DecisionError` rather than named stance fields.
Load the published map as the source of shipped defaults. When the
unreachable-engine residue is applied, record at least stage, failure mode, and
decision id present only where a decision was rendered. Keep the three-factor
unsafe-demo exception out of the stance table. Assert map equality in
`tests/test_layer_conformance.py` the way ops-warden's stance test does.
Acceptance:
- `_require_lane_approval` (or its successor) reads `pep-stance.yaml`.
- Changing the YAML without changing the code fails the test, and the reverse.
- Fail-closed production evidence includes named stance fields, never a secret.
- Build/test remain fail-open *relative to access-engine* and still require
the existing lane-approval check; that residue stays documented as a gap
until T02.
## Consume access-engine decision records
```task
id: SECRETS-WP-0008-T02
status: wait
priority: high
```
Blocked on the durable ActionAuthorization serving path owned with
`SECRETS-WP-0007-T04` / State Hub / `access-engine`.
Once that object is served, a protected side effect holds a decision record
identifying the request it was rendered for. Replay is permitted iff the
canonical request digest matches and the lifetime holds. Do not cache the
verdict. Do not treat State Hub status, a local fixture, or catalog admission
as the decision.
Acceptance:
- Every live privileged production handler passes one shared consume function.
- Wrong digest, expired lifetime, superseded status, or missing record fail
closed before OpenBao.
- Evidence carries the decision id; a fail-closed residue still records the
stance instead.
- Local fixtures cannot authorize steady-state production.
## Emit evidence under the §9.6 bound
```task
id: SECRETS-WP-0008-T03
status: todo
priority: high
```
Classify each privileged action as load-bearing or attributive. Today's trail
is attributive local JSONL plus best-effort State Hub notes; completeness is
already not claimed and must stay that way until the destination is
`audit-core`.
Load-bearing classes (at least live destroy, production revoke/suspend of a
control, and dual-control provision once those are enabled) require atomic
emission via a **local** outbox and a declared cadence or heartbeat. Do not
place a synchronous `audit-core` dependency inside the state-change
transaction. Attributive classes may keep the current non-blocking trade if
that trade stays declared.
Acceptance:
- A machine-readable classification exists next to the declaration.
- No control in this repository branches on presence or absence of a local
evidence record.
- Load-bearing emission is queued locally; an `audit-core` outage does not
block a revocation.
- Documentation uses the sound claim: the archive proves records it holds were
not altered or truncated after arrival.
## Accept the SSH-CA signing engine surface
```task
id: SECRETS-WP-0008-T04
status: todo
priority: medium
```
ops-warden's `ssh-ca-signing-write` gap names this engine as intended owner.
Accept the **engine API**, decline the Staff lane. Publish a contract
ops-warden can call after an `access-engine` decision: sign a short-lived
certificate for an `adm`/`agt`/`atm` actor under the caller's request, present
no additional authority, reconstruct as the caller's action.
Do not implement a replacement client until ops-warden assents the contract.
Do not unpark the standalone `warden-sign` AppRole as break-glass.
Acceptance:
- A non-secret contract document in `docs/` states request, decision binding,
lifetime, evidence, and non-responsibilities.
- ops-warden has a recorded chance to assent or contest.
- No OpenBao SSH-CA write is added to this CLI ahead of that assent.
- `layer.yaml` owner_status remains `proposed` until the surface ships.
## Accept the secret-use evidence surface
```task
id: SECRETS-WP-0008-T05
status: todo
priority: medium
```
kings-guard's secret-abuse posture is fixture-driven because no engine exposes
lease, revocation, mount, rotation, and delivery-session metadata. `route` and
`audit` are operator summaries over local JSONL and are not that surface.
Publish a read-only engine contract of non-secret metadata, bounded by §9.6:
it cannot prove omission at source. Coordinate destination with `audit-core`.
Do not give kings-guard a vault client, and do not return values.
Acceptance:
- Contract lists fields, freshness, and the evidence bound.
- kings-guard has a recorded chance to assent or contest.
- No secret material, token, or accessor is in the surface.
- `layer.yaml` owner_status remains `proposed` until the surface ships.
## No standing engine credential
```task
id: SECRETS-WP-0008-T06
status: wait
priority: medium
```
Blocked on the platform-owned OpenBao JWT mount/role (railiance-platform) and
on `SECRETS-WP-0007-T05` wiring. Companion §7 / statute §3.4: an agent holds
no long-lived credential of its own. Authority is per task, time-bounded, and
attributable to the principal it acts for.
Bootstrap token files remain break-glass, heavily evidenced, never an implicit
fallback from service identity.
Acceptance:
- Steady-state CLI/API auth is the reviewed service identity, not
`--bootstrap-token-file`.
- Bootstrap is a named break-glass provider with distinct evidence.
- No fallback from JWT/service-auth failure into bootstrap or AppRole.
- Issued delivery sessions continue to self-revoke as in WP-0007-T05.
## Exit criteria
- The published PEP stance is the shipped default and stance application is
recorded by name.
- Production live side effects require an access-engine decision record, or
remain fail-closed with a recorded stance.
- Evidence classification and destination match §9.6; completeness is not
claimed.
- SSH-CA and secret-use surfaces are either contracted with the declaring
repositories or explicitly declined in this repository's own voice.
- Bootstrap is not the steady-state engine credential.
- `layer.yaml` / `pep-stance.yaml` / INTENT frontmatter stay in one voice.
- No raw secret values in Git, State Hub, chat, prompts, workplans, evidence,
or argv.