package policy import ( "context" "testing" "codeberg.org/VARASYS/ZDDC/zddc/internal/zddc" "github.com/open-policy-agent/opa/rego" ) // TestFederalRego_DivergencesFromStandard validates the federal-mode // variant by asserting both that: // // (a) most cascade scenarios produce the same verdict as standard // (the federal rule reduces to standard whenever no parent deny // intersects a leaf allow), AND // // (b) the specific scenarios where the rules differ (a leaf-level // allow overlaying an ancestor's deny) produce DIFFERENT verdicts: // standard says allow (leaf wins); federal says deny (ancestor // deny is absolute — NIST AC-6 default). // // Like the standard parity test, this imports the OPA library as a // test-only dependency. The federal Rego is a deployable artifact // (operators dump it via --print-rego=federal); the parity guard // here proves the artifact behaves as documented. func TestFederalRego_DivergencesFromStandard(t *testing.T) { ctx := context.Background() standard, err := rego.New( rego.Query("data.zddc.access.allow"), rego.Module("access.rego", ReferenceRego), ).PrepareForEval(ctx) if err != nil { t.Fatalf("compile standard rego: %v", err) } federal, err := rego.New( rego.Query("data.zddc.access_federal.allow"), rego.Module("access_federal.rego", FederalRego), ).PrepareForEval(ctx) if err != nil { t.Fatalf("compile federal rego: %v", err) } allow := func(p ...string) zddc.ZddcFile { return zddc.ZddcFile{ACL: zddc.ACLRules{Allow: p}} } deny := func(p ...string) zddc.ZddcFile { return zddc.ZddcFile{ACL: zddc.ACLRules{Deny: p}} } empty := zddc.ZddcFile{} cases := []struct { name string chain zddc.PolicyChain email string wantStandard bool wantFederal bool divergesByDesign bool // true if standard and federal must disagree here }{ // ── Cases where the two policies must AGREE ──────────────── { "empty chain, no files", zddc.PolicyChain{HasAnyFile: false}, "alice@example.com", true, true, false, }, { "files exist, no rule matches → both deny", zddc.PolicyChain{Levels: []zddc.ZddcFile{allow("*@trusted.com")}, HasAnyFile: true}, "alice@example.com", false, false, false, }, { "leaf allow with no ancestor deny → both allow", zddc.PolicyChain{Levels: []zddc.ZddcFile{empty, allow("*@example.com")}, HasAnyFile: true}, "alice@example.com", true, true, false, }, { "only deny anywhere → both deny", zddc.PolicyChain{Levels: []zddc.ZddcFile{deny("alice@example.com")}, HasAnyFile: true}, "alice@example.com", false, false, false, }, { "glob allow, no deny → both allow", zddc.PolicyChain{Levels: []zddc.ZddcFile{allow("*@example.com")}, HasAnyFile: true}, "alice@example.com", true, true, false, }, // ── The signature divergence: leaf allow overlaying ancestor deny ── { "leaf allows what parent denied → standard allows, federal denies (AC-6)", zddc.PolicyChain{Levels: []zddc.ZddcFile{ deny("alice@example.com"), allow("alice@example.com"), }, HasAnyFile: true}, "alice@example.com", true, // standard: leaf wins false, // federal: parent deny is absolute true, }, { "deep leaf re-allows after middle deny → standard allows, federal denies", zddc.PolicyChain{Levels: []zddc.ZddcFile{ allow("*@example.com"), deny("alice@example.com"), allow("alice@example.com"), }, HasAnyFile: true}, "alice@example.com", true, false, true, }, { "glob deny at root, specific allow at leaf → both differ", zddc.PolicyChain{Levels: []zddc.ZddcFile{ deny("*@example.com"), allow("alice@example.com"), }, HasAnyFile: true}, "alice@example.com", true, false, true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { input := AllowInput{Path: "/test", PolicyChain: chainToSerializable(tc.chain)} input.User.Email = tc.email regoInput, err := canonicalInput(input) if err != nil { t.Fatalf("encode input: %v", err) } std, err := standard.Eval(ctx, rego.EvalInput(regoInput)) if err != nil { t.Fatalf("standard eval: %v", err) } fed, err := federal.Eval(ctx, rego.EvalInput(regoInput)) if err != nil { t.Fatalf("federal eval: %v", err) } if len(std) == 0 || len(fed) == 0 { t.Fatal("rego returned empty result set") } stdAllow := std[0].Expressions[0].Value.(bool) fedAllow := fed[0].Expressions[0].Value.(bool) if stdAllow != tc.wantStandard { t.Errorf("standard rego: got %v, want %v", stdAllow, tc.wantStandard) } if fedAllow != tc.wantFederal { t.Errorf("federal rego: got %v, want %v", fedAllow, tc.wantFederal) } // Cross-check the divergence flag itself: if we said the cases // must disagree, they must; if we said they agree, they must. diverges := stdAllow != fedAllow if diverges != tc.divergesByDesign { t.Errorf("divergence = %v, want %v (standard=%v, federal=%v)", diverges, tc.divergesByDesign, stdAllow, fedAllow) } }) } } // TestFederalRego_RegoCompiles is a sanity check that the embedded // federal Rego file parses without error in OPA, separate from the // behavior tests. Catches accidental syntax breakage in // access_federal.rego before running the (slower) parity matrix. func TestFederalRego_RegoCompiles(t *testing.T) { _, err := rego.New( rego.Query("data.zddc.access_federal.allow"), rego.Module("access_federal.rego", FederalRego), ).PrepareForEval(context.Background()) if err != nil { t.Fatalf("federal rego does not compile: %v", err) } }