approval-engine flagged the class one message earlier -- a contract whose
examples contradict its prose gets implemented as its examples -- and
yesterday's package was a fresh instance of it, committed while flagging
it.
The published rule required context.approval.status == "approved" and
counted context.approval.approvals[].subject_id. Neither field exists.
approval-engine's approval_claim.schema.json has `state` (whose operative
value is `valid`, not `approved`) and carries no approver list at all. The
rule was unsatisfiable: every live destroy would have denied
dual_control_required no matter how good the approval was. It failed
closed, so it was never a hole, but it was policy written against a shape
of our own devising rather than a published one.
The rule now consumes valid_now from the real claim, guarded on kind and
issuer. valid_now is the summary predicate that already folds in the
distinct-approver threshold, with reason_code insufficient_approvers for
a claim that failed it -- so this is also the correct layering, not just
the correct shape. Counting approvers here is exactly the duplication
GH-DEC-2026-005 removes; the compensating property is reconstructability
at the issuer under 9.6, which is approval-engine's.
Recorded as a correction section in the package and the vocabulary doc
rather than quietly rewritten. 25 Rego tests and 29 fixtures pass,
covering insufficient_approvers, consumed, revoked, approved-but-not-yet-
valid, foreign issuer, and wrong kind.
Two things the package deliberately does not do, both now written down:
it does not compare binding.pdp_digest, because the request digest is
computed after policy evaluation and a Rego rule cannot see it; and it
makes no cross-check that the claim was approved for this action and
target, because the claim's binding uses approval-engine's vocabulary and
no mapping between the two is published. Inventing one would silently
accept a claim approved for something else. Both belong to the PEP until
a mapping exists, and that is worth closing before SECRETS-WP-0007-T04
makes destroy reachable.
Also swept the other published fixtures on approval-engine's reasoning.
One more instance: the inner decision in examples/caring/action_authorization.json
declared contract_version flex-auth.decision-record.v1 while its
provenance omitted policy_package_digest, registry_snapshot_digest, and
input_claim_digests -- all published contract fields since 2026-09-02.
Completed. The remaining example context vocabularies are consumer-owned
and match their integrations.
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
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