From 25e14537f33c1f293773f42fbda268f11a65c72c Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 3 Dec 2025 15:26:04 +0100 Subject: [PATCH 1/6] add generic policies support to policy devel eval Signed-off-by: Sylwester Piskozub --- app/cli/cmd/policy_develop_eval.go | 1 - app/cli/internal/policydevel/eval.go | 29 ++++++++--- app/cli/internal/policydevel/eval_test.go | 49 +++++++++++++++++++ .../policydevel/testdata/generic-policy.yaml | 32 ++++++++++++ 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 app/cli/internal/policydevel/testdata/generic-policy.yaml diff --git a/app/cli/cmd/policy_develop_eval.go b/app/cli/cmd/policy_develop_eval.go index 0b0565939..097578aca 100644 --- a/app/cli/cmd/policy_develop_eval.go +++ b/app/cli/cmd/policy_develop_eval.go @@ -70,7 +70,6 @@ evaluates the policy against the provided material or attestation.`, } cmd.Flags().StringVar(&materialPath, "material", "", "Path to material or attestation file") - cobra.CheckErr(cmd.MarkFlagRequired("material")) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("Kind of the material: %q", schemaapi.ListAvailableMaterialKind())) cmd.Flags().StringSliceVar(&annotations, "annotation", []string{}, "Key-value pairs of material annotations (key=value)") cmd.Flags().StringVarP(&policyPath, "policy", "p", "policy.yaml", "Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy)") diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index 83036f6ad..f3e96311e 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -61,18 +61,18 @@ type EvalSummaryDebugInfo struct { } func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { - // 1. Create crafting schema - policies, err := createPolicies(opts.PolicyPath, opts.Inputs) + // 1. Craft material with annotations + material, err := craftMaterial(opts.MaterialPath, opts.MaterialKind, &logger) if err != nil { return nil, err } + material.Annotations = opts.Annotations - // 2. Craft material with annotations - material, err := craftMaterial(opts.MaterialPath, opts.MaterialKind, &logger) + // 2. Create policy attachment + policies, err := createPolicies(opts.PolicyPath, opts.Inputs, material.GetId()) if err != nil { return nil, err } - material.Annotations = opts.Annotations // 3. Verify material against policy summary, err := verifyMaterial(policies, material, opts.MaterialPath, opts.Debug, opts.AllowedHostnames, opts.AttestationClient, &logger) @@ -83,7 +83,7 @@ func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { return summary, nil } -func createPolicies(policyPath string, inputs map[string]string) (*v1.Policies, error) { +func createPolicies(policyPath string, inputs map[string]string, materialID string) (*v1.Policies, error) { // Check if the policy path already has a scheme (chainloop://, http://, https://, file://) ref := policyPath scheme, _ := policies.RefParts(policyPath) @@ -97,6 +97,10 @@ func createPolicies(policyPath string, inputs map[string]string) (*v1.Policies, { Policy: &v1.PolicyAttachment_Ref{Ref: ref}, With: inputs, + // Add selector to support generic policies + Selector: &v1.PolicyAttachment_MaterialSelector{ + Name: materialID, + }, }, }, Attestation: nil, @@ -170,6 +174,19 @@ func craftMaterial(materialPath, materialKind string, logger *zerolog.Logger) (* Uploader: nil, // Skip uploads } + // If no material path provided, create empty material + if materialPath == "" { + return &v12.Attestation_Material{ + Id: "empty-input", + M: &v12.Attestation_Material_String_{ + String_: &v12.Attestation_Material_KeyVal{ + Id: "empty-input", + Value: "{}", + }, + }, + }, nil + } + // Explicit kind if materialKind != "" { kind, ok := v1.CraftingSchema_Material_MaterialType_value[materialKind] diff --git a/app/cli/internal/policydevel/eval_test.go b/app/cli/internal/policydevel/eval_test.go index cc45e2fa1..3c156269c 100644 --- a/app/cli/internal/policydevel/eval_test.go +++ b/app/cli/internal/policydevel/eval_test.go @@ -188,3 +188,52 @@ func TestEvaluateSimplifiedPolicies(t *testing.T) { assert.Contains(t, result.Result.Violations[0], "too few components") }) } + +func TestEvaluateGenericPolicies(t *testing.T) { + logger := zerolog.New(os.Stderr) + + t.Run("generic policy with valid input - no violations", func(t *testing.T) { + opts := &EvalOptions{ + PolicyPath: "testdata/generic-policy.yaml", + Inputs: map[string]string{ + "environment": "staging", + "approved": "false", + }, + } + + result, err := Evaluate(opts, logger) + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.Result.Skipped) + assert.Empty(t, result.Result.SkipReasons) + assert.Empty(t, result.Result.Violations, "Expected no violations for valid staging deployment") + }) + + t.Run("generic policy with production unapproved - violation", func(t *testing.T) { + opts := &EvalOptions{ + PolicyPath: "testdata/generic-policy.yaml", + Inputs: map[string]string{ + "environment": "production", + "approved": "false", + }, + } + + result, err := Evaluate(opts, logger) + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.Result.Skipped) + assert.Len(t, result.Result.Violations, 1) + assert.Contains(t, result.Result.Violations[0], "production requires approval") + }) + + t.Run("generic policy without required input - error", func(t *testing.T) { + opts := &EvalOptions{ + PolicyPath: "testdata/generic-policy.yaml", + Inputs: map[string]string{}, + } + + _, err := Evaluate(opts, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "missing required input") + }) +} diff --git a/app/cli/internal/policydevel/testdata/generic-policy.yaml b/app/cli/internal/policydevel/testdata/generic-policy.yaml new file mode 100644 index 000000000..71507fccb --- /dev/null +++ b/app/cli/internal/policydevel/testdata/generic-policy.yaml @@ -0,0 +1,32 @@ +apiVersion: workflowcontract.chainloop.dev/v1 +kind: Policy +metadata: + name: deployment-validation + description: Validates deployment configuration for any environment +spec: + inputs: + - name: environment + description: Deployment environment (e.g., staging, production) + required: true + - name: approved + description: Whether the deployment is approved (true/false) + required: false + policies: + - embedded: | + package main + + import rego.v1 + + result := { + "violations": violations, + } + + # Convert string to boolean for approved + is_approved := input.args.approved == "true" + + # Policy checks + violations contains msg if { + input.args.environment == "production" + not is_approved + msg := "Deployment to production requires approval" + } From c8007b49c88cd8523f4b0ccdd6741ba82a4d640f Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Thu, 4 Dec 2025 09:54:47 +0100 Subject: [PATCH 2/6] lint Signed-off-by: Sylwester Piskozub --- app/cli/internal/policydevel/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index f3e96311e..21ae7b8c9 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -177,7 +177,7 @@ func craftMaterial(materialPath, materialKind string, logger *zerolog.Logger) (* // If no material path provided, create empty material if materialPath == "" { return &v12.Attestation_Material{ - Id: "empty-input", + Id: "empty-input", M: &v12.Attestation_Material_String_{ String_: &v12.Attestation_Material_KeyVal{ Id: "empty-input", From 5b46d0cc78ed3ab4c933c71ee1f4d1e4d2e5ee71 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Thu, 4 Dec 2025 12:39:18 +0100 Subject: [PATCH 3/6] expose generic evaluation in policy verifier Signed-off-by: Sylwester Piskozub --- app/cli/internal/policydevel/eval.go | 84 ++++++++++++------- app/cli/internal/policydevel/eval_test.go | 82 ++++++++++++++++++ .../testdata/kind-only-policy.yaml | 35 ++++++++ .../testdata/multi-kind-policy.yaml | 35 ++++++++ pkg/policies/policies.go | 22 +++++ 5 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 app/cli/internal/policydevel/testdata/kind-only-policy.yaml create mode 100644 app/cli/internal/policydevel/testdata/multi-kind-policy.yaml diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index 21ae7b8c9..3d783e11f 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -61,6 +61,12 @@ type EvalSummaryDebugInfo struct { } func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { + // Check if this is a generic policy evaluation + if opts.MaterialPath == "" { + return evaluateGeneric(opts, logger) + } + + // Material-based evaluation // 1. Craft material with annotations material, err := craftMaterial(opts.MaterialPath, opts.MaterialKind, &logger) if err != nil { @@ -83,6 +89,38 @@ func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { return summary, nil } +func evaluateGeneric(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { + // Create policy attachment without material selector + ref := opts.PolicyPath + scheme, _ := policies.RefParts(opts.PolicyPath) + if scheme == "" { + // Default to file:// + ref = fmt.Sprintf("file://%s", opts.PolicyPath) + } + + attachment := &v1.PolicyAttachment{ + Policy: &v1.PolicyAttachment_Ref{Ref: ref}, + With: opts.Inputs, + } + + // Create policy verifier + verifierOpts := buildPolicyVerifierOptions(opts.AllowedHostnames, opts.Debug) + pol := &v1.Policies{} + v := policies.NewPolicyVerifier(pol, opts.AttestationClient, &logger, verifierOpts...) + + // Evaluate generic policy + policyEv, err := v.EvaluateGeneric(context.Background(), attachment, nil) + if err != nil { + return nil, err + } + + if policyEv == nil { + return nil, fmt.Errorf("no execution branch matched, or all of them were ignored") + } + + return buildEvalSummary(policyEv, opts.Debug), nil +} + func createPolicies(policyPath string, inputs map[string]string, materialID string) (*v1.Policies, error) { // Check if the policy path already has a scheme (chainloop://, http://, https://, file://) ref := policyPath @@ -97,10 +135,6 @@ func createPolicies(policyPath string, inputs map[string]string, materialID stri { Policy: &v1.PolicyAttachment_Ref{Ref: ref}, With: inputs, - // Add selector to support generic policies - Selector: &v1.PolicyAttachment_MaterialSelector{ - Name: materialID, - }, }, }, Attestation: nil, @@ -108,14 +142,7 @@ func createPolicies(policyPath string, inputs map[string]string, materialID stri } func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materialPath string, debug bool, allowedHostnames []string, attestationClient controlplanev1.AttestationServiceClient, logger *zerolog.Logger) (*EvalSummary, error) { - var opts []policies.PolicyVerifierOption - if len(allowedHostnames) > 0 { - opts = append(opts, policies.WithAllowedHostnames(allowedHostnames...)) - } - - opts = append(opts, policies.WithIncludeRawData(debug)) - opts = append(opts, policies.WithEnablePrint(enablePrint)) - + opts := buildPolicyVerifierOptions(allowedHostnames, debug) v := policies.NewPolicyVerifier(pol, attestationClient, logger, opts...) policyEvs, err := v.VerifyMaterial(context.Background(), material, materialPath) if err != nil { @@ -127,8 +154,22 @@ func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materi } // Only one evaluation expected for a single policy attachment - policyEv := policyEvs[0] + return buildEvalSummary(policyEvs[0], debug), nil +} +// buildPolicyVerifierOptions creates common policy verifier options +func buildPolicyVerifierOptions(allowedHostnames []string, debug bool) []policies.PolicyVerifierOption { + var opts []policies.PolicyVerifierOption + if len(allowedHostnames) > 0 { + opts = append(opts, policies.WithAllowedHostnames(allowedHostnames...)) + } + opts = append(opts, policies.WithIncludeRawData(debug)) + opts = append(opts, policies.WithEnablePrint(enablePrint)) + return opts +} + +// buildEvalSummary converts a PolicyEvaluation to an EvalSummary +func buildEvalSummary(policyEv *v12.PolicyEvaluation, debug bool) *EvalSummary { summary := &EvalSummary{ Result: &EvalResult{ Skipped: policyEv.GetSkipped(), @@ -153,7 +194,7 @@ func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materi if rr == nil { continue } - // Take the first input found, as we only allow one material input + // Take the first input found if len(summary.DebugInfo.Inputs) == 0 && rr.Input != nil { summary.DebugInfo.Inputs = append(summary.DebugInfo.Inputs, json.RawMessage(rr.Input)) } @@ -164,7 +205,7 @@ func verifyMaterial(pol *v1.Policies, material *v12.Attestation_Material, materi } } - return summary, nil + return summary } func craftMaterial(materialPath, materialKind string, logger *zerolog.Logger) (*v12.Attestation_Material, error) { @@ -174,19 +215,6 @@ func craftMaterial(materialPath, materialKind string, logger *zerolog.Logger) (* Uploader: nil, // Skip uploads } - // If no material path provided, create empty material - if materialPath == "" { - return &v12.Attestation_Material{ - Id: "empty-input", - M: &v12.Attestation_Material_String_{ - String_: &v12.Attestation_Material_KeyVal{ - Id: "empty-input", - Value: "{}", - }, - }, - }, nil - } - // Explicit kind if materialKind != "" { kind, ok := v1.CraftingSchema_Material_MaterialType_value[materialKind] diff --git a/app/cli/internal/policydevel/eval_test.go b/app/cli/internal/policydevel/eval_test.go index 3c156269c..4b6007194 100644 --- a/app/cli/internal/policydevel/eval_test.go +++ b/app/cli/internal/policydevel/eval_test.go @@ -236,4 +236,86 @@ func TestEvaluateGenericPolicies(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "missing required input") }) + + t.Run("kind-only policy without material - should fail", func(t *testing.T) { + opts := &EvalOptions{ + PolicyPath: "testdata/kind-only-policy.yaml", + Inputs: map[string]string{ + "environment": "production", + }, + } + + _, err := Evaluate(opts, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "no execution branch matched, or all of them were ignored") + }) +} + +func TestEvaluateMultiKindPolicies(t *testing.T) { + logger := zerolog.New(os.Stderr) + + t.Run("multi-kind policy with generic evaluation - only generic script runs", func(t *testing.T) { + opts := &EvalOptions{ + PolicyPath: "testdata/multi-kind-policy.yaml", + Inputs: map[string]string{ + "environment": "production", + }, + } + + result, err := Evaluate(opts, logger) + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.Result.Skipped) + + require.Len(t, result.Result.Violations, 1) + assert.Contains(t, result.Result.Violations[0], "Generic check") + assert.NotContains(t, result.Result.Violations[0], "SBOM-specific") + }) + + t.Run("multi-kind policy with generic evaluation - staging no violations", func(t *testing.T) { + opts := &EvalOptions{ + PolicyPath: "testdata/multi-kind-policy.yaml", + Inputs: map[string]string{ + "environment": "staging", + }, + } + + result, err := Evaluate(opts, logger) + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.Result.Skipped) + + assert.Empty(t, result.Result.Violations) + }) + + t.Run("multi-kind policy with SBOM material - only SBOM-specific policy runs", func(t *testing.T) { + tempDir := t.TempDir() + + sbomContent := `{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "components": [] + }` + sbomPath := filepath.Join(tempDir, "test-sbom.json") + require.NoError(t, os.WriteFile(sbomPath, []byte(sbomContent), 0600)) + + opts := &EvalOptions{ + PolicyPath: "testdata/multi-kind-policy.yaml", + MaterialPath: sbomPath, + MaterialKind: "SBOM_CYCLONEDX_JSON", + Inputs: map[string]string{ + "environment": "production", + }, + } + + result, err := Evaluate(opts, logger) + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.Result.Skipped) + + require.Len(t, result.Result.Violations, 2) + assert.Contains(t, result.Result.Violations[0], "Generic check") + assert.Contains(t, result.Result.Violations[1], "SBOM-specific check") + }) } diff --git a/app/cli/internal/policydevel/testdata/kind-only-policy.yaml b/app/cli/internal/policydevel/testdata/kind-only-policy.yaml new file mode 100644 index 000000000..463ed3e0d --- /dev/null +++ b/app/cli/internal/policydevel/testdata/kind-only-policy.yaml @@ -0,0 +1,35 @@ +apiVersion: workflowcontract.chainloop.dev/v1 +kind: Policy +metadata: + name: kind-only-policy + description: Policy with only kind-specific scripts (no generic) +spec: + inputs: + - name: environment + description: Deployment environment + required: true + policies: + - kind: SBOM_CYCLONEDX_JSON + embedded: | + package main + import rego.v1 + + result := { + "violations": violations, + } + + violations contains "SBOM check failed" if { + true + } + - kind: CONTAINER_IMAGE + embedded: | + package main + import rego.v1 + + result := { + "violations": violations, + } + + violations contains "Container check failed" if { + true + } diff --git a/app/cli/internal/policydevel/testdata/multi-kind-policy.yaml b/app/cli/internal/policydevel/testdata/multi-kind-policy.yaml new file mode 100644 index 000000000..e47745881 --- /dev/null +++ b/app/cli/internal/policydevel/testdata/multi-kind-policy.yaml @@ -0,0 +1,35 @@ +apiVersion: workflowcontract.chainloop.dev/v1 +kind: Policy +metadata: + name: multi-kind-policy + description: Policy with multiple kinds - generic and material-specific +spec: + inputs: + - name: environment + description: Deployment environment + required: true + policies: + - embedded: | + package main + import rego.v1 + + result := { + "violations": violations, + } + + violations contains msg if { + input.args.environment == "production" + msg := "Generic check: production deployments require additional validation" + } + - kind: SBOM_CYCLONEDX_JSON + embedded: | + package main + import rego.v1 + + result := { + "violations": violations, + } + + violations contains "SBOM-specific check: this should not run for generic evaluation" if { + true + } diff --git a/pkg/policies/policies.go b/pkg/policies/policies.go index 1dfe21af9..0a9e633fb 100644 --- a/pkg/policies/policies.go +++ b/pkg/policies/policies.go @@ -159,6 +159,28 @@ func (pv *PolicyVerifier) VerifyMaterial(ctx context.Context, material *v12.Atte return result, nil } +// EvaluateGeneric evaluates a single policy attachment without material context. +// This is used for generic policies that operate purely on input arguments. +func (pv *PolicyVerifier) EvaluateGeneric(ctx context.Context, attachment *v1.PolicyAttachment, input []byte) (*v12.PolicyEvaluation, error) { + // Use empty JSON if no input provided + if len(input) == 0 { + input = []byte("{}") + } + + // Evaluate without material context + ev, err := pv.evaluatePolicyAttachment(ctx, attachment, input, + &evalOpts{ + kind: v1.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED, + name: "", + }, + ) + if err != nil { + return nil, NewPolicyError(err) + } + + return ev, nil +} + type evalOpts struct { name string kind v1.CraftingSchema_Material_MaterialType From 7b6a67d16e89dfe602d94d6b144ef1243e2b51c2 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Sun, 7 Dec 2025 15:08:42 +0100 Subject: [PATCH 4/6] lint Signed-off-by: Sylwester Piskozub --- app/cli/internal/policydevel/eval.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index eea1106dc..17c159e22 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -77,7 +77,7 @@ func Evaluate(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, error) { material.Annotations = opts.Annotations // 2. Create policy attachment - policies, err := createPolicies(opts.PolicyPath, opts.Inputs, material.GetId()) + policies, err := createPolicies(opts.PolicyPath, opts.Inputs) if err != nil { return nil, err } @@ -123,7 +123,7 @@ func evaluateGeneric(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, er return buildEvalSummary(policyEv, opts.Debug), nil } -func createPolicies(policyPath string, inputs map[string]string, materialID string) (*v1.Policies, error) { +func createPolicies(policyPath string, inputs map[string]string) (*v1.Policies, error) { // Check if the policy path already has a scheme (chainloop://, http://, https://, file://) ref := policyPath scheme, _ := policies.RefParts(policyPath) From 3cc02c375b8c54a776c8ef3adc73373b0768f760 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Sun, 7 Dec 2025 15:13:23 +0100 Subject: [PATCH 5/6] update comment Signed-off-by: Sylwester Piskozub --- pkg/policies/policies.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/policies/policies.go b/pkg/policies/policies.go index 0a9e633fb..7a13c9ef8 100644 --- a/pkg/policies/policies.go +++ b/pkg/policies/policies.go @@ -159,13 +159,10 @@ func (pv *PolicyVerifier) VerifyMaterial(ctx context.Context, material *v12.Atte return result, nil } -// EvaluateGeneric evaluates a single policy attachment without material context. -// This is used for generic policies that operate purely on input arguments. -func (pv *PolicyVerifier) EvaluateGeneric(ctx context.Context, attachment *v1.PolicyAttachment, input []byte) (*v12.PolicyEvaluation, error) { - // Use empty JSON if no input provided - if len(input) == 0 { - input = []byte("{}") - } +// EvaluateGeneric evaluates a single policy attachment. +func (pv *PolicyVerifier) EvaluateGeneric(ctx context.Context, attachment *v1.PolicyAttachment) (*v12.PolicyEvaluation, error) { + // Use empty JSON as material input + input := []byte("{}") // Evaluate without material context ev, err := pv.evaluatePolicyAttachment(ctx, attachment, input, From 10bff0ef59da9cf33afe0e04ca58a5bf3a3f0ead Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Sun, 7 Dec 2025 15:30:22 +0100 Subject: [PATCH 6/6] fix func call Signed-off-by: Sylwester Piskozub --- app/cli/internal/policydevel/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cli/internal/policydevel/eval.go b/app/cli/internal/policydevel/eval.go index 17c159e22..c34f49713 100644 --- a/app/cli/internal/policydevel/eval.go +++ b/app/cli/internal/policydevel/eval.go @@ -111,7 +111,7 @@ func evaluateGeneric(opts *EvalOptions, logger zerolog.Logger) (*EvalSummary, er v := policies.NewPolicyVerifier(pol, opts.AttestationClient, &logger, verifierOpts...) // Evaluate generic policy - policyEv, err := v.EvaluateGeneric(context.Background(), attachment, nil) + policyEv, err := v.EvaluateGeneric(context.Background(), attachment) if err != nil { return nil, err }