flex-auth/docs/secrets-engine-action-vocabulary.md
tegwick f75db59a8c
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
Publish secrets-engine.catalog-lane.lifecycle v1 (FLEX-WP-0021 T01, T02)
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
2026-09-06 08:02:52 +02:00

4.1 KiB

secrets-engine action vocabulary

Status: published Date: 2026-09-06 Workplan: FLEX-WP-0021-T01 Source: secrets-engine docs/gated-actions.md, read out of their cli.py Package: secrets-engine.catalog-lane.lifecycle v1 (examples/secrets-engine/policy_package.md)

FLEX-WP-0021-T01 required secrets-engine to name this list rather than letting flex-auth derive it, and made an inferred action a blocker rather than a default. That was not ceremony: four of the twelve would have been inferred wrongly, and one action that looks obvious does not exist at all.

The example vocabulary secrets-engine.lifecycle/v1 this replaces is not this list and was never published (FLEX-DEC-2026-005).

Request shape

Field Value
resource.type secret-catalog-lane
resource.system secrets-engine
resource.id the catalog id
resource.attributes.stage deployment stage
resource.attributes.fields sorted; populated for provision, rotate, verify, exec; empty list otherwise
resource.attributes.policy_targets / auth_targets sorted
context.approval approval claim; required for destroy only

Empty lists are sent rather than omitted or guessed.

The twelve actions

Action Reaches OpenBao Notes
apply yes apply --dry-run does not reach the gate
provision yes carries fields
rotate yes carries fields
verify yes carries fields
handoff yes
wrap yes
exec yes carries fields
deactivate yes also the gate for the CLI verb revoke
suspend yes
destroy yes dual control; not reachable live yet
compromise no local delivery overlay only
reactivate no local delivery overlay only

Four things an inferred list gets wrong

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. The package asserts this (test_revoke_is_not_an_actionunknown_action).

2. destroy is defined but not reachable live. Its handler raises before the gate, so only --dry-run renders. It stays in the vocabulary as the dual-control case; no live destroy Check arrives until SECRETS-WP-0007-T04 lands. Keeping it in the package now means the rule is reviewed and fixtured before the path opens rather than in the same change that opens it.

3. compromise and reactivate touch no OpenBao object. They mutate local delivery-overlay state only. They are gated because they change delivery posture, not because they write to the backend — a distinction worth recording, because a reviewer looking for backend writes would otherwise read their presence as a mistake.

4. Seven CLI surfaces never reach the gate and must not appear in the package: plan, apply --dry-run, route, audit, catalog, decision inspect, and evidence.

apply --dry-run is the one flex-auth cannot enforce. apply is an action and apply --dry-run is not, but both would arrive as apply — the distinction is invisible to the PDP. What keeps them apart is that the PEP does not call the gate for a dry run. flex-auth records the limit rather than implying a control it does not have.

Dual control on destroy

destroy requires context.approval marked approved with at least two distinct approver subject ids. Repeated entries from one subject count once.

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 the decision is step 2; the PEP validates across both. This package checks that the claim says approved with distinct approvers. It does not re-derive the claim's temporal validity, signature, or supersession state — those belong to approval-engine to assert and to the PEP to verify against the live claim. A PDP re-deriving them from a caller-supplied blob would be inventing an authority it does not have.