approval-engine/schemas/approval_claim.schema.json

201 lines
5.6 KiB
JSON
Raw Normal View History

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://approval-engine.netkingdom/schemas/approval_claim.schema.json",
"title": "ApprovalClaim",
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"description": "Input claim that access-engine consumes. This is a fact about an approval object, not a decision. Yields to the Taxonomy request-claim schema (statute \u00a717) when that artifact exists and is assented; do not treat this local shape as permanent.",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"kind",
"issuer",
"approval_id",
"state",
"valid_now",
"consumed",
"binding",
"freshness",
"validity",
"reason_code"
],
"properties": {
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"schema_version": {
"const": "0.1"
},
"kind": {
"const": "approval-claim"
},
"yields_to": {
"type": "string",
"description": "Taxonomy artifact this contract yields to. Informational; consumers must not branch on it."
},
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"issuer": {
"const": "approval-engine"
},
"approval_id": {
"type": "string",
"format": "uuid"
},
"state": {
"type": "string",
"enum": [
"requested",
"approved",
"valid",
"consumed",
"superseded",
"revoked",
"expired"
]
},
"valid_now": {
"type": "boolean",
"description": "True only when the object is approved, inside its validity window, and not consumed, superseded, revoked, or expired. Not a permission."
},
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"consumed": {
"type": "boolean"
},
"binding": {
"$ref": "#/$defs/binding"
},
"freshness": {
"$ref": "#/$defs/freshness"
},
"validity": {
"$ref": "#/$defs/validity"
},
"reason_code": {
"type": "string",
"enum": [
"ok",
"requested",
"not_yet_valid",
"insufficient_approvers",
"expired",
"revoked",
"superseded",
"consumed"
]
}
},
"not": {
"anyOf": [
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
{
"required": [
"effect"
]
},
{
"required": [
"decision"
]
},
{
"required": [
"allow"
]
},
{
"required": [
"deny"
]
}
]
},
"$defs": {
"binding": {
"type": "object",
"additionalProperties": false,
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"required": [
"action",
"target",
"actor",
"principal",
"purpose",
"digest",
Implement GH-DEC-2026-008: declared PDP-path intent, enforced at issue Gate House ruled binding.pdp_digest is the binding correspondence on the GH-DEC-2026-003 path and is required there, having rejected a vocabulary mapping for the reasons we gave. It asked this engine to record the PDP digest at issue for approvals intended for that path, and to have the claim state which approvals those are rather than leaving it to the requester's memory. Schema v3 adds approvals.pdp_path. create() refuses pdp_path true without a pdp_digest, so an approval that would be unusable on the path fails at issue rather than at the protected side effect. The claim exposes binding.pdp_path, which makes it a guarantee rather than a hint: pdp_path true implies pdp_digest is non-null. Intent is declared and never inferred. A pdp_digest that happens to be present is not a declaration anybody made, so a recorded digest alone leaves pdp_path false, legacy rows migrate to false rather than being back-filled from their digests, and a successor inherits its predecessor's declaration. Approvals issued before the ruling stay usable by consumers in this engine's own vocabulary and are simply not usable on the PDP path -- the ruling's intended cost, stated as such. Schema, both published examples, a v2-to-v3 migration test asserting survivors keep their digest while declaring no path intent, and tests for refusal at issue, claim exposure, non-inference, and successor inheritance. 102 tests pass (8 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 14:51:23 +02:00
"pdp_digest",
"pdp_path"
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
],
"properties": {
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"action": {
"type": "string",
"minLength": 1
},
"target": {
"type": "object"
},
"actor": {
"type": "string",
"minLength": 1
},
"principal": {
"type": "string",
"minLength": 1
},
"purpose": {
"type": "string",
"minLength": 1
},
"digest": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$",
"description": "SHA-256 over the canonical JSON of action, actor, principal, purpose, target (sorted keys, RFC 8259). Distinguishes approved from approved-for-this-exact-request."
},
"pdp_digest": {
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"type": [
"string",
"null"
],
"pattern": "^sha256:[0-9a-f]{64}$",
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
"description": "The flex-auth NewDecisionBinding request_digest recorded at issue time, or null when the approval was not issued against a PDP decision. Always present so its absence is a stated fact rather than a missing key. Recorded at issue, so it is the digest of the action request as it stands before this claim is embedded in it. At execute time on a claim-bearing request the comparison target is the PDP's claim-excluded digest -- flex-auth publishes it as binding.approval_binding_digest (FLEX-DEC-2026-007) -- and NOT binding.request_digest, which covers the carried claim and therefore can never equal a digest recorded before that claim existed. When non-null, a consumer MUST compare this to the PDP's published exclusion-scoped digest and MUST NOT re-derive the native digest as a substitute, and MUST NOT guess the exclusion rule. A PEP on a privileged lane MUST refuse a claim whose pdp_digest is null."
Implement GH-DEC-2026-008: declared PDP-path intent, enforced at issue Gate House ruled binding.pdp_digest is the binding correspondence on the GH-DEC-2026-003 path and is required there, having rejected a vocabulary mapping for the reasons we gave. It asked this engine to record the PDP digest at issue for approvals intended for that path, and to have the claim state which approvals those are rather than leaving it to the requester's memory. Schema v3 adds approvals.pdp_path. create() refuses pdp_path true without a pdp_digest, so an approval that would be unusable on the path fails at issue rather than at the protected side effect. The claim exposes binding.pdp_path, which makes it a guarantee rather than a hint: pdp_path true implies pdp_digest is non-null. Intent is declared and never inferred. A pdp_digest that happens to be present is not a declaration anybody made, so a recorded digest alone leaves pdp_path false, legacy rows migrate to false rather than being back-filled from their digests, and a successor inherits its predecessor's declaration. Approvals issued before the ruling stay usable by consumers in this engine's own vocabulary and are simply not usable on the PDP path -- the ruling's intended cost, stated as such. Schema, both published examples, a v2-to-v3 migration test asserting survivors keep their digest while declaring no path intent, and tests for refusal at issue, claim exposure, non-inference, and successor inheritance. 102 tests pass (8 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 14:51:23 +02:00
},
"pdp_path": {
"type": "boolean",
"description": "Whether this approval was declared at issue for the GH-DEC-2026-003 PDP consumption path. GH-DEC-2026-008 requires pdp_digest on that path, so this engine refuses to create a pdp_path approval without one; a true value therefore guarantees pdp_digest is non-null. A consumer on that path MUST require pdp_path true and MUST NOT infer path intent from a pdp_digest that merely happens to be present."
}
}
},
"freshness": {
"type": "object",
"additionalProperties": false,
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"required": [
"observed_at",
"ttl_seconds",
"not_after"
],
"properties": {
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"observed_at": {
"type": "string",
"format": "date-time"
},
"ttl_seconds": {
"type": "integer",
"minimum": 1
},
"not_after": {
"type": "string",
"format": "date-time"
}
}
},
"validity": {
"type": "object",
"additionalProperties": false,
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"required": [
"not_before",
"expires_at"
],
"properties": {
State pdp_digest explicitly; decline to publish a vocabulary mapping flex-auth asked whether this engine should publish an action/target mapping between the claim binding's vocabulary (secrets.kv.destroy, {"id": "lane-openbao-root"}) and a policy package's (destroy, lane:...), since their package makes no cross-check that a claim was approved for the action being decided. Answered no. A PIP asserting that one vocabulary's action means another's would author policy semantics it does not own, over vocabularies it does not own, and the failure mode is asymmetric: a wrong mapping silently accepts a claim approved for a different action, which is worse than no mapping. binding.pdp_digest is the correspondence and sidesteps vocabulary entirely -- it compares the PDP's own digest to the PDP's own digest, with no translation by anyone. Implemented the part that was ours. pdp_digest was emitted only when recorded, so a consumer could not distinguish "not issued against a decision" from "we forgot to look". It is now always present and null in that case, required-but-nullable in the schema, and documented as something a PEP on a privileged lane must refuse. This engine states the fact; enforcing the lane's policy stays with the consumer. Both published examples were already contradicting the updated schema by omitting the field -- the same fixture-versus-contract defect flex-auth hit twice this week and that secrets-engine implemented. Fixed both, made them cover the PDP-bound and unbound shapes so neither is inferred from the other, and added tests/test_examples.py to validate every example against the schema so the class cannot recur here. jsonschema added as a dev dependency. 94 tests pass (6 new). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND Assistant: claude-code Assistant-Model: opus Assistant-Process: 411227@bnt-lap001 Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 09:32:11 +02:00
"not_before": {
"type": "string",
"format": "date-time"
},
"expires_at": {
"type": "string",
"format": "date-time"
}
}
}
}
}