From fca38e101d57cddb80e584a6c2754bd9f474f02a Mon Sep 17 00:00:00 2001 From: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com> Date: Mon, 23 Mar 2026 04:03:32 +0530 Subject: [PATCH 1/6] feat: add gate support for policy groups Signed-off-by: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com> --- .../workflowcontract/v1/crafting_schema.ts | 22 ++++- ...t.v1.PolicyGroupAttachment.jsonschema.json | 4 + ...tract.v1.PolicyGroupAttachment.schema.json | 4 + .../workflowcontract/v1/crafting_schema.pb.go | 23 ++++- .../workflowcontract/v1/crafting_schema.proto | 5 + pkg/policies/policy_groups.go | 16 +++- pkg/policies/policy_groups_test.go | 96 +++++++++++++++++++ 7 files changed, 163 insertions(+), 7 deletions(-) diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index eb9266e35..6e4f85592 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -627,6 +627,13 @@ export interface PolicyGroupAttachment { with: { [key: string]: string }; /** policy names to skip (matched against metadata.name) */ skip: string[]; + /** + * Controls whether policy violations act as a gate for every policy in the group. + * - true: policy violations are blocking for all policies in this group + * - false: policy violations are non-blocking for all policies in this group + * - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate + */ + gate?: boolean | undefined; } export interface PolicyGroupAttachment_WithEntry { @@ -2448,7 +2455,7 @@ export const AutoMatch = { }; function createBasePolicyGroupAttachment(): PolicyGroupAttachment { - return { ref: "", with: {}, skip: [] }; + return { ref: "", with: {}, skip: [], gate: undefined }; } export const PolicyGroupAttachment = { @@ -2462,6 +2469,9 @@ export const PolicyGroupAttachment = { for (const v of message.skip) { writer.uint32(26).string(v!); } + if (message.gate !== undefined) { + writer.uint32(32).bool(message.gate); + } return writer; }, @@ -2496,6 +2506,13 @@ export const PolicyGroupAttachment = { message.skip.push(reader.string()); continue; + case 4: + if (tag !== 32) { + break; + } + + message.gate = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -2515,6 +2532,7 @@ export const PolicyGroupAttachment = { }, {}) : {}, skip: Array.isArray(object?.skip) ? object.skip.map((e: any) => String(e)) : [], + gate: isSet(object.gate) ? Boolean(object.gate) : undefined, }; }, @@ -2532,6 +2550,7 @@ export const PolicyGroupAttachment = { } else { obj.skip = []; } + message.gate !== undefined && (obj.gate = message.gate); return obj; }, @@ -2549,6 +2568,7 @@ export const PolicyGroupAttachment = { return acc; }, {}); message.skip = object.skip?.map((e) => e) || []; + message.gate = object.gate ?? undefined; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json index dff01af36..de44dc873 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json @@ -4,6 +4,10 @@ "additionalProperties": false, "description": "Represents a group attachment in a contract", "properties": { + "gate": { + "description": "Controls whether policy violations act as a gate for every policy in the group.\n - true: policy violations are blocking for all policies in this group\n - false: policy violations are non-blocking for all policies in this group\n - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate", + "type": "boolean" + }, "ref": { "description": "Group reference, it might be an URL or a provider reference", "minLength": 1, diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json index 8bebd1633..e797466e8 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json @@ -4,6 +4,10 @@ "additionalProperties": false, "description": "Represents a group attachment in a contract", "properties": { + "gate": { + "description": "Controls whether policy violations act as a gate for every policy in the group.\n - true: policy violations are blocking for all policies in this group\n - false: policy violations are non-blocking for all policies in this group\n - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate", + "type": "boolean" + }, "ref": { "description": "Group reference, it might be an URL or a provider reference", "minLength": 1, diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index 21d9dc212..7aa85d60d 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -1408,7 +1408,12 @@ type PolicyGroupAttachment struct { // group arguments With map[string]string `protobuf:"bytes,2,rep,name=with,proto3" json:"with,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // policy names to skip (matched against metadata.name) - Skip []string `protobuf:"bytes,3,rep,name=skip,proto3" json:"skip,omitempty"` + Skip []string `protobuf:"bytes,3,rep,name=skip,proto3" json:"skip,omitempty"` + // Controls whether policy violations act as a gate for every policy in the group. + // - true: policy violations are blocking for all policies in this group + // - false: policy violations are non-blocking for all policies in this group + // - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate + Gate *bool `protobuf:"varint,4,opt,name=gate,proto3,oneof" json:"gate,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1464,6 +1469,13 @@ func (x *PolicyGroupAttachment) GetSkip() []string { return nil } +func (x *PolicyGroupAttachment) GetGate() bool { + if x != nil && x.Gate != nil { + return *x.Gate + } + return false +} + // Represents a group or policies type PolicyGroup struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2058,14 +2070,16 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\x04path\x18\x01 \x01(\tB\x02\x18\x01H\x00R\x04path\x12\x1c\n" + "\bembedded\x18\x02 \x01(\tH\x00R\bembedded\x12\x12\n" + "\x03ref\x18\x03 \x01(\tH\x00R\x03refB\x0f\n" + - "\x06source\x12\x05\xbaH\x02\b\x01\"\xc9\x01\n" + + "\x06source\x12\x05\xbaH\x02\b\x01\"\xeb\x01\n" + "\x15PolicyGroupAttachment\x12\x19\n" + "\x03ref\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x03ref\x12H\n" + "\x04with\x18\x02 \x03(\v24.workflowcontract.v1.PolicyGroupAttachment.WithEntryR\x04with\x12\x12\n" + - "\x04skip\x18\x03 \x03(\tR\x04skip\x1a7\n" + + "\x04skip\x18\x03 \x03(\tR\x04skip\x12\x17\n" + + "\x04gate\x18\x04 \x01(\bH\x00R\x04gate\x88\x01\x01\x1a7\n" + "\tWithEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc7\a\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\a\n" + + "\x05_gate\"\xc7\a\n" + "\vPolicyGroup\x12[\n" + "\vapi_version\x18\x01 \x01(\tB:\xbaH7r5R\x10chainloop.dev/v1R!workflowcontract.chainloop.dev/v1R\n" + "apiVersion\x12&\n" + @@ -2202,6 +2216,7 @@ func file_workflowcontract_v1_crafting_schema_proto_init() { (*AutoMatch_Embedded)(nil), (*AutoMatch_Ref)(nil), } + file_workflowcontract_v1_crafting_schema_proto_msgTypes[12].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index 707a697b0..198110ef1 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -389,6 +389,11 @@ message PolicyGroupAttachment { map with = 2; // policy names to skip (matched against metadata.name) repeated string skip = 3; + // Controls whether policy violations act as a gate for every policy in the group. + // - true: policy violations are blocking for all policies in this group + // - false: policy violations are non-blocking for all policies in this group + // - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate + optional bool gate = 4; } // Represents a group or policies diff --git a/pkg/policies/policy_groups.go b/pkg/policies/policy_groups.go index d9a1bb220..e35ececac 100644 --- a/pkg/policies/policy_groups.go +++ b/pkg/policies/policy_groups.go @@ -91,7 +91,7 @@ func (pgv *PolicyGroupVerifier) VerifyMaterial(ctx context.Context, material *ap return nil, NewPolicyError(err) } - ev, err := pgv.evaluatePolicyAttachment(ctx, policyAtt, subject, + ev, err := pgv.evaluatePolicyAttachment(ctx, inheritGroupGate(policyAtt, groupAtt), subject, &evalOpts{kind: material.MaterialType, name: material.GetId(), bindings: groupArgs}, ) if err != nil { @@ -154,7 +154,7 @@ func (pgv *PolicyGroupVerifier) VerifyStatement(ctx context.Context, statement * return nil, NewPolicyError(err) } - ev, err := pgv.evaluatePolicyAttachment(ctx, attachment, material, + ev, err := pgv.evaluatePolicyAttachment(ctx, inheritGroupGate(attachment, groupAtt), material, &evalOpts{kind: v1.CraftingSchema_Material_ATTESTATION, bindings: groupArgs}, ) if err != nil { @@ -181,6 +181,18 @@ func (pgv *PolicyGroupVerifier) VerifyStatement(ctx context.Context, statement * return result, nil } +func inheritGroupGate(policyAtt *v1.PolicyAttachment, groupAtt *v1.PolicyGroupAttachment) *v1.PolicyAttachment { + if policyAtt == nil || groupAtt == nil || groupAtt.Gate == nil { + return policyAtt + } + + cloned := *policyAtt + groupGate := groupAtt.GetGate() + cloned.Gate = &groupGate + + return &cloned +} + type LoadPolicyGroupOptions struct { Client v13.AttestationServiceClient Logger *zerolog.Logger diff --git a/pkg/policies/policy_groups_test.go b/pkg/policies/policy_groups_test.go index fa7625f55..68c86168a 100644 --- a/pkg/policies/policy_groups_test.go +++ b/pkg/policies/policy_groups_test.go @@ -40,6 +40,10 @@ func TestPolicyGroups(t *testing.T) { suite.Run(t, new(groupsTestSuite)) } +func boolPtr(b bool) *bool { + return &b +} + func (s *groupsTestSuite) TestLoadGroupSpec() { var cases = []struct { name string @@ -647,3 +651,95 @@ func (s *groupsTestSuite) TestAttestationPhaseFilteringInGroups() { }) } } + +func (s *groupsTestSuite) TestInheritGroupGate() { + policyGate := false + groupGate := true + + cases := []struct { + name string + policyAtt *v1.PolicyAttachment + groupAtt *v1.PolicyGroupAttachment + expectedGate *bool + }{ + { + name: "unset group gate leaves policy unchanged", + policyAtt: &v1.PolicyAttachment{}, + groupAtt: &v1.PolicyGroupAttachment{}, + expectedGate: nil, + }, + { + name: "group gate sets policy gate when policy gate unset", + policyAtt: &v1.PolicyAttachment{}, + groupAtt: &v1.PolicyGroupAttachment{Gate: &groupGate}, + expectedGate: boolPtr(true), + }, + { + name: "group gate overrides policy gate", + policyAtt: &v1.PolicyAttachment{ + Gate: &policyGate, + }, + groupAtt: &v1.PolicyGroupAttachment{Gate: &groupGate}, + expectedGate: boolPtr(true), + }, + } + + for _, tc := range cases { + s.Run(tc.name, func() { + got := inheritGroupGate(tc.policyAtt, tc.groupAtt) + + if tc.expectedGate == nil { + s.Nil(got.Gate) + return + } + + s.Require().NotNil(got.Gate) + s.Equal(*tc.expectedGate, got.GetGate()) + }) + } +} + +func (s *groupsTestSuite) TestVerifyMaterialInheritsGroupGate() { + schema := &v1.CraftingSchema{ + PolicyGroups: []*v1.PolicyGroupAttachment{ + { + Ref: "file://testdata/policy_group_multikind.yaml", + Gate: boolPtr(true), + }, + }, + } + + material := &api.Attestation_Material{ + M: &api.Attestation_Material_Artifact_{Artifact: &api.Attestation_Material_Artifact{ + Content: []byte(`{"specVersion": "1.4"}`), + }}, + MaterialType: v1.CraftingSchema_Material_OPENVEX, + InlineCas: true, + } + + verifier := NewPolicyGroupVerifier(schema.GetPolicyGroups(), nil, nil, &s.logger, WithDefaultGate(false)) + evs, err := verifier.VerifyMaterial(context.Background(), material, "") + + s.Require().NoError(err) + s.Len(evs, 1) + s.True(evs[0].GetGate()) +} + +func (s *groupsTestSuite) TestVerifyStatementInheritsGroupGate() { + schema := &v1.CraftingSchema{ + PolicyGroups: []*v1.PolicyGroupAttachment{ + { + Ref: "file://testdata/policy_group.yaml", + Gate: boolPtr(true), + }, + }, + } + + verifier := NewPolicyGroupVerifier(schema.GetPolicyGroups(), nil, nil, &s.logger, WithDefaultGate(false)) + statement := loadStatement("testdata/statement.json", &s.Suite) + + evs, err := verifier.VerifyStatement(context.Background(), statement) + s.Require().NoError(err) + s.Len(evs, 1) + s.True(evs[0].GetGate()) +} From 1291733b75ba1fcddd2537b8afc9b3f05056cec9 Mon Sep 17 00:00:00 2001 From: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com> Date: Mon, 23 Mar 2026 04:14:27 +0530 Subject: [PATCH 2/6] fix: regenerate Signed-off-by: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com> --- .../gen/frontend/workflowcontract/v1/crafting_schema.ts | 8 ++++---- ...kflowcontract.v1.PolicyGroupAttachment.jsonschema.json | 2 +- .../workflowcontract.v1.PolicyGroupAttachment.schema.json | 2 +- .../api/workflowcontract/v1/crafting_schema.pb.go | 8 ++++---- .../api/workflowcontract/v1/crafting_schema.proto | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index 6e4f85592..04b8e1d2d 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -628,10 +628,10 @@ export interface PolicyGroupAttachment { /** policy names to skip (matched against metadata.name) */ skip: string[]; /** - * Controls whether policy violations act as a gate for every policy in the group. - * - true: policy violations are blocking for all policies in this group - * - false: policy violations are non-blocking for all policies in this group - * - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate + * Controls whether policy violations act as a gate for this group. + * - true: policy violations are blocking for this policy group + * - false: policy violations are non-blocking for this policy group + * - unset: inherit organization-level default behavior */ gate?: boolean | undefined; } diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json index de44dc873..8fd71a4dd 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.jsonschema.json @@ -5,7 +5,7 @@ "description": "Represents a group attachment in a contract", "properties": { "gate": { - "description": "Controls whether policy violations act as a gate for every policy in the group.\n - true: policy violations are blocking for all policies in this group\n - false: policy violations are non-blocking for all policies in this group\n - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate", + "description": "Controls whether policy violations act as a gate for this group.\n - true: policy violations are blocking for this policy group\n - false: policy violations are non-blocking for this policy group\n - unset: inherit organization-level default behavior", "type": "boolean" }, "ref": { diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json index e797466e8..bed2a06f2 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroupAttachment.schema.json @@ -5,7 +5,7 @@ "description": "Represents a group attachment in a contract", "properties": { "gate": { - "description": "Controls whether policy violations act as a gate for every policy in the group.\n - true: policy violations are blocking for all policies in this group\n - false: policy violations are non-blocking for all policies in this group\n - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate", + "description": "Controls whether policy violations act as a gate for this group.\n - true: policy violations are blocking for this policy group\n - false: policy violations are non-blocking for this policy group\n - unset: inherit organization-level default behavior", "type": "boolean" }, "ref": { diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index 7aa85d60d..ff6c3c819 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -1409,10 +1409,10 @@ type PolicyGroupAttachment struct { With map[string]string `protobuf:"bytes,2,rep,name=with,proto3" json:"with,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // policy names to skip (matched against metadata.name) Skip []string `protobuf:"bytes,3,rep,name=skip,proto3" json:"skip,omitempty"` - // Controls whether policy violations act as a gate for every policy in the group. - // - true: policy violations are blocking for all policies in this group - // - false: policy violations are non-blocking for all policies in this group - // - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate + // Controls whether policy violations act as a gate for this group. + // - true: policy violations are blocking for this policy group + // - false: policy violations are non-blocking for this policy group + // - unset: inherit organization-level default behavior Gate *bool `protobuf:"varint,4,opt,name=gate,proto3,oneof" json:"gate,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index 198110ef1..2f54c33ed 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -389,10 +389,10 @@ message PolicyGroupAttachment { map with = 2; // policy names to skip (matched against metadata.name) repeated string skip = 3; - // Controls whether policy violations act as a gate for every policy in the group. - // - true: policy violations are blocking for all policies in this group - // - false: policy violations are non-blocking for all policies in this group - // - unset: inherit organization-level default behavior, unless a policy attachment sets its own gate + // Controls whether policy violations act as a gate for this group. + // - true: policy violations are blocking for this policy group + // - false: policy violations are non-blocking for this policy group + // - unset: inherit organization-level default behavior optional bool gate = 4; } From f8369c11df189ebf9a34f20abbd17ff6fb695622 Mon Sep 17 00:00:00 2001 From: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com> Date: Mon, 23 Mar 2026 04:23:26 +0530 Subject: [PATCH 3/6] fix: linter Signed-off-by: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com> --- pkg/policies/policy_groups.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/policies/policy_groups.go b/pkg/policies/policy_groups.go index e35ececac..14ffd2f58 100644 --- a/pkg/policies/policy_groups.go +++ b/pkg/policies/policy_groups.go @@ -28,6 +28,7 @@ import ( intoto "github.com/in-toto/attestation/go/v1" "github.com/rs/zerolog" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" ) type PolicyGroupVerifier struct { @@ -186,11 +187,11 @@ func inheritGroupGate(policyAtt *v1.PolicyAttachment, groupAtt *v1.PolicyGroupAt return policyAtt } - cloned := *policyAtt + cloned := proto.Clone(policyAtt).(*v1.PolicyAttachment) groupGate := groupAtt.GetGate() cloned.Gate = &groupGate - return &cloned + return cloned } type LoadPolicyGroupOptions struct { From ae9d7d0267b1ba8cdbcda3088e1e083b92104348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Insaurralde?= Date: Mon, 23 Mar 2026 21:15:50 -0300 Subject: [PATCH 4/6] fix: update license header year in policy_groups.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matías Insaurralde --- pkg/policies/policy_groups.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/policies/policy_groups.go b/pkg/policies/policy_groups.go index 14ffd2f58..d11654402 100644 --- a/pkg/policies/policy_groups.go +++ b/pkg/policies/policy_groups.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From e92d1c9d73998970080a3196f644e6505eb19a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Insaurralde?= Date: Mon, 23 Mar 2026 21:17:00 -0300 Subject: [PATCH 5/6] test: add nil policyAtt case to TestInheritGroupGate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matías Insaurralde --- pkg/policies/policy_groups_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/policies/policy_groups_test.go b/pkg/policies/policy_groups_test.go index 68c86168a..ee9104984 100644 --- a/pkg/policies/policy_groups_test.go +++ b/pkg/policies/policy_groups_test.go @@ -660,8 +660,15 @@ func (s *groupsTestSuite) TestInheritGroupGate() { name string policyAtt *v1.PolicyAttachment groupAtt *v1.PolicyGroupAttachment + expectedNil bool expectedGate *bool }{ + { + name: "nil policy attachment is returned as-is", + policyAtt: nil, + groupAtt: &v1.PolicyGroupAttachment{Gate: &groupGate}, + expectedNil: true, + }, { name: "unset group gate leaves policy unchanged", policyAtt: &v1.PolicyAttachment{}, @@ -688,6 +695,11 @@ func (s *groupsTestSuite) TestInheritGroupGate() { s.Run(tc.name, func() { got := inheritGroupGate(tc.policyAtt, tc.groupAtt) + if tc.expectedNil { + s.Nil(got) + return + } + if tc.expectedGate == nil { s.Nil(got.Gate) return From aade9546aaf3a98802de713ef54425749952a6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Insaurralde?= Date: Mon, 23 Mar 2026 21:19:03 -0300 Subject: [PATCH 6/6] refactor: rename inheritGroupGate to applyGroupGate for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function overrides the policy gate with the group-level gate rather than inheriting a default, so applyGroupGate better reflects the actual behavior. Signed-off-by: Matías Insaurralde --- pkg/policies/policy_groups.go | 6 +++--- pkg/policies/policy_groups_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/policies/policy_groups.go b/pkg/policies/policy_groups.go index d11654402..f77bdd97f 100644 --- a/pkg/policies/policy_groups.go +++ b/pkg/policies/policy_groups.go @@ -92,7 +92,7 @@ func (pgv *PolicyGroupVerifier) VerifyMaterial(ctx context.Context, material *ap return nil, NewPolicyError(err) } - ev, err := pgv.evaluatePolicyAttachment(ctx, inheritGroupGate(policyAtt, groupAtt), subject, + ev, err := pgv.evaluatePolicyAttachment(ctx, applyGroupGate(policyAtt, groupAtt), subject, &evalOpts{kind: material.MaterialType, name: material.GetId(), bindings: groupArgs}, ) if err != nil { @@ -155,7 +155,7 @@ func (pgv *PolicyGroupVerifier) VerifyStatement(ctx context.Context, statement * return nil, NewPolicyError(err) } - ev, err := pgv.evaluatePolicyAttachment(ctx, inheritGroupGate(attachment, groupAtt), material, + ev, err := pgv.evaluatePolicyAttachment(ctx, applyGroupGate(attachment, groupAtt), material, &evalOpts{kind: v1.CraftingSchema_Material_ATTESTATION, bindings: groupArgs}, ) if err != nil { @@ -182,7 +182,7 @@ func (pgv *PolicyGroupVerifier) VerifyStatement(ctx context.Context, statement * return result, nil } -func inheritGroupGate(policyAtt *v1.PolicyAttachment, groupAtt *v1.PolicyGroupAttachment) *v1.PolicyAttachment { +func applyGroupGate(policyAtt *v1.PolicyAttachment, groupAtt *v1.PolicyGroupAttachment) *v1.PolicyAttachment { if policyAtt == nil || groupAtt == nil || groupAtt.Gate == nil { return policyAtt } diff --git a/pkg/policies/policy_groups_test.go b/pkg/policies/policy_groups_test.go index ee9104984..887d294d4 100644 --- a/pkg/policies/policy_groups_test.go +++ b/pkg/policies/policy_groups_test.go @@ -652,7 +652,7 @@ func (s *groupsTestSuite) TestAttestationPhaseFilteringInGroups() { } } -func (s *groupsTestSuite) TestInheritGroupGate() { +func (s *groupsTestSuite) TestApplyGroupGate() { policyGate := false groupGate := true @@ -693,7 +693,7 @@ func (s *groupsTestSuite) TestInheritGroupGate() { for _, tc := range cases { s.Run(tc.name, func() { - got := inheritGroupGate(tc.policyAtt, tc.groupAtt) + got := applyGroupGate(tc.policyAtt, tc.groupAtt) if tc.expectedNil { s.Nil(got)