package api import "testing" func claimBearing() CheckRequest { return CheckRequest{ Tenant: "tenant:platform", Subject: SubjectRef{ID: "secrets-engine", Type: "service"}, Action: "destroy", Resource: ResourceRef{ID: "lane:glas-primary", Type: "secret-catalog-lane", System: "secrets-engine"}, Context: map[string]any{ "approval": map[string]any{"kind": "approval-claim", "valid_now": true}, }, } } func claimFree() CheckRequest { r := claimBearing() r.Context = nil return r } // TestApprovalBindingDigestSurvivesAttachingTheClaim is the property the whole // field exists for. An approval's pdp_digest is recorded at issue time against a // claim-free Check; the request that later carries the claim must still be able // to name it. func TestApprovalBindingDigestSurvivesAttachingTheClaim(t *testing.T) { atIssue := RequestDigest(claimFree()) atExecute := ApprovalBindingDigest(claimBearing()) if atIssue != atExecute { t.Fatalf("approval binding digest moved when the claim was attached:\n issue: %s\n execute: %s", atIssue, atExecute) } } // TestRequestDigestStillMovesWhenTheClaimIsAttached guards the reason this is a // second digest rather than a redefinition of the first. request_digest remains // the full replay identity: attaching a claim changes the request, so it must // change the digest. Collapsing the two would let an allow obtained with a valid // claim be replayed against a request carrying none. func TestRequestDigestStillMovesWhenTheClaimIsAttached(t *testing.T) { if RequestDigest(claimFree()) == RequestDigest(claimBearing()) { t.Fatal("request_digest ignored context.approval — replay identity must cover the claim") } } // TestTheTwoDigestsDisagreeOnAClaimBearingRequest asserts the distinction is // real rather than decorative. A test that the two functions disagree is how a // distinction that looks like duplication is defended. func TestTheTwoDigestsDisagreeOnAClaimBearingRequest(t *testing.T) { req := claimBearing() if RequestDigest(req) == ApprovalBindingDigest(req) { t.Fatal("the two digests agree on a claim-bearing request; one of them is not doing its job") } } // TestApprovalBindingDigestEqualsRequestDigestWithoutAClaim keeps the field // honest for every consumer that never sends one. func TestApprovalBindingDigestEqualsRequestDigestWithoutAClaim(t *testing.T) { req := claimFree() if RequestDigest(req) != ApprovalBindingDigest(req) { t.Fatal("digests differ on a claim-free request; they must be the same value") } } // TestBindingOmitsApprovalDigestWhenNoClaimIsPresent avoids publishing a field // that would read as a second identity on every ordinary decision. func TestBindingOmitsApprovalDigestWhenNoClaimIsPresent(t *testing.T) { if got := NewDecisionBinding(claimFree()).ApprovalBindingDigest; got != "" { t.Fatalf("expected no approval_binding_digest on a claim-free request, got %q", got) } if got := NewDecisionBinding(claimBearing()).ApprovalBindingDigest; got == "" { t.Fatal("expected approval_binding_digest on a claim-bearing request") } } // TestOtherContextClaimsStillBindUnderTheApprovalDigest confirms the exclusion is // surgical: only the approval key leaves the material. func TestOtherContextClaimsStillBindUnderTheApprovalDigest(t *testing.T) { a := claimBearing() a.Context["purpose"] = "rotate-exposed-key" b := claimBearing() b.Context["purpose"] = "something-else" if ApprovalBindingDigest(a) == ApprovalBindingDigest(b) { t.Fatal("approval binding digest ignored a non-approval context claim") } }