Publish secrets-engine.catalog-lane.lifecycle v1 (FLEX-WP-0021 T01, T02)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 52s

secrets-engine delivered the action vocabulary T01 asked for: twelve
actions read out of cli.py, not the example vocabulary. The gate earned
its keep -- four would have been inferred wrongly, and revoke, the
obvious thirteenth, does not exist as an action at all. It gates as
deactivate, which is also reached from lifecycle deactivate.

docs/secrets-engine-action-vocabulary.md records the list and the four
traps. examples/secrets-engine/ carries the package, both manifests, a
loadable registry snapshot, 26 fixtures, five check requests, and a
README. validate -kind policy is valid with 22/22 Rego tests and 26/26
fixtures.

allow_ttl is 15m, stated in the package rather than inherited from the
engine default: these decisions authorize live secret operations, so the
reliance window belongs where a reviewer sees it. Per FLEX-DEC-2026-004
it is authority to issue, not authority to keep using what the operation
produced.

destroy is the dual-control case, gated on a context.approval claim
marked approved with two distinct approvers, repeated entries counting
once. flex-auth checks what the claim says and deliberately does not
re-derive its temporal validity, signature, or supersession -- those are
approval-engine's to assert and the PEP's to verify against the live
claim, per the split accepted in FLEX-DEC-2026-006. It stays in the
package though its handler raises before the gate, so the rule is
reviewed and fixtured before SECRETS-WP-0007-T04 opens the path.

Following the FLEX-WP-0010-T02 precedent the denial ladder has no
action_not_granted branch: one subject holding all twelve actions could
never reach it, and a rule that cannot fail reads as control that is not
there. Registering a second calling identity is the revisit trigger.

Not deployed. No flex-auth-secrets-engine pin exists yet (T04), so their
policy pin stays unset and fail-closed until T05.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
This commit is contained in:
tegwick 2026-09-06 08:02:52 +02:00
parent b9e6d2d52b
commit f75db59a8c
13 changed files with 1861 additions and 3 deletions

View file

@ -0,0 +1,36 @@
# secrets-engine example
Policy package, manifests, and fixtures for `secrets-engine`'s gated
catalog-lane operations. Opened by `FLEX-DEC-2026-005`, carried by
`FLEX-WP-0021`.
| File | What it is |
| --- | --- |
| `policy_package.md` | `secrets-engine.catalog-lane.lifecycle` v1, `allow_ttl: 15m` |
| `protected_system_manifest.yaml` | the `secret-catalog-lane` resource type and twelve actions |
| `subject_manifest.yaml` | the single `secrets-engine` service identity |
| `registry_snapshot.json` | loadable snapshot combining both manifests |
| `policy_fixtures.yaml` | 26 fixtures — 11 allows, dual control both ways, and every denial branch |
| `check_request_*.json` | standalone requests for `POST /v1/check` |
The action vocabulary is **secrets-engine's**, delivered under
`FLEX-WP-0021-T01` and recorded in
[`../../docs/secrets-engine-action-vocabulary.md`](../../docs/secrets-engine-action-vocabulary.md).
Read that before changing any action string here.
## Verify
```bash
go run ./cmd/flex-auth validate -kind policy -file examples/secrets-engine/policy_package.md
go run ./cmd/flex-auth load-registry -file examples/secrets-engine/registry_snapshot.json
```
22 Rego tests and 26 fixtures.
## Not yet deployed
There is no `flex-auth-secrets-engine` pin yet (`FLEX-WP-0021-T04`), so
secrets-engine has no address to call. Their policy pin
(`SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` / `_VERSION`) stays **unset and
fail-closed** until `FLEX-WP-0021-T05` hands them the published package and the
Service DNS. Do not configure it from this directory.

View file

@ -0,0 +1,33 @@
{
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"approval": {
"status": "approved",
"approvals": [
{
"subject_id": "human:alice"
},
{
"subject_id": "human:bob"
}
]
}
}
}

View file

@ -0,0 +1,23 @@
{
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
}

View file

@ -0,0 +1,21 @@
{
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
}

View file

@ -0,0 +1,21 @@
{
"id": "check:secrets-engine-revoke",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "revoke",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
}

View file

@ -0,0 +1,23 @@
{
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "some-other-service",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
}

View file

@ -0,0 +1,791 @@
[
{
"id": "fixture:secrets-engine-apply-allow",
"request": {
"id": "check:secrets-engine-apply",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "apply",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-provision-allow",
"request": {
"id": "check:secrets-engine-provision",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "provision",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-rotate-allow",
"request": {
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-verify-allow",
"request": {
"id": "check:secrets-engine-verify",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "verify",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-handoff-allow",
"request": {
"id": "check:secrets-engine-handoff",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "handoff",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-wrap-allow",
"request": {
"id": "check:secrets-engine-wrap",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "wrap",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-exec-allow",
"request": {
"id": "check:secrets-engine-exec",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "exec",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-deactivate-allow",
"request": {
"id": "check:secrets-engine-deactivate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "deactivate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-suspend-allow",
"request": {
"id": "check:secrets-engine-suspend",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "suspend",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-compromise-allow",
"request": {
"id": "check:secrets-engine-compromise",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "compromise",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-reactivate-allow",
"request": {
"id": "check:secrets-engine-reactivate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "reactivate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-destroy-dual-control-allow",
"request": {
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"approval": {
"status": "approved",
"approvals": [
{
"subject_id": "human:alice"
},
{
"subject_id": "human:bob"
}
]
}
}
},
"expect": {
"effect": "allow",
"reason": "catalog_lane_policy_matched"
}
},
{
"id": "fixture:secrets-engine-destroy-no-claim-deny",
"request": {
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "dual_control_required"
}
},
{
"id": "fixture:secrets-engine-destroy-single-approver-deny",
"request": {
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"approval": {
"status": "approved",
"approvals": [
{
"subject_id": "human:alice"
}
]
}
}
},
"expect": {
"effect": "deny",
"reason": "dual_control_required"
}
},
{
"id": "fixture:secrets-engine-destroy-repeated-approver-deny",
"request": {
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"approval": {
"status": "approved",
"approvals": [
{
"subject_id": "human:alice"
},
{
"subject_id": "human:alice"
}
]
}
}
},
"expect": {
"effect": "deny",
"reason": "dual_control_required"
}
},
{
"id": "fixture:secrets-engine-destroy-pending-claim-deny",
"request": {
"id": "check:secrets-engine-destroy",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "destroy",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"approval": {
"status": "pending",
"approvals": [
{
"subject_id": "human:alice"
},
{
"subject_id": "human:bob"
}
]
}
}
},
"expect": {
"effect": "deny",
"reason": "dual_control_required"
}
},
{
"id": "fixture:secrets-engine-revoke-not-an-action-deny",
"request": {
"id": "check:secrets-engine-revoke",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "revoke",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:secrets-engine-ungated-plan-deny",
"request": {
"id": "check:secrets-engine-plan",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "plan",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:secrets-engine-ungated-route-deny",
"request": {
"id": "check:secrets-engine-route",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "route",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:secrets-engine-ungated-audit-deny",
"request": {
"id": "check:secrets-engine-audit",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "audit",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:secrets-engine-ungated-catalog-deny",
"request": {
"id": "check:secrets-engine-catalog",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "catalog",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:secrets-engine-ungated-evidence-deny",
"request": {
"id": "check:secrets-engine-evidence",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "evidence",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:secrets-engine-unknown-subject-deny",
"request": {
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "some-other-service",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:secrets-engine-wrong-subject-type-deny",
"request": {
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "human"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "wrong_subject_type"
}
},
{
"id": "fixture:secrets-engine-wrong-system-deny",
"request": {
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "some-other-system",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "wrong_system"
}
},
{
"id": "fixture:secrets-engine-wrong-resource-type-deny",
"request": {
"id": "check:secrets-engine-rotate",
"tenant": "tenant:platform",
"subject": {
"id": "secrets-engine",
"type": "service"
},
"action": "rotate",
"resource": {
"id": "lane:glas-primary",
"type": "secret",
"system": "secrets-engine",
"attributes": {
"stage": "prod",
"fields": [
"password"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "wrong_resource_type"
}
}
]

View file

@ -0,0 +1,386 @@
---
id: secrets-engine.catalog-lane.lifecycle
name: secrets-engine catalog-lane lifecycle authorization
namespace: secrets-engine:secret-catalog-lane
version: v1
status: ready
package: flexauth.secrets_engine.catalog_lane
allow_ttl: 15m
actions:
- apply
- provision
- rotate
- verify
- handoff
- wrap
- exec
- deactivate
- suspend
- destroy
- compromise
- reactivate
owner: team:platform-security
fixtures:
- policy_fixtures.yaml
caring:
profile: caring-0.4.0-rc2
enforce: false
canonical_roles:
- Operator
organization_relations:
- ServiceProvider
scopes:
- level: Platform
id: platform:secrets-engine
tenant: tenant:platform
planes:
- Secret
- Policy
- Audit
capabilities:
- Create
- EditAny
- Execute
- Archive
- Restore
- View
- Audit
exposure_modes:
- Metadata
conditions:
- DualApprovalRequired
restrictions:
- PrivilegeEscalationBlocked
activation:
mode: local
metadata:
source: examples/secrets-engine/policy_package.md
flex_auth_contract: protected-system-v0
action_vocabulary: docs/secrets-engine-action-vocabulary.md
---
# secrets-engine catalog-lane lifecycle authorization
This package authorizes `secrets-engine`'s gated CLI operations over a secret
catalog lane. `secrets-engine` keeps custody of the secret material, the
OpenBao objects, and the local delivery overlay; flex-auth decides whether a
specific lifecycle operation is allowed now.
Opened by `FLEX-DEC-2026-005` and carried by `FLEX-WP-0021`.
## The vocabulary is theirs, not ours
The twelve action values below were **delivered by secrets-engine**
(`FLEX-WP-0021-T01`, their `docs/gated-actions.md`), read out of `cli.py`
rather than derived from the `secrets-engine.lifecycle/v1` example vocabulary
that this package replaces. `FLEX-WP-0021-T01` made an inferred action a
blocker rather than a default, and four of the twelve would have been inferred
wrongly. They are recorded in
[`../../docs/secrets-engine-action-vocabulary.md`](../../docs/secrets-engine-action-vocabulary.md).
The four that an inferred list would have gotten wrong are encoded here, not
assumed:
1. **`revoke` is not an action.** The CLI verb `revoke` gates as `deactivate`,
which is also reached from `lifecycle deactivate`. There is no `revoke`
value and one must not be added — `test_revoke_is_not_an_action` asserts it
denies `unknown_action`.
2. **`destroy` is defined but not reachable live.** Its handler raises before
the gate, so only `--dry-run` renders today. It stays in the vocabulary as
the dual-control case; no live `destroy` Check arrives until
`SECRETS-WP-0007-T04` lands.
3. **`compromise` and `reactivate` touch no OpenBao object.** They mutate local
overlay state only. They are gated because they change delivery posture, not
because they write to the backend.
4. **Seven CLI surfaces never reach the gate** and must not appear here:
`plan`, `apply --dry-run`, `route`, `audit`, `catalog`, `decision inspect`,
and `evidence`. `test_ungated_surfaces_are_not_actions` asserts they deny.
On the fourth: `apply` *is* an action and `apply --dry-run` is not, but that
distinction is invisible to flex-auth — both would arrive as `apply`. The PEP
does not call the gate for a dry run, and that is the only thing keeping them
apart. flex-auth cannot enforce it and does not pretend to.
## Request shape
`resource.type` is `secret-catalog-lane`, `resource.system` is
`secrets-engine`, and the catalog id is `resource.id`
(`FLEX-DEC-2026-005`; secrets-engine implemented this in their commit
`627810b`). `resource.attributes` carries `stage` plus sorted `fields`,
`policy_targets`, and `auth_targets`. `fields` is populated for `provision`,
`rotate`, `verify`, and `exec`; the rest send an empty list rather than a
guess.
## Allow lifetime
`allow_ttl: 15m`, stated explicitly rather than inherited from the engine
default. These decisions authorize live secret operations, so the window in
which one may be relied on is a property worth writing down in the package
where a reviewer sees it, not one to leave implicit.
Per `FLEX-DEC-2026-004`, that lifetime is authority to *issue* the operation,
not authority to keep using anything the operation produced. A wrapped or
delivered secret's own lifetime is secrets-engine's to bound.
## Dual control on `destroy`
`destroy` is the one action that requires more than a known caller. It requires
an **approval claim** on `context.approval`, marked `approved`, naming at least
two distinct approver subject ids.
flex-auth consumes that claim as an input claim and **never mutates it**
(`security-layer-model_v0.7` §9.4). Per `FLEX-DEC-2026-006` the approval fact
is `approval-engine`'s step-1 artifact and this decision is step 2; the PEP
validates across both. Concretely, this package checks that the claim *says*
approved with distinct approvers. It does **not** re-derive the claim's
temporal validity, its signature, or its supersession state — those are
`approval-engine`'s to assert and the PEP's to verify against the live claim.
A PDP that re-derived them from a caller-supplied blob would be inventing an
authority it does not have.
### Decision: one subject, so no `action_not_granted` branch
**Decision (`FLEX-WP-0021-T02`, 2026-09-06): the denial ladder stops at
`unknown_subject` plus `dual_control_required`.**
secrets-engine named exactly one calling identity. With one subject holding all
twelve actions, an `action_not_granted` branch could never fire. Following the
precedent set in `tenant-engine.write-api.mutate` (`FLEX-WP-0010-T02`), a rule
that cannot fail is worse than no rule: it reads to a later reviewer as though
per-action grants were separately controlled when they are not.
`dual_control_required` is a real branch — it fires whenever `destroy` arrives
without a satisfying claim, and the fixtures exercise both sides.
**Revisit when** a second calling identity is registered against
`system: "secrets-engine"`, or when secrets-engine splits its CLI identity by
lane or stage. Adding the branch then is additive to this package: no consumer
change and no request-shape change.
## Rules
```rego
import future.keywords.contains
import future.keywords.if
import future.keywords.in
valid_actions := {
"apply",
"provision",
"rotate",
"verify",
"handoff",
"wrap",
"exec",
"deactivate",
"suspend",
"destroy",
"compromise",
"reactivate",
}
dual_control_actions := {"destroy"}
known_subjects := {"secrets-engine"}
decision := {"effect": "allow", "reason": "catalog_lane_policy_matched"} if {
allowed
} else := {"effect": "deny", "reason": first_denial} if {
true
}
well_formed if {
input.resource.system == "secrets-engine"
input.resource.type == "secret-catalog-lane"
input.action in valid_actions
input.subject.type == "service"
input.subject.id in known_subjects
}
allowed if {
well_formed
not input.action in dual_control_actions
}
allowed if {
well_formed
input.action in dual_control_actions
dual_control_satisfied
}
approver_ids := {id |
some entry in input.context.approval.approvals
id := entry.subject_id
}
dual_control_satisfied if {
input.context.approval.status == "approved"
count(approver_ids) >= 2
}
default first_denial := "no_matching_rule"
first_denial := "wrong_system" if {
input.resource.system != "secrets-engine"
} else := "wrong_resource_type" if {
input.resource.type != "secret-catalog-lane"
} else := "unknown_action" if {
not input.action in valid_actions
} else := "wrong_subject_type" if {
input.subject.type != "service"
} else := "unknown_subject" if {
not input.subject.id in known_subjects
} else := "dual_control_required" if {
input.action in dual_control_actions
}
```
## Tests
```rego test
package flexauth.secrets_engine.catalog_lane_test
import future.keywords.every
import future.keywords.if
import future.keywords.in
import data.flexauth.secrets_engine.catalog_lane
lane(action) := {
"id": "check:secrets-engine",
"tenant": "tenant:platform",
"subject": {"id": "secrets-engine", "type": "service"},
"action": action,
"resource": {
"id": "lane:glas-primary",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {"stage": "prod", "fields": [], "policy_targets": [], "auth_targets": []}
},
"context": {}
}
approved_destroy := object.union(lane("destroy"), {"context": {"approval": {
"status": "approved",
"approvals": [{"subject_id": "human:alice"}, {"subject_id": "human:bob"}]
}}})
test_apply_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("apply")
}
test_provision_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("provision")
}
test_rotate_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("rotate")
}
test_verify_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("verify")
}
test_handoff_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("handoff")
}
test_wrap_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("wrap")
}
test_exec_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("exec")
}
test_deactivate_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("deactivate")
}
test_suspend_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("suspend")
}
test_compromise_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("compromise")
}
test_reactivate_allowed if {
catalog_lane.decision.effect == "allow" with input as lane("reactivate")
}
test_destroy_with_dual_control_allowed if {
catalog_lane.decision.effect == "allow" with input as approved_destroy
}
test_destroy_without_claim_denied if {
catalog_lane.decision.reason == "dual_control_required" with input as lane("destroy")
}
test_destroy_single_approver_denied if {
catalog_lane.decision.reason == "dual_control_required" with input as object.union(
lane("destroy"),
{"context": {"approval": {
"status": "approved",
"approvals": [{"subject_id": "human:alice"}]
}}}
)
}
test_destroy_repeated_approver_counts_once if {
catalog_lane.decision.reason == "dual_control_required" with input as object.union(
lane("destroy"),
{"context": {"approval": {
"status": "approved",
"approvals": [{"subject_id": "human:alice"}, {"subject_id": "human:alice"}]
}}}
)
}
test_destroy_unapproved_claim_denied if {
catalog_lane.decision.reason == "dual_control_required" with input as object.union(
lane("destroy"),
{"context": {"approval": {
"status": "pending",
"approvals": [{"subject_id": "human:alice"}, {"subject_id": "human:bob"}]
}}}
)
}
test_revoke_is_not_an_action if {
catalog_lane.decision.reason == "unknown_action" with input as lane("revoke")
}
test_ungated_surfaces_are_not_actions if {
every surface in ["plan", "route", "audit", "catalog", "decision", "evidence", "inspect"] {
catalog_lane.decision.reason == "unknown_action" with input as lane(surface)
}
}
test_unknown_subject_denied if {
catalog_lane.decision.reason == "unknown_subject" with input as object.union(
lane("rotate"),
{"subject": {"id": "some-other-service", "type": "service"}}
)
}
test_wrong_subject_type_denied if {
catalog_lane.decision.reason == "wrong_subject_type" with input as object.union(
lane("rotate"),
{"subject": {"id": "secrets-engine", "type": "human"}}
)
}
test_wrong_system_denied if {
catalog_lane.decision.reason == "wrong_system" with input as object.union(
lane("rotate"),
{"resource": {"id": "lane:glas-primary", "type": "secret-catalog-lane", "system": "some-other-system"}}
)
}
test_wrong_resource_type_denied if {
catalog_lane.decision.reason == "wrong_resource_type" with input as object.union(
lane("rotate"),
{"resource": {"id": "lane:glas-primary", "type": "secret", "system": "secrets-engine"}}
)
}
```

View file

@ -0,0 +1,105 @@
id: secrets-engine
name: Secrets Engine
resource_types:
- name: secret-catalog-lane
scope_level: Resource
planes:
- Secret
- Policy
- Audit
metadata:
description: >-
A secret catalog lane: one catalog entry's delivery path at a given
stage. The catalog id is resource.id; stage plus sorted fields,
policy_targets, and auth_targets are resource.attributes.
flex-auth never sees secret material, only the lane's coordinates.
actions:
- name: apply
capabilities: [EditAny, Audit]
planes: [Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
note: >-
apply --dry-run never reaches the gate, but the distinction is
invisible here; both would arrive as "apply".
- name: provision
capabilities: [Create, Audit]
planes: [Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: rotate
capabilities: [EditAny, Audit]
planes: [Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: verify
capabilities: [View, Audit]
planes: [Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: handoff
capabilities: [EditAny, Audit]
planes: [Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: wrap
capabilities: [Execute, Audit]
planes: [Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: exec
capabilities: [Execute, Audit]
planes: [Secret, Execution, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: deactivate
capabilities: [Archive, Audit]
planes: [Secret, Policy, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
note: >-
Reached from both the CLI verb "revoke" and "lifecycle deactivate".
There is no "revoke" action value.
- name: suspend
capabilities: [Archive, Audit]
planes: [Secret, Policy, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
- name: destroy
capabilities: [DeleteAny, Audit]
planes: [Secret, Policy, Audit]
exposure_modes: [Metadata]
metadata:
required_context:
- approval
note: >-
Dual control. Defined but not reachable live: the handler raises
before the gate, so only --dry-run renders until SECRETS-WP-0007-T04.
- name: compromise
capabilities: [EditAny, Audit]
planes: [Policy, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
note: Mutates local delivery overlay state only; touches no OpenBao object.
- name: reactivate
capabilities: [Restore, Audit]
planes: [Policy, Audit]
exposure_modes: [Metadata]
metadata:
required_context: []
note: Mutates local delivery overlay state only; touches no OpenBao object.
caring_profiles:
- caring-0.4.0-rc2
metadata:
flex_auth_contract: protected-system-v0
action_vocabulary_source: secrets-engine docs/gated-actions.md (FLEX-WP-0021-T01)

View file

@ -0,0 +1,282 @@
{
"systems": [
{
"id": "secrets-engine",
"name": "Secrets Engine",
"resource_types": [
{
"name": "secret-catalog-lane",
"scope_level": "Resource",
"planes": [
"Secret",
"Policy",
"Audit"
],
"metadata": {
"description": "A secret catalog lane: one catalog entry's delivery path at a given stage. The catalog id is resource.id; stage plus sorted fields, policy_targets, and auth_targets are resource.attributes. flex-auth never sees secret material, only the lane's coordinates."
}
}
],
"actions": [
{
"name": "apply",
"capabilities": [
"EditAny",
"Audit"
],
"planes": [
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [],
"note": "apply --dry-run never reaches the gate, but the distinction is invisible here; both would arrive as \"apply\"."
}
},
{
"name": "provision",
"capabilities": [
"Create",
"Audit"
],
"planes": [
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "rotate",
"capabilities": [
"EditAny",
"Audit"
],
"planes": [
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "verify",
"capabilities": [
"View",
"Audit"
],
"planes": [
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "handoff",
"capabilities": [
"EditAny",
"Audit"
],
"planes": [
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "wrap",
"capabilities": [
"Execute",
"Audit"
],
"planes": [
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "exec",
"capabilities": [
"Execute",
"Audit"
],
"planes": [
"Secret",
"Execution",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "deactivate",
"capabilities": [
"Archive",
"Audit"
],
"planes": [
"Secret",
"Policy",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [],
"note": "Reached from both the CLI verb \"revoke\" and \"lifecycle deactivate\". There is no \"revoke\" action value."
}
},
{
"name": "suspend",
"capabilities": [
"Archive",
"Audit"
],
"planes": [
"Secret",
"Policy",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": []
}
},
{
"name": "destroy",
"capabilities": [
"DeleteAny",
"Audit"
],
"planes": [
"Secret",
"Policy",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [
"approval"
],
"note": "Dual control. Defined but not reachable live: the handler raises before the gate, so only --dry-run renders until SECRETS-WP-0007-T04."
}
},
{
"name": "compromise",
"capabilities": [
"EditAny",
"Audit"
],
"planes": [
"Policy",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [],
"note": "Mutates local delivery overlay state only; touches no OpenBao object."
}
},
{
"name": "reactivate",
"capabilities": [
"Restore",
"Audit"
],
"planes": [
"Policy",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [],
"note": "Mutates local delivery overlay state only; touches no OpenBao object."
}
}
],
"caring_profiles": [
"caring-0.4.0-rc2"
],
"metadata": {
"flex_auth_contract": "protected-system-v0",
"action_vocabulary_source": "secrets-engine docs/gated-actions.md (FLEX-WP-0021-T01)"
}
}
],
"resource_manifests": [],
"tenants": [
{
"id": "tenant:platform",
"name": "Platform Tenant"
}
],
"subjects": [
{
"id": "secrets-engine",
"type": "Service",
"display_name": "secrets-engine service principal",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:secrets-engine-lane-operators"
],
"tenant": "tenant:platform",
"metadata": {
"description": "secrets-engine's own service identity, the single calling identity for the twelve gated catalog-lane actions it sends to POST /v1/check. Because it is the only subject, the package has no action_not_granted branch (FLEX-WP-0021-T02); registering a second identity is the revisit trigger."
}
}
],
"groups": [
{
"id": "group:secrets-engine-lane-operators",
"display_name": "secrets-engine Catalog Lane Operators",
"members": [
"secrets-engine"
],
"tenant": "tenant:platform"
}
],
"relationships": []
}

View file

@ -0,0 +1,27 @@
id: subjects:secrets-engine-operators
tenants:
- id: tenant:platform
name: Platform Tenant
subjects:
- id: secrets-engine
type: Service
display_name: secrets-engine service principal
organization_relation: ServiceProvider
roles:
- Operator
groups:
- group:secrets-engine-lane-operators
tenant: tenant:platform
metadata:
description: >-
secrets-engine's own service identity, the single calling identity for
the twelve gated catalog-lane actions it sends to POST /v1/check.
Because it is the only subject, the package has no action_not_granted
branch (FLEX-WP-0021-T02); registering a second identity is the
revisit trigger.
groups:
- id: group:secrets-engine-lane-operators
display_name: secrets-engine Catalog Lane Operators
members:
- secrets-engine
tenant: tenant:platform