flex-auth/examples/caring/action_authorization.json
tegwick 68ad039a3f Fix the destroy rule: it was written against an invented claim shape
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
2026-09-06 08:11:14 +02:00

124 lines
3.6 KiB
JSON

{
"schema_version": "0.1",
"id": "8bfc20be-47a4-4fb0-97a2-bf0a920afad8",
"status": "approved",
"request": {
"id": "check:secrets-engine-destroy-example",
"subject": {
"id": "user:alice",
"type": "Human"
},
"action": "destroy",
"resource": {
"id": "catalog:example-build-test-token",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "build",
"fields": [
"token"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"purpose": "contract-test"
}
},
"validity": {
"not_before": "2026-08-23T10:00:00Z",
"expires_at": "2026-08-23T10:15:00Z"
},
"approvals": {
"required_count": 2,
"entries": [
{
"subject_id": "user:alice",
"approved_at": "2026-08-23T10:01:00Z",
"assurance": "mfa",
"evidence_ref": "approval:alice"
},
{
"subject_id": "user:bob",
"approved_at": "2026-08-23T10:02:00Z",
"assurance": "mfa",
"evidence_ref": "approval:bob"
}
]
},
"decision": {
"id": "decision:contract-example",
"contract_version": "flex-auth.decision-record.v1",
"request_id": "check:secrets-engine-destroy-example",
"effect": "allow",
"reason": "destruction_approved",
"matched_policy_version": "v1",
"matched_rule": "allow_destroy",
"resource": {
"id": "catalog:example-build-test-token",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "build",
"fields": [
"token"
],
"policy_targets": [],
"auth_targets": []
}
},
"subject": {
"id": "user:alice",
"type": "Human"
},
"binding": {
"subject": {
"id": "user:alice",
"type": "Human"
},
"action": "destroy",
"resource": {
"id": "catalog:example-build-test-token",
"type": "secret-catalog-lane",
"system": "secrets-engine",
"attributes": {
"stage": "build",
"fields": [
"token"
],
"policy_targets": [],
"auth_targets": []
}
},
"context": {
"purpose": "contract-test"
},
"request_digest": "sha256:eb9d856a060813aebe039f19b82d4be1c4589fa6c0429cf20e15fb115db3ef9a"
},
"lifetime": {
"kind": "ttl",
"ttl": "15m",
"not_before": "2026-08-23T10:00:30Z",
"expires_at": "2026-08-23T10:15:30Z"
},
"provenance": {
"evaluator": "flex-auth/local",
"mode": "standalone",
"policy_package": "secrets-engine.catalog-lane.lifecycle",
"policy_version": "v1",
"policy_package_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"registry_snapshot_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"input_claim_digests": {
"context": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
},
"decision_time": "2026-08-23T10:00:30Z",
"policy_package_note": "Reserved coordinate, not yet published (FLEX-DEC-2026-005). Illustrative only."
}
},
"provenance": {
"note": "DEFERRED EXAMPLE (FLEX-DEC-2026-006). Not a contract. The approval fact's authority is approval-engine; the decision's authority is flex-auth. State Hub is never the authority for either -- do not derive a provenance.authority constant from this file.",
"approval_authority": "approval-engine",
"decision_authority": "flex-auth"
}
}