From 54ba89e92c70667267c4a0f5ab489c7b0b921727 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sat, 18 Apr 2026 12:36:13 +0200 Subject: [PATCH 1/4] feat(api): add canonical PolicyStatusSummary to workflow run list and describe Adds a canonical, server-computed PolicyStatus enum (NOT_APPLICABLE, PASSED, SKIPPED, WARNING, BLOCKED, BYPASSED) and a PolicyStatusSummary (status + total/passed/skipped/violated counters) to both WorkflowRunItem and AttestationItem.PolicyEvaluationStatus. A single derivation helper feeds both list and describe paths. The summary is materialized on the workflow_run row at attestation-ingest time so the list handler stays an index scan. Also introduces a PolicyStatusFilter aligned 1:1 with the enum; the coarse PolicyViolationsFilter and the redundant evaluations_count / violations_count fields on PolicyEvaluationStatus are marked deprecated. Closes PFM-5616. Signed-off-by: Miguel Martinez Trivino --- .../controlplane/v1/response_messages.pb.go | 679 +++++++++++++----- .../controlplane/v1/response_messages.proto | 70 +- .../api/controlplane/v1/workflow_run.pb.go | 111 +-- .../api/controlplane/v1/workflow_run.proto | 6 +- .../controlplane/v1/response_messages.ts | 337 ++++++++- .../frontend/controlplane/v1/workflow_run.ts | 27 +- ...tem.PolicyEvaluationStatus.jsonschema.json | 12 +- ...ionItem.PolicyEvaluationStatus.schema.json | 12 +- ...ane.v1.PolicyStatusSummary.jsonschema.json | 56 ++ ...olplane.v1.PolicyStatusSummary.schema.json | 56 ++ ...olplane.v1.WorkflowRunItem.jsonschema.json | 8 + ...ontrolplane.v1.WorkflowRunItem.schema.json | 8 + ...kflowRunServiceListRequest.jsonschema.json | 50 +- ....WorkflowRunServiceListRequest.schema.json | 50 +- .../internal/service/attestation.go | 2 + .../internal/service/workflowrun.go | 67 +- .../pkg/biz/mocks/WorkflowRunRepo.go | 35 +- app/controlplane/pkg/biz/workflowrun.go | 20 +- .../ent/migrate/migrations/20260418100730.sql | 6 + .../pkg/data/ent/migrate/migrations/atlas.sum | 3 +- .../pkg/data/ent/migrate/schema.go | 24 +- app/controlplane/pkg/data/ent/mutation.go | 567 ++++++++++++++- .../pkg/data/ent/schema/workflowrun.go | 14 +- app/controlplane/pkg/data/ent/workflowrun.go | 74 +- .../pkg/data/ent/workflowrun/where.go | 250 +++++++ .../pkg/data/ent/workflowrun/workflowrun.go | 67 ++ .../pkg/data/ent/workflowrun_create.go | 475 ++++++++++++ .../pkg/data/ent/workflowrun_update.go | 350 +++++++++ app/controlplane/pkg/data/workflowrun.go | 112 ++- .../renderer/chainloop/chainloop.go | 4 + .../renderer/chainloop/policy_status.go | 87 +++ .../renderer/chainloop/policy_status_test.go | 210 ++++++ pkg/attestation/renderer/chainloop/v02.go | 17 +- .../renderer/chainloop/v02_test.go | 4 + 34 files changed, 3523 insertions(+), 347 deletions(-) create mode 100644 app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json create mode 100644 app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json create mode 100644 app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql create mode 100644 pkg/attestation/renderer/chainloop/policy_status.go create mode 100644 pkg/attestation/renderer/chainloop/policy_status_test.go diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index fa8b0b7c5..5f2d651b5 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -98,6 +98,11 @@ func (RunStatus) EnumDescriptor() ([]byte, []int) { return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{0} } +// Deprecated: use PolicyStatusFilter which aligns 1:1 with PolicyStatus and +// lets callers distinguish warning/blocked/bypassed from the coarse with/ +// without-violations split. +// +// Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. type PolicyViolationsFilter int32 const ( @@ -147,6 +152,140 @@ func (PolicyViolationsFilter) EnumDescriptor() ([]byte, []int) { return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{1} } +// Canonical, server-computed categorical policy outcome for an attestation. +// Collapses the raw enforcement/bypass/violation signals on +// AttestationItem.PolicyEvaluationStatus into a single flat value so that +// list and describe surfaces can render a consistent badge without +// re-deriving. +type PolicyStatus int32 + +const ( + PolicyStatus_POLICY_STATUS_UNSPECIFIED PolicyStatus = 0 + // No policies were evaluated on this run + PolicyStatus_POLICY_STATUS_NOT_APPLICABLE PolicyStatus = 1 + // Policies ran with no violations and no skips + PolicyStatus_POLICY_STATUS_PASSED PolicyStatus = 2 + // No violations but at least one evaluation was skipped + PolicyStatus_POLICY_STATUS_SKIPPED PolicyStatus = 3 + // Has violations but enforcement is advisory — run succeeded + PolicyStatus_POLICY_STATUS_WARNING PolicyStatus = 4 + // Has gated violations or enforced strategy with violations; not bypassed + PolicyStatus_POLICY_STATUS_BLOCKED PolicyStatus = 5 + // Enforcement would have blocked the run but was bypassed + PolicyStatus_POLICY_STATUS_BYPASSED PolicyStatus = 6 +) + +// Enum value maps for PolicyStatus. +var ( + PolicyStatus_name = map[int32]string{ + 0: "POLICY_STATUS_UNSPECIFIED", + 1: "POLICY_STATUS_NOT_APPLICABLE", + 2: "POLICY_STATUS_PASSED", + 3: "POLICY_STATUS_SKIPPED", + 4: "POLICY_STATUS_WARNING", + 5: "POLICY_STATUS_BLOCKED", + 6: "POLICY_STATUS_BYPASSED", + } + PolicyStatus_value = map[string]int32{ + "POLICY_STATUS_UNSPECIFIED": 0, + "POLICY_STATUS_NOT_APPLICABLE": 1, + "POLICY_STATUS_PASSED": 2, + "POLICY_STATUS_SKIPPED": 3, + "POLICY_STATUS_WARNING": 4, + "POLICY_STATUS_BLOCKED": 5, + "POLICY_STATUS_BYPASSED": 6, + } +) + +func (x PolicyStatus) Enum() *PolicyStatus { + p := new(PolicyStatus) + *p = x + return p +} + +func (x PolicyStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PolicyStatus) Descriptor() protoreflect.EnumDescriptor { + return file_controlplane_v1_response_messages_proto_enumTypes[2].Descriptor() +} + +func (PolicyStatus) Type() protoreflect.EnumType { + return &file_controlplane_v1_response_messages_proto_enumTypes[2] +} + +func (x PolicyStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PolicyStatus.Descriptor instead. +func (PolicyStatus) EnumDescriptor() ([]byte, []int) { + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{2} +} + +// Server-side filter aligned 1:1 with PolicyStatus values. +type PolicyStatusFilter int32 + +const ( + PolicyStatusFilter_POLICY_STATUS_FILTER_UNSPECIFIED PolicyStatusFilter = 0 + PolicyStatusFilter_POLICY_STATUS_FILTER_NOT_APPLICABLE PolicyStatusFilter = 1 + PolicyStatusFilter_POLICY_STATUS_FILTER_PASSED PolicyStatusFilter = 2 + PolicyStatusFilter_POLICY_STATUS_FILTER_SKIPPED PolicyStatusFilter = 3 + PolicyStatusFilter_POLICY_STATUS_FILTER_WARNING PolicyStatusFilter = 4 + PolicyStatusFilter_POLICY_STATUS_FILTER_BLOCKED PolicyStatusFilter = 5 + PolicyStatusFilter_POLICY_STATUS_FILTER_BYPASSED PolicyStatusFilter = 6 +) + +// Enum value maps for PolicyStatusFilter. +var ( + PolicyStatusFilter_name = map[int32]string{ + 0: "POLICY_STATUS_FILTER_UNSPECIFIED", + 1: "POLICY_STATUS_FILTER_NOT_APPLICABLE", + 2: "POLICY_STATUS_FILTER_PASSED", + 3: "POLICY_STATUS_FILTER_SKIPPED", + 4: "POLICY_STATUS_FILTER_WARNING", + 5: "POLICY_STATUS_FILTER_BLOCKED", + 6: "POLICY_STATUS_FILTER_BYPASSED", + } + PolicyStatusFilter_value = map[string]int32{ + "POLICY_STATUS_FILTER_UNSPECIFIED": 0, + "POLICY_STATUS_FILTER_NOT_APPLICABLE": 1, + "POLICY_STATUS_FILTER_PASSED": 2, + "POLICY_STATUS_FILTER_SKIPPED": 3, + "POLICY_STATUS_FILTER_WARNING": 4, + "POLICY_STATUS_FILTER_BLOCKED": 5, + "POLICY_STATUS_FILTER_BYPASSED": 6, + } +) + +func (x PolicyStatusFilter) Enum() *PolicyStatusFilter { + p := new(PolicyStatusFilter) + *p = x + return p +} + +func (x PolicyStatusFilter) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PolicyStatusFilter) Descriptor() protoreflect.EnumDescriptor { + return file_controlplane_v1_response_messages_proto_enumTypes[3].Descriptor() +} + +func (PolicyStatusFilter) Type() protoreflect.EnumType { + return &file_controlplane_v1_response_messages_proto_enumTypes[3] +} + +func (x PolicyStatusFilter) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PolicyStatusFilter.Descriptor instead. +func (PolicyStatusFilter) EnumDescriptor() ([]byte, []int) { + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3} +} + type MembershipRole int32 const ( @@ -189,11 +328,11 @@ func (x MembershipRole) String() string { } func (MembershipRole) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[2].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[4].Descriptor() } func (MembershipRole) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[2] + return &file_controlplane_v1_response_messages_proto_enumTypes[4] } func (x MembershipRole) Number() protoreflect.EnumNumber { @@ -202,7 +341,7 @@ func (x MembershipRole) Number() protoreflect.EnumNumber { // Deprecated: Use MembershipRole.Descriptor instead. func (MembershipRole) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{2} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4} } type AllowListError int32 @@ -235,11 +374,11 @@ func (x AllowListError) String() string { } func (AllowListError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[3].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[5].Descriptor() } func (AllowListError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[3] + return &file_controlplane_v1_response_messages_proto_enumTypes[5] } func (x AllowListError) Number() protoreflect.EnumNumber { @@ -248,7 +387,7 @@ func (x AllowListError) Number() protoreflect.EnumNumber { // Deprecated: Use AllowListError.Descriptor instead. func (AllowListError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} } type FederatedAuthError int32 @@ -281,11 +420,11 @@ func (x FederatedAuthError) String() string { } func (FederatedAuthError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[4].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[6].Descriptor() } func (FederatedAuthError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[4] + return &file_controlplane_v1_response_messages_proto_enumTypes[6] } func (x FederatedAuthError) Number() protoreflect.EnumNumber { @@ -294,7 +433,7 @@ func (x FederatedAuthError) Number() protoreflect.EnumNumber { // Deprecated: Use FederatedAuthError.Descriptor instead. func (FederatedAuthError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} } type UserWithNoMembershipError int32 @@ -327,11 +466,11 @@ func (x UserWithNoMembershipError) String() string { } func (UserWithNoMembershipError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[5].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[7].Descriptor() } func (UserWithNoMembershipError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[5] + return &file_controlplane_v1_response_messages_proto_enumTypes[7] } func (x UserWithNoMembershipError) Number() protoreflect.EnumNumber { @@ -340,7 +479,7 @@ func (x UserWithNoMembershipError) Number() protoreflect.EnumNumber { // Deprecated: Use UserWithNoMembershipError.Descriptor instead. func (UserWithNoMembershipError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} } type UserNotMemberOfOrgError int32 @@ -373,11 +512,11 @@ func (x UserNotMemberOfOrgError) String() string { } func (UserNotMemberOfOrgError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[6].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[8].Descriptor() } func (UserNotMemberOfOrgError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[6] + return &file_controlplane_v1_response_messages_proto_enumTypes[8] } func (x UserNotMemberOfOrgError) Number() protoreflect.EnumNumber { @@ -386,7 +525,7 @@ func (x UserNotMemberOfOrgError) Number() protoreflect.EnumNumber { // Deprecated: Use UserNotMemberOfOrgError.Descriptor instead. func (UserNotMemberOfOrgError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} } type WorkflowContractVersionItem_RawBody_Format int32 @@ -425,11 +564,11 @@ func (x WorkflowContractVersionItem_RawBody_Format) String() string { } func (WorkflowContractVersionItem_RawBody_Format) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[7].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[9].Descriptor() } func (WorkflowContractVersionItem_RawBody_Format) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[7] + return &file_controlplane_v1_response_messages_proto_enumTypes[9] } func (x WorkflowContractVersionItem_RawBody_Format) Number() protoreflect.EnumNumber { @@ -438,7 +577,7 @@ func (x WorkflowContractVersionItem_RawBody_Format) Number() protoreflect.EnumNu // Deprecated: Use WorkflowContractVersionItem_RawBody_Format.Descriptor instead. func (WorkflowContractVersionItem_RawBody_Format) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{11, 0, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12, 0, 0} } type OrgItem_PolicyViolationBlockingStrategy int32 @@ -474,11 +613,11 @@ func (x OrgItem_PolicyViolationBlockingStrategy) String() string { } func (OrgItem_PolicyViolationBlockingStrategy) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[8].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[10].Descriptor() } func (OrgItem_PolicyViolationBlockingStrategy) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[8] + return &file_controlplane_v1_response_messages_proto_enumTypes[10] } func (x OrgItem_PolicyViolationBlockingStrategy) Number() protoreflect.EnumNumber { @@ -487,7 +626,7 @@ func (x OrgItem_PolicyViolationBlockingStrategy) Number() protoreflect.EnumNumbe // Deprecated: Use OrgItem_PolicyViolationBlockingStrategy.Descriptor instead. func (OrgItem_PolicyViolationBlockingStrategy) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{14, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15, 0} } type CASBackendItem_ValidationStatus int32 @@ -523,11 +662,11 @@ func (x CASBackendItem_ValidationStatus) String() string { } func (CASBackendItem_ValidationStatus) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[9].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[11].Descriptor() } func (CASBackendItem_ValidationStatus) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[9] + return &file_controlplane_v1_response_messages_proto_enumTypes[11] } func (x CASBackendItem_ValidationStatus) Number() protoreflect.EnumNumber { @@ -536,7 +675,7 @@ func (x CASBackendItem_ValidationStatus) Number() protoreflect.EnumNumber { // Deprecated: Use CASBackendItem_ValidationStatus.Descriptor instead. func (CASBackendItem_ValidationStatus) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16, 0} } type WorkflowItem struct { @@ -700,8 +839,12 @@ type WorkflowRunItem struct { Version *ProjectVersion `protobuf:"bytes,13,opt,name=version,proto3" json:"version,omitempty"` // Whether the run has policy violations (null if no policies were evaluated) HasPolicyViolations *bool `protobuf:"varint,14,opt,name=has_policy_violations,json=hasPolicyViolations,proto3,oneof" json:"has_policy_violations,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Canonical policy status summary for this run (null if no policies were + // evaluated). Carries both the categorical PolicyStatus and per-evaluation + // counters so list consumers can render a badge without calling View. + PolicySummary *PolicyStatusSummary `protobuf:"bytes,15,opt,name=policy_summary,json=policySummary,proto3" json:"policy_summary,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WorkflowRunItem) Reset() { @@ -833,6 +976,13 @@ func (x *WorkflowRunItem) GetHasPolicyViolations() bool { return false } +func (x *WorkflowRunItem) GetPolicySummary() *PolicyStatusSummary { + if x != nil { + return x.PolicySummary + } + return nil +} + type ProjectVersion struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -910,6 +1060,90 @@ func (x *ProjectVersion) GetReleasedAt() *timestamppb.Timestamp { return nil } +// PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation +// counters. It is surfaced on both WorkflowRunItem (list response) and +// AttestationItem.PolicyEvaluationStatus (describe response) and is computed +// by a single backend helper so list and describe cannot drift. +type PolicyStatusSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status PolicyStatus `protobuf:"varint,1,opt,name=status,proto3,enum=controlplane.v1.PolicyStatus" json:"status,omitempty"` + // Total number of policy evaluations that ran for this attestation + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + // Number of evaluations with no violations and not skipped + Passed int32 `protobuf:"varint,3,opt,name=passed,proto3" json:"passed,omitempty"` + // Number of evaluations that were skipped + Skipped int32 `protobuf:"varint,4,opt,name=skipped,proto3" json:"skipped,omitempty"` + // Total number of violations across all evaluations + Violated int32 `protobuf:"varint,5,opt,name=violated,proto3" json:"violated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyStatusSummary) Reset() { + *x = PolicyStatusSummary{} + mi := &file_controlplane_v1_response_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyStatusSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyStatusSummary) ProtoMessage() {} + +func (x *PolicyStatusSummary) ProtoReflect() protoreflect.Message { + mi := &file_controlplane_v1_response_messages_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyStatusSummary.ProtoReflect.Descriptor instead. +func (*PolicyStatusSummary) Descriptor() ([]byte, []int) { + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3} +} + +func (x *PolicyStatusSummary) GetStatus() PolicyStatus { + if x != nil { + return x.Status + } + return PolicyStatus_POLICY_STATUS_UNSPECIFIED +} + +func (x *PolicyStatusSummary) GetTotal() int32 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *PolicyStatusSummary) GetPassed() int32 { + if x != nil { + return x.Passed + } + return 0 +} + +func (x *PolicyStatusSummary) GetSkipped() int32 { + if x != nil { + return x.Skipped + } + return 0 +} + +func (x *PolicyStatusSummary) GetViolated() int32 { + if x != nil { + return x.Violated + } + return 0 +} + type AttestationItem struct { state protoimpl.MessageState `protogen:"open.v1"` // encoded DSEE envelope @@ -933,7 +1167,7 @@ type AttestationItem struct { func (x *AttestationItem) Reset() { *x = AttestationItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[3] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -945,7 +1179,7 @@ func (x *AttestationItem) String() string { func (*AttestationItem) ProtoMessage() {} func (x *AttestationItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[3] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -958,7 +1192,7 @@ func (x *AttestationItem) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestationItem.ProtoReflect.Descriptor instead. func (*AttestationItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4} } // Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. @@ -1027,7 +1261,7 @@ type PolicyEvaluations struct { func (x *PolicyEvaluations) Reset() { *x = PolicyEvaluations{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[4] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1039,7 +1273,7 @@ func (x *PolicyEvaluations) String() string { func (*PolicyEvaluations) ProtoMessage() {} func (x *PolicyEvaluations) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[4] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1052,7 +1286,7 @@ func (x *PolicyEvaluations) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyEvaluations.ProtoReflect.Descriptor instead. func (*PolicyEvaluations) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} } func (x *PolicyEvaluations) GetEvaluations() []*PolicyEvaluation { @@ -1086,7 +1320,7 @@ type PolicyEvaluation struct { func (x *PolicyEvaluation) Reset() { *x = PolicyEvaluation{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1098,7 +1332,7 @@ func (x *PolicyEvaluation) String() string { func (*PolicyEvaluation) ProtoMessage() {} func (x *PolicyEvaluation) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[5] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1111,7 +1345,7 @@ func (x *PolicyEvaluation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyEvaluation.ProtoReflect.Descriptor instead. func (*PolicyEvaluation) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} } func (x *PolicyEvaluation) GetName() string { @@ -1230,7 +1464,7 @@ type PolicyViolation struct { func (x *PolicyViolation) Reset() { *x = PolicyViolation{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1242,7 +1476,7 @@ func (x *PolicyViolation) String() string { func (*PolicyViolation) ProtoMessage() {} func (x *PolicyViolation) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[6] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1255,7 +1489,7 @@ func (x *PolicyViolation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyViolation.ProtoReflect.Descriptor instead. func (*PolicyViolation) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} } func (x *PolicyViolation) GetSubject() string { @@ -1284,7 +1518,7 @@ type PolicyReference struct { func (x *PolicyReference) Reset() { *x = PolicyReference{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1296,7 +1530,7 @@ func (x *PolicyReference) String() string { func (*PolicyReference) ProtoMessage() {} func (x *PolicyReference) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[7] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1309,7 +1543,7 @@ func (x *PolicyReference) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyReference.ProtoReflect.Descriptor instead. func (*PolicyReference) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} } func (x *PolicyReference) GetName() string { @@ -1365,7 +1599,7 @@ type WorkflowContractItem struct { func (x *WorkflowContractItem) Reset() { *x = WorkflowContractItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1377,7 +1611,7 @@ func (x *WorkflowContractItem) String() string { func (*WorkflowContractItem) ProtoMessage() {} func (x *WorkflowContractItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[8] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1390,7 +1624,7 @@ func (x *WorkflowContractItem) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowContractItem.ProtoReflect.Descriptor instead. func (*WorkflowContractItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{9} } func (x *WorkflowContractItem) GetId() string { @@ -1479,7 +1713,7 @@ type ScopedEntity struct { func (x *ScopedEntity) Reset() { *x = ScopedEntity{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1491,7 +1725,7 @@ func (x *ScopedEntity) String() string { func (*ScopedEntity) ProtoMessage() {} func (x *ScopedEntity) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[9] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1504,7 +1738,7 @@ func (x *ScopedEntity) ProtoReflect() protoreflect.Message { // Deprecated: Use ScopedEntity.ProtoReflect.Descriptor instead. func (*ScopedEntity) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{9} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{10} } func (x *ScopedEntity) GetType() string { @@ -1539,7 +1773,7 @@ type WorkflowRef struct { func (x *WorkflowRef) Reset() { *x = WorkflowRef{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1551,7 +1785,7 @@ func (x *WorkflowRef) String() string { func (*WorkflowRef) ProtoMessage() {} func (x *WorkflowRef) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[10] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1564,7 +1798,7 @@ func (x *WorkflowRef) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowRef.ProtoReflect.Descriptor instead. func (*WorkflowRef) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{10} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{11} } func (x *WorkflowRef) GetId() string { @@ -1608,7 +1842,7 @@ type WorkflowContractVersionItem struct { func (x *WorkflowContractVersionItem) Reset() { *x = WorkflowContractVersionItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1620,7 +1854,7 @@ func (x *WorkflowContractVersionItem) String() string { func (*WorkflowContractVersionItem) ProtoMessage() {} func (x *WorkflowContractVersionItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[11] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1633,7 +1867,7 @@ func (x *WorkflowContractVersionItem) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowContractVersionItem.ProtoReflect.Descriptor instead. func (*WorkflowContractVersionItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{11} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12} } func (x *WorkflowContractVersionItem) GetId() string { @@ -1723,7 +1957,7 @@ type User struct { func (x *User) Reset() { *x = User{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1735,7 +1969,7 @@ func (x *User) String() string { func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[12] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1748,7 +1982,7 @@ func (x *User) ProtoReflect() protoreflect.Message { // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{13} } func (x *User) GetId() string { @@ -1815,7 +2049,7 @@ type OrgMembershipItem struct { func (x *OrgMembershipItem) Reset() { *x = OrgMembershipItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1827,7 +2061,7 @@ func (x *OrgMembershipItem) String() string { func (*OrgMembershipItem) ProtoMessage() {} func (x *OrgMembershipItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[13] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1840,7 +2074,7 @@ func (x *OrgMembershipItem) ProtoReflect() protoreflect.Message { // Deprecated: Use OrgMembershipItem.ProtoReflect.Descriptor instead. func (*OrgMembershipItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{13} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{14} } func (x *OrgMembershipItem) GetId() string { @@ -1914,7 +2148,7 @@ type OrgItem struct { func (x *OrgItem) Reset() { *x = OrgItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1926,7 +2160,7 @@ func (x *OrgItem) String() string { func (*OrgItem) ProtoMessage() {} func (x *OrgItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[14] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1939,7 +2173,7 @@ func (x *OrgItem) ProtoReflect() protoreflect.Message { // Deprecated: Use OrgItem.ProtoReflect.Descriptor instead. func (*OrgItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{14} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15} } func (x *OrgItem) GetId() string { @@ -2042,7 +2276,7 @@ type CASBackendItem struct { func (x *CASBackendItem) Reset() { *x = CASBackendItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2054,7 +2288,7 @@ func (x *CASBackendItem) String() string { func (*CASBackendItem) ProtoMessage() {} func (x *CASBackendItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[15] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2067,7 +2301,7 @@ func (x *CASBackendItem) ProtoReflect() protoreflect.Message { // Deprecated: Use CASBackendItem.ProtoReflect.Descriptor instead. func (*CASBackendItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16} } func (x *CASBackendItem) GetId() string { @@ -2187,7 +2421,7 @@ type APITokenItem struct { func (x *APITokenItem) Reset() { *x = APITokenItem{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2199,7 +2433,7 @@ func (x *APITokenItem) String() string { func (*APITokenItem) ProtoMessage() {} func (x *APITokenItem) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[16] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2212,7 +2446,7 @@ func (x *APITokenItem) ProtoReflect() protoreflect.Message { // Deprecated: Use APITokenItem.ProtoReflect.Descriptor instead. func (*APITokenItem) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{17} } func (x *APITokenItem) GetId() string { @@ -2292,17 +2526,25 @@ type AttestationItem_PolicyEvaluationStatus struct { Blocked bool `protobuf:"varint,3,opt,name=blocked,proto3" json:"blocked,omitempty"` HasViolations bool `protobuf:"varint,4,opt,name=has_violations,json=hasViolations,proto3" json:"has_violations,omitempty"` HasGatedViolations bool `protobuf:"varint,5,opt,name=has_gated_violations,json=hasGatedViolations,proto3" json:"has_gated_violations,omitempty"` - // Total number of policy evaluations + // Deprecated: use summary.total instead. + // + // Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. EvaluationsCount int32 `protobuf:"varint,6,opt,name=evaluations_count,json=evaluationsCount,proto3" json:"evaluations_count,omitempty"` - // Total number of policy violations across all evaluations + // Deprecated: use summary.violated instead. + // + // Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. ViolationsCount int32 `protobuf:"varint,7,opt,name=violations_count,json=violationsCount,proto3" json:"violations_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Canonical categorical status + counters. Single source of truth for UI + // consumers — consumers that re-derive from the raw bools above tend to + // disagree on semantics, especially around gating and bypass. + Summary *PolicyStatusSummary `protobuf:"bytes,8,opt,name=summary,proto3" json:"summary,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AttestationItem_PolicyEvaluationStatus) Reset() { *x = AttestationItem_PolicyEvaluationStatus{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[19] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2314,7 +2556,7 @@ func (x *AttestationItem_PolicyEvaluationStatus) String() string { func (*AttestationItem_PolicyEvaluationStatus) ProtoMessage() {} func (x *AttestationItem_PolicyEvaluationStatus) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[19] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2327,7 +2569,7 @@ func (x *AttestationItem_PolicyEvaluationStatus) ProtoReflect() protoreflect.Mes // Deprecated: Use AttestationItem_PolicyEvaluationStatus.ProtoReflect.Descriptor instead. func (*AttestationItem_PolicyEvaluationStatus) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3, 2} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4, 2} } func (x *AttestationItem_PolicyEvaluationStatus) GetStrategy() string { @@ -2365,6 +2607,7 @@ func (x *AttestationItem_PolicyEvaluationStatus) GetHasGatedViolations() bool { return false } +// Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. func (x *AttestationItem_PolicyEvaluationStatus) GetEvaluationsCount() int32 { if x != nil { return x.EvaluationsCount @@ -2372,6 +2615,7 @@ func (x *AttestationItem_PolicyEvaluationStatus) GetEvaluationsCount() int32 { return 0 } +// Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. func (x *AttestationItem_PolicyEvaluationStatus) GetViolationsCount() int32 { if x != nil { return x.ViolationsCount @@ -2379,6 +2623,13 @@ func (x *AttestationItem_PolicyEvaluationStatus) GetViolationsCount() int32 { return 0 } +func (x *AttestationItem_PolicyEvaluationStatus) GetSummary() *PolicyStatusSummary { + if x != nil { + return x.Summary + } + return nil +} + type AttestationItem_EnvVariable struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -2389,7 +2640,7 @@ type AttestationItem_EnvVariable struct { func (x *AttestationItem_EnvVariable) Reset() { *x = AttestationItem_EnvVariable{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[20] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2401,7 +2652,7 @@ func (x *AttestationItem_EnvVariable) String() string { func (*AttestationItem_EnvVariable) ProtoMessage() {} func (x *AttestationItem_EnvVariable) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[20] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2414,7 +2665,7 @@ func (x *AttestationItem_EnvVariable) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestationItem_EnvVariable.ProtoReflect.Descriptor instead. func (*AttestationItem_EnvVariable) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3, 3} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4, 3} } func (x *AttestationItem_EnvVariable) GetName() string { @@ -2462,7 +2713,7 @@ type AttestationItem_Material struct { func (x *AttestationItem_Material) Reset() { *x = AttestationItem_Material{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2474,7 +2725,7 @@ func (x *AttestationItem_Material) String() string { func (*AttestationItem_Material) ProtoMessage() {} func (x *AttestationItem_Material) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[21] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2487,7 +2738,7 @@ func (x *AttestationItem_Material) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestationItem_Material.ProtoReflect.Descriptor instead. func (*AttestationItem_Material) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3, 4} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4, 4} } func (x *AttestationItem_Material) GetName() string { @@ -2571,7 +2822,7 @@ type WorkflowContractVersionItem_RawBody struct { func (x *WorkflowContractVersionItem_RawBody) Reset() { *x = WorkflowContractVersionItem_RawBody{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[26] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2583,7 +2834,7 @@ func (x *WorkflowContractVersionItem_RawBody) String() string { func (*WorkflowContractVersionItem_RawBody) ProtoMessage() {} func (x *WorkflowContractVersionItem_RawBody) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[26] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2596,7 +2847,7 @@ func (x *WorkflowContractVersionItem_RawBody) ProtoReflect() protoreflect.Messag // Deprecated: Use WorkflowContractVersionItem_RawBody.ProtoReflect.Descriptor instead. func (*WorkflowContractVersionItem_RawBody) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{11, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{12, 0} } func (x *WorkflowContractVersionItem_RawBody) GetBody() []byte { @@ -2623,7 +2874,7 @@ type CASBackendItem_Limits struct { func (x *CASBackendItem_Limits) Reset() { *x = CASBackendItem_Limits{} - mi := &file_controlplane_v1_response_messages_proto_msgTypes[27] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2635,7 +2886,7 @@ func (x *CASBackendItem_Limits) String() string { func (*CASBackendItem_Limits) ProtoMessage() {} func (x *CASBackendItem_Limits) ProtoReflect() protoreflect.Message { - mi := &file_controlplane_v1_response_messages_proto_msgTypes[27] + mi := &file_controlplane_v1_response_messages_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2648,7 +2899,7 @@ func (x *CASBackendItem_Limits) ProtoReflect() protoreflect.Message { // Deprecated: Use CASBackendItem_Limits.ProtoReflect.Descriptor instead. func (*CASBackendItem_Limits) Descriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{15, 0} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{16, 0} } func (x *CASBackendItem_Limits) GetMaxBytes() int64 { @@ -2679,7 +2930,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x18contract_revision_latest\x18\v \x01(\x05R\x16contractRevisionLatest\x12\x16\n" + "\x06public\x18\t \x01(\bR\x06public\x12 \n" + "\vdescription\x18\n" + - " \x01(\tR\vdescription\"\x82\x06\n" + + " \x01(\tR\vdescription\"\xcf\x06\n" + "\x0fWorkflowRunItem\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x129\n" + "\n" + @@ -2698,7 +2949,8 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + " \x01(\x05R\x14contractRevisionUsed\x128\n" + "\x18contract_revision_latest\x18\v \x01(\x05R\x16contractRevisionLatest\x129\n" + "\aversion\x18\r \x01(\v2\x1f.controlplane.v1.ProjectVersionR\aversion\x127\n" + - "\x15has_policy_violations\x18\x0e \x01(\bH\x00R\x13hasPolicyViolations\x88\x01\x01B\x18\n" + + "\x15has_policy_violations\x18\x0e \x01(\bH\x00R\x13hasPolicyViolations\x88\x01\x01\x12K\n" + + "\x0epolicy_summary\x18\x0f \x01(\v2$.controlplane.v1.PolicyStatusSummaryR\rpolicySummaryB\x18\n" + "\x16_has_policy_violations\"\xd2\x01\n" + "\x0eProjectVersion\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + @@ -2709,7 +2961,13 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\n" + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + "\vreleased_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "releasedAt\"\xdc\v\n" + + "releasedAt\"\xb0\x01\n" + + "\x13PolicyStatusSummary\x125\n" + + "\x06status\x18\x01 \x01(\x0e2\x1d.controlplane.v1.PolicyStatusR\x06status\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\x12\x16\n" + + "\x06passed\x18\x03 \x01(\x05R\x06passed\x12\x18\n" + + "\askipped\x18\x04 \x01(\x05R\askipped\x12\x1a\n" + + "\bviolated\x18\x05 \x01(\x05R\bviolated\"\xa4\f\n" + "\x0fAttestationItem\x12\x1e\n" + "\benvelope\x18\x03 \x01(\fB\x02\x18\x01R\benvelope\x12\x16\n" + "\x06bundle\x18\n" + @@ -2725,15 +2983,16 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ah\n" + "\x16PolicyEvaluationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x128\n" + - "\x05value\x18\x02 \x01(\v2\".controlplane.v1.PolicyEvaluationsR\x05value:\x028\x01\x1a\x9b\x02\n" + + "\x05value\x18\x02 \x01(\v2\".controlplane.v1.PolicyEvaluationsR\x05value:\x028\x01\x1a\xe3\x02\n" + "\x16PolicyEvaluationStatus\x12\x1a\n" + "\bstrategy\x18\x01 \x01(\tR\bstrategy\x12\x1a\n" + "\bbypassed\x18\x02 \x01(\bR\bbypassed\x12\x18\n" + "\ablocked\x18\x03 \x01(\bR\ablocked\x12%\n" + "\x0ehas_violations\x18\x04 \x01(\bR\rhasViolations\x120\n" + - "\x14has_gated_violations\x18\x05 \x01(\bR\x12hasGatedViolations\x12+\n" + - "\x11evaluations_count\x18\x06 \x01(\x05R\x10evaluationsCount\x12)\n" + - "\x10violations_count\x18\a \x01(\x05R\x0fviolationsCount\x1a7\n" + + "\x14has_gated_violations\x18\x05 \x01(\bR\x12hasGatedViolations\x12/\n" + + "\x11evaluations_count\x18\x06 \x01(\x05B\x02\x18\x01R\x10evaluationsCount\x12-\n" + + "\x10violations_count\x18\a \x01(\x05B\x02\x18\x01R\x0fviolationsCount\x12>\n" + + "\asummary\x18\b \x01(\v2$.controlplane.v1.PolicyStatusSummaryR\asummary\x1a7\n" + "\vEnvVariable\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value\x1a\x9a\x03\n" + @@ -2920,11 +3179,27 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x14RUN_STATUS_SUCCEEDED\x10\x02\x12\x15\n" + "\x11RUN_STATUS_FAILED\x10\x03\x12\x16\n" + "\x12RUN_STATUS_EXPIRED\x10\x04\x12\x18\n" + - "\x14RUN_STATUS_CANCELLED\x10\x05*\xa1\x01\n" + + "\x14RUN_STATUS_CANCELLED\x10\x05*\xa5\x01\n" + "\x16PolicyViolationsFilter\x12(\n" + "$POLICY_VIOLATIONS_FILTER_UNSPECIFIED\x10\x00\x12,\n" + "(POLICY_VIOLATIONS_FILTER_WITH_VIOLATIONS\x10\x01\x12/\n" + - "+POLICY_VIOLATIONS_FILTER_WITHOUT_VIOLATIONS\x10\x02*\xd4\x01\n" + + "+POLICY_VIOLATIONS_FILTER_WITHOUT_VIOLATIONS\x10\x02\x1a\x02\x18\x01*\xd6\x01\n" + + "\fPolicyStatus\x12\x1d\n" + + "\x19POLICY_STATUS_UNSPECIFIED\x10\x00\x12 \n" + + "\x1cPOLICY_STATUS_NOT_APPLICABLE\x10\x01\x12\x18\n" + + "\x14POLICY_STATUS_PASSED\x10\x02\x12\x19\n" + + "\x15POLICY_STATUS_SKIPPED\x10\x03\x12\x19\n" + + "\x15POLICY_STATUS_WARNING\x10\x04\x12\x19\n" + + "\x15POLICY_STATUS_BLOCKED\x10\x05\x12\x1a\n" + + "\x16POLICY_STATUS_BYPASSED\x10\x06*\x8d\x02\n" + + "\x12PolicyStatusFilter\x12$\n" + + " POLICY_STATUS_FILTER_UNSPECIFIED\x10\x00\x12'\n" + + "#POLICY_STATUS_FILTER_NOT_APPLICABLE\x10\x01\x12\x1f\n" + + "\x1bPOLICY_STATUS_FILTER_PASSED\x10\x02\x12 \n" + + "\x1cPOLICY_STATUS_FILTER_SKIPPED\x10\x03\x12 \n" + + "\x1cPOLICY_STATUS_FILTER_WARNING\x10\x04\x12 \n" + + "\x1cPOLICY_STATUS_FILTER_BLOCKED\x10\x05\x12!\n" + + "\x1dPOLICY_STATUS_FILTER_BYPASSED\x10\x06*\xd4\x01\n" + "\x0eMembershipRole\x12\x1f\n" + "\x1bMEMBERSHIP_ROLE_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aMEMBERSHIP_ROLE_ORG_VIEWER\x10\x01\x12\x1d\n" + @@ -2957,111 +3232,117 @@ func file_controlplane_v1_response_messages_proto_rawDescGZIP() []byte { return file_controlplane_v1_response_messages_proto_rawDescData } -var file_controlplane_v1_response_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 10) -var file_controlplane_v1_response_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_controlplane_v1_response_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 12) +var file_controlplane_v1_response_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_controlplane_v1_response_messages_proto_goTypes = []any{ (RunStatus)(0), // 0: controlplane.v1.RunStatus (PolicyViolationsFilter)(0), // 1: controlplane.v1.PolicyViolationsFilter - (MembershipRole)(0), // 2: controlplane.v1.MembershipRole - (AllowListError)(0), // 3: controlplane.v1.AllowListError - (FederatedAuthError)(0), // 4: controlplane.v1.FederatedAuthError - (UserWithNoMembershipError)(0), // 5: controlplane.v1.UserWithNoMembershipError - (UserNotMemberOfOrgError)(0), // 6: controlplane.v1.UserNotMemberOfOrgError - (WorkflowContractVersionItem_RawBody_Format)(0), // 7: controlplane.v1.WorkflowContractVersionItem.RawBody.Format - (OrgItem_PolicyViolationBlockingStrategy)(0), // 8: controlplane.v1.OrgItem.PolicyViolationBlockingStrategy - (CASBackendItem_ValidationStatus)(0), // 9: controlplane.v1.CASBackendItem.ValidationStatus - (*WorkflowItem)(nil), // 10: controlplane.v1.WorkflowItem - (*WorkflowRunItem)(nil), // 11: controlplane.v1.WorkflowRunItem - (*ProjectVersion)(nil), // 12: controlplane.v1.ProjectVersion - (*AttestationItem)(nil), // 13: controlplane.v1.AttestationItem - (*PolicyEvaluations)(nil), // 14: controlplane.v1.PolicyEvaluations - (*PolicyEvaluation)(nil), // 15: controlplane.v1.PolicyEvaluation - (*PolicyViolation)(nil), // 16: controlplane.v1.PolicyViolation - (*PolicyReference)(nil), // 17: controlplane.v1.PolicyReference - (*WorkflowContractItem)(nil), // 18: controlplane.v1.WorkflowContractItem - (*ScopedEntity)(nil), // 19: controlplane.v1.ScopedEntity - (*WorkflowRef)(nil), // 20: controlplane.v1.WorkflowRef - (*WorkflowContractVersionItem)(nil), // 21: controlplane.v1.WorkflowContractVersionItem - (*User)(nil), // 22: controlplane.v1.User - (*OrgMembershipItem)(nil), // 23: controlplane.v1.OrgMembershipItem - (*OrgItem)(nil), // 24: controlplane.v1.OrgItem - (*CASBackendItem)(nil), // 25: controlplane.v1.CASBackendItem - (*APITokenItem)(nil), // 26: controlplane.v1.APITokenItem - nil, // 27: controlplane.v1.AttestationItem.AnnotationsEntry - nil, // 28: controlplane.v1.AttestationItem.PolicyEvaluationsEntry - (*AttestationItem_PolicyEvaluationStatus)(nil), // 29: controlplane.v1.AttestationItem.PolicyEvaluationStatus - (*AttestationItem_EnvVariable)(nil), // 30: controlplane.v1.AttestationItem.EnvVariable - (*AttestationItem_Material)(nil), // 31: controlplane.v1.AttestationItem.Material - nil, // 32: controlplane.v1.AttestationItem.Material.AnnotationsEntry - nil, // 33: controlplane.v1.PolicyEvaluation.AnnotationsEntry - nil, // 34: controlplane.v1.PolicyEvaluation.WithEntry - nil, // 35: controlplane.v1.PolicyReference.DigestEntry - (*WorkflowContractVersionItem_RawBody)(nil), // 36: controlplane.v1.WorkflowContractVersionItem.RawBody - (*CASBackendItem_Limits)(nil), // 37: controlplane.v1.CASBackendItem.Limits - (*timestamppb.Timestamp)(nil), // 38: google.protobuf.Timestamp - (v1.CraftingSchema_Runner_RunnerType)(0), // 39: workflowcontract.v1.CraftingSchema.Runner.RunnerType - (*v1.CraftingSchema)(nil), // 40: workflowcontract.v1.CraftingSchema + (PolicyStatus)(0), // 2: controlplane.v1.PolicyStatus + (PolicyStatusFilter)(0), // 3: controlplane.v1.PolicyStatusFilter + (MembershipRole)(0), // 4: controlplane.v1.MembershipRole + (AllowListError)(0), // 5: controlplane.v1.AllowListError + (FederatedAuthError)(0), // 6: controlplane.v1.FederatedAuthError + (UserWithNoMembershipError)(0), // 7: controlplane.v1.UserWithNoMembershipError + (UserNotMemberOfOrgError)(0), // 8: controlplane.v1.UserNotMemberOfOrgError + (WorkflowContractVersionItem_RawBody_Format)(0), // 9: controlplane.v1.WorkflowContractVersionItem.RawBody.Format + (OrgItem_PolicyViolationBlockingStrategy)(0), // 10: controlplane.v1.OrgItem.PolicyViolationBlockingStrategy + (CASBackendItem_ValidationStatus)(0), // 11: controlplane.v1.CASBackendItem.ValidationStatus + (*WorkflowItem)(nil), // 12: controlplane.v1.WorkflowItem + (*WorkflowRunItem)(nil), // 13: controlplane.v1.WorkflowRunItem + (*ProjectVersion)(nil), // 14: controlplane.v1.ProjectVersion + (*PolicyStatusSummary)(nil), // 15: controlplane.v1.PolicyStatusSummary + (*AttestationItem)(nil), // 16: controlplane.v1.AttestationItem + (*PolicyEvaluations)(nil), // 17: controlplane.v1.PolicyEvaluations + (*PolicyEvaluation)(nil), // 18: controlplane.v1.PolicyEvaluation + (*PolicyViolation)(nil), // 19: controlplane.v1.PolicyViolation + (*PolicyReference)(nil), // 20: controlplane.v1.PolicyReference + (*WorkflowContractItem)(nil), // 21: controlplane.v1.WorkflowContractItem + (*ScopedEntity)(nil), // 22: controlplane.v1.ScopedEntity + (*WorkflowRef)(nil), // 23: controlplane.v1.WorkflowRef + (*WorkflowContractVersionItem)(nil), // 24: controlplane.v1.WorkflowContractVersionItem + (*User)(nil), // 25: controlplane.v1.User + (*OrgMembershipItem)(nil), // 26: controlplane.v1.OrgMembershipItem + (*OrgItem)(nil), // 27: controlplane.v1.OrgItem + (*CASBackendItem)(nil), // 28: controlplane.v1.CASBackendItem + (*APITokenItem)(nil), // 29: controlplane.v1.APITokenItem + nil, // 30: controlplane.v1.AttestationItem.AnnotationsEntry + nil, // 31: controlplane.v1.AttestationItem.PolicyEvaluationsEntry + (*AttestationItem_PolicyEvaluationStatus)(nil), // 32: controlplane.v1.AttestationItem.PolicyEvaluationStatus + (*AttestationItem_EnvVariable)(nil), // 33: controlplane.v1.AttestationItem.EnvVariable + (*AttestationItem_Material)(nil), // 34: controlplane.v1.AttestationItem.Material + nil, // 35: controlplane.v1.AttestationItem.Material.AnnotationsEntry + nil, // 36: controlplane.v1.PolicyEvaluation.AnnotationsEntry + nil, // 37: controlplane.v1.PolicyEvaluation.WithEntry + nil, // 38: controlplane.v1.PolicyReference.DigestEntry + (*WorkflowContractVersionItem_RawBody)(nil), // 39: controlplane.v1.WorkflowContractVersionItem.RawBody + (*CASBackendItem_Limits)(nil), // 40: controlplane.v1.CASBackendItem.Limits + (*timestamppb.Timestamp)(nil), // 41: google.protobuf.Timestamp + (v1.CraftingSchema_Runner_RunnerType)(0), // 42: workflowcontract.v1.CraftingSchema.Runner.RunnerType + (*v1.CraftingSchema)(nil), // 43: workflowcontract.v1.CraftingSchema } var file_controlplane_v1_response_messages_proto_depIdxs = []int32{ - 38, // 0: controlplane.v1.WorkflowItem.created_at:type_name -> google.protobuf.Timestamp - 11, // 1: controlplane.v1.WorkflowItem.last_run:type_name -> controlplane.v1.WorkflowRunItem - 38, // 2: controlplane.v1.WorkflowRunItem.created_at:type_name -> google.protobuf.Timestamp - 38, // 3: controlplane.v1.WorkflowRunItem.finished_at:type_name -> google.protobuf.Timestamp + 41, // 0: controlplane.v1.WorkflowItem.created_at:type_name -> google.protobuf.Timestamp + 13, // 1: controlplane.v1.WorkflowItem.last_run:type_name -> controlplane.v1.WorkflowRunItem + 41, // 2: controlplane.v1.WorkflowRunItem.created_at:type_name -> google.protobuf.Timestamp + 41, // 3: controlplane.v1.WorkflowRunItem.finished_at:type_name -> google.protobuf.Timestamp 0, // 4: controlplane.v1.WorkflowRunItem.status:type_name -> controlplane.v1.RunStatus - 10, // 5: controlplane.v1.WorkflowRunItem.workflow:type_name -> controlplane.v1.WorkflowItem - 39, // 6: controlplane.v1.WorkflowRunItem.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType - 21, // 7: controlplane.v1.WorkflowRunItem.contract_version:type_name -> controlplane.v1.WorkflowContractVersionItem - 12, // 8: controlplane.v1.WorkflowRunItem.version:type_name -> controlplane.v1.ProjectVersion - 38, // 9: controlplane.v1.ProjectVersion.created_at:type_name -> google.protobuf.Timestamp - 38, // 10: controlplane.v1.ProjectVersion.released_at:type_name -> google.protobuf.Timestamp - 30, // 11: controlplane.v1.AttestationItem.env_vars:type_name -> controlplane.v1.AttestationItem.EnvVariable - 31, // 12: controlplane.v1.AttestationItem.materials:type_name -> controlplane.v1.AttestationItem.Material - 27, // 13: controlplane.v1.AttestationItem.annotations:type_name -> controlplane.v1.AttestationItem.AnnotationsEntry - 28, // 14: controlplane.v1.AttestationItem.policy_evaluations:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationsEntry - 29, // 15: controlplane.v1.AttestationItem.policy_evaluation_status:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationStatus - 15, // 16: controlplane.v1.PolicyEvaluations.evaluations:type_name -> controlplane.v1.PolicyEvaluation - 33, // 17: controlplane.v1.PolicyEvaluation.annotations:type_name -> controlplane.v1.PolicyEvaluation.AnnotationsEntry - 34, // 18: controlplane.v1.PolicyEvaluation.with:type_name -> controlplane.v1.PolicyEvaluation.WithEntry - 16, // 19: controlplane.v1.PolicyEvaluation.violations:type_name -> controlplane.v1.PolicyViolation - 17, // 20: controlplane.v1.PolicyEvaluation.policy_reference:type_name -> controlplane.v1.PolicyReference - 17, // 21: controlplane.v1.PolicyEvaluation.group_reference:type_name -> controlplane.v1.PolicyReference - 35, // 22: controlplane.v1.PolicyReference.digest:type_name -> controlplane.v1.PolicyReference.DigestEntry - 38, // 23: controlplane.v1.WorkflowContractItem.created_at:type_name -> google.protobuf.Timestamp - 38, // 24: controlplane.v1.WorkflowContractItem.updated_at:type_name -> google.protobuf.Timestamp - 38, // 25: controlplane.v1.WorkflowContractItem.latest_revision_created_at:type_name -> google.protobuf.Timestamp - 20, // 26: controlplane.v1.WorkflowContractItem.workflow_refs:type_name -> controlplane.v1.WorkflowRef - 19, // 27: controlplane.v1.WorkflowContractItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity - 38, // 28: controlplane.v1.WorkflowContractVersionItem.created_at:type_name -> google.protobuf.Timestamp - 40, // 29: controlplane.v1.WorkflowContractVersionItem.v1:type_name -> workflowcontract.v1.CraftingSchema - 36, // 30: controlplane.v1.WorkflowContractVersionItem.raw_contract:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody - 38, // 31: controlplane.v1.User.created_at:type_name -> google.protobuf.Timestamp - 38, // 32: controlplane.v1.User.updated_at:type_name -> google.protobuf.Timestamp - 24, // 33: controlplane.v1.OrgMembershipItem.org:type_name -> controlplane.v1.OrgItem - 22, // 34: controlplane.v1.OrgMembershipItem.user:type_name -> controlplane.v1.User - 38, // 35: controlplane.v1.OrgMembershipItem.created_at:type_name -> google.protobuf.Timestamp - 38, // 36: controlplane.v1.OrgMembershipItem.updated_at:type_name -> google.protobuf.Timestamp - 2, // 37: controlplane.v1.OrgMembershipItem.role:type_name -> controlplane.v1.MembershipRole - 38, // 38: controlplane.v1.OrgItem.created_at:type_name -> google.protobuf.Timestamp - 38, // 39: controlplane.v1.OrgItem.updated_at:type_name -> google.protobuf.Timestamp - 8, // 40: controlplane.v1.OrgItem.default_policy_violation_strategy:type_name -> controlplane.v1.OrgItem.PolicyViolationBlockingStrategy - 38, // 41: controlplane.v1.CASBackendItem.created_at:type_name -> google.protobuf.Timestamp - 38, // 42: controlplane.v1.CASBackendItem.validated_at:type_name -> google.protobuf.Timestamp - 9, // 43: controlplane.v1.CASBackendItem.validation_status:type_name -> controlplane.v1.CASBackendItem.ValidationStatus - 37, // 44: controlplane.v1.CASBackendItem.limits:type_name -> controlplane.v1.CASBackendItem.Limits - 38, // 45: controlplane.v1.CASBackendItem.updated_at:type_name -> google.protobuf.Timestamp - 19, // 46: controlplane.v1.APITokenItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity - 38, // 47: controlplane.v1.APITokenItem.created_at:type_name -> google.protobuf.Timestamp - 38, // 48: controlplane.v1.APITokenItem.revoked_at:type_name -> google.protobuf.Timestamp - 38, // 49: controlplane.v1.APITokenItem.expires_at:type_name -> google.protobuf.Timestamp - 38, // 50: controlplane.v1.APITokenItem.last_used_at:type_name -> google.protobuf.Timestamp - 14, // 51: controlplane.v1.AttestationItem.PolicyEvaluationsEntry.value:type_name -> controlplane.v1.PolicyEvaluations - 32, // 52: controlplane.v1.AttestationItem.Material.annotations:type_name -> controlplane.v1.AttestationItem.Material.AnnotationsEntry - 7, // 53: controlplane.v1.WorkflowContractVersionItem.RawBody.format:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody.Format - 54, // [54:54] is the sub-list for method output_type - 54, // [54:54] is the sub-list for method input_type - 54, // [54:54] is the sub-list for extension type_name - 54, // [54:54] is the sub-list for extension extendee - 0, // [0:54] is the sub-list for field type_name + 12, // 5: controlplane.v1.WorkflowRunItem.workflow:type_name -> controlplane.v1.WorkflowItem + 42, // 6: controlplane.v1.WorkflowRunItem.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType + 24, // 7: controlplane.v1.WorkflowRunItem.contract_version:type_name -> controlplane.v1.WorkflowContractVersionItem + 14, // 8: controlplane.v1.WorkflowRunItem.version:type_name -> controlplane.v1.ProjectVersion + 15, // 9: controlplane.v1.WorkflowRunItem.policy_summary:type_name -> controlplane.v1.PolicyStatusSummary + 41, // 10: controlplane.v1.ProjectVersion.created_at:type_name -> google.protobuf.Timestamp + 41, // 11: controlplane.v1.ProjectVersion.released_at:type_name -> google.protobuf.Timestamp + 2, // 12: controlplane.v1.PolicyStatusSummary.status:type_name -> controlplane.v1.PolicyStatus + 33, // 13: controlplane.v1.AttestationItem.env_vars:type_name -> controlplane.v1.AttestationItem.EnvVariable + 34, // 14: controlplane.v1.AttestationItem.materials:type_name -> controlplane.v1.AttestationItem.Material + 30, // 15: controlplane.v1.AttestationItem.annotations:type_name -> controlplane.v1.AttestationItem.AnnotationsEntry + 31, // 16: controlplane.v1.AttestationItem.policy_evaluations:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationsEntry + 32, // 17: controlplane.v1.AttestationItem.policy_evaluation_status:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationStatus + 18, // 18: controlplane.v1.PolicyEvaluations.evaluations:type_name -> controlplane.v1.PolicyEvaluation + 36, // 19: controlplane.v1.PolicyEvaluation.annotations:type_name -> controlplane.v1.PolicyEvaluation.AnnotationsEntry + 37, // 20: controlplane.v1.PolicyEvaluation.with:type_name -> controlplane.v1.PolicyEvaluation.WithEntry + 19, // 21: controlplane.v1.PolicyEvaluation.violations:type_name -> controlplane.v1.PolicyViolation + 20, // 22: controlplane.v1.PolicyEvaluation.policy_reference:type_name -> controlplane.v1.PolicyReference + 20, // 23: controlplane.v1.PolicyEvaluation.group_reference:type_name -> controlplane.v1.PolicyReference + 38, // 24: controlplane.v1.PolicyReference.digest:type_name -> controlplane.v1.PolicyReference.DigestEntry + 41, // 25: controlplane.v1.WorkflowContractItem.created_at:type_name -> google.protobuf.Timestamp + 41, // 26: controlplane.v1.WorkflowContractItem.updated_at:type_name -> google.protobuf.Timestamp + 41, // 27: controlplane.v1.WorkflowContractItem.latest_revision_created_at:type_name -> google.protobuf.Timestamp + 23, // 28: controlplane.v1.WorkflowContractItem.workflow_refs:type_name -> controlplane.v1.WorkflowRef + 22, // 29: controlplane.v1.WorkflowContractItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity + 41, // 30: controlplane.v1.WorkflowContractVersionItem.created_at:type_name -> google.protobuf.Timestamp + 43, // 31: controlplane.v1.WorkflowContractVersionItem.v1:type_name -> workflowcontract.v1.CraftingSchema + 39, // 32: controlplane.v1.WorkflowContractVersionItem.raw_contract:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody + 41, // 33: controlplane.v1.User.created_at:type_name -> google.protobuf.Timestamp + 41, // 34: controlplane.v1.User.updated_at:type_name -> google.protobuf.Timestamp + 27, // 35: controlplane.v1.OrgMembershipItem.org:type_name -> controlplane.v1.OrgItem + 25, // 36: controlplane.v1.OrgMembershipItem.user:type_name -> controlplane.v1.User + 41, // 37: controlplane.v1.OrgMembershipItem.created_at:type_name -> google.protobuf.Timestamp + 41, // 38: controlplane.v1.OrgMembershipItem.updated_at:type_name -> google.protobuf.Timestamp + 4, // 39: controlplane.v1.OrgMembershipItem.role:type_name -> controlplane.v1.MembershipRole + 41, // 40: controlplane.v1.OrgItem.created_at:type_name -> google.protobuf.Timestamp + 41, // 41: controlplane.v1.OrgItem.updated_at:type_name -> google.protobuf.Timestamp + 10, // 42: controlplane.v1.OrgItem.default_policy_violation_strategy:type_name -> controlplane.v1.OrgItem.PolicyViolationBlockingStrategy + 41, // 43: controlplane.v1.CASBackendItem.created_at:type_name -> google.protobuf.Timestamp + 41, // 44: controlplane.v1.CASBackendItem.validated_at:type_name -> google.protobuf.Timestamp + 11, // 45: controlplane.v1.CASBackendItem.validation_status:type_name -> controlplane.v1.CASBackendItem.ValidationStatus + 40, // 46: controlplane.v1.CASBackendItem.limits:type_name -> controlplane.v1.CASBackendItem.Limits + 41, // 47: controlplane.v1.CASBackendItem.updated_at:type_name -> google.protobuf.Timestamp + 22, // 48: controlplane.v1.APITokenItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity + 41, // 49: controlplane.v1.APITokenItem.created_at:type_name -> google.protobuf.Timestamp + 41, // 50: controlplane.v1.APITokenItem.revoked_at:type_name -> google.protobuf.Timestamp + 41, // 51: controlplane.v1.APITokenItem.expires_at:type_name -> google.protobuf.Timestamp + 41, // 52: controlplane.v1.APITokenItem.last_used_at:type_name -> google.protobuf.Timestamp + 17, // 53: controlplane.v1.AttestationItem.PolicyEvaluationsEntry.value:type_name -> controlplane.v1.PolicyEvaluations + 15, // 54: controlplane.v1.AttestationItem.PolicyEvaluationStatus.summary:type_name -> controlplane.v1.PolicyStatusSummary + 35, // 55: controlplane.v1.AttestationItem.Material.annotations:type_name -> controlplane.v1.AttestationItem.Material.AnnotationsEntry + 9, // 56: controlplane.v1.WorkflowContractVersionItem.RawBody.format:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody.Format + 57, // [57:57] is the sub-list for method output_type + 57, // [57:57] is the sub-list for method input_type + 57, // [57:57] is the sub-list for extension type_name + 57, // [57:57] is the sub-list for extension extendee + 0, // [0:57] is the sub-list for field type_name } func init() { file_controlplane_v1_response_messages_proto_init() } @@ -3070,18 +3351,18 @@ func file_controlplane_v1_response_messages_proto_init() { return } file_controlplane_v1_response_messages_proto_msgTypes[1].OneofWrappers = []any{} - file_controlplane_v1_response_messages_proto_msgTypes[11].OneofWrappers = []any{ + file_controlplane_v1_response_messages_proto_msgTypes[12].OneofWrappers = []any{ (*WorkflowContractVersionItem_V1)(nil), } - file_controlplane_v1_response_messages_proto_msgTypes[14].OneofWrappers = []any{} file_controlplane_v1_response_messages_proto_msgTypes[15].OneofWrappers = []any{} + file_controlplane_v1_response_messages_proto_msgTypes[16].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_controlplane_v1_response_messages_proto_rawDesc), len(file_controlplane_v1_response_messages_proto_rawDesc)), - NumEnums: 10, - NumMessages: 28, + NumEnums: 12, + NumMessages: 29, NumExtensions: 0, NumServices: 0, }, diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index e90a1a213..928bd936d 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -69,6 +69,11 @@ message WorkflowRunItem { // Whether the run has policy violations (null if no policies were evaluated) optional bool has_policy_violations = 14; + + // Canonical policy status summary for this run (null if no policies were + // evaluated). Carries both the categorical PolicyStatus and per-evaluation + // counters so list consumers can render a badge without calling View. + PolicyStatusSummary policy_summary = 15; } message ProjectVersion { @@ -89,12 +94,65 @@ enum RunStatus { RUN_STATUS_CANCELLED = 5; } +// Deprecated: use PolicyStatusFilter which aligns 1:1 with PolicyStatus and +// lets callers distinguish warning/blocked/bypassed from the coarse with/ +// without-violations split. enum PolicyViolationsFilter { + option deprecated = true; + POLICY_VIOLATIONS_FILTER_UNSPECIFIED = 0; POLICY_VIOLATIONS_FILTER_WITH_VIOLATIONS = 1; POLICY_VIOLATIONS_FILTER_WITHOUT_VIOLATIONS = 2; } +// Canonical, server-computed categorical policy outcome for an attestation. +// Collapses the raw enforcement/bypass/violation signals on +// AttestationItem.PolicyEvaluationStatus into a single flat value so that +// list and describe surfaces can render a consistent badge without +// re-deriving. +enum PolicyStatus { + POLICY_STATUS_UNSPECIFIED = 0; + // No policies were evaluated on this run + POLICY_STATUS_NOT_APPLICABLE = 1; + // Policies ran with no violations and no skips + POLICY_STATUS_PASSED = 2; + // No violations but at least one evaluation was skipped + POLICY_STATUS_SKIPPED = 3; + // Has violations but enforcement is advisory — run succeeded + POLICY_STATUS_WARNING = 4; + // Has gated violations or enforced strategy with violations; not bypassed + POLICY_STATUS_BLOCKED = 5; + // Enforcement would have blocked the run but was bypassed + POLICY_STATUS_BYPASSED = 6; +} + +// PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation +// counters. It is surfaced on both WorkflowRunItem (list response) and +// AttestationItem.PolicyEvaluationStatus (describe response) and is computed +// by a single backend helper so list and describe cannot drift. +message PolicyStatusSummary { + PolicyStatus status = 1; + // Total number of policy evaluations that ran for this attestation + int32 total = 2; + // Number of evaluations with no violations and not skipped + int32 passed = 3; + // Number of evaluations that were skipped + int32 skipped = 4; + // Total number of violations across all evaluations + int32 violated = 5; +} + +// Server-side filter aligned 1:1 with PolicyStatus values. +enum PolicyStatusFilter { + POLICY_STATUS_FILTER_UNSPECIFIED = 0; + POLICY_STATUS_FILTER_NOT_APPLICABLE = 1; + POLICY_STATUS_FILTER_PASSED = 2; + POLICY_STATUS_FILTER_SKIPPED = 3; + POLICY_STATUS_FILTER_WARNING = 4; + POLICY_STATUS_FILTER_BLOCKED = 5; + POLICY_STATUS_FILTER_BYPASSED = 6; +} + message AttestationItem { // encoded DSEE envelope bytes envelope = 3 [deprecated = true]; @@ -117,10 +175,14 @@ message AttestationItem { bool blocked = 3; bool has_violations = 4; bool has_gated_violations = 5; - // Total number of policy evaluations - int32 evaluations_count = 6; - // Total number of policy violations across all evaluations - int32 violations_count = 7; + // Deprecated: use summary.total instead. + int32 evaluations_count = 6 [deprecated = true]; + // Deprecated: use summary.violated instead. + int32 violations_count = 7 [deprecated = true]; + // Canonical categorical status + counters. Single source of truth for UI + // consumers — consumers that re-derive from the raw bools above tend to + // disagree on semantics, especially around gating and bypass. + PolicyStatusSummary summary = 8; } message EnvVariable { diff --git a/app/controlplane/api/controlplane/v1/workflow_run.pb.go b/app/controlplane/api/controlplane/v1/workflow_run.pb.go index 86e76a8fa..cb385924c 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.pb.go +++ b/app/controlplane/api/controlplane/v1/workflow_run.pb.go @@ -951,7 +951,13 @@ type WorkflowRunServiceListRequest struct { // by project version ProjectVersion string `protobuf:"bytes,5,opt,name=project_version,json=projectVersion,proto3" json:"project_version,omitempty"` // by policy violations status + // Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with + // the canonical PolicyStatus enum. When both are set, policy_status wins. + // + // Deprecated: Marked as deprecated in controlplane/v1/workflow_run.proto. PolicyViolations PolicyViolationsFilter `protobuf:"varint,6,opt,name=policy_violations,json=policyViolations,proto3,enum=controlplane.v1.PolicyViolationsFilter" json:"policy_violations,omitempty"` + // by canonical policy status + PolicyStatus PolicyStatusFilter `protobuf:"varint,7,opt,name=policy_status,json=policyStatus,proto3,enum=controlplane.v1.PolicyStatusFilter" json:"policy_status,omitempty"` // pagination options Pagination *CursorPaginationRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields @@ -1016,6 +1022,7 @@ func (x *WorkflowRunServiceListRequest) GetProjectVersion() string { return "" } +// Deprecated: Marked as deprecated in controlplane/v1/workflow_run.proto. func (x *WorkflowRunServiceListRequest) GetPolicyViolations() PolicyViolationsFilter { if x != nil { return x.PolicyViolations @@ -1023,6 +1030,13 @@ func (x *WorkflowRunServiceListRequest) GetPolicyViolations() PolicyViolationsFi return PolicyViolationsFilter_POLICY_VIOLATIONS_FILTER_UNSPECIFIED } +func (x *WorkflowRunServiceListRequest) GetPolicyStatus() PolicyStatusFilter { + if x != nil { + return x.PolicyStatus + } + return PolicyStatusFilter_POLICY_STATUS_FILTER_UNSPECIFIED +} + func (x *WorkflowRunServiceListRequest) GetPagination() *CursorPaginationRequest { if x != nil { return x.Pagination @@ -1811,14 +1825,15 @@ const file_controlplane_v1_workflow_run_proto_rawDesc = "" + "\x18TRIGGER_TYPE_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14TRIGGER_TYPE_FAILURE\x10\x01\x12\x1d\n" + "\x19TRIGGER_TYPE_CANCELLATION\x10\x02\"\"\n" + - " AttestationServiceCancelResponse\"\x8c\x05\n" + + " AttestationServiceCancelResponse\"\xda\x05\n" + "\x1dWorkflowRunServiceListRequest\x12\xac\x01\n" + "\rworkflow_name\x18\x01 \x01(\tB\x86\x01\xbaH\x82\x01\xba\x01|\n" + "\rname.dns-1123\x12:must contain only lowercase letters, numbers, and hyphens.\x1a/this.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')\xd8\x01\x01R\fworkflowName\x12!\n" + "\fproject_name\x18\x04 \x01(\tR\vprojectName\x122\n" + "\x06status\x18\x03 \x01(\x0e2\x1a.controlplane.v1.RunStatusR\x06status\x124\n" + - "\x0fproject_version\x18\x05 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\x0eprojectVersion\x12T\n" + - "\x11policy_violations\x18\x06 \x01(\x0e2'.controlplane.v1.PolicyViolationsFilterR\x10policyViolations\x12H\n" + + "\x0fproject_version\x18\x05 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\x0eprojectVersion\x12X\n" + + "\x11policy_violations\x18\x06 \x01(\x0e2'.controlplane.v1.PolicyViolationsFilterB\x02\x18\x01R\x10policyViolations\x12H\n" + + "\rpolicy_status\x18\a \x01(\x0e2#.controlplane.v1.PolicyStatusFilterR\fpolicyStatus\x12H\n" + "\n" + "pagination\x18\x02 \x01(\v2(.controlplane.v1.CursorPaginationRequestR\n" + "pagination:\x8e\x01\xbaH\x8a\x01\x1a\x87\x01\n" + @@ -1913,12 +1928,13 @@ var file_controlplane_v1_workflow_run_proto_goTypes = []any{ (v1.CraftingSchema_Runner_RunnerType)(0), // 32: workflowcontract.v1.CraftingSchema.Runner.RunnerType (RunStatus)(0), // 33: controlplane.v1.RunStatus (PolicyViolationsFilter)(0), // 34: controlplane.v1.PolicyViolationsFilter - (*CursorPaginationRequest)(nil), // 35: controlplane.v1.CursorPaginationRequest - (*WorkflowRunItem)(nil), // 36: controlplane.v1.WorkflowRunItem - (*CursorPaginationResponse)(nil), // 37: controlplane.v1.CursorPaginationResponse - (*WorkflowContractVersionItem)(nil), // 38: controlplane.v1.WorkflowContractVersionItem - (*AttestationItem)(nil), // 39: controlplane.v1.AttestationItem - (*CASBackendItem)(nil), // 40: controlplane.v1.CASBackendItem + (PolicyStatusFilter)(0), // 35: controlplane.v1.PolicyStatusFilter + (*CursorPaginationRequest)(nil), // 36: controlplane.v1.CursorPaginationRequest + (*WorkflowRunItem)(nil), // 37: controlplane.v1.WorkflowRunItem + (*CursorPaginationResponse)(nil), // 38: controlplane.v1.CursorPaginationResponse + (*WorkflowContractVersionItem)(nil), // 39: controlplane.v1.WorkflowContractVersionItem + (*AttestationItem)(nil), // 40: controlplane.v1.AttestationItem + (*CASBackendItem)(nil), // 41: controlplane.v1.CASBackendItem } var file_controlplane_v1_workflow_run_proto_depIdxs = []int32{ 29, // 0: controlplane.v1.FindOrCreateWorkflowResponse.result:type_name -> controlplane.v1.WorkflowItem @@ -1933,44 +1949,45 @@ var file_controlplane_v1_workflow_run_proto_depIdxs = []int32{ 0, // 9: controlplane.v1.AttestationServiceCancelRequest.trigger:type_name -> controlplane.v1.AttestationServiceCancelRequest.TriggerType 33, // 10: controlplane.v1.WorkflowRunServiceListRequest.status:type_name -> controlplane.v1.RunStatus 34, // 11: controlplane.v1.WorkflowRunServiceListRequest.policy_violations:type_name -> controlplane.v1.PolicyViolationsFilter - 35, // 12: controlplane.v1.WorkflowRunServiceListRequest.pagination:type_name -> controlplane.v1.CursorPaginationRequest - 36, // 13: controlplane.v1.WorkflowRunServiceListResponse.result:type_name -> controlplane.v1.WorkflowRunItem - 37, // 14: controlplane.v1.WorkflowRunServiceListResponse.pagination:type_name -> controlplane.v1.CursorPaginationResponse - 26, // 15: controlplane.v1.WorkflowRunServiceViewResponse.result:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.Result - 28, // 16: controlplane.v1.AttestationServiceGetUploadCredsResponse.result:type_name -> controlplane.v1.AttestationServiceGetUploadCredsResponse.Result - 29, // 17: controlplane.v1.AttestationServiceGetContractResponse.Result.workflow:type_name -> controlplane.v1.WorkflowItem - 38, // 18: controlplane.v1.AttestationServiceGetContractResponse.Result.contract:type_name -> controlplane.v1.WorkflowContractVersionItem - 36, // 19: controlplane.v1.AttestationServiceInitResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem - 24, // 20: controlplane.v1.AttestationServiceInitResponse.Result.signing_options:type_name -> controlplane.v1.AttestationServiceInitResponse.SigningOptions - 36, // 21: controlplane.v1.WorkflowRunServiceViewResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem - 39, // 22: controlplane.v1.WorkflowRunServiceViewResponse.Result.attestation:type_name -> controlplane.v1.AttestationItem - 27, // 23: controlplane.v1.WorkflowRunServiceViewResponse.Result.verification:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.VerificationResult - 40, // 24: controlplane.v1.AttestationServiceGetUploadCredsResponse.Result.backend:type_name -> controlplane.v1.CASBackendItem - 1, // 25: controlplane.v1.AttestationService.FindOrCreateWorkflow:input_type -> controlplane.v1.FindOrCreateWorkflowRequest - 8, // 26: controlplane.v1.AttestationService.GetContract:input_type -> controlplane.v1.AttestationServiceGetContractRequest - 10, // 27: controlplane.v1.AttestationService.Init:input_type -> controlplane.v1.AttestationServiceInitRequest - 12, // 28: controlplane.v1.AttestationService.Store:input_type -> controlplane.v1.AttestationServiceStoreRequest - 20, // 29: controlplane.v1.AttestationService.GetUploadCreds:input_type -> controlplane.v1.AttestationServiceGetUploadCredsRequest - 14, // 30: controlplane.v1.AttestationService.Cancel:input_type -> controlplane.v1.AttestationServiceCancelRequest - 3, // 31: controlplane.v1.AttestationService.GetPolicy:input_type -> controlplane.v1.AttestationServiceGetPolicyRequest - 6, // 32: controlplane.v1.AttestationService.GetPolicyGroup:input_type -> controlplane.v1.AttestationServiceGetPolicyGroupRequest - 16, // 33: controlplane.v1.WorkflowRunService.List:input_type -> controlplane.v1.WorkflowRunServiceListRequest - 18, // 34: controlplane.v1.WorkflowRunService.View:input_type -> controlplane.v1.WorkflowRunServiceViewRequest - 2, // 35: controlplane.v1.AttestationService.FindOrCreateWorkflow:output_type -> controlplane.v1.FindOrCreateWorkflowResponse - 9, // 36: controlplane.v1.AttestationService.GetContract:output_type -> controlplane.v1.AttestationServiceGetContractResponse - 11, // 37: controlplane.v1.AttestationService.Init:output_type -> controlplane.v1.AttestationServiceInitResponse - 13, // 38: controlplane.v1.AttestationService.Store:output_type -> controlplane.v1.AttestationServiceStoreResponse - 21, // 39: controlplane.v1.AttestationService.GetUploadCreds:output_type -> controlplane.v1.AttestationServiceGetUploadCredsResponse - 15, // 40: controlplane.v1.AttestationService.Cancel:output_type -> controlplane.v1.AttestationServiceCancelResponse - 4, // 41: controlplane.v1.AttestationService.GetPolicy:output_type -> controlplane.v1.AttestationServiceGetPolicyResponse - 7, // 42: controlplane.v1.AttestationService.GetPolicyGroup:output_type -> controlplane.v1.AttestationServiceGetPolicyGroupResponse - 17, // 43: controlplane.v1.WorkflowRunService.List:output_type -> controlplane.v1.WorkflowRunServiceListResponse - 19, // 44: controlplane.v1.WorkflowRunService.View:output_type -> controlplane.v1.WorkflowRunServiceViewResponse - 35, // [35:45] is the sub-list for method output_type - 25, // [25:35] is the sub-list for method input_type - 25, // [25:25] is the sub-list for extension type_name - 25, // [25:25] is the sub-list for extension extendee - 0, // [0:25] is the sub-list for field type_name + 35, // 12: controlplane.v1.WorkflowRunServiceListRequest.policy_status:type_name -> controlplane.v1.PolicyStatusFilter + 36, // 13: controlplane.v1.WorkflowRunServiceListRequest.pagination:type_name -> controlplane.v1.CursorPaginationRequest + 37, // 14: controlplane.v1.WorkflowRunServiceListResponse.result:type_name -> controlplane.v1.WorkflowRunItem + 38, // 15: controlplane.v1.WorkflowRunServiceListResponse.pagination:type_name -> controlplane.v1.CursorPaginationResponse + 26, // 16: controlplane.v1.WorkflowRunServiceViewResponse.result:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.Result + 28, // 17: controlplane.v1.AttestationServiceGetUploadCredsResponse.result:type_name -> controlplane.v1.AttestationServiceGetUploadCredsResponse.Result + 29, // 18: controlplane.v1.AttestationServiceGetContractResponse.Result.workflow:type_name -> controlplane.v1.WorkflowItem + 39, // 19: controlplane.v1.AttestationServiceGetContractResponse.Result.contract:type_name -> controlplane.v1.WorkflowContractVersionItem + 37, // 20: controlplane.v1.AttestationServiceInitResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem + 24, // 21: controlplane.v1.AttestationServiceInitResponse.Result.signing_options:type_name -> controlplane.v1.AttestationServiceInitResponse.SigningOptions + 37, // 22: controlplane.v1.WorkflowRunServiceViewResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem + 40, // 23: controlplane.v1.WorkflowRunServiceViewResponse.Result.attestation:type_name -> controlplane.v1.AttestationItem + 27, // 24: controlplane.v1.WorkflowRunServiceViewResponse.Result.verification:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.VerificationResult + 41, // 25: controlplane.v1.AttestationServiceGetUploadCredsResponse.Result.backend:type_name -> controlplane.v1.CASBackendItem + 1, // 26: controlplane.v1.AttestationService.FindOrCreateWorkflow:input_type -> controlplane.v1.FindOrCreateWorkflowRequest + 8, // 27: controlplane.v1.AttestationService.GetContract:input_type -> controlplane.v1.AttestationServiceGetContractRequest + 10, // 28: controlplane.v1.AttestationService.Init:input_type -> controlplane.v1.AttestationServiceInitRequest + 12, // 29: controlplane.v1.AttestationService.Store:input_type -> controlplane.v1.AttestationServiceStoreRequest + 20, // 30: controlplane.v1.AttestationService.GetUploadCreds:input_type -> controlplane.v1.AttestationServiceGetUploadCredsRequest + 14, // 31: controlplane.v1.AttestationService.Cancel:input_type -> controlplane.v1.AttestationServiceCancelRequest + 3, // 32: controlplane.v1.AttestationService.GetPolicy:input_type -> controlplane.v1.AttestationServiceGetPolicyRequest + 6, // 33: controlplane.v1.AttestationService.GetPolicyGroup:input_type -> controlplane.v1.AttestationServiceGetPolicyGroupRequest + 16, // 34: controlplane.v1.WorkflowRunService.List:input_type -> controlplane.v1.WorkflowRunServiceListRequest + 18, // 35: controlplane.v1.WorkflowRunService.View:input_type -> controlplane.v1.WorkflowRunServiceViewRequest + 2, // 36: controlplane.v1.AttestationService.FindOrCreateWorkflow:output_type -> controlplane.v1.FindOrCreateWorkflowResponse + 9, // 37: controlplane.v1.AttestationService.GetContract:output_type -> controlplane.v1.AttestationServiceGetContractResponse + 11, // 38: controlplane.v1.AttestationService.Init:output_type -> controlplane.v1.AttestationServiceInitResponse + 13, // 39: controlplane.v1.AttestationService.Store:output_type -> controlplane.v1.AttestationServiceStoreResponse + 21, // 40: controlplane.v1.AttestationService.GetUploadCreds:output_type -> controlplane.v1.AttestationServiceGetUploadCredsResponse + 15, // 41: controlplane.v1.AttestationService.Cancel:output_type -> controlplane.v1.AttestationServiceCancelResponse + 4, // 42: controlplane.v1.AttestationService.GetPolicy:output_type -> controlplane.v1.AttestationServiceGetPolicyResponse + 7, // 43: controlplane.v1.AttestationService.GetPolicyGroup:output_type -> controlplane.v1.AttestationServiceGetPolicyGroupResponse + 17, // 44: controlplane.v1.WorkflowRunService.List:output_type -> controlplane.v1.WorkflowRunServiceListResponse + 19, // 45: controlplane.v1.WorkflowRunService.View:output_type -> controlplane.v1.WorkflowRunServiceViewResponse + 36, // [36:46] is the sub-list for method output_type + 26, // [26:36] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name } func init() { file_controlplane_v1_workflow_run_proto_init() } diff --git a/app/controlplane/api/controlplane/v1/workflow_run.proto b/app/controlplane/api/controlplane/v1/workflow_run.proto index 48cabc315..8abcd1ffb 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.proto +++ b/app/controlplane/api/controlplane/v1/workflow_run.proto @@ -215,7 +215,11 @@ message WorkflowRunServiceListRequest { ignore: IGNORE_IF_ZERO_VALUE }]; // by policy violations status - PolicyViolationsFilter policy_violations = 6; + // Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with + // the canonical PolicyStatus enum. When both are set, policy_status wins. + PolicyViolationsFilter policy_violations = 6 [deprecated = true]; + // by canonical policy status + PolicyStatusFilter policy_status = 7; // pagination options CursorPaginationRequest pagination = 2; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index ed9f68968..04d948e7f 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -68,6 +68,13 @@ export function runStatusToJSON(object: RunStatus): string { } } +/** + * Deprecated: use PolicyStatusFilter which aligns 1:1 with PolicyStatus and + * lets callers distinguish warning/blocked/bypassed from the coarse with/ + * without-violations split. + * + * @deprecated + */ export enum PolicyViolationsFilter { POLICY_VIOLATIONS_FILTER_UNSPECIFIED = 0, POLICY_VIOLATIONS_FILTER_WITH_VIOLATIONS = 1, @@ -107,6 +114,146 @@ export function policyViolationsFilterToJSON(object: PolicyViolationsFilter): st } } +/** + * Canonical, server-computed categorical policy outcome for an attestation. + * Collapses the raw enforcement/bypass/violation signals on + * AttestationItem.PolicyEvaluationStatus into a single flat value so that + * list and describe surfaces can render a consistent badge without + * re-deriving. + */ +export enum PolicyStatus { + POLICY_STATUS_UNSPECIFIED = 0, + /** POLICY_STATUS_NOT_APPLICABLE - No policies were evaluated on this run */ + POLICY_STATUS_NOT_APPLICABLE = 1, + /** POLICY_STATUS_PASSED - Policies ran with no violations and no skips */ + POLICY_STATUS_PASSED = 2, + /** POLICY_STATUS_SKIPPED - No violations but at least one evaluation was skipped */ + POLICY_STATUS_SKIPPED = 3, + /** POLICY_STATUS_WARNING - Has violations but enforcement is advisory — run succeeded */ + POLICY_STATUS_WARNING = 4, + /** POLICY_STATUS_BLOCKED - Has gated violations or enforced strategy with violations; not bypassed */ + POLICY_STATUS_BLOCKED = 5, + /** POLICY_STATUS_BYPASSED - Enforcement would have blocked the run but was bypassed */ + POLICY_STATUS_BYPASSED = 6, + UNRECOGNIZED = -1, +} + +export function policyStatusFromJSON(object: any): PolicyStatus { + switch (object) { + case 0: + case "POLICY_STATUS_UNSPECIFIED": + return PolicyStatus.POLICY_STATUS_UNSPECIFIED; + case 1: + case "POLICY_STATUS_NOT_APPLICABLE": + return PolicyStatus.POLICY_STATUS_NOT_APPLICABLE; + case 2: + case "POLICY_STATUS_PASSED": + return PolicyStatus.POLICY_STATUS_PASSED; + case 3: + case "POLICY_STATUS_SKIPPED": + return PolicyStatus.POLICY_STATUS_SKIPPED; + case 4: + case "POLICY_STATUS_WARNING": + return PolicyStatus.POLICY_STATUS_WARNING; + case 5: + case "POLICY_STATUS_BLOCKED": + return PolicyStatus.POLICY_STATUS_BLOCKED; + case 6: + case "POLICY_STATUS_BYPASSED": + return PolicyStatus.POLICY_STATUS_BYPASSED; + case -1: + case "UNRECOGNIZED": + default: + return PolicyStatus.UNRECOGNIZED; + } +} + +export function policyStatusToJSON(object: PolicyStatus): string { + switch (object) { + case PolicyStatus.POLICY_STATUS_UNSPECIFIED: + return "POLICY_STATUS_UNSPECIFIED"; + case PolicyStatus.POLICY_STATUS_NOT_APPLICABLE: + return "POLICY_STATUS_NOT_APPLICABLE"; + case PolicyStatus.POLICY_STATUS_PASSED: + return "POLICY_STATUS_PASSED"; + case PolicyStatus.POLICY_STATUS_SKIPPED: + return "POLICY_STATUS_SKIPPED"; + case PolicyStatus.POLICY_STATUS_WARNING: + return "POLICY_STATUS_WARNING"; + case PolicyStatus.POLICY_STATUS_BLOCKED: + return "POLICY_STATUS_BLOCKED"; + case PolicyStatus.POLICY_STATUS_BYPASSED: + return "POLICY_STATUS_BYPASSED"; + case PolicyStatus.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +/** Server-side filter aligned 1:1 with PolicyStatus values. */ +export enum PolicyStatusFilter { + POLICY_STATUS_FILTER_UNSPECIFIED = 0, + POLICY_STATUS_FILTER_NOT_APPLICABLE = 1, + POLICY_STATUS_FILTER_PASSED = 2, + POLICY_STATUS_FILTER_SKIPPED = 3, + POLICY_STATUS_FILTER_WARNING = 4, + POLICY_STATUS_FILTER_BLOCKED = 5, + POLICY_STATUS_FILTER_BYPASSED = 6, + UNRECOGNIZED = -1, +} + +export function policyStatusFilterFromJSON(object: any): PolicyStatusFilter { + switch (object) { + case 0: + case "POLICY_STATUS_FILTER_UNSPECIFIED": + return PolicyStatusFilter.POLICY_STATUS_FILTER_UNSPECIFIED; + case 1: + case "POLICY_STATUS_FILTER_NOT_APPLICABLE": + return PolicyStatusFilter.POLICY_STATUS_FILTER_NOT_APPLICABLE; + case 2: + case "POLICY_STATUS_FILTER_PASSED": + return PolicyStatusFilter.POLICY_STATUS_FILTER_PASSED; + case 3: + case "POLICY_STATUS_FILTER_SKIPPED": + return PolicyStatusFilter.POLICY_STATUS_FILTER_SKIPPED; + case 4: + case "POLICY_STATUS_FILTER_WARNING": + return PolicyStatusFilter.POLICY_STATUS_FILTER_WARNING; + case 5: + case "POLICY_STATUS_FILTER_BLOCKED": + return PolicyStatusFilter.POLICY_STATUS_FILTER_BLOCKED; + case 6: + case "POLICY_STATUS_FILTER_BYPASSED": + return PolicyStatusFilter.POLICY_STATUS_FILTER_BYPASSED; + case -1: + case "UNRECOGNIZED": + default: + return PolicyStatusFilter.UNRECOGNIZED; + } +} + +export function policyStatusFilterToJSON(object: PolicyStatusFilter): string { + switch (object) { + case PolicyStatusFilter.POLICY_STATUS_FILTER_UNSPECIFIED: + return "POLICY_STATUS_FILTER_UNSPECIFIED"; + case PolicyStatusFilter.POLICY_STATUS_FILTER_NOT_APPLICABLE: + return "POLICY_STATUS_FILTER_NOT_APPLICABLE"; + case PolicyStatusFilter.POLICY_STATUS_FILTER_PASSED: + return "POLICY_STATUS_FILTER_PASSED"; + case PolicyStatusFilter.POLICY_STATUS_FILTER_SKIPPED: + return "POLICY_STATUS_FILTER_SKIPPED"; + case PolicyStatusFilter.POLICY_STATUS_FILTER_WARNING: + return "POLICY_STATUS_FILTER_WARNING"; + case PolicyStatusFilter.POLICY_STATUS_FILTER_BLOCKED: + return "POLICY_STATUS_FILTER_BLOCKED"; + case PolicyStatusFilter.POLICY_STATUS_FILTER_BYPASSED: + return "POLICY_STATUS_FILTER_BYPASSED"; + case PolicyStatusFilter.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + export enum MembershipRole { MEMBERSHIP_ROLE_UNSPECIFIED = 0, MEMBERSHIP_ROLE_ORG_VIEWER = 1, @@ -342,7 +489,15 @@ export interface WorkflowRunItem { /** The version of the project the attestation was initiated with */ version?: ProjectVersion; /** Whether the run has policy violations (null if no policies were evaluated) */ - hasPolicyViolations?: boolean | undefined; + hasPolicyViolations?: + | boolean + | undefined; + /** + * Canonical policy status summary for this run (null if no policies were + * evaluated). Carries both the categorical PolicyStatus and per-evaluation + * counters so list consumers can render a badge without calling View. + */ + policySummary?: PolicyStatusSummary; } export interface ProjectVersion { @@ -354,6 +509,24 @@ export interface ProjectVersion { releasedAt?: Date; } +/** + * PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation + * counters. It is surfaced on both WorkflowRunItem (list response) and + * AttestationItem.PolicyEvaluationStatus (describe response) and is computed + * by a single backend helper so list and describe cannot drift. + */ +export interface PolicyStatusSummary { + status: PolicyStatus; + /** Total number of policy evaluations that ran for this attestation */ + total: number; + /** Number of evaluations with no violations and not skipped */ + passed: number; + /** Number of evaluations that were skipped */ + skipped: number; + /** Total number of violations across all evaluations */ + violated: number; +} + export interface AttestationItem { /** * encoded DSEE envelope @@ -392,10 +565,24 @@ export interface AttestationItem_PolicyEvaluationStatus { blocked: boolean; hasViolations: boolean; hasGatedViolations: boolean; - /** Total number of policy evaluations */ + /** + * Deprecated: use summary.total instead. + * + * @deprecated + */ evaluationsCount: number; - /** Total number of policy violations across all evaluations */ + /** + * Deprecated: use summary.violated instead. + * + * @deprecated + */ violationsCount: number; + /** + * Canonical categorical status + counters. Single source of truth for UI + * consumers — consumers that re-derive from the raw bools above tend to + * disagree on semantics, especially around gating and bypass. + */ + summary?: PolicyStatusSummary; } export interface AttestationItem_EnvVariable { @@ -995,6 +1182,7 @@ function createBaseWorkflowRunItem(): WorkflowRunItem { contractRevisionLatest: 0, version: undefined, hasPolicyViolations: undefined, + policySummary: undefined, }; } @@ -1042,6 +1230,9 @@ export const WorkflowRunItem = { if (message.hasPolicyViolations !== undefined) { writer.uint32(112).bool(message.hasPolicyViolations); } + if (message.policySummary !== undefined) { + PolicyStatusSummary.encode(message.policySummary, writer.uint32(122).fork()).ldelim(); + } return writer; }, @@ -1150,6 +1341,13 @@ export const WorkflowRunItem = { message.hasPolicyViolations = reader.bool(); continue; + case 15: + if (tag !== 122) { + break; + } + + message.policySummary = PolicyStatusSummary.decode(reader, reader.uint32()); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1177,6 +1375,7 @@ export const WorkflowRunItem = { contractRevisionLatest: isSet(object.contractRevisionLatest) ? Number(object.contractRevisionLatest) : 0, version: isSet(object.version) ? ProjectVersion.fromJSON(object.version) : undefined, hasPolicyViolations: isSet(object.hasPolicyViolations) ? Boolean(object.hasPolicyViolations) : undefined, + policySummary: isSet(object.policySummary) ? PolicyStatusSummary.fromJSON(object.policySummary) : undefined, }; }, @@ -1201,6 +1400,8 @@ export const WorkflowRunItem = { message.version !== undefined && (obj.version = message.version ? ProjectVersion.toJSON(message.version) : undefined); message.hasPolicyViolations !== undefined && (obj.hasPolicyViolations = message.hasPolicyViolations); + message.policySummary !== undefined && + (obj.policySummary = message.policySummary ? PolicyStatusSummary.toJSON(message.policySummary) : undefined); return obj; }, @@ -1230,6 +1431,9 @@ export const WorkflowRunItem = { ? ProjectVersion.fromPartial(object.version) : undefined; message.hasPolicyViolations = object.hasPolicyViolations ?? undefined; + message.policySummary = (object.policySummary !== undefined && object.policySummary !== null) + ? PolicyStatusSummary.fromPartial(object.policySummary) + : undefined; return message; }, }; @@ -1344,6 +1548,116 @@ export const ProjectVersion = { }, }; +function createBasePolicyStatusSummary(): PolicyStatusSummary { + return { status: 0, total: 0, passed: 0, skipped: 0, violated: 0 }; +} + +export const PolicyStatusSummary = { + encode(message: PolicyStatusSummary, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.status !== 0) { + writer.uint32(8).int32(message.status); + } + if (message.total !== 0) { + writer.uint32(16).int32(message.total); + } + if (message.passed !== 0) { + writer.uint32(24).int32(message.passed); + } + if (message.skipped !== 0) { + writer.uint32(32).int32(message.skipped); + } + if (message.violated !== 0) { + writer.uint32(40).int32(message.violated); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): PolicyStatusSummary { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePolicyStatusSummary(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.status = reader.int32() as any; + continue; + case 2: + if (tag !== 16) { + break; + } + + message.total = reader.int32(); + continue; + case 3: + if (tag !== 24) { + break; + } + + message.passed = reader.int32(); + continue; + case 4: + if (tag !== 32) { + break; + } + + message.skipped = reader.int32(); + continue; + case 5: + if (tag !== 40) { + break; + } + + message.violated = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): PolicyStatusSummary { + return { + status: isSet(object.status) ? policyStatusFromJSON(object.status) : 0, + total: isSet(object.total) ? Number(object.total) : 0, + passed: isSet(object.passed) ? Number(object.passed) : 0, + skipped: isSet(object.skipped) ? Number(object.skipped) : 0, + violated: isSet(object.violated) ? Number(object.violated) : 0, + }; + }, + + toJSON(message: PolicyStatusSummary): unknown { + const obj: any = {}; + message.status !== undefined && (obj.status = policyStatusToJSON(message.status)); + message.total !== undefined && (obj.total = Math.round(message.total)); + message.passed !== undefined && (obj.passed = Math.round(message.passed)); + message.skipped !== undefined && (obj.skipped = Math.round(message.skipped)); + message.violated !== undefined && (obj.violated = Math.round(message.violated)); + return obj; + }, + + create, I>>(base?: I): PolicyStatusSummary { + return PolicyStatusSummary.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): PolicyStatusSummary { + const message = createBasePolicyStatusSummary(); + message.status = object.status ?? 0; + message.total = object.total ?? 0; + message.passed = object.passed ?? 0; + message.skipped = object.skipped ?? 0; + message.violated = object.violated ?? 0; + return message; + }, +}; + function createBaseAttestationItem(): AttestationItem { return { envelope: new Uint8Array(0), @@ -1722,6 +2036,7 @@ function createBaseAttestationItem_PolicyEvaluationStatus(): AttestationItem_Pol hasGatedViolations: false, evaluationsCount: 0, violationsCount: 0, + summary: undefined, }; } @@ -1748,6 +2063,9 @@ export const AttestationItem_PolicyEvaluationStatus = { if (message.violationsCount !== 0) { writer.uint32(56).int32(message.violationsCount); } + if (message.summary !== undefined) { + PolicyStatusSummary.encode(message.summary, writer.uint32(66).fork()).ldelim(); + } return writer; }, @@ -1807,6 +2125,13 @@ export const AttestationItem_PolicyEvaluationStatus = { message.violationsCount = reader.int32(); continue; + case 8: + if (tag !== 66) { + break; + } + + message.summary = PolicyStatusSummary.decode(reader, reader.uint32()); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1825,6 +2150,7 @@ export const AttestationItem_PolicyEvaluationStatus = { hasGatedViolations: isSet(object.hasGatedViolations) ? Boolean(object.hasGatedViolations) : false, evaluationsCount: isSet(object.evaluationsCount) ? Number(object.evaluationsCount) : 0, violationsCount: isSet(object.violationsCount) ? Number(object.violationsCount) : 0, + summary: isSet(object.summary) ? PolicyStatusSummary.fromJSON(object.summary) : undefined, }; }, @@ -1837,6 +2163,8 @@ export const AttestationItem_PolicyEvaluationStatus = { message.hasGatedViolations !== undefined && (obj.hasGatedViolations = message.hasGatedViolations); message.evaluationsCount !== undefined && (obj.evaluationsCount = Math.round(message.evaluationsCount)); message.violationsCount !== undefined && (obj.violationsCount = Math.round(message.violationsCount)); + message.summary !== undefined && + (obj.summary = message.summary ? PolicyStatusSummary.toJSON(message.summary) : undefined); return obj; }, @@ -1857,6 +2185,9 @@ export const AttestationItem_PolicyEvaluationStatus = { message.hasGatedViolations = object.hasGatedViolations ?? false; message.evaluationsCount = object.evaluationsCount ?? 0; message.violationsCount = object.violationsCount ?? 0; + message.summary = (object.summary !== undefined && object.summary !== null) + ? PolicyStatusSummary.fromPartial(object.summary) + : undefined; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts index eaa841007..55f8572d7 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts @@ -13,6 +13,9 @@ import { CursorPaginationRequest, CursorPaginationResponse } from "./pagination" import { AttestationItem, CASBackendItem, + PolicyStatusFilter, + policyStatusFilterFromJSON, + policyStatusFilterToJSON, PolicyViolationsFilter, policyViolationsFilterFromJSON, policyViolationsFilterToJSON, @@ -214,8 +217,16 @@ export interface WorkflowRunServiceListRequest { status: RunStatus; /** by project version */ projectVersion: string; - /** by policy violations status */ + /** + * by policy violations status + * Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with + * the canonical PolicyStatus enum. When both are set, policy_status wins. + * + * @deprecated + */ policyViolations: PolicyViolationsFilter; + /** by canonical policy status */ + policyStatus: PolicyStatusFilter; /** pagination options */ pagination?: CursorPaginationRequest; } @@ -1879,6 +1890,7 @@ function createBaseWorkflowRunServiceListRequest(): WorkflowRunServiceListReques status: 0, projectVersion: "", policyViolations: 0, + policyStatus: 0, pagination: undefined, }; } @@ -1900,6 +1912,9 @@ export const WorkflowRunServiceListRequest = { if (message.policyViolations !== 0) { writer.uint32(48).int32(message.policyViolations); } + if (message.policyStatus !== 0) { + writer.uint32(56).int32(message.policyStatus); + } if (message.pagination !== undefined) { CursorPaginationRequest.encode(message.pagination, writer.uint32(18).fork()).ldelim(); } @@ -1948,6 +1963,13 @@ export const WorkflowRunServiceListRequest = { message.policyViolations = reader.int32() as any; continue; + case 7: + if (tag !== 56) { + break; + } + + message.policyStatus = reader.int32() as any; + continue; case 2: if (tag !== 18) { break; @@ -1971,6 +1993,7 @@ export const WorkflowRunServiceListRequest = { status: isSet(object.status) ? runStatusFromJSON(object.status) : 0, projectVersion: isSet(object.projectVersion) ? String(object.projectVersion) : "", policyViolations: isSet(object.policyViolations) ? policyViolationsFilterFromJSON(object.policyViolations) : 0, + policyStatus: isSet(object.policyStatus) ? policyStatusFilterFromJSON(object.policyStatus) : 0, pagination: isSet(object.pagination) ? CursorPaginationRequest.fromJSON(object.pagination) : undefined, }; }, @@ -1983,6 +2006,7 @@ export const WorkflowRunServiceListRequest = { message.projectVersion !== undefined && (obj.projectVersion = message.projectVersion); message.policyViolations !== undefined && (obj.policyViolations = policyViolationsFilterToJSON(message.policyViolations)); + message.policyStatus !== undefined && (obj.policyStatus = policyStatusFilterToJSON(message.policyStatus)); message.pagination !== undefined && (obj.pagination = message.pagination ? CursorPaginationRequest.toJSON(message.pagination) : undefined); return obj; @@ -2001,6 +2025,7 @@ export const WorkflowRunServiceListRequest = { message.status = object.status ?? 0; message.projectVersion = object.projectVersion ?? ""; message.policyViolations = object.policyViolations ?? 0; + message.policyStatus = object.policyStatus ?? 0; message.pagination = (object.pagination !== undefined && object.pagination !== null) ? CursorPaginationRequest.fromPartial(object.pagination) : undefined; diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json index 1f40e6028..d84b22248 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json @@ -4,7 +4,7 @@ "additionalProperties": false, "patternProperties": { "^(evaluations_count)$": { - "description": "Total number of policy evaluations", + "description": "Deprecated: use summary.total instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" @@ -16,7 +16,7 @@ "type": "boolean" }, "^(violations_count)$": { - "description": "Total number of policy violations across all evaluations", + "description": "Deprecated: use summary.violated instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" @@ -30,7 +30,7 @@ "type": "boolean" }, "evaluationsCount": { - "description": "Total number of policy evaluations", + "description": "Deprecated: use summary.total instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" @@ -44,8 +44,12 @@ "strategy": { "type": "string" }, + "summary": { + "$ref": "controlplane.v1.PolicyStatusSummary.jsonschema.json", + "description": "Canonical categorical status + counters. Single source of truth for UI\n consumers — consumers that re-derive from the raw bools above tend to\n disagree on semantics, especially around gating and bypass." + }, "violationsCount": { - "description": "Total number of policy violations across all evaluations", + "description": "Deprecated: use summary.violated instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json index bf75df7fa..db7fc7e01 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json @@ -4,7 +4,7 @@ "additionalProperties": false, "patternProperties": { "^(evaluationsCount)$": { - "description": "Total number of policy evaluations", + "description": "Deprecated: use summary.total instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" @@ -16,7 +16,7 @@ "type": "boolean" }, "^(violationsCount)$": { - "description": "Total number of policy violations across all evaluations", + "description": "Deprecated: use summary.violated instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" @@ -30,7 +30,7 @@ "type": "boolean" }, "evaluations_count": { - "description": "Total number of policy evaluations", + "description": "Deprecated: use summary.total instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" @@ -44,8 +44,12 @@ "strategy": { "type": "string" }, + "summary": { + "$ref": "controlplane.v1.PolicyStatusSummary.schema.json", + "description": "Canonical categorical status + counters. Single source of truth for UI\n consumers — consumers that re-derive from the raw bools above tend to\n disagree on semantics, especially around gating and bypass." + }, "violations_count": { - "description": "Total number of policy violations across all evaluations", + "description": "Deprecated: use summary.violated instead.", "maximum": 2147483647, "minimum": -2147483648, "type": "integer" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json new file mode 100644 index 000000000..a8addf852 --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json @@ -0,0 +1,56 @@ +{ + "$id": "controlplane.v1.PolicyStatusSummary.jsonschema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation\n counters. It is surfaced on both WorkflowRunItem (list response) and\n AttestationItem.PolicyEvaluationStatus (describe response) and is computed\n by a single backend helper so list and describe cannot drift.", + "properties": { + "passed": { + "description": "Number of evaluations with no violations and not skipped", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "skipped": { + "description": "Number of evaluations that were skipped", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "status": { + "anyOf": [ + { + "enum": [ + "POLICY_STATUS_UNSPECIFIED", + "POLICY_STATUS_NOT_APPLICABLE", + "POLICY_STATUS_PASSED", + "POLICY_STATUS_SKIPPED", + "POLICY_STATUS_WARNING", + "POLICY_STATUS_BLOCKED", + "POLICY_STATUS_BYPASSED" + ], + "title": "Policy Status", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ] + }, + "total": { + "description": "Total number of policy evaluations that ran for this attestation", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "violated": { + "description": "Total number of violations across all evaluations", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "title": "Policy Status Summary", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json new file mode 100644 index 000000000..519a2bb0a --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json @@ -0,0 +1,56 @@ +{ + "$id": "controlplane.v1.PolicyStatusSummary.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation\n counters. It is surfaced on both WorkflowRunItem (list response) and\n AttestationItem.PolicyEvaluationStatus (describe response) and is computed\n by a single backend helper so list and describe cannot drift.", + "properties": { + "passed": { + "description": "Number of evaluations with no violations and not skipped", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "skipped": { + "description": "Number of evaluations that were skipped", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "status": { + "anyOf": [ + { + "enum": [ + "POLICY_STATUS_UNSPECIFIED", + "POLICY_STATUS_NOT_APPLICABLE", + "POLICY_STATUS_PASSED", + "POLICY_STATUS_SKIPPED", + "POLICY_STATUS_WARNING", + "POLICY_STATUS_BLOCKED", + "POLICY_STATUS_BYPASSED" + ], + "title": "Policy Status", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ] + }, + "total": { + "description": "Total number of policy evaluations that ran for this attestation", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + }, + "violated": { + "description": "Total number of violations across all evaluations", + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + }, + "title": "Policy Status Summary", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json index cfa5aee10..f9c3f1eb4 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json @@ -31,6 +31,10 @@ "^(job_url)$": { "type": "string" }, + "^(policy_summary)$": { + "$ref": "controlplane.v1.PolicyStatusSummary.jsonschema.json", + "description": "Canonical policy status summary for this run (null if no policies were\n evaluated). Carries both the categorical PolicyStatus and per-evaluation\n counters so list consumers can render a badge without calling View." + }, "^(runner_type)$": { "anyOf": [ { @@ -89,6 +93,10 @@ "jobUrl": { "type": "string" }, + "policySummary": { + "$ref": "controlplane.v1.PolicyStatusSummary.jsonschema.json", + "description": "Canonical policy status summary for this run (null if no policies were\n evaluated). Carries both the categorical PolicyStatus and per-evaluation\n counters so list consumers can render a badge without calling View." + }, "reason": { "type": "string" }, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json index 67c1560a8..a25191490 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json @@ -31,6 +31,10 @@ "^(jobUrl)$": { "type": "string" }, + "^(policySummary)$": { + "$ref": "controlplane.v1.PolicyStatusSummary.schema.json", + "description": "Canonical policy status summary for this run (null if no policies were\n evaluated). Carries both the categorical PolicyStatus and per-evaluation\n counters so list consumers can render a badge without calling View." + }, "^(runnerType)$": { "anyOf": [ { @@ -89,6 +93,10 @@ "job_url": { "type": "string" }, + "policy_summary": { + "$ref": "controlplane.v1.PolicyStatusSummary.schema.json", + "description": "Canonical policy status summary for this run (null if no policies were\n evaluated). Carries both the categorical PolicyStatus and per-evaluation\n counters so list consumers can render a badge without calling View." + }, "reason": { "type": "string" }, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json index b9f8f1544..a963a8ec8 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json @@ -3,6 +3,29 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "patternProperties": { + "^(policy_status)$": { + "anyOf": [ + { + "enum": [ + "POLICY_STATUS_FILTER_UNSPECIFIED", + "POLICY_STATUS_FILTER_NOT_APPLICABLE", + "POLICY_STATUS_FILTER_PASSED", + "POLICY_STATUS_FILTER_SKIPPED", + "POLICY_STATUS_FILTER_WARNING", + "POLICY_STATUS_FILTER_BLOCKED", + "POLICY_STATUS_FILTER_BYPASSED" + ], + "title": "Policy Status Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by canonical policy status" + }, "^(policy_violations)$": { "anyOf": [ { @@ -20,7 +43,7 @@ "type": "integer" } ], - "description": "by policy violations status" + "description": "by policy violations status\n Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with\n the canonical PolicyStatus enum. When both are set, policy_status wins." }, "^(project_name)$": { "description": "Not required since filtering by workflow and project is optional", @@ -41,6 +64,29 @@ "$ref": "controlplane.v1.CursorPaginationRequest.jsonschema.json", "description": "pagination options" }, + "policyStatus": { + "anyOf": [ + { + "enum": [ + "POLICY_STATUS_FILTER_UNSPECIFIED", + "POLICY_STATUS_FILTER_NOT_APPLICABLE", + "POLICY_STATUS_FILTER_PASSED", + "POLICY_STATUS_FILTER_SKIPPED", + "POLICY_STATUS_FILTER_WARNING", + "POLICY_STATUS_FILTER_BLOCKED", + "POLICY_STATUS_FILTER_BYPASSED" + ], + "title": "Policy Status Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by canonical policy status" + }, "policyViolations": { "anyOf": [ { @@ -58,7 +104,7 @@ "type": "integer" } ], - "description": "by policy violations status" + "description": "by policy violations status\n Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with\n the canonical PolicyStatus enum. When both are set, policy_status wins." }, "projectName": { "description": "Not required since filtering by workflow and project is optional", diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json index 465fac74f..a198a9d61 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json @@ -3,6 +3,29 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "patternProperties": { + "^(policyStatus)$": { + "anyOf": [ + { + "enum": [ + "POLICY_STATUS_FILTER_UNSPECIFIED", + "POLICY_STATUS_FILTER_NOT_APPLICABLE", + "POLICY_STATUS_FILTER_PASSED", + "POLICY_STATUS_FILTER_SKIPPED", + "POLICY_STATUS_FILTER_WARNING", + "POLICY_STATUS_FILTER_BLOCKED", + "POLICY_STATUS_FILTER_BYPASSED" + ], + "title": "Policy Status Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by canonical policy status" + }, "^(policyViolations)$": { "anyOf": [ { @@ -20,7 +43,7 @@ "type": "integer" } ], - "description": "by policy violations status" + "description": "by policy violations status\n Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with\n the canonical PolicyStatus enum. When both are set, policy_status wins." }, "^(projectName)$": { "description": "Not required since filtering by workflow and project is optional", @@ -41,6 +64,29 @@ "$ref": "controlplane.v1.CursorPaginationRequest.schema.json", "description": "pagination options" }, + "policy_status": { + "anyOf": [ + { + "enum": [ + "POLICY_STATUS_FILTER_UNSPECIFIED", + "POLICY_STATUS_FILTER_NOT_APPLICABLE", + "POLICY_STATUS_FILTER_PASSED", + "POLICY_STATUS_FILTER_SKIPPED", + "POLICY_STATUS_FILTER_WARNING", + "POLICY_STATUS_FILTER_BLOCKED", + "POLICY_STATUS_FILTER_BYPASSED" + ], + "title": "Policy Status Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by canonical policy status" + }, "policy_violations": { "anyOf": [ { @@ -58,7 +104,7 @@ "type": "integer" } ], - "description": "by policy violations status" + "description": "by policy violations status\n Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with\n the canonical PolicyStatus enum. When both are set, policy_status wins." }, "project_name": { "description": "Not required since filtering by workflow and project is optional", diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index 9feeca93c..a2cfd9573 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -541,6 +541,7 @@ func bizAttestationToPb(att *biz.Attestation, predicate chainloop.NormalizablePr } policyEvaluationStatus := predicate.GetPolicyEvaluationStatus() + summary := chainloop.DerivePolicyStatusSummary(policyEvaluationStatus) return &cpAPI.AttestationItem{ Envelope: encodedAttestation, @@ -557,6 +558,7 @@ func bizAttestationToPb(att *biz.Attestation, predicate chainloop.NormalizablePr HasGatedViolations: policyEvaluationStatus.HasGatedViolations, EvaluationsCount: int32(policyEvaluationStatus.EvaluationsCount), ViolationsCount: int32(policyEvaluationStatus.ViolationsCount), + Summary: bizPolicyStatusSummaryToPb(&summary), }, Bundle: att.Bundle, }, nil diff --git a/app/controlplane/internal/service/workflowrun.go b/app/controlplane/internal/service/workflowrun.go index 66dd3a41d..ea9310b73 100644 --- a/app/controlplane/internal/service/workflowrun.go +++ b/app/controlplane/internal/service/workflowrun.go @@ -171,12 +171,21 @@ func (s *WorkflowRunService) List(ctx context.Context, req *pb.WorkflowRunServic filters.Status = st } - // by policy violations status + // by policy violations status (legacy, coarse — kept for back-compat) + //nolint:staticcheck // honoring the deprecated field for older clients if req.GetPolicyViolations() != pb.PolicyViolationsFilter_POLICY_VIOLATIONS_FILTER_UNSPECIFIED { + //nolint:staticcheck // honoring the deprecated field for older clients hasViolations := req.GetPolicyViolations() == pb.PolicyViolationsFilter_POLICY_VIOLATIONS_FILTER_WITH_VIOLATIONS filters.PolicyViolationsFilter = &hasViolations } + // by canonical policy status — takes precedence over policy_violations + // when both are set (documented on the request message). + if req.GetPolicyStatus() != pb.PolicyStatusFilter_POLICY_STATUS_FILTER_UNSPECIFIED { + s := pbPolicyStatusFilterToBiz(req.GetPolicyStatus()) + filters.PolicyStatus = &s + } + p := req.GetPagination() paginationOpts, err := pagination.NewCursor(p.GetCursor(), int(p.GetLimit())) if err != nil { @@ -312,6 +321,7 @@ func bizWorkFlowRunToPb(wfr *biz.WorkflowRun) *pb.WorkflowRunItem { ContractRevisionLatest: int32(wfr.ContractRevisionLatest), Version: bizProjectVersionToPb(wfr.ProjectVersion), HasPolicyViolations: wfr.HasPolicyViolations, + PolicySummary: bizPolicyStatusSummaryToPb(wfr.PolicyStatus), } if wfr.FinishedAt != nil { @@ -378,6 +388,61 @@ func bizWorkflowRunStatusToPb(st biz.WorkflowRunStatus) pb.RunStatus { return m[st] } +// bizPolicyStatusSummaryToPb maps the domain summary onto its protobuf shape. +// Returns nil for nil input so rows predating the materialization change +// travel over the wire as "no policy_summary present" and clients can fall +// back to has_policy_violations. +func bizPolicyStatusSummaryToPb(s *chainloop.PolicyStatusSummary) *pb.PolicyStatusSummary { + if s == nil { + return nil + } + return &pb.PolicyStatusSummary{ + Status: bizPolicyStatusToPb(s.Status), + Total: int32(s.Total), + Passed: int32(s.Passed), + Skipped: int32(s.Skipped), + Violated: int32(s.Violated), + } +} + +func bizPolicyStatusToPb(s chainloop.PolicyStatus) pb.PolicyStatus { + switch s { + case chainloop.PolicyStatusNotApplicable: + return pb.PolicyStatus_POLICY_STATUS_NOT_APPLICABLE + case chainloop.PolicyStatusPassed: + return pb.PolicyStatus_POLICY_STATUS_PASSED + case chainloop.PolicyStatusSkipped: + return pb.PolicyStatus_POLICY_STATUS_SKIPPED + case chainloop.PolicyStatusWarning: + return pb.PolicyStatus_POLICY_STATUS_WARNING + case chainloop.PolicyStatusBlocked: + return pb.PolicyStatus_POLICY_STATUS_BLOCKED + case chainloop.PolicyStatusBypassed: + return pb.PolicyStatus_POLICY_STATUS_BYPASSED + default: + return pb.PolicyStatus_POLICY_STATUS_UNSPECIFIED + } +} + +func pbPolicyStatusFilterToBiz(f pb.PolicyStatusFilter) chainloop.PolicyStatus { + switch f { + case pb.PolicyStatusFilter_POLICY_STATUS_FILTER_NOT_APPLICABLE: + return chainloop.PolicyStatusNotApplicable + case pb.PolicyStatusFilter_POLICY_STATUS_FILTER_PASSED: + return chainloop.PolicyStatusPassed + case pb.PolicyStatusFilter_POLICY_STATUS_FILTER_SKIPPED: + return chainloop.PolicyStatusSkipped + case pb.PolicyStatusFilter_POLICY_STATUS_FILTER_WARNING: + return chainloop.PolicyStatusWarning + case pb.PolicyStatusFilter_POLICY_STATUS_FILTER_BLOCKED: + return chainloop.PolicyStatusBlocked + case pb.PolicyStatusFilter_POLICY_STATUS_FILTER_BYPASSED: + return chainloop.PolicyStatusBypassed + default: + return chainloop.PolicyStatusUnspecified + } +} + // validateAndGetProjectID finds a project by name and verifies it's in the visible projects list func (s *WorkflowRunService) validateAndGetProjectID(ctx context.Context, orgID, projectName string, visibleProjectIDs []uuid.UUID) (uuid.UUID, error) { project, err := s.projectUseCase.FindProjectByReference(ctx, orgID, &biz.IdentityReference{Name: &projectName}) diff --git a/app/controlplane/pkg/biz/mocks/WorkflowRunRepo.go b/app/controlplane/pkg/biz/mocks/WorkflowRunRepo.go index 7ca0b57da..7eaf82b84 100644 --- a/app/controlplane/pkg/biz/mocks/WorkflowRunRepo.go +++ b/app/controlplane/pkg/biz/mocks/WorkflowRunRepo.go @@ -10,6 +10,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/pagination" + "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" "github.com/google/uuid" mock "github.com/stretchr/testify/mock" ) @@ -742,37 +743,37 @@ func (_c *WorkflowRunRepo_SaveAttestationBundle_Call) RunAndReturn(run func(ctx return _c } -// UpdatePolicyViolationsStatus provides a mock function for the type WorkflowRunRepo -func (_mock *WorkflowRunRepo) UpdatePolicyViolationsStatus(ctx context.Context, ID uuid.UUID, hasPolicyViolations bool) error { - ret := _mock.Called(ctx, ID, hasPolicyViolations) +// UpdatePolicyStatus provides a mock function for the type WorkflowRunRepo +func (_mock *WorkflowRunRepo) UpdatePolicyStatus(ctx context.Context, ID uuid.UUID, summary *chainloop.PolicyStatusSummary) error { + ret := _mock.Called(ctx, ID, summary) if len(ret) == 0 { - panic("no return value specified for UpdatePolicyViolationsStatus") + panic("no return value specified for UpdatePolicyStatus") } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, bool) error); ok { - r0 = returnFunc(ctx, ID, hasPolicyViolations) + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID, *chainloop.PolicyStatusSummary) error); ok { + r0 = returnFunc(ctx, ID, summary) } else { r0 = ret.Error(0) } return r0 } -// WorkflowRunRepo_UpdatePolicyViolationsStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdatePolicyViolationsStatus' -type WorkflowRunRepo_UpdatePolicyViolationsStatus_Call struct { +// WorkflowRunRepo_UpdatePolicyStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdatePolicyStatus' +type WorkflowRunRepo_UpdatePolicyStatus_Call struct { *mock.Call } -// UpdatePolicyViolationsStatus is a helper method to define mock.On call +// UpdatePolicyStatus is a helper method to define mock.On call // - ctx context.Context // - ID uuid.UUID -// - hasPolicyViolations bool -func (_e *WorkflowRunRepo_Expecter) UpdatePolicyViolationsStatus(ctx interface{}, ID interface{}, hasPolicyViolations interface{}) *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call { - return &WorkflowRunRepo_UpdatePolicyViolationsStatus_Call{Call: _e.mock.On("UpdatePolicyViolationsStatus", ctx, ID, hasPolicyViolations)} +// - summary *chainloop.PolicyStatusSummary +func (_e *WorkflowRunRepo_Expecter) UpdatePolicyStatus(ctx interface{}, ID interface{}, summary interface{}) *WorkflowRunRepo_UpdatePolicyStatus_Call { + return &WorkflowRunRepo_UpdatePolicyStatus_Call{Call: _e.mock.On("UpdatePolicyStatus", ctx, ID, summary)} } -func (_c *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call) Run(run func(ctx context.Context, ID uuid.UUID, hasPolicyViolations bool)) *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call { +func (_c *WorkflowRunRepo_UpdatePolicyStatus_Call) Run(run func(ctx context.Context, ID uuid.UUID, summary *chainloop.PolicyStatusSummary)) *WorkflowRunRepo_UpdatePolicyStatus_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -782,9 +783,9 @@ func (_c *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call) Run(run func(ctx co if args[1] != nil { arg1 = args[1].(uuid.UUID) } - var arg2 bool + var arg2 *chainloop.PolicyStatusSummary if args[2] != nil { - arg2 = args[2].(bool) + arg2 = args[2].(*chainloop.PolicyStatusSummary) } run( arg0, @@ -795,12 +796,12 @@ func (_c *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call) Run(run func(ctx co return _c } -func (_c *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call) Return(err error) *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call { +func (_c *WorkflowRunRepo_UpdatePolicyStatus_Call) Return(err error) *WorkflowRunRepo_UpdatePolicyStatus_Call { _c.Call.Return(err) return _c } -func (_c *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call) RunAndReturn(run func(ctx context.Context, ID uuid.UUID, hasPolicyViolations bool) error) *WorkflowRunRepo_UpdatePolicyViolationsStatus_Call { +func (_c *WorkflowRunRepo_UpdatePolicyStatus_Call) RunAndReturn(run func(ctx context.Context, ID uuid.UUID, summary *chainloop.PolicyStatusSummary) error) *WorkflowRunRepo_UpdatePolicyStatus_Call { _c.Call.Return(run) return _c } diff --git a/app/controlplane/pkg/biz/workflowrun.go b/app/controlplane/pkg/biz/workflowrun.go index 8eab3c953..8436b0a44 100644 --- a/app/controlplane/pkg/biz/workflowrun.go +++ b/app/controlplane/pkg/biz/workflowrun.go @@ -58,6 +58,9 @@ type WorkflowRun struct { ProjectVersion *ProjectVersion // Whether the run has policy violations (nil if no policies were evaluated) HasPolicyViolations *bool + // Canonical policy outcome summary (nil if no policies were evaluated or if + // the row predates the policy-summary materialization change). + PolicyStatus *chainloop.PolicyStatusSummary } type Attestation struct { @@ -95,7 +98,7 @@ type WorkflowRunRepo interface { MarkAsFinished(ctx context.Context, ID uuid.UUID, status WorkflowRunStatus, reason string) error SaveAttestationBundle(ctx context.Context, ID uuid.UUID, digest string, bundle []byte) error GetBundle(ctx context.Context, wrID uuid.UUID) ([]byte, error) - UpdatePolicyViolationsStatus(ctx context.Context, ID uuid.UUID, hasPolicyViolations bool) error + UpdatePolicyStatus(ctx context.Context, ID uuid.UUID, summary *chainloop.PolicyStatusSummary) error List(ctx context.Context, orgID uuid.UUID, f *RunListFilters, p *pagination.CursorOptions) ([]*WorkflowRun, string, error) // List the runs that have not finished and are older than a given time ListNotFinishedOlderThan(ctx context.Context, olderThan time.Time, limit int) ([]*WorkflowRun, error) @@ -382,13 +385,11 @@ func (uc *WorkflowRunUseCase) SaveAttestation(ctx context.Context, id string, bu return nil, fmt.Errorf("saving attestation bundle: %w", err) } - // Extract and save policy violations status from the predicate - var hasPolicyViolations bool - if policyStatus := predicate.GetPolicyEvaluationStatus(); policyStatus != nil { - hasPolicyViolations = policyStatus.HasViolations - } - if err := uc.wfRunRepo.UpdatePolicyViolationsStatus(ctx, runID, hasPolicyViolations); err != nil { - return nil, fmt.Errorf("updating policy violations status: %w", err) + // Compute and persist the canonical policy status summary so the list + // handler can render a status badge without pulling the full attestation. + summary := chainloop.DerivePolicyStatusSummary(predicate.GetPolicyEvaluationStatus()) + if err := uc.wfRunRepo.UpdatePolicyStatus(ctx, runID, &summary); err != nil { + return nil, fmt.Errorf("updating policy status: %w", err) } return &digest, nil @@ -400,6 +401,9 @@ type RunListFilters struct { Status WorkflowRunStatus ProjectIDs []uuid.UUID PolicyViolationsFilter *bool + // Filter by canonical policy status. When both PolicyStatus and + // PolicyViolationsFilter are set, PolicyStatus takes precedence. + PolicyStatus *chainloop.PolicyStatus } // List the workflowruns associated with an org and optionally filtered by a workflow diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql b/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql new file mode 100644 index 000000000..d6f448237 --- /dev/null +++ b/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql @@ -0,0 +1,6 @@ +-- atlas:txmode none + +-- Modify "workflow_runs" table +ALTER TABLE "workflow_runs" ADD COLUMN "policy_status" character varying NULL, ADD COLUMN "policy_evaluations_total" integer NULL, ADD COLUMN "policy_evaluations_passed" integer NULL, ADD COLUMN "policy_evaluations_skipped" integer NULL, ADD COLUMN "policy_violations_count" integer NULL; +-- Create index "workflowrun_policy_status" to table: "workflow_runs" +CREATE INDEX CONCURRENTLY "workflowrun_policy_status" ON "workflow_runs" ("policy_status"); diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum index 75f6daade..b2d5a8af4 100644 --- a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:sNY7GgdTnqEyDG2nzVPtN7Vb4WM2agXX+GKsQJQvnCg= +h1:51ubLMpxihVrDovBvFzCYWr5wW+dHyhs223p4O5uX2s= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -129,3 +129,4 @@ h1:sNY7GgdTnqEyDG2nzVPtN7Vb4WM2agXX+GKsQJQvnCg= 20260318160301.sql h1:kH88s6pOi7Vprydb7xrzgY55JhMxfzY32txpQ8a1wEE= 20260408122048.sql h1:imfswpfmBlpP1l149/wCLN5HkN3/sGIQ3GnxaSnwOZE= 20260416153232.sql h1:xjEfZuMOo1lgZm3VUYGHpNOhpJixncVZuMRg0jiH+7A= +20260418100730.sql h1:oElg9D/TDuynW42As+caQ50ZiWvUePp1BS0fP4TW5H8= diff --git a/app/controlplane/pkg/data/ent/migrate/schema.go b/app/controlplane/pkg/data/ent/migrate/schema.go index 0786697a2..e1a62a61a 100644 --- a/app/controlplane/pkg/data/ent/migrate/schema.go +++ b/app/controlplane/pkg/data/ent/migrate/schema.go @@ -766,6 +766,11 @@ var ( {Name: "contract_revision_used", Type: field.TypeInt}, {Name: "contract_revision_latest", Type: field.TypeInt}, {Name: "has_policy_violations", Type: field.TypeBool, Nullable: true}, + {Name: "policy_status", Type: field.TypeEnum, Nullable: true, Enums: []string{"NOT_APPLICABLE", "PASSED", "SKIPPED", "WARNING", "BLOCKED", "BYPASSED"}}, + {Name: "policy_evaluations_total", Type: field.TypeInt32, Nullable: true}, + {Name: "policy_evaluations_passed", Type: field.TypeInt32, Nullable: true}, + {Name: "policy_evaluations_skipped", Type: field.TypeInt32, Nullable: true}, + {Name: "policy_violations_count", Type: field.TypeInt32, Nullable: true}, {Name: "version_id", Type: field.TypeUUID}, {Name: "workflow_id", Type: field.TypeUUID}, {Name: "workflow_run_contract_version", Type: field.TypeUUID, Nullable: true}, @@ -778,19 +783,19 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "workflow_runs_project_versions_runs", - Columns: []*schema.Column{WorkflowRunsColumns[13]}, + Columns: []*schema.Column{WorkflowRunsColumns[18]}, RefColumns: []*schema.Column{ProjectVersionsColumns[0]}, OnDelete: schema.NoAction, }, { Symbol: "workflow_runs_workflows_workflowruns", - Columns: []*schema.Column{WorkflowRunsColumns[14]}, + Columns: []*schema.Column{WorkflowRunsColumns[19]}, RefColumns: []*schema.Column{WorkflowsColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "workflow_runs_workflow_contract_versions_contract_version", - Columns: []*schema.Column{WorkflowRunsColumns[15]}, + Columns: []*schema.Column{WorkflowRunsColumns[20]}, RefColumns: []*schema.Column{WorkflowContractVersionsColumns[0]}, OnDelete: schema.Cascade, }, @@ -809,7 +814,7 @@ var ( { Name: "workflowrun_workflow_id_created_at", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[14], WorkflowRunsColumns[1]}, + Columns: []*schema.Column{WorkflowRunsColumns[19], WorkflowRunsColumns[1]}, Annotation: &entsql.IndexAnnotation{ DescColumns: map[string]bool{ WorkflowRunsColumns[1].Name: true, @@ -819,7 +824,7 @@ var ( { Name: "workflowrun_workflow_id_state_created_at", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[14], WorkflowRunsColumns[3], WorkflowRunsColumns[1]}, + Columns: []*schema.Column{WorkflowRunsColumns[19], WorkflowRunsColumns[3], WorkflowRunsColumns[1]}, Annotation: &entsql.IndexAnnotation{ DescColumns: map[string]bool{ WorkflowRunsColumns[1].Name: true, @@ -844,12 +849,17 @@ var ( { Name: "workflowrun_workflow_id", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[14]}, + Columns: []*schema.Column{WorkflowRunsColumns[19]}, }, { Name: "workflowrun_version_id_workflow_id", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[13], WorkflowRunsColumns[14]}, + Columns: []*schema.Column{WorkflowRunsColumns[18], WorkflowRunsColumns[19]}, + }, + { + Name: "workflowrun_policy_status", + Unique: false, + Columns: []*schema.Column{WorkflowRunsColumns[13]}, }, }, } diff --git a/app/controlplane/pkg/data/ent/mutation.go b/app/controlplane/pkg/data/ent/mutation.go index 8f9634a50..5f19c83b1 100644 --- a/app/controlplane/pkg/data/ent/mutation.go +++ b/app/controlplane/pkg/data/ent/mutation.go @@ -17909,38 +17909,47 @@ func (m *WorkflowContractVersionMutation) ResetEdge(name string) error { // WorkflowRunMutation represents an operation that mutates the WorkflowRun nodes in the graph. type WorkflowRunMutation struct { config - op Op - typ string - id *uuid.UUID - created_at *time.Time - finished_at *time.Time - state *biz.WorkflowRunStatus - reason *string - run_url *string - runner_type *string - attestation **dsse.Envelope - attestation_digest *string - attestation_state *[]byte - contract_revision_used *int - addcontract_revision_used *int - contract_revision_latest *int - addcontract_revision_latest *int - has_policy_violations *bool - clearedFields map[string]struct{} - workflow *uuid.UUID - clearedworkflow bool - contract_version *uuid.UUID - clearedcontract_version bool - cas_backends map[uuid.UUID]struct{} - removedcas_backends map[uuid.UUID]struct{} - clearedcas_backends bool - version *uuid.UUID - clearedversion bool - attestation_bundle *uuid.UUID - clearedattestation_bundle bool - done bool - oldValue func(context.Context) (*WorkflowRun, error) - predicates []predicate.WorkflowRun + op Op + typ string + id *uuid.UUID + created_at *time.Time + finished_at *time.Time + state *biz.WorkflowRunStatus + reason *string + run_url *string + runner_type *string + attestation **dsse.Envelope + attestation_digest *string + attestation_state *[]byte + contract_revision_used *int + addcontract_revision_used *int + contract_revision_latest *int + addcontract_revision_latest *int + has_policy_violations *bool + policy_status *workflowrun.PolicyStatus + policy_evaluations_total *int32 + addpolicy_evaluations_total *int32 + policy_evaluations_passed *int32 + addpolicy_evaluations_passed *int32 + policy_evaluations_skipped *int32 + addpolicy_evaluations_skipped *int32 + policy_violations_count *int32 + addpolicy_violations_count *int32 + clearedFields map[string]struct{} + workflow *uuid.UUID + clearedworkflow bool + contract_version *uuid.UUID + clearedcontract_version bool + cas_backends map[uuid.UUID]struct{} + removedcas_backends map[uuid.UUID]struct{} + clearedcas_backends bool + version *uuid.UUID + clearedversion bool + attestation_bundle *uuid.UUID + clearedattestation_bundle bool + done bool + oldValue func(context.Context) (*WorkflowRun, error) + predicates []predicate.WorkflowRun } var _ ent.Mutation = (*WorkflowRunMutation)(nil) @@ -18695,6 +18704,335 @@ func (m *WorkflowRunMutation) ResetHasPolicyViolations() { delete(m.clearedFields, workflowrun.FieldHasPolicyViolations) } +// SetPolicyStatus sets the "policy_status" field. +func (m *WorkflowRunMutation) SetPolicyStatus(ws workflowrun.PolicyStatus) { + m.policy_status = &ws +} + +// PolicyStatus returns the value of the "policy_status" field in the mutation. +func (m *WorkflowRunMutation) PolicyStatus() (r workflowrun.PolicyStatus, exists bool) { + v := m.policy_status + if v == nil { + return + } + return *v, true +} + +// OldPolicyStatus returns the old "policy_status" field's value of the WorkflowRun entity. +// If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *WorkflowRunMutation) OldPolicyStatus(ctx context.Context) (v *workflowrun.PolicyStatus, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPolicyStatus is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPolicyStatus requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPolicyStatus: %w", err) + } + return oldValue.PolicyStatus, nil +} + +// ClearPolicyStatus clears the value of the "policy_status" field. +func (m *WorkflowRunMutation) ClearPolicyStatus() { + m.policy_status = nil + m.clearedFields[workflowrun.FieldPolicyStatus] = struct{}{} +} + +// PolicyStatusCleared returns if the "policy_status" field was cleared in this mutation. +func (m *WorkflowRunMutation) PolicyStatusCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldPolicyStatus] + return ok +} + +// ResetPolicyStatus resets all changes to the "policy_status" field. +func (m *WorkflowRunMutation) ResetPolicyStatus() { + m.policy_status = nil + delete(m.clearedFields, workflowrun.FieldPolicyStatus) +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (m *WorkflowRunMutation) SetPolicyEvaluationsTotal(i int32) { + m.policy_evaluations_total = &i + m.addpolicy_evaluations_total = nil +} + +// PolicyEvaluationsTotal returns the value of the "policy_evaluations_total" field in the mutation. +func (m *WorkflowRunMutation) PolicyEvaluationsTotal() (r int32, exists bool) { + v := m.policy_evaluations_total + if v == nil { + return + } + return *v, true +} + +// OldPolicyEvaluationsTotal returns the old "policy_evaluations_total" field's value of the WorkflowRun entity. +// If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *WorkflowRunMutation) OldPolicyEvaluationsTotal(ctx context.Context) (v *int32, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPolicyEvaluationsTotal is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPolicyEvaluationsTotal requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPolicyEvaluationsTotal: %w", err) + } + return oldValue.PolicyEvaluationsTotal, nil +} + +// AddPolicyEvaluationsTotal adds i to the "policy_evaluations_total" field. +func (m *WorkflowRunMutation) AddPolicyEvaluationsTotal(i int32) { + if m.addpolicy_evaluations_total != nil { + *m.addpolicy_evaluations_total += i + } else { + m.addpolicy_evaluations_total = &i + } +} + +// AddedPolicyEvaluationsTotal returns the value that was added to the "policy_evaluations_total" field in this mutation. +func (m *WorkflowRunMutation) AddedPolicyEvaluationsTotal() (r int32, exists bool) { + v := m.addpolicy_evaluations_total + if v == nil { + return + } + return *v, true +} + +// ClearPolicyEvaluationsTotal clears the value of the "policy_evaluations_total" field. +func (m *WorkflowRunMutation) ClearPolicyEvaluationsTotal() { + m.policy_evaluations_total = nil + m.addpolicy_evaluations_total = nil + m.clearedFields[workflowrun.FieldPolicyEvaluationsTotal] = struct{}{} +} + +// PolicyEvaluationsTotalCleared returns if the "policy_evaluations_total" field was cleared in this mutation. +func (m *WorkflowRunMutation) PolicyEvaluationsTotalCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldPolicyEvaluationsTotal] + return ok +} + +// ResetPolicyEvaluationsTotal resets all changes to the "policy_evaluations_total" field. +func (m *WorkflowRunMutation) ResetPolicyEvaluationsTotal() { + m.policy_evaluations_total = nil + m.addpolicy_evaluations_total = nil + delete(m.clearedFields, workflowrun.FieldPolicyEvaluationsTotal) +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (m *WorkflowRunMutation) SetPolicyEvaluationsPassed(i int32) { + m.policy_evaluations_passed = &i + m.addpolicy_evaluations_passed = nil +} + +// PolicyEvaluationsPassed returns the value of the "policy_evaluations_passed" field in the mutation. +func (m *WorkflowRunMutation) PolicyEvaluationsPassed() (r int32, exists bool) { + v := m.policy_evaluations_passed + if v == nil { + return + } + return *v, true +} + +// OldPolicyEvaluationsPassed returns the old "policy_evaluations_passed" field's value of the WorkflowRun entity. +// If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *WorkflowRunMutation) OldPolicyEvaluationsPassed(ctx context.Context) (v *int32, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPolicyEvaluationsPassed is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPolicyEvaluationsPassed requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPolicyEvaluationsPassed: %w", err) + } + return oldValue.PolicyEvaluationsPassed, nil +} + +// AddPolicyEvaluationsPassed adds i to the "policy_evaluations_passed" field. +func (m *WorkflowRunMutation) AddPolicyEvaluationsPassed(i int32) { + if m.addpolicy_evaluations_passed != nil { + *m.addpolicy_evaluations_passed += i + } else { + m.addpolicy_evaluations_passed = &i + } +} + +// AddedPolicyEvaluationsPassed returns the value that was added to the "policy_evaluations_passed" field in this mutation. +func (m *WorkflowRunMutation) AddedPolicyEvaluationsPassed() (r int32, exists bool) { + v := m.addpolicy_evaluations_passed + if v == nil { + return + } + return *v, true +} + +// ClearPolicyEvaluationsPassed clears the value of the "policy_evaluations_passed" field. +func (m *WorkflowRunMutation) ClearPolicyEvaluationsPassed() { + m.policy_evaluations_passed = nil + m.addpolicy_evaluations_passed = nil + m.clearedFields[workflowrun.FieldPolicyEvaluationsPassed] = struct{}{} +} + +// PolicyEvaluationsPassedCleared returns if the "policy_evaluations_passed" field was cleared in this mutation. +func (m *WorkflowRunMutation) PolicyEvaluationsPassedCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldPolicyEvaluationsPassed] + return ok +} + +// ResetPolicyEvaluationsPassed resets all changes to the "policy_evaluations_passed" field. +func (m *WorkflowRunMutation) ResetPolicyEvaluationsPassed() { + m.policy_evaluations_passed = nil + m.addpolicy_evaluations_passed = nil + delete(m.clearedFields, workflowrun.FieldPolicyEvaluationsPassed) +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (m *WorkflowRunMutation) SetPolicyEvaluationsSkipped(i int32) { + m.policy_evaluations_skipped = &i + m.addpolicy_evaluations_skipped = nil +} + +// PolicyEvaluationsSkipped returns the value of the "policy_evaluations_skipped" field in the mutation. +func (m *WorkflowRunMutation) PolicyEvaluationsSkipped() (r int32, exists bool) { + v := m.policy_evaluations_skipped + if v == nil { + return + } + return *v, true +} + +// OldPolicyEvaluationsSkipped returns the old "policy_evaluations_skipped" field's value of the WorkflowRun entity. +// If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *WorkflowRunMutation) OldPolicyEvaluationsSkipped(ctx context.Context) (v *int32, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPolicyEvaluationsSkipped is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPolicyEvaluationsSkipped requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPolicyEvaluationsSkipped: %w", err) + } + return oldValue.PolicyEvaluationsSkipped, nil +} + +// AddPolicyEvaluationsSkipped adds i to the "policy_evaluations_skipped" field. +func (m *WorkflowRunMutation) AddPolicyEvaluationsSkipped(i int32) { + if m.addpolicy_evaluations_skipped != nil { + *m.addpolicy_evaluations_skipped += i + } else { + m.addpolicy_evaluations_skipped = &i + } +} + +// AddedPolicyEvaluationsSkipped returns the value that was added to the "policy_evaluations_skipped" field in this mutation. +func (m *WorkflowRunMutation) AddedPolicyEvaluationsSkipped() (r int32, exists bool) { + v := m.addpolicy_evaluations_skipped + if v == nil { + return + } + return *v, true +} + +// ClearPolicyEvaluationsSkipped clears the value of the "policy_evaluations_skipped" field. +func (m *WorkflowRunMutation) ClearPolicyEvaluationsSkipped() { + m.policy_evaluations_skipped = nil + m.addpolicy_evaluations_skipped = nil + m.clearedFields[workflowrun.FieldPolicyEvaluationsSkipped] = struct{}{} +} + +// PolicyEvaluationsSkippedCleared returns if the "policy_evaluations_skipped" field was cleared in this mutation. +func (m *WorkflowRunMutation) PolicyEvaluationsSkippedCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldPolicyEvaluationsSkipped] + return ok +} + +// ResetPolicyEvaluationsSkipped resets all changes to the "policy_evaluations_skipped" field. +func (m *WorkflowRunMutation) ResetPolicyEvaluationsSkipped() { + m.policy_evaluations_skipped = nil + m.addpolicy_evaluations_skipped = nil + delete(m.clearedFields, workflowrun.FieldPolicyEvaluationsSkipped) +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (m *WorkflowRunMutation) SetPolicyViolationsCount(i int32) { + m.policy_violations_count = &i + m.addpolicy_violations_count = nil +} + +// PolicyViolationsCount returns the value of the "policy_violations_count" field in the mutation. +func (m *WorkflowRunMutation) PolicyViolationsCount() (r int32, exists bool) { + v := m.policy_violations_count + if v == nil { + return + } + return *v, true +} + +// OldPolicyViolationsCount returns the old "policy_violations_count" field's value of the WorkflowRun entity. +// If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *WorkflowRunMutation) OldPolicyViolationsCount(ctx context.Context) (v *int32, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPolicyViolationsCount is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPolicyViolationsCount requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPolicyViolationsCount: %w", err) + } + return oldValue.PolicyViolationsCount, nil +} + +// AddPolicyViolationsCount adds i to the "policy_violations_count" field. +func (m *WorkflowRunMutation) AddPolicyViolationsCount(i int32) { + if m.addpolicy_violations_count != nil { + *m.addpolicy_violations_count += i + } else { + m.addpolicy_violations_count = &i + } +} + +// AddedPolicyViolationsCount returns the value that was added to the "policy_violations_count" field in this mutation. +func (m *WorkflowRunMutation) AddedPolicyViolationsCount() (r int32, exists bool) { + v := m.addpolicy_violations_count + if v == nil { + return + } + return *v, true +} + +// ClearPolicyViolationsCount clears the value of the "policy_violations_count" field. +func (m *WorkflowRunMutation) ClearPolicyViolationsCount() { + m.policy_violations_count = nil + m.addpolicy_violations_count = nil + m.clearedFields[workflowrun.FieldPolicyViolationsCount] = struct{}{} +} + +// PolicyViolationsCountCleared returns if the "policy_violations_count" field was cleared in this mutation. +func (m *WorkflowRunMutation) PolicyViolationsCountCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldPolicyViolationsCount] + return ok +} + +// ResetPolicyViolationsCount resets all changes to the "policy_violations_count" field. +func (m *WorkflowRunMutation) ResetPolicyViolationsCount() { + m.policy_violations_count = nil + m.addpolicy_violations_count = nil + delete(m.clearedFields, workflowrun.FieldPolicyViolationsCount) +} + // ClearWorkflow clears the "workflow" edge to the Workflow entity. func (m *WorkflowRunMutation) ClearWorkflow() { m.clearedworkflow = true @@ -18915,7 +19253,7 @@ func (m *WorkflowRunMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *WorkflowRunMutation) Fields() []string { - fields := make([]string, 0, 14) + fields := make([]string, 0, 19) if m.created_at != nil { fields = append(fields, workflowrun.FieldCreatedAt) } @@ -18958,6 +19296,21 @@ func (m *WorkflowRunMutation) Fields() []string { if m.has_policy_violations != nil { fields = append(fields, workflowrun.FieldHasPolicyViolations) } + if m.policy_status != nil { + fields = append(fields, workflowrun.FieldPolicyStatus) + } + if m.policy_evaluations_total != nil { + fields = append(fields, workflowrun.FieldPolicyEvaluationsTotal) + } + if m.policy_evaluations_passed != nil { + fields = append(fields, workflowrun.FieldPolicyEvaluationsPassed) + } + if m.policy_evaluations_skipped != nil { + fields = append(fields, workflowrun.FieldPolicyEvaluationsSkipped) + } + if m.policy_violations_count != nil { + fields = append(fields, workflowrun.FieldPolicyViolationsCount) + } return fields } @@ -18994,6 +19347,16 @@ func (m *WorkflowRunMutation) Field(name string) (ent.Value, bool) { return m.WorkflowID() case workflowrun.FieldHasPolicyViolations: return m.HasPolicyViolations() + case workflowrun.FieldPolicyStatus: + return m.PolicyStatus() + case workflowrun.FieldPolicyEvaluationsTotal: + return m.PolicyEvaluationsTotal() + case workflowrun.FieldPolicyEvaluationsPassed: + return m.PolicyEvaluationsPassed() + case workflowrun.FieldPolicyEvaluationsSkipped: + return m.PolicyEvaluationsSkipped() + case workflowrun.FieldPolicyViolationsCount: + return m.PolicyViolationsCount() } return nil, false } @@ -19031,6 +19394,16 @@ func (m *WorkflowRunMutation) OldField(ctx context.Context, name string) (ent.Va return m.OldWorkflowID(ctx) case workflowrun.FieldHasPolicyViolations: return m.OldHasPolicyViolations(ctx) + case workflowrun.FieldPolicyStatus: + return m.OldPolicyStatus(ctx) + case workflowrun.FieldPolicyEvaluationsTotal: + return m.OldPolicyEvaluationsTotal(ctx) + case workflowrun.FieldPolicyEvaluationsPassed: + return m.OldPolicyEvaluationsPassed(ctx) + case workflowrun.FieldPolicyEvaluationsSkipped: + return m.OldPolicyEvaluationsSkipped(ctx) + case workflowrun.FieldPolicyViolationsCount: + return m.OldPolicyViolationsCount(ctx) } return nil, fmt.Errorf("unknown WorkflowRun field %s", name) } @@ -19138,6 +19511,41 @@ func (m *WorkflowRunMutation) SetField(name string, value ent.Value) error { } m.SetHasPolicyViolations(v) return nil + case workflowrun.FieldPolicyStatus: + v, ok := value.(workflowrun.PolicyStatus) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPolicyStatus(v) + return nil + case workflowrun.FieldPolicyEvaluationsTotal: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPolicyEvaluationsTotal(v) + return nil + case workflowrun.FieldPolicyEvaluationsPassed: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPolicyEvaluationsPassed(v) + return nil + case workflowrun.FieldPolicyEvaluationsSkipped: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPolicyEvaluationsSkipped(v) + return nil + case workflowrun.FieldPolicyViolationsCount: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPolicyViolationsCount(v) + return nil } return fmt.Errorf("unknown WorkflowRun field %s", name) } @@ -19152,6 +19560,18 @@ func (m *WorkflowRunMutation) AddedFields() []string { if m.addcontract_revision_latest != nil { fields = append(fields, workflowrun.FieldContractRevisionLatest) } + if m.addpolicy_evaluations_total != nil { + fields = append(fields, workflowrun.FieldPolicyEvaluationsTotal) + } + if m.addpolicy_evaluations_passed != nil { + fields = append(fields, workflowrun.FieldPolicyEvaluationsPassed) + } + if m.addpolicy_evaluations_skipped != nil { + fields = append(fields, workflowrun.FieldPolicyEvaluationsSkipped) + } + if m.addpolicy_violations_count != nil { + fields = append(fields, workflowrun.FieldPolicyViolationsCount) + } return fields } @@ -19164,6 +19584,14 @@ func (m *WorkflowRunMutation) AddedField(name string) (ent.Value, bool) { return m.AddedContractRevisionUsed() case workflowrun.FieldContractRevisionLatest: return m.AddedContractRevisionLatest() + case workflowrun.FieldPolicyEvaluationsTotal: + return m.AddedPolicyEvaluationsTotal() + case workflowrun.FieldPolicyEvaluationsPassed: + return m.AddedPolicyEvaluationsPassed() + case workflowrun.FieldPolicyEvaluationsSkipped: + return m.AddedPolicyEvaluationsSkipped() + case workflowrun.FieldPolicyViolationsCount: + return m.AddedPolicyViolationsCount() } return nil, false } @@ -19187,6 +19615,34 @@ func (m *WorkflowRunMutation) AddField(name string, value ent.Value) error { } m.AddContractRevisionLatest(v) return nil + case workflowrun.FieldPolicyEvaluationsTotal: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPolicyEvaluationsTotal(v) + return nil + case workflowrun.FieldPolicyEvaluationsPassed: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPolicyEvaluationsPassed(v) + return nil + case workflowrun.FieldPolicyEvaluationsSkipped: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPolicyEvaluationsSkipped(v) + return nil + case workflowrun.FieldPolicyViolationsCount: + v, ok := value.(int32) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPolicyViolationsCount(v) + return nil } return fmt.Errorf("unknown WorkflowRun numeric field %s", name) } @@ -19219,6 +19675,21 @@ func (m *WorkflowRunMutation) ClearedFields() []string { if m.FieldCleared(workflowrun.FieldHasPolicyViolations) { fields = append(fields, workflowrun.FieldHasPolicyViolations) } + if m.FieldCleared(workflowrun.FieldPolicyStatus) { + fields = append(fields, workflowrun.FieldPolicyStatus) + } + if m.FieldCleared(workflowrun.FieldPolicyEvaluationsTotal) { + fields = append(fields, workflowrun.FieldPolicyEvaluationsTotal) + } + if m.FieldCleared(workflowrun.FieldPolicyEvaluationsPassed) { + fields = append(fields, workflowrun.FieldPolicyEvaluationsPassed) + } + if m.FieldCleared(workflowrun.FieldPolicyEvaluationsSkipped) { + fields = append(fields, workflowrun.FieldPolicyEvaluationsSkipped) + } + if m.FieldCleared(workflowrun.FieldPolicyViolationsCount) { + fields = append(fields, workflowrun.FieldPolicyViolationsCount) + } return fields } @@ -19257,6 +19728,21 @@ func (m *WorkflowRunMutation) ClearField(name string) error { case workflowrun.FieldHasPolicyViolations: m.ClearHasPolicyViolations() return nil + case workflowrun.FieldPolicyStatus: + m.ClearPolicyStatus() + return nil + case workflowrun.FieldPolicyEvaluationsTotal: + m.ClearPolicyEvaluationsTotal() + return nil + case workflowrun.FieldPolicyEvaluationsPassed: + m.ClearPolicyEvaluationsPassed() + return nil + case workflowrun.FieldPolicyEvaluationsSkipped: + m.ClearPolicyEvaluationsSkipped() + return nil + case workflowrun.FieldPolicyViolationsCount: + m.ClearPolicyViolationsCount() + return nil } return fmt.Errorf("unknown WorkflowRun nullable field %s", name) } @@ -19307,6 +19793,21 @@ func (m *WorkflowRunMutation) ResetField(name string) error { case workflowrun.FieldHasPolicyViolations: m.ResetHasPolicyViolations() return nil + case workflowrun.FieldPolicyStatus: + m.ResetPolicyStatus() + return nil + case workflowrun.FieldPolicyEvaluationsTotal: + m.ResetPolicyEvaluationsTotal() + return nil + case workflowrun.FieldPolicyEvaluationsPassed: + m.ResetPolicyEvaluationsPassed() + return nil + case workflowrun.FieldPolicyEvaluationsSkipped: + m.ResetPolicyEvaluationsSkipped() + return nil + case workflowrun.FieldPolicyViolationsCount: + m.ResetPolicyViolationsCount() + return nil } return fmt.Errorf("unknown WorkflowRun field %s", name) } diff --git a/app/controlplane/pkg/data/ent/schema/workflowrun.go b/app/controlplane/pkg/data/ent/schema/workflowrun.go index dc778c3de..a23c1b217 100644 --- a/app/controlplane/pkg/data/ent/schema/workflowrun.go +++ b/app/controlplane/pkg/data/ent/schema/workflowrun.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. @@ -61,6 +61,16 @@ func (WorkflowRun) Fields() []ent.Field { field.UUID("workflow_id", uuid.UUID{}).Immutable(), // Whether the run has policy violations (nullable for backward compatibility) field.Bool("has_policy_violations").Optional().Nillable(), + // Canonical policy status summary — all fields nullable so pre-existing + // rows (before this column set was introduced) remain valid and are + // interpreted as NOT_APPLICABLE by the service layer. + field.Enum("policy_status"). + Values("NOT_APPLICABLE", "PASSED", "SKIPPED", "WARNING", "BLOCKED", "BYPASSED"). + Optional().Nillable(), + field.Int32("policy_evaluations_total").Optional().Nillable(), + field.Int32("policy_evaluations_passed").Optional().Nillable(), + field.Int32("policy_evaluations_skipped").Optional().Nillable(), + field.Int32("policy_violations_count").Optional().Nillable(), } } @@ -94,5 +104,7 @@ func (WorkflowRun) Indexes() []ent.Index { index.Edges("workflow"), // Workflow run counts per project version index.Fields("version_id", "workflow_id"), + // List filtering on canonical policy status + index.Fields("policy_status"), } } diff --git a/app/controlplane/pkg/data/ent/workflowrun.go b/app/controlplane/pkg/data/ent/workflowrun.go index 0e4db9786..d8708e038 100644 --- a/app/controlplane/pkg/data/ent/workflowrun.go +++ b/app/controlplane/pkg/data/ent/workflowrun.go @@ -53,6 +53,16 @@ type WorkflowRun struct { WorkflowID uuid.UUID `json:"workflow_id,omitempty"` // HasPolicyViolations holds the value of the "has_policy_violations" field. HasPolicyViolations *bool `json:"has_policy_violations,omitempty"` + // PolicyStatus holds the value of the "policy_status" field. + PolicyStatus *workflowrun.PolicyStatus `json:"policy_status,omitempty"` + // PolicyEvaluationsTotal holds the value of the "policy_evaluations_total" field. + PolicyEvaluationsTotal *int32 `json:"policy_evaluations_total,omitempty"` + // PolicyEvaluationsPassed holds the value of the "policy_evaluations_passed" field. + PolicyEvaluationsPassed *int32 `json:"policy_evaluations_passed,omitempty"` + // PolicyEvaluationsSkipped holds the value of the "policy_evaluations_skipped" field. + PolicyEvaluationsSkipped *int32 `json:"policy_evaluations_skipped,omitempty"` + // PolicyViolationsCount holds the value of the "policy_violations_count" field. + PolicyViolationsCount *int32 `json:"policy_violations_count,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the WorkflowRunQuery when eager-loading is set. Edges WorkflowRunEdges `json:"edges"` @@ -139,9 +149,9 @@ func (*WorkflowRun) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case workflowrun.FieldHasPolicyViolations: values[i] = new(sql.NullBool) - case workflowrun.FieldContractRevisionUsed, workflowrun.FieldContractRevisionLatest: + case workflowrun.FieldContractRevisionUsed, workflowrun.FieldContractRevisionLatest, workflowrun.FieldPolicyEvaluationsTotal, workflowrun.FieldPolicyEvaluationsPassed, workflowrun.FieldPolicyEvaluationsSkipped, workflowrun.FieldPolicyViolationsCount: values[i] = new(sql.NullInt64) - case workflowrun.FieldState, workflowrun.FieldReason, workflowrun.FieldRunURL, workflowrun.FieldRunnerType, workflowrun.FieldAttestationDigest: + case workflowrun.FieldState, workflowrun.FieldReason, workflowrun.FieldRunURL, workflowrun.FieldRunnerType, workflowrun.FieldAttestationDigest, workflowrun.FieldPolicyStatus: values[i] = new(sql.NullString) case workflowrun.FieldCreatedAt, workflowrun.FieldFinishedAt: values[i] = new(sql.NullTime) @@ -257,6 +267,41 @@ func (_m *WorkflowRun) assignValues(columns []string, values []any) error { _m.HasPolicyViolations = new(bool) *_m.HasPolicyViolations = value.Bool } + case workflowrun.FieldPolicyStatus: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field policy_status", values[i]) + } else if value.Valid { + _m.PolicyStatus = new(workflowrun.PolicyStatus) + *_m.PolicyStatus = workflowrun.PolicyStatus(value.String) + } + case workflowrun.FieldPolicyEvaluationsTotal: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field policy_evaluations_total", values[i]) + } else if value.Valid { + _m.PolicyEvaluationsTotal = new(int32) + *_m.PolicyEvaluationsTotal = int32(value.Int64) + } + case workflowrun.FieldPolicyEvaluationsPassed: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field policy_evaluations_passed", values[i]) + } else if value.Valid { + _m.PolicyEvaluationsPassed = new(int32) + *_m.PolicyEvaluationsPassed = int32(value.Int64) + } + case workflowrun.FieldPolicyEvaluationsSkipped: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field policy_evaluations_skipped", values[i]) + } else if value.Valid { + _m.PolicyEvaluationsSkipped = new(int32) + *_m.PolicyEvaluationsSkipped = int32(value.Int64) + } + case workflowrun.FieldPolicyViolationsCount: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field policy_violations_count", values[i]) + } else if value.Valid { + _m.PolicyViolationsCount = new(int32) + *_m.PolicyViolationsCount = int32(value.Int64) + } case workflowrun.ForeignKeys[0]: if value, ok := values[i].(*sql.NullScanner); !ok { return fmt.Errorf("unexpected type %T for field workflow_run_contract_version", values[i]) @@ -368,6 +413,31 @@ func (_m *WorkflowRun) String() string { builder.WriteString("has_policy_violations=") builder.WriteString(fmt.Sprintf("%v", *v)) } + builder.WriteString(", ") + if v := _m.PolicyStatus; v != nil { + builder.WriteString("policy_status=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.PolicyEvaluationsTotal; v != nil { + builder.WriteString("policy_evaluations_total=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.PolicyEvaluationsPassed; v != nil { + builder.WriteString("policy_evaluations_passed=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.PolicyEvaluationsSkipped; v != nil { + builder.WriteString("policy_evaluations_skipped=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.PolicyViolationsCount; v != nil { + builder.WriteString("policy_violations_count=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } builder.WriteByte(')') return builder.String() } diff --git a/app/controlplane/pkg/data/ent/workflowrun/where.go b/app/controlplane/pkg/data/ent/workflowrun/where.go index a93dcbad0..829c6ea59 100644 --- a/app/controlplane/pkg/data/ent/workflowrun/where.go +++ b/app/controlplane/pkg/data/ent/workflowrun/where.go @@ -117,6 +117,26 @@ func HasPolicyViolations(v bool) predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldEQ(FieldHasPolicyViolations, v)) } +// PolicyEvaluationsTotal applies equality check predicate on the "policy_evaluations_total" field. It's identical to PolicyEvaluationsTotalEQ. +func PolicyEvaluationsTotal(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsPassed applies equality check predicate on the "policy_evaluations_passed" field. It's identical to PolicyEvaluationsPassedEQ. +func PolicyEvaluationsPassed(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsSkipped applies equality check predicate on the "policy_evaluations_skipped" field. It's identical to PolicyEvaluationsSkippedEQ. +func PolicyEvaluationsSkipped(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyViolationsCount applies equality check predicate on the "policy_violations_count" field. It's identical to PolicyViolationsCountEQ. +func PolicyViolationsCount(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyViolationsCount, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldEQ(FieldCreatedAt, v)) @@ -737,6 +757,236 @@ func HasPolicyViolationsNotNil() predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldNotNull(FieldHasPolicyViolations)) } +// PolicyStatusEQ applies the EQ predicate on the "policy_status" field. +func PolicyStatusEQ(v PolicyStatus) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyStatus, v)) +} + +// PolicyStatusNEQ applies the NEQ predicate on the "policy_status" field. +func PolicyStatusNEQ(v PolicyStatus) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNEQ(FieldPolicyStatus, v)) +} + +// PolicyStatusIn applies the In predicate on the "policy_status" field. +func PolicyStatusIn(vs ...PolicyStatus) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIn(FieldPolicyStatus, vs...)) +} + +// PolicyStatusNotIn applies the NotIn predicate on the "policy_status" field. +func PolicyStatusNotIn(vs ...PolicyStatus) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotIn(FieldPolicyStatus, vs...)) +} + +// PolicyStatusIsNil applies the IsNil predicate on the "policy_status" field. +func PolicyStatusIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldPolicyStatus)) +} + +// PolicyStatusNotNil applies the NotNil predicate on the "policy_status" field. +func PolicyStatusNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyStatus)) +} + +// PolicyEvaluationsTotalEQ applies the EQ predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsTotalNEQ applies the NEQ predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalNEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNEQ(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsTotalIn applies the In predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIn(FieldPolicyEvaluationsTotal, vs...)) +} + +// PolicyEvaluationsTotalNotIn applies the NotIn predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalNotIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotIn(FieldPolicyEvaluationsTotal, vs...)) +} + +// PolicyEvaluationsTotalGT applies the GT predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalGT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGT(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsTotalGTE applies the GTE predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalGTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGTE(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsTotalLT applies the LT predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalLT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLT(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsTotalLTE applies the LTE predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalLTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLTE(FieldPolicyEvaluationsTotal, v)) +} + +// PolicyEvaluationsTotalIsNil applies the IsNil predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldPolicyEvaluationsTotal)) +} + +// PolicyEvaluationsTotalNotNil applies the NotNil predicate on the "policy_evaluations_total" field. +func PolicyEvaluationsTotalNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyEvaluationsTotal)) +} + +// PolicyEvaluationsPassedEQ applies the EQ predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsPassedNEQ applies the NEQ predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedNEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNEQ(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsPassedIn applies the In predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIn(FieldPolicyEvaluationsPassed, vs...)) +} + +// PolicyEvaluationsPassedNotIn applies the NotIn predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedNotIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotIn(FieldPolicyEvaluationsPassed, vs...)) +} + +// PolicyEvaluationsPassedGT applies the GT predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedGT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGT(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsPassedGTE applies the GTE predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedGTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGTE(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsPassedLT applies the LT predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedLT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLT(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsPassedLTE applies the LTE predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedLTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLTE(FieldPolicyEvaluationsPassed, v)) +} + +// PolicyEvaluationsPassedIsNil applies the IsNil predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldPolicyEvaluationsPassed)) +} + +// PolicyEvaluationsPassedNotNil applies the NotNil predicate on the "policy_evaluations_passed" field. +func PolicyEvaluationsPassedNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyEvaluationsPassed)) +} + +// PolicyEvaluationsSkippedEQ applies the EQ predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyEvaluationsSkippedNEQ applies the NEQ predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedNEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNEQ(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyEvaluationsSkippedIn applies the In predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIn(FieldPolicyEvaluationsSkipped, vs...)) +} + +// PolicyEvaluationsSkippedNotIn applies the NotIn predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedNotIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotIn(FieldPolicyEvaluationsSkipped, vs...)) +} + +// PolicyEvaluationsSkippedGT applies the GT predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedGT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGT(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyEvaluationsSkippedGTE applies the GTE predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedGTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGTE(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyEvaluationsSkippedLT applies the LT predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedLT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLT(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyEvaluationsSkippedLTE applies the LTE predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedLTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLTE(FieldPolicyEvaluationsSkipped, v)) +} + +// PolicyEvaluationsSkippedIsNil applies the IsNil predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldPolicyEvaluationsSkipped)) +} + +// PolicyEvaluationsSkippedNotNil applies the NotNil predicate on the "policy_evaluations_skipped" field. +func PolicyEvaluationsSkippedNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyEvaluationsSkipped)) +} + +// PolicyViolationsCountEQ applies the EQ predicate on the "policy_violations_count" field. +func PolicyViolationsCountEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyViolationsCount, v)) +} + +// PolicyViolationsCountNEQ applies the NEQ predicate on the "policy_violations_count" field. +func PolicyViolationsCountNEQ(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNEQ(FieldPolicyViolationsCount, v)) +} + +// PolicyViolationsCountIn applies the In predicate on the "policy_violations_count" field. +func PolicyViolationsCountIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIn(FieldPolicyViolationsCount, vs...)) +} + +// PolicyViolationsCountNotIn applies the NotIn predicate on the "policy_violations_count" field. +func PolicyViolationsCountNotIn(vs ...int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotIn(FieldPolicyViolationsCount, vs...)) +} + +// PolicyViolationsCountGT applies the GT predicate on the "policy_violations_count" field. +func PolicyViolationsCountGT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGT(FieldPolicyViolationsCount, v)) +} + +// PolicyViolationsCountGTE applies the GTE predicate on the "policy_violations_count" field. +func PolicyViolationsCountGTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldGTE(FieldPolicyViolationsCount, v)) +} + +// PolicyViolationsCountLT applies the LT predicate on the "policy_violations_count" field. +func PolicyViolationsCountLT(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLT(FieldPolicyViolationsCount, v)) +} + +// PolicyViolationsCountLTE applies the LTE predicate on the "policy_violations_count" field. +func PolicyViolationsCountLTE(v int32) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldLTE(FieldPolicyViolationsCount, v)) +} + +// PolicyViolationsCountIsNil applies the IsNil predicate on the "policy_violations_count" field. +func PolicyViolationsCountIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldPolicyViolationsCount)) +} + +// PolicyViolationsCountNotNil applies the NotNil predicate on the "policy_violations_count" field. +func PolicyViolationsCountNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyViolationsCount)) +} + // HasWorkflow applies the HasEdge predicate on the "workflow" edge. func HasWorkflow() predicate.WorkflowRun { return predicate.WorkflowRun(func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go b/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go index 22a31b4af..23bc23f9e 100644 --- a/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go +++ b/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go @@ -45,6 +45,16 @@ const ( FieldWorkflowID = "workflow_id" // FieldHasPolicyViolations holds the string denoting the has_policy_violations field in the database. FieldHasPolicyViolations = "has_policy_violations" + // FieldPolicyStatus holds the string denoting the policy_status field in the database. + FieldPolicyStatus = "policy_status" + // FieldPolicyEvaluationsTotal holds the string denoting the policy_evaluations_total field in the database. + FieldPolicyEvaluationsTotal = "policy_evaluations_total" + // FieldPolicyEvaluationsPassed holds the string denoting the policy_evaluations_passed field in the database. + FieldPolicyEvaluationsPassed = "policy_evaluations_passed" + // FieldPolicyEvaluationsSkipped holds the string denoting the policy_evaluations_skipped field in the database. + FieldPolicyEvaluationsSkipped = "policy_evaluations_skipped" + // FieldPolicyViolationsCount holds the string denoting the policy_violations_count field in the database. + FieldPolicyViolationsCount = "policy_violations_count" // EdgeWorkflow holds the string denoting the workflow edge name in mutations. EdgeWorkflow = "workflow" // EdgeContractVersion holds the string denoting the contract_version edge name in mutations. @@ -109,6 +119,11 @@ var Columns = []string{ FieldVersionID, FieldWorkflowID, FieldHasPolicyViolations, + FieldPolicyStatus, + FieldPolicyEvaluationsTotal, + FieldPolicyEvaluationsPassed, + FieldPolicyEvaluationsSkipped, + FieldPolicyViolationsCount, } // ForeignKeys holds the SQL foreign-keys that are owned by the "workflow_runs" @@ -157,6 +172,33 @@ func StateValidator(s biz.WorkflowRunStatus) error { } } +// PolicyStatus defines the type for the "policy_status" enum field. +type PolicyStatus string + +// PolicyStatus values. +const ( + PolicyStatusNOT_APPLICABLE PolicyStatus = "NOT_APPLICABLE" + PolicyStatusPASSED PolicyStatus = "PASSED" + PolicyStatusSKIPPED PolicyStatus = "SKIPPED" + PolicyStatusWARNING PolicyStatus = "WARNING" + PolicyStatusBLOCKED PolicyStatus = "BLOCKED" + PolicyStatusBYPASSED PolicyStatus = "BYPASSED" +) + +func (ps PolicyStatus) String() string { + return string(ps) +} + +// PolicyStatusValidator is a validator for the "policy_status" field enum values. It is called by the builders before save. +func PolicyStatusValidator(ps PolicyStatus) error { + switch ps { + case PolicyStatusNOT_APPLICABLE, PolicyStatusPASSED, PolicyStatusSKIPPED, PolicyStatusWARNING, PolicyStatusBLOCKED, PolicyStatusBYPASSED: + return nil + default: + return fmt.Errorf("workflowrun: invalid enum value for policy_status field: %q", ps) + } +} + // OrderOption defines the ordering options for the WorkflowRun queries. type OrderOption func(*sql.Selector) @@ -225,6 +267,31 @@ func ByHasPolicyViolations(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldHasPolicyViolations, opts...).ToFunc() } +// ByPolicyStatus orders the results by the policy_status field. +func ByPolicyStatus(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPolicyStatus, opts...).ToFunc() +} + +// ByPolicyEvaluationsTotal orders the results by the policy_evaluations_total field. +func ByPolicyEvaluationsTotal(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPolicyEvaluationsTotal, opts...).ToFunc() +} + +// ByPolicyEvaluationsPassed orders the results by the policy_evaluations_passed field. +func ByPolicyEvaluationsPassed(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPolicyEvaluationsPassed, opts...).ToFunc() +} + +// ByPolicyEvaluationsSkipped orders the results by the policy_evaluations_skipped field. +func ByPolicyEvaluationsSkipped(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPolicyEvaluationsSkipped, opts...).ToFunc() +} + +// ByPolicyViolationsCount orders the results by the policy_violations_count field. +func ByPolicyViolationsCount(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPolicyViolationsCount, opts...).ToFunc() +} + // ByWorkflowField orders the results by workflow field. func ByWorkflowField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/workflowrun_create.go b/app/controlplane/pkg/data/ent/workflowrun_create.go index 2199ebdf2..252344fc0 100644 --- a/app/controlplane/pkg/data/ent/workflowrun_create.go +++ b/app/controlplane/pkg/data/ent/workflowrun_create.go @@ -179,6 +179,76 @@ func (_c *WorkflowRunCreate) SetNillableHasPolicyViolations(v *bool) *WorkflowRu return _c } +// SetPolicyStatus sets the "policy_status" field. +func (_c *WorkflowRunCreate) SetPolicyStatus(v workflowrun.PolicyStatus) *WorkflowRunCreate { + _c.mutation.SetPolicyStatus(v) + return _c +} + +// SetNillablePolicyStatus sets the "policy_status" field if the given value is not nil. +func (_c *WorkflowRunCreate) SetNillablePolicyStatus(v *workflowrun.PolicyStatus) *WorkflowRunCreate { + if v != nil { + _c.SetPolicyStatus(*v) + } + return _c +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (_c *WorkflowRunCreate) SetPolicyEvaluationsTotal(v int32) *WorkflowRunCreate { + _c.mutation.SetPolicyEvaluationsTotal(v) + return _c +} + +// SetNillablePolicyEvaluationsTotal sets the "policy_evaluations_total" field if the given value is not nil. +func (_c *WorkflowRunCreate) SetNillablePolicyEvaluationsTotal(v *int32) *WorkflowRunCreate { + if v != nil { + _c.SetPolicyEvaluationsTotal(*v) + } + return _c +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (_c *WorkflowRunCreate) SetPolicyEvaluationsPassed(v int32) *WorkflowRunCreate { + _c.mutation.SetPolicyEvaluationsPassed(v) + return _c +} + +// SetNillablePolicyEvaluationsPassed sets the "policy_evaluations_passed" field if the given value is not nil. +func (_c *WorkflowRunCreate) SetNillablePolicyEvaluationsPassed(v *int32) *WorkflowRunCreate { + if v != nil { + _c.SetPolicyEvaluationsPassed(*v) + } + return _c +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (_c *WorkflowRunCreate) SetPolicyEvaluationsSkipped(v int32) *WorkflowRunCreate { + _c.mutation.SetPolicyEvaluationsSkipped(v) + return _c +} + +// SetNillablePolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field if the given value is not nil. +func (_c *WorkflowRunCreate) SetNillablePolicyEvaluationsSkipped(v *int32) *WorkflowRunCreate { + if v != nil { + _c.SetPolicyEvaluationsSkipped(*v) + } + return _c +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (_c *WorkflowRunCreate) SetPolicyViolationsCount(v int32) *WorkflowRunCreate { + _c.mutation.SetPolicyViolationsCount(v) + return _c +} + +// SetNillablePolicyViolationsCount sets the "policy_violations_count" field if the given value is not nil. +func (_c *WorkflowRunCreate) SetNillablePolicyViolationsCount(v *int32) *WorkflowRunCreate { + if v != nil { + _c.SetPolicyViolationsCount(*v) + } + return _c +} + // SetID sets the "id" field. func (_c *WorkflowRunCreate) SetID(v uuid.UUID) *WorkflowRunCreate { _c.mutation.SetID(v) @@ -330,6 +400,11 @@ func (_c *WorkflowRunCreate) check() error { if _, ok := _c.mutation.WorkflowID(); !ok { return &ValidationError{Name: "workflow_id", err: errors.New(`ent: missing required field "WorkflowRun.workflow_id"`)} } + if v, ok := _c.mutation.PolicyStatus(); ok { + if err := workflowrun.PolicyStatusValidator(v); err != nil { + return &ValidationError{Name: "policy_status", err: fmt.Errorf(`ent: validator failed for field "WorkflowRun.policy_status": %w`, err)} + } + } if len(_c.mutation.WorkflowIDs()) == 0 { return &ValidationError{Name: "workflow", err: errors.New(`ent: missing required edge "WorkflowRun.workflow"`)} } @@ -420,6 +495,26 @@ func (_c *WorkflowRunCreate) createSpec() (*WorkflowRun, *sqlgraph.CreateSpec) { _spec.SetField(workflowrun.FieldHasPolicyViolations, field.TypeBool, value) _node.HasPolicyViolations = &value } + if value, ok := _c.mutation.PolicyStatus(); ok { + _spec.SetField(workflowrun.FieldPolicyStatus, field.TypeEnum, value) + _node.PolicyStatus = &value + } + if value, ok := _c.mutation.PolicyEvaluationsTotal(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32, value) + _node.PolicyEvaluationsTotal = &value + } + if value, ok := _c.mutation.PolicyEvaluationsPassed(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32, value) + _node.PolicyEvaluationsPassed = &value + } + if value, ok := _c.mutation.PolicyEvaluationsSkipped(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32, value) + _node.PolicyEvaluationsSkipped = &value + } + if value, ok := _c.mutation.PolicyViolationsCount(); ok { + _spec.SetField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32, value) + _node.PolicyViolationsCount = &value + } if nodes := _c.mutation.WorkflowIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -759,6 +854,120 @@ func (u *WorkflowRunUpsert) ClearHasPolicyViolations() *WorkflowRunUpsert { return u } +// SetPolicyStatus sets the "policy_status" field. +func (u *WorkflowRunUpsert) SetPolicyStatus(v workflowrun.PolicyStatus) *WorkflowRunUpsert { + u.Set(workflowrun.FieldPolicyStatus, v) + return u +} + +// UpdatePolicyStatus sets the "policy_status" field to the value that was provided on create. +func (u *WorkflowRunUpsert) UpdatePolicyStatus() *WorkflowRunUpsert { + u.SetExcluded(workflowrun.FieldPolicyStatus) + return u +} + +// ClearPolicyStatus clears the value of the "policy_status" field. +func (u *WorkflowRunUpsert) ClearPolicyStatus() *WorkflowRunUpsert { + u.SetNull(workflowrun.FieldPolicyStatus) + return u +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (u *WorkflowRunUpsert) SetPolicyEvaluationsTotal(v int32) *WorkflowRunUpsert { + u.Set(workflowrun.FieldPolicyEvaluationsTotal, v) + return u +} + +// UpdatePolicyEvaluationsTotal sets the "policy_evaluations_total" field to the value that was provided on create. +func (u *WorkflowRunUpsert) UpdatePolicyEvaluationsTotal() *WorkflowRunUpsert { + u.SetExcluded(workflowrun.FieldPolicyEvaluationsTotal) + return u +} + +// AddPolicyEvaluationsTotal adds v to the "policy_evaluations_total" field. +func (u *WorkflowRunUpsert) AddPolicyEvaluationsTotal(v int32) *WorkflowRunUpsert { + u.Add(workflowrun.FieldPolicyEvaluationsTotal, v) + return u +} + +// ClearPolicyEvaluationsTotal clears the value of the "policy_evaluations_total" field. +func (u *WorkflowRunUpsert) ClearPolicyEvaluationsTotal() *WorkflowRunUpsert { + u.SetNull(workflowrun.FieldPolicyEvaluationsTotal) + return u +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsert) SetPolicyEvaluationsPassed(v int32) *WorkflowRunUpsert { + u.Set(workflowrun.FieldPolicyEvaluationsPassed, v) + return u +} + +// UpdatePolicyEvaluationsPassed sets the "policy_evaluations_passed" field to the value that was provided on create. +func (u *WorkflowRunUpsert) UpdatePolicyEvaluationsPassed() *WorkflowRunUpsert { + u.SetExcluded(workflowrun.FieldPolicyEvaluationsPassed) + return u +} + +// AddPolicyEvaluationsPassed adds v to the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsert) AddPolicyEvaluationsPassed(v int32) *WorkflowRunUpsert { + u.Add(workflowrun.FieldPolicyEvaluationsPassed, v) + return u +} + +// ClearPolicyEvaluationsPassed clears the value of the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsert) ClearPolicyEvaluationsPassed() *WorkflowRunUpsert { + u.SetNull(workflowrun.FieldPolicyEvaluationsPassed) + return u +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsert) SetPolicyEvaluationsSkipped(v int32) *WorkflowRunUpsert { + u.Set(workflowrun.FieldPolicyEvaluationsSkipped, v) + return u +} + +// UpdatePolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field to the value that was provided on create. +func (u *WorkflowRunUpsert) UpdatePolicyEvaluationsSkipped() *WorkflowRunUpsert { + u.SetExcluded(workflowrun.FieldPolicyEvaluationsSkipped) + return u +} + +// AddPolicyEvaluationsSkipped adds v to the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsert) AddPolicyEvaluationsSkipped(v int32) *WorkflowRunUpsert { + u.Add(workflowrun.FieldPolicyEvaluationsSkipped, v) + return u +} + +// ClearPolicyEvaluationsSkipped clears the value of the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsert) ClearPolicyEvaluationsSkipped() *WorkflowRunUpsert { + u.SetNull(workflowrun.FieldPolicyEvaluationsSkipped) + return u +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (u *WorkflowRunUpsert) SetPolicyViolationsCount(v int32) *WorkflowRunUpsert { + u.Set(workflowrun.FieldPolicyViolationsCount, v) + return u +} + +// UpdatePolicyViolationsCount sets the "policy_violations_count" field to the value that was provided on create. +func (u *WorkflowRunUpsert) UpdatePolicyViolationsCount() *WorkflowRunUpsert { + u.SetExcluded(workflowrun.FieldPolicyViolationsCount) + return u +} + +// AddPolicyViolationsCount adds v to the "policy_violations_count" field. +func (u *WorkflowRunUpsert) AddPolicyViolationsCount(v int32) *WorkflowRunUpsert { + u.Add(workflowrun.FieldPolicyViolationsCount, v) + return u +} + +// ClearPolicyViolationsCount clears the value of the "policy_violations_count" field. +func (u *WorkflowRunUpsert) ClearPolicyViolationsCount() *WorkflowRunUpsert { + u.SetNull(workflowrun.FieldPolicyViolationsCount) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. // Using this option is equivalent to using: // @@ -1051,6 +1260,139 @@ func (u *WorkflowRunUpsertOne) ClearHasPolicyViolations() *WorkflowRunUpsertOne }) } +// SetPolicyStatus sets the "policy_status" field. +func (u *WorkflowRunUpsertOne) SetPolicyStatus(v workflowrun.PolicyStatus) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyStatus(v) + }) +} + +// UpdatePolicyStatus sets the "policy_status" field to the value that was provided on create. +func (u *WorkflowRunUpsertOne) UpdatePolicyStatus() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyStatus() + }) +} + +// ClearPolicyStatus clears the value of the "policy_status" field. +func (u *WorkflowRunUpsertOne) ClearPolicyStatus() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyStatus() + }) +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (u *WorkflowRunUpsertOne) SetPolicyEvaluationsTotal(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyEvaluationsTotal(v) + }) +} + +// AddPolicyEvaluationsTotal adds v to the "policy_evaluations_total" field. +func (u *WorkflowRunUpsertOne) AddPolicyEvaluationsTotal(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyEvaluationsTotal(v) + }) +} + +// UpdatePolicyEvaluationsTotal sets the "policy_evaluations_total" field to the value that was provided on create. +func (u *WorkflowRunUpsertOne) UpdatePolicyEvaluationsTotal() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyEvaluationsTotal() + }) +} + +// ClearPolicyEvaluationsTotal clears the value of the "policy_evaluations_total" field. +func (u *WorkflowRunUpsertOne) ClearPolicyEvaluationsTotal() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyEvaluationsTotal() + }) +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsertOne) SetPolicyEvaluationsPassed(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyEvaluationsPassed(v) + }) +} + +// AddPolicyEvaluationsPassed adds v to the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsertOne) AddPolicyEvaluationsPassed(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyEvaluationsPassed(v) + }) +} + +// UpdatePolicyEvaluationsPassed sets the "policy_evaluations_passed" field to the value that was provided on create. +func (u *WorkflowRunUpsertOne) UpdatePolicyEvaluationsPassed() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyEvaluationsPassed() + }) +} + +// ClearPolicyEvaluationsPassed clears the value of the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsertOne) ClearPolicyEvaluationsPassed() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyEvaluationsPassed() + }) +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsertOne) SetPolicyEvaluationsSkipped(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyEvaluationsSkipped(v) + }) +} + +// AddPolicyEvaluationsSkipped adds v to the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsertOne) AddPolicyEvaluationsSkipped(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyEvaluationsSkipped(v) + }) +} + +// UpdatePolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field to the value that was provided on create. +func (u *WorkflowRunUpsertOne) UpdatePolicyEvaluationsSkipped() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyEvaluationsSkipped() + }) +} + +// ClearPolicyEvaluationsSkipped clears the value of the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsertOne) ClearPolicyEvaluationsSkipped() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyEvaluationsSkipped() + }) +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (u *WorkflowRunUpsertOne) SetPolicyViolationsCount(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyViolationsCount(v) + }) +} + +// AddPolicyViolationsCount adds v to the "policy_violations_count" field. +func (u *WorkflowRunUpsertOne) AddPolicyViolationsCount(v int32) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyViolationsCount(v) + }) +} + +// UpdatePolicyViolationsCount sets the "policy_violations_count" field to the value that was provided on create. +func (u *WorkflowRunUpsertOne) UpdatePolicyViolationsCount() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyViolationsCount() + }) +} + +// ClearPolicyViolationsCount clears the value of the "policy_violations_count" field. +func (u *WorkflowRunUpsertOne) ClearPolicyViolationsCount() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyViolationsCount() + }) +} + // Exec executes the query. func (u *WorkflowRunUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -1510,6 +1852,139 @@ func (u *WorkflowRunUpsertBulk) ClearHasPolicyViolations() *WorkflowRunUpsertBul }) } +// SetPolicyStatus sets the "policy_status" field. +func (u *WorkflowRunUpsertBulk) SetPolicyStatus(v workflowrun.PolicyStatus) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyStatus(v) + }) +} + +// UpdatePolicyStatus sets the "policy_status" field to the value that was provided on create. +func (u *WorkflowRunUpsertBulk) UpdatePolicyStatus() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyStatus() + }) +} + +// ClearPolicyStatus clears the value of the "policy_status" field. +func (u *WorkflowRunUpsertBulk) ClearPolicyStatus() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyStatus() + }) +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (u *WorkflowRunUpsertBulk) SetPolicyEvaluationsTotal(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyEvaluationsTotal(v) + }) +} + +// AddPolicyEvaluationsTotal adds v to the "policy_evaluations_total" field. +func (u *WorkflowRunUpsertBulk) AddPolicyEvaluationsTotal(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyEvaluationsTotal(v) + }) +} + +// UpdatePolicyEvaluationsTotal sets the "policy_evaluations_total" field to the value that was provided on create. +func (u *WorkflowRunUpsertBulk) UpdatePolicyEvaluationsTotal() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyEvaluationsTotal() + }) +} + +// ClearPolicyEvaluationsTotal clears the value of the "policy_evaluations_total" field. +func (u *WorkflowRunUpsertBulk) ClearPolicyEvaluationsTotal() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyEvaluationsTotal() + }) +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsertBulk) SetPolicyEvaluationsPassed(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyEvaluationsPassed(v) + }) +} + +// AddPolicyEvaluationsPassed adds v to the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsertBulk) AddPolicyEvaluationsPassed(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyEvaluationsPassed(v) + }) +} + +// UpdatePolicyEvaluationsPassed sets the "policy_evaluations_passed" field to the value that was provided on create. +func (u *WorkflowRunUpsertBulk) UpdatePolicyEvaluationsPassed() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyEvaluationsPassed() + }) +} + +// ClearPolicyEvaluationsPassed clears the value of the "policy_evaluations_passed" field. +func (u *WorkflowRunUpsertBulk) ClearPolicyEvaluationsPassed() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyEvaluationsPassed() + }) +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsertBulk) SetPolicyEvaluationsSkipped(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyEvaluationsSkipped(v) + }) +} + +// AddPolicyEvaluationsSkipped adds v to the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsertBulk) AddPolicyEvaluationsSkipped(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyEvaluationsSkipped(v) + }) +} + +// UpdatePolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field to the value that was provided on create. +func (u *WorkflowRunUpsertBulk) UpdatePolicyEvaluationsSkipped() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyEvaluationsSkipped() + }) +} + +// ClearPolicyEvaluationsSkipped clears the value of the "policy_evaluations_skipped" field. +func (u *WorkflowRunUpsertBulk) ClearPolicyEvaluationsSkipped() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyEvaluationsSkipped() + }) +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (u *WorkflowRunUpsertBulk) SetPolicyViolationsCount(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyViolationsCount(v) + }) +} + +// AddPolicyViolationsCount adds v to the "policy_violations_count" field. +func (u *WorkflowRunUpsertBulk) AddPolicyViolationsCount(v int32) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.AddPolicyViolationsCount(v) + }) +} + +// UpdatePolicyViolationsCount sets the "policy_violations_count" field to the value that was provided on create. +func (u *WorkflowRunUpsertBulk) UpdatePolicyViolationsCount() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyViolationsCount() + }) +} + +// ClearPolicyViolationsCount clears the value of the "policy_violations_count" field. +func (u *WorkflowRunUpsertBulk) ClearPolicyViolationsCount() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyViolationsCount() + }) +} + // Exec executes the query. func (u *WorkflowRunUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/app/controlplane/pkg/data/ent/workflowrun_update.go b/app/controlplane/pkg/data/ent/workflowrun_update.go index df7d06bc1..8423af64a 100644 --- a/app/controlplane/pkg/data/ent/workflowrun_update.go +++ b/app/controlplane/pkg/data/ent/workflowrun_update.go @@ -250,6 +250,134 @@ func (_u *WorkflowRunUpdate) ClearHasPolicyViolations() *WorkflowRunUpdate { return _u } +// SetPolicyStatus sets the "policy_status" field. +func (_u *WorkflowRunUpdate) SetPolicyStatus(v workflowrun.PolicyStatus) *WorkflowRunUpdate { + _u.mutation.SetPolicyStatus(v) + return _u +} + +// SetNillablePolicyStatus sets the "policy_status" field if the given value is not nil. +func (_u *WorkflowRunUpdate) SetNillablePolicyStatus(v *workflowrun.PolicyStatus) *WorkflowRunUpdate { + if v != nil { + _u.SetPolicyStatus(*v) + } + return _u +} + +// ClearPolicyStatus clears the value of the "policy_status" field. +func (_u *WorkflowRunUpdate) ClearPolicyStatus() *WorkflowRunUpdate { + _u.mutation.ClearPolicyStatus() + return _u +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (_u *WorkflowRunUpdate) SetPolicyEvaluationsTotal(v int32) *WorkflowRunUpdate { + _u.mutation.ResetPolicyEvaluationsTotal() + _u.mutation.SetPolicyEvaluationsTotal(v) + return _u +} + +// SetNillablePolicyEvaluationsTotal sets the "policy_evaluations_total" field if the given value is not nil. +func (_u *WorkflowRunUpdate) SetNillablePolicyEvaluationsTotal(v *int32) *WorkflowRunUpdate { + if v != nil { + _u.SetPolicyEvaluationsTotal(*v) + } + return _u +} + +// AddPolicyEvaluationsTotal adds value to the "policy_evaluations_total" field. +func (_u *WorkflowRunUpdate) AddPolicyEvaluationsTotal(v int32) *WorkflowRunUpdate { + _u.mutation.AddPolicyEvaluationsTotal(v) + return _u +} + +// ClearPolicyEvaluationsTotal clears the value of the "policy_evaluations_total" field. +func (_u *WorkflowRunUpdate) ClearPolicyEvaluationsTotal() *WorkflowRunUpdate { + _u.mutation.ClearPolicyEvaluationsTotal() + return _u +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (_u *WorkflowRunUpdate) SetPolicyEvaluationsPassed(v int32) *WorkflowRunUpdate { + _u.mutation.ResetPolicyEvaluationsPassed() + _u.mutation.SetPolicyEvaluationsPassed(v) + return _u +} + +// SetNillablePolicyEvaluationsPassed sets the "policy_evaluations_passed" field if the given value is not nil. +func (_u *WorkflowRunUpdate) SetNillablePolicyEvaluationsPassed(v *int32) *WorkflowRunUpdate { + if v != nil { + _u.SetPolicyEvaluationsPassed(*v) + } + return _u +} + +// AddPolicyEvaluationsPassed adds value to the "policy_evaluations_passed" field. +func (_u *WorkflowRunUpdate) AddPolicyEvaluationsPassed(v int32) *WorkflowRunUpdate { + _u.mutation.AddPolicyEvaluationsPassed(v) + return _u +} + +// ClearPolicyEvaluationsPassed clears the value of the "policy_evaluations_passed" field. +func (_u *WorkflowRunUpdate) ClearPolicyEvaluationsPassed() *WorkflowRunUpdate { + _u.mutation.ClearPolicyEvaluationsPassed() + return _u +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (_u *WorkflowRunUpdate) SetPolicyEvaluationsSkipped(v int32) *WorkflowRunUpdate { + _u.mutation.ResetPolicyEvaluationsSkipped() + _u.mutation.SetPolicyEvaluationsSkipped(v) + return _u +} + +// SetNillablePolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field if the given value is not nil. +func (_u *WorkflowRunUpdate) SetNillablePolicyEvaluationsSkipped(v *int32) *WorkflowRunUpdate { + if v != nil { + _u.SetPolicyEvaluationsSkipped(*v) + } + return _u +} + +// AddPolicyEvaluationsSkipped adds value to the "policy_evaluations_skipped" field. +func (_u *WorkflowRunUpdate) AddPolicyEvaluationsSkipped(v int32) *WorkflowRunUpdate { + _u.mutation.AddPolicyEvaluationsSkipped(v) + return _u +} + +// ClearPolicyEvaluationsSkipped clears the value of the "policy_evaluations_skipped" field. +func (_u *WorkflowRunUpdate) ClearPolicyEvaluationsSkipped() *WorkflowRunUpdate { + _u.mutation.ClearPolicyEvaluationsSkipped() + return _u +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (_u *WorkflowRunUpdate) SetPolicyViolationsCount(v int32) *WorkflowRunUpdate { + _u.mutation.ResetPolicyViolationsCount() + _u.mutation.SetPolicyViolationsCount(v) + return _u +} + +// SetNillablePolicyViolationsCount sets the "policy_violations_count" field if the given value is not nil. +func (_u *WorkflowRunUpdate) SetNillablePolicyViolationsCount(v *int32) *WorkflowRunUpdate { + if v != nil { + _u.SetPolicyViolationsCount(*v) + } + return _u +} + +// AddPolicyViolationsCount adds value to the "policy_violations_count" field. +func (_u *WorkflowRunUpdate) AddPolicyViolationsCount(v int32) *WorkflowRunUpdate { + _u.mutation.AddPolicyViolationsCount(v) + return _u +} + +// ClearPolicyViolationsCount clears the value of the "policy_violations_count" field. +func (_u *WorkflowRunUpdate) ClearPolicyViolationsCount() *WorkflowRunUpdate { + _u.mutation.ClearPolicyViolationsCount() + return _u +} + // SetContractVersionID sets the "contract_version" edge to the WorkflowContractVersion entity by ID. func (_u *WorkflowRunUpdate) SetContractVersionID(id uuid.UUID) *WorkflowRunUpdate { _u.mutation.SetContractVersionID(id) @@ -386,6 +514,11 @@ func (_u *WorkflowRunUpdate) check() error { return &ValidationError{Name: "state", err: fmt.Errorf(`ent: validator failed for field "WorkflowRun.state": %w`, err)} } } + if v, ok := _u.mutation.PolicyStatus(); ok { + if err := workflowrun.PolicyStatusValidator(v); err != nil { + return &ValidationError{Name: "policy_status", err: fmt.Errorf(`ent: validator failed for field "WorkflowRun.policy_status": %w`, err)} + } + } if _u.mutation.WorkflowCleared() && len(_u.mutation.WorkflowIDs()) > 0 { return errors.New(`ent: clearing a required unique edge "WorkflowRun.workflow"`) } @@ -476,6 +609,48 @@ func (_u *WorkflowRunUpdate) sqlSave(ctx context.Context) (_node int, err error) if _u.mutation.HasPolicyViolationsCleared() { _spec.ClearField(workflowrun.FieldHasPolicyViolations, field.TypeBool) } + if value, ok := _u.mutation.PolicyStatus(); ok { + _spec.SetField(workflowrun.FieldPolicyStatus, field.TypeEnum, value) + } + if _u.mutation.PolicyStatusCleared() { + _spec.ClearField(workflowrun.FieldPolicyStatus, field.TypeEnum) + } + if value, ok := _u.mutation.PolicyEvaluationsTotal(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyEvaluationsTotal(); ok { + _spec.AddField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32, value) + } + if _u.mutation.PolicyEvaluationsTotalCleared() { + _spec.ClearField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32) + } + if value, ok := _u.mutation.PolicyEvaluationsPassed(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyEvaluationsPassed(); ok { + _spec.AddField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32, value) + } + if _u.mutation.PolicyEvaluationsPassedCleared() { + _spec.ClearField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32) + } + if value, ok := _u.mutation.PolicyEvaluationsSkipped(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyEvaluationsSkipped(); ok { + _spec.AddField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32, value) + } + if _u.mutation.PolicyEvaluationsSkippedCleared() { + _spec.ClearField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32) + } + if value, ok := _u.mutation.PolicyViolationsCount(); ok { + _spec.SetField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyViolationsCount(); ok { + _spec.AddField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32, value) + } + if _u.mutation.PolicyViolationsCountCleared() { + _spec.ClearField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32) + } if _u.mutation.ContractVersionCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -844,6 +1019,134 @@ func (_u *WorkflowRunUpdateOne) ClearHasPolicyViolations() *WorkflowRunUpdateOne return _u } +// SetPolicyStatus sets the "policy_status" field. +func (_u *WorkflowRunUpdateOne) SetPolicyStatus(v workflowrun.PolicyStatus) *WorkflowRunUpdateOne { + _u.mutation.SetPolicyStatus(v) + return _u +} + +// SetNillablePolicyStatus sets the "policy_status" field if the given value is not nil. +func (_u *WorkflowRunUpdateOne) SetNillablePolicyStatus(v *workflowrun.PolicyStatus) *WorkflowRunUpdateOne { + if v != nil { + _u.SetPolicyStatus(*v) + } + return _u +} + +// ClearPolicyStatus clears the value of the "policy_status" field. +func (_u *WorkflowRunUpdateOne) ClearPolicyStatus() *WorkflowRunUpdateOne { + _u.mutation.ClearPolicyStatus() + return _u +} + +// SetPolicyEvaluationsTotal sets the "policy_evaluations_total" field. +func (_u *WorkflowRunUpdateOne) SetPolicyEvaluationsTotal(v int32) *WorkflowRunUpdateOne { + _u.mutation.ResetPolicyEvaluationsTotal() + _u.mutation.SetPolicyEvaluationsTotal(v) + return _u +} + +// SetNillablePolicyEvaluationsTotal sets the "policy_evaluations_total" field if the given value is not nil. +func (_u *WorkflowRunUpdateOne) SetNillablePolicyEvaluationsTotal(v *int32) *WorkflowRunUpdateOne { + if v != nil { + _u.SetPolicyEvaluationsTotal(*v) + } + return _u +} + +// AddPolicyEvaluationsTotal adds value to the "policy_evaluations_total" field. +func (_u *WorkflowRunUpdateOne) AddPolicyEvaluationsTotal(v int32) *WorkflowRunUpdateOne { + _u.mutation.AddPolicyEvaluationsTotal(v) + return _u +} + +// ClearPolicyEvaluationsTotal clears the value of the "policy_evaluations_total" field. +func (_u *WorkflowRunUpdateOne) ClearPolicyEvaluationsTotal() *WorkflowRunUpdateOne { + _u.mutation.ClearPolicyEvaluationsTotal() + return _u +} + +// SetPolicyEvaluationsPassed sets the "policy_evaluations_passed" field. +func (_u *WorkflowRunUpdateOne) SetPolicyEvaluationsPassed(v int32) *WorkflowRunUpdateOne { + _u.mutation.ResetPolicyEvaluationsPassed() + _u.mutation.SetPolicyEvaluationsPassed(v) + return _u +} + +// SetNillablePolicyEvaluationsPassed sets the "policy_evaluations_passed" field if the given value is not nil. +func (_u *WorkflowRunUpdateOne) SetNillablePolicyEvaluationsPassed(v *int32) *WorkflowRunUpdateOne { + if v != nil { + _u.SetPolicyEvaluationsPassed(*v) + } + return _u +} + +// AddPolicyEvaluationsPassed adds value to the "policy_evaluations_passed" field. +func (_u *WorkflowRunUpdateOne) AddPolicyEvaluationsPassed(v int32) *WorkflowRunUpdateOne { + _u.mutation.AddPolicyEvaluationsPassed(v) + return _u +} + +// ClearPolicyEvaluationsPassed clears the value of the "policy_evaluations_passed" field. +func (_u *WorkflowRunUpdateOne) ClearPolicyEvaluationsPassed() *WorkflowRunUpdateOne { + _u.mutation.ClearPolicyEvaluationsPassed() + return _u +} + +// SetPolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field. +func (_u *WorkflowRunUpdateOne) SetPolicyEvaluationsSkipped(v int32) *WorkflowRunUpdateOne { + _u.mutation.ResetPolicyEvaluationsSkipped() + _u.mutation.SetPolicyEvaluationsSkipped(v) + return _u +} + +// SetNillablePolicyEvaluationsSkipped sets the "policy_evaluations_skipped" field if the given value is not nil. +func (_u *WorkflowRunUpdateOne) SetNillablePolicyEvaluationsSkipped(v *int32) *WorkflowRunUpdateOne { + if v != nil { + _u.SetPolicyEvaluationsSkipped(*v) + } + return _u +} + +// AddPolicyEvaluationsSkipped adds value to the "policy_evaluations_skipped" field. +func (_u *WorkflowRunUpdateOne) AddPolicyEvaluationsSkipped(v int32) *WorkflowRunUpdateOne { + _u.mutation.AddPolicyEvaluationsSkipped(v) + return _u +} + +// ClearPolicyEvaluationsSkipped clears the value of the "policy_evaluations_skipped" field. +func (_u *WorkflowRunUpdateOne) ClearPolicyEvaluationsSkipped() *WorkflowRunUpdateOne { + _u.mutation.ClearPolicyEvaluationsSkipped() + return _u +} + +// SetPolicyViolationsCount sets the "policy_violations_count" field. +func (_u *WorkflowRunUpdateOne) SetPolicyViolationsCount(v int32) *WorkflowRunUpdateOne { + _u.mutation.ResetPolicyViolationsCount() + _u.mutation.SetPolicyViolationsCount(v) + return _u +} + +// SetNillablePolicyViolationsCount sets the "policy_violations_count" field if the given value is not nil. +func (_u *WorkflowRunUpdateOne) SetNillablePolicyViolationsCount(v *int32) *WorkflowRunUpdateOne { + if v != nil { + _u.SetPolicyViolationsCount(*v) + } + return _u +} + +// AddPolicyViolationsCount adds value to the "policy_violations_count" field. +func (_u *WorkflowRunUpdateOne) AddPolicyViolationsCount(v int32) *WorkflowRunUpdateOne { + _u.mutation.AddPolicyViolationsCount(v) + return _u +} + +// ClearPolicyViolationsCount clears the value of the "policy_violations_count" field. +func (_u *WorkflowRunUpdateOne) ClearPolicyViolationsCount() *WorkflowRunUpdateOne { + _u.mutation.ClearPolicyViolationsCount() + return _u +} + // SetContractVersionID sets the "contract_version" edge to the WorkflowContractVersion entity by ID. func (_u *WorkflowRunUpdateOne) SetContractVersionID(id uuid.UUID) *WorkflowRunUpdateOne { _u.mutation.SetContractVersionID(id) @@ -993,6 +1296,11 @@ func (_u *WorkflowRunUpdateOne) check() error { return &ValidationError{Name: "state", err: fmt.Errorf(`ent: validator failed for field "WorkflowRun.state": %w`, err)} } } + if v, ok := _u.mutation.PolicyStatus(); ok { + if err := workflowrun.PolicyStatusValidator(v); err != nil { + return &ValidationError{Name: "policy_status", err: fmt.Errorf(`ent: validator failed for field "WorkflowRun.policy_status": %w`, err)} + } + } if _u.mutation.WorkflowCleared() && len(_u.mutation.WorkflowIDs()) > 0 { return errors.New(`ent: clearing a required unique edge "WorkflowRun.workflow"`) } @@ -1100,6 +1408,48 @@ func (_u *WorkflowRunUpdateOne) sqlSave(ctx context.Context) (_node *WorkflowRun if _u.mutation.HasPolicyViolationsCleared() { _spec.ClearField(workflowrun.FieldHasPolicyViolations, field.TypeBool) } + if value, ok := _u.mutation.PolicyStatus(); ok { + _spec.SetField(workflowrun.FieldPolicyStatus, field.TypeEnum, value) + } + if _u.mutation.PolicyStatusCleared() { + _spec.ClearField(workflowrun.FieldPolicyStatus, field.TypeEnum) + } + if value, ok := _u.mutation.PolicyEvaluationsTotal(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyEvaluationsTotal(); ok { + _spec.AddField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32, value) + } + if _u.mutation.PolicyEvaluationsTotalCleared() { + _spec.ClearField(workflowrun.FieldPolicyEvaluationsTotal, field.TypeInt32) + } + if value, ok := _u.mutation.PolicyEvaluationsPassed(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyEvaluationsPassed(); ok { + _spec.AddField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32, value) + } + if _u.mutation.PolicyEvaluationsPassedCleared() { + _spec.ClearField(workflowrun.FieldPolicyEvaluationsPassed, field.TypeInt32) + } + if value, ok := _u.mutation.PolicyEvaluationsSkipped(); ok { + _spec.SetField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyEvaluationsSkipped(); ok { + _spec.AddField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32, value) + } + if _u.mutation.PolicyEvaluationsSkippedCleared() { + _spec.ClearField(workflowrun.FieldPolicyEvaluationsSkipped, field.TypeInt32) + } + if value, ok := _u.mutation.PolicyViolationsCount(); ok { + _spec.SetField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32, value) + } + if value, ok := _u.mutation.AddedPolicyViolationsCount(); ok { + _spec.AddField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32, value) + } + if _u.mutation.PolicyViolationsCountCleared() { + _spec.ClearField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32) + } if _u.mutation.ContractVersionCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/app/controlplane/pkg/data/workflowrun.go b/app/controlplane/pkg/data/workflowrun.go index 03f9de520..41d4353fd 100644 --- a/app/controlplane/pkg/data/workflowrun.go +++ b/app/controlplane/pkg/data/workflowrun.go @@ -31,6 +31,7 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/pkg/data/ent/workflow" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/data/ent/workflowrun" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/pagination" + "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" "github.com/go-kratos/kratos/v2/log" "github.com/google/uuid" ) @@ -221,11 +222,28 @@ func (r *WorkflowRunRepo) SaveAttestationBundle(ctx context.Context, id uuid.UUI }) } -// UpdatePolicyViolationsStatus updates the policy violations status for a workflow run -func (r *WorkflowRunRepo) UpdatePolicyViolationsStatus(ctx context.Context, id uuid.UUID, hasPolicyViolations bool) error { - run, err := r.data.DB.WorkflowRun.UpdateOneID(id). - SetHasPolicyViolations(hasPolicyViolations). - Save(ctx) +// UpdatePolicyStatus persists the canonical policy status summary plus the +// legacy has_policy_violations bool (kept populated for back-compat with older +// clients still reading WorkflowRunItem.has_policy_violations). +func (r *WorkflowRunRepo) UpdatePolicyStatus(ctx context.Context, id uuid.UUID, summary *chainloop.PolicyStatusSummary) error { + if summary == nil { + return errors.New("policy status summary is required") + } + + entStatus, err := policyStatusToEnt(summary.Status) + if err != nil { + return err + } + + update := r.data.DB.WorkflowRun.UpdateOneID(id). + SetHasPolicyViolations(summary.Violated > 0). + SetPolicyStatus(entStatus). + SetPolicyEvaluationsTotal(int32(summary.Total)). + SetPolicyEvaluationsPassed(int32(summary.Passed)). + SetPolicyEvaluationsSkipped(int32(summary.Skipped)). + SetPolicyViolationsCount(int32(summary.Violated)) + + run, err := update.Save(ctx) if err != nil && !ent.IsNotFound(err) { return err } else if run == nil { @@ -235,6 +253,48 @@ func (r *WorkflowRunRepo) UpdatePolicyViolationsStatus(ctx context.Context, id u return nil } +// policyStatusToEnt maps the domain PolicyStatus onto the ent-generated enum. +// PolicyStatusUnspecified collapses to NOT_APPLICABLE so every persisted row +// has a deterministic categorical value. +func policyStatusToEnt(s chainloop.PolicyStatus) (workflowrun.PolicyStatus, error) { + switch s { + case chainloop.PolicyStatusNotApplicable, chainloop.PolicyStatusUnspecified: + return workflowrun.PolicyStatusNOT_APPLICABLE, nil + case chainloop.PolicyStatusPassed: + return workflowrun.PolicyStatusPASSED, nil + case chainloop.PolicyStatusSkipped: + return workflowrun.PolicyStatusSKIPPED, nil + case chainloop.PolicyStatusWarning: + return workflowrun.PolicyStatusWARNING, nil + case chainloop.PolicyStatusBlocked: + return workflowrun.PolicyStatusBLOCKED, nil + case chainloop.PolicyStatusBypassed: + return workflowrun.PolicyStatusBYPASSED, nil + default: + return "", fmt.Errorf("unknown policy status: %q", s) + } +} + +// entToPolicyStatus is the inverse of policyStatusToEnt. +func entToPolicyStatus(s workflowrun.PolicyStatus) chainloop.PolicyStatus { + switch s { + case workflowrun.PolicyStatusNOT_APPLICABLE: + return chainloop.PolicyStatusNotApplicable + case workflowrun.PolicyStatusPASSED: + return chainloop.PolicyStatusPassed + case workflowrun.PolicyStatusSKIPPED: + return chainloop.PolicyStatusSkipped + case workflowrun.PolicyStatusWARNING: + return chainloop.PolicyStatusWarning + case workflowrun.PolicyStatusBLOCKED: + return chainloop.PolicyStatusBlocked + case workflowrun.PolicyStatusBYPASSED: + return chainloop.PolicyStatusBypassed + default: + return chainloop.PolicyStatusUnspecified + } +} + func (r *WorkflowRunRepo) GetBundle(ctx context.Context, wrID uuid.UUID) ([]byte, error) { att, err := r.data.DB.Attestation.Query().Where(attestation.WorkflowrunID(wrID)).First(ctx) if err != nil { @@ -306,13 +366,20 @@ func (r *WorkflowRunRepo) List(ctx context.Context, orgID uuid.UUID, filters *bi q = q.Where(workflowrun.VersionID(*filters.VersionID)) } - // Append the policy violations filter if present - if filters != nil && filters.PolicyViolationsFilter != nil { + // Canonical policy status filter takes precedence over the legacy + // violations-only filter. Only one is applied to avoid contradictory + // predicates when a client sends both. + switch { + case filters != nil && filters.PolicyStatus != nil: + entStatus, err := policyStatusToEnt(*filters.PolicyStatus) + if err != nil { + return nil, "", err + } + q = q.Where(workflowrun.PolicyStatusEQ(entStatus)) + case filters != nil && filters.PolicyViolationsFilter != nil: if *filters.PolicyViolationsFilter { - // Filter for runs WITH violations (has_policy_violations = true) q = q.Where(workflowrun.HasPolicyViolations(true)) } else { - // Filter for runs WITHOUT violations (has_policy_violations = false) q = q.Where(workflowrun.HasPolicyViolations(false)) } } @@ -391,6 +458,7 @@ func entWrToBizWr(ctx context.Context, wr *ent.WorkflowRun) (*biz.WorkflowRun, e ContractRevisionUsed: wr.ContractRevisionUsed, ContractRevisionLatest: wr.ContractRevisionLatest, HasPolicyViolations: wr.HasPolicyViolations, + PolicyStatus: entWrPolicySummary(wr), Attestation: &biz.Attestation{ Digest: wr.AttestationDigest, }, @@ -426,3 +494,29 @@ func entWrToBizWr(ctx context.Context, wr *ent.WorkflowRun) (*biz.WorkflowRun, e return r, nil } + +// entWrPolicySummary builds the domain summary from the materialized columns. +// Returns nil when the row predates the materialization change (all columns +// NULL) so callers can tell "never computed" apart from "computed and empty". +func entWrPolicySummary(wr *ent.WorkflowRun) *chainloop.PolicyStatusSummary { + if wr.PolicyStatus == nil { + return nil + } + + s := &chainloop.PolicyStatusSummary{ + Status: entToPolicyStatus(*wr.PolicyStatus), + } + if wr.PolicyEvaluationsTotal != nil { + s.Total = int(*wr.PolicyEvaluationsTotal) + } + if wr.PolicyEvaluationsPassed != nil { + s.Passed = int(*wr.PolicyEvaluationsPassed) + } + if wr.PolicyEvaluationsSkipped != nil { + s.Skipped = int(*wr.PolicyEvaluationsSkipped) + } + if wr.PolicyViolationsCount != nil { + s.Violated = int(*wr.PolicyViolationsCount) + } + return s +} diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index b1a7947f2..06a8ba617 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -61,6 +61,10 @@ type PolicyEvaluationStatus struct { EvaluationsCount int // Total number of policy violations across all evaluations ViolationsCount int + // Number of evaluations that were skipped (did not run). + SkippedCount int + // Number of evaluations that passed: neither skipped nor with violations. + PassedCount int } type NormalizedMaterial struct { diff --git a/pkg/attestation/renderer/chainloop/policy_status.go b/pkg/attestation/renderer/chainloop/policy_status.go new file mode 100644 index 000000000..d66fe8ad3 --- /dev/null +++ b/pkg/attestation/renderer/chainloop/policy_status.go @@ -0,0 +1,87 @@ +// +// Copyright 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package chainloop + +// PolicyStatus is the canonical, categorical policy outcome for an attestation. +// It collapses the raw enforcement/bypass/violation signals carried by +// PolicyEvaluationStatus into a single flat value so that list and describe +// surfaces can render a consistent badge without re-deriving. +type PolicyStatus string + +const ( + PolicyStatusUnspecified PolicyStatus = "" + PolicyStatusNotApplicable PolicyStatus = "NOT_APPLICABLE" + PolicyStatusPassed PolicyStatus = "PASSED" + PolicyStatusSkipped PolicyStatus = "SKIPPED" + PolicyStatusWarning PolicyStatus = "WARNING" + PolicyStatusBlocked PolicyStatus = "BLOCKED" + PolicyStatusBypassed PolicyStatus = "BYPASSED" +) + +// PolicyStatusSummary bundles the categorical status with the per-evaluation +// counters the UI needs to render a breakdown ("3/5 passed") without pulling +// the full attestation envelope. +type PolicyStatusSummary struct { + Status PolicyStatus + Total int + Passed int + Skipped int + Violated int +} + +// DerivePolicyStatusSummary is the single source of truth for computing the +// canonical PolicyStatus from the raw signals on PolicyEvaluationStatus. +// +// Rules (applied in order): +// +// total == 0 -> NOT_APPLICABLE +// (hasGatedViolations || strategy==ENFORCED) & bypass -> BYPASSED +// (hasGatedViolations || strategy==ENFORCED) -> BLOCKED +// hasViolations -> WARNING +// skipped > 0 -> SKIPPED +// otherwise -> PASSED +// +// The rule order matters: a bypassed gated violation resolves to BYPASSED, +// not BLOCKED; a warning (advisory violation) wins over a SKIPPED sibling. +func DerivePolicyStatusSummary(s *PolicyEvaluationStatus) PolicyStatusSummary { + if s == nil || s.EvaluationsCount == 0 { + return PolicyStatusSummary{Status: PolicyStatusNotApplicable} + } + + summary := PolicyStatusSummary{ + Total: s.EvaluationsCount, + Passed: s.PassedCount, + Skipped: s.SkippedCount, + Violated: s.ViolationsCount, + } + + enforced := s.HasGatedViolations || s.Strategy == PolicyViolationBlockingStrategyEnforced + + switch { + case enforced && s.Bypassed: + summary.Status = PolicyStatusBypassed + case enforced && s.HasViolations: + summary.Status = PolicyStatusBlocked + case s.HasViolations: + summary.Status = PolicyStatusWarning + case s.SkippedCount > 0: + summary.Status = PolicyStatusSkipped + default: + summary.Status = PolicyStatusPassed + } + + return summary +} diff --git a/pkg/attestation/renderer/chainloop/policy_status_test.go b/pkg/attestation/renderer/chainloop/policy_status_test.go new file mode 100644 index 000000000..2237b98a5 --- /dev/null +++ b/pkg/attestation/renderer/chainloop/policy_status_test.go @@ -0,0 +1,210 @@ +// +// Copyright 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package chainloop + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDerivePolicyStatusSummary(t *testing.T) { + enforced := PolicyViolationBlockingStrategyEnforced + advisory := PolicyViolationBlockingStrategyAdvisory + + testCases := []struct { + name string + status *PolicyEvaluationStatus + want PolicyStatusSummary + }{ + { + name: "nil input is not applicable", + status: nil, + want: PolicyStatusSummary{Status: PolicyStatusNotApplicable}, + }, + { + name: "no evaluations is not applicable", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 0, + }, + want: PolicyStatusSummary{Status: PolicyStatusNotApplicable}, + }, + { + name: "all evaluations pass", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 3, + PassedCount: 3, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusPassed, + Total: 3, + Passed: 3, + }, + }, + { + name: "some skipped, none violated => skipped", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 3, + PassedCount: 2, + SkippedCount: 1, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusSkipped, + Total: 3, + Passed: 2, + Skipped: 1, + }, + }, + { + name: "all skipped, none violated => skipped", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 2, + SkippedCount: 2, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusSkipped, + Total: 2, + Skipped: 2, + }, + }, + { + name: "advisory violation with no gating => warning", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 2, + PassedCount: 1, + ViolationsCount: 3, + HasViolations: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusWarning, + Total: 2, + Passed: 1, + Violated: 3, + }, + }, + { + name: "gated violation, not bypassed => blocked", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 2, + PassedCount: 1, + ViolationsCount: 1, + HasViolations: true, + HasGatedViolations: true, + Blocked: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusBlocked, + Total: 2, + Passed: 1, + Violated: 1, + }, + }, + { + name: "enforced strategy with violations, not bypassed => blocked", + status: &PolicyEvaluationStatus{ + Strategy: enforced, + EvaluationsCount: 2, + PassedCount: 1, + ViolationsCount: 2, + HasViolations: true, + Blocked: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusBlocked, + Total: 2, + Passed: 1, + Violated: 2, + }, + }, + { + name: "gated violation bypassed => bypassed", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 2, + PassedCount: 1, + ViolationsCount: 1, + HasViolations: true, + HasGatedViolations: true, + Bypassed: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusBypassed, + Total: 2, + Passed: 1, + Violated: 1, + }, + }, + { + name: "enforced strategy with violations bypassed => bypassed", + status: &PolicyEvaluationStatus{ + Strategy: enforced, + EvaluationsCount: 1, + ViolationsCount: 1, + HasViolations: true, + Bypassed: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusBypassed, + Total: 1, + Violated: 1, + }, + }, + { + name: "enforced but no violations yet => passed", + status: &PolicyEvaluationStatus{ + Strategy: enforced, + EvaluationsCount: 2, + PassedCount: 2, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusPassed, + Total: 2, + Passed: 2, + }, + }, + { + name: "warning + skipped mix => warning (violations dominate skips)", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 3, + PassedCount: 1, + SkippedCount: 1, + ViolationsCount: 1, + HasViolations: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusWarning, + Total: 3, + Passed: 1, + Skipped: 1, + Violated: 1, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := DerivePolicyStatusSummary(tc.status) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index cf48aa3a6..f53d60422 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -56,6 +56,10 @@ type ProvenancePredicateV02 struct { PolicyEvaluationsCount int `json:"policyEvaluationsCount"` // Total number of policy violations across all evaluations PolicyViolationsCount int `json:"policyViolationsCount"` + // Number of evaluations that were skipped (did not run) + PolicySkippedCount int `json:"policySkippedCount,omitempty"` + // Number of evaluations that passed (no violations and not skipped) + PolicyPassedCount int `json:"policyPassedCount,omitempty"` // Whether the attestation has policy violations in gated policies PolicyHasGatedViolations bool `json:"policyHasGatedViolations,omitempty"` // Whether we want to block the attestation on policy violations @@ -262,6 +266,8 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) { PolicyHasViolations: evalResult.hasViolations, PolicyEvaluationsCount: evalResult.evaluationsCount, PolicyViolationsCount: evalResult.violationsCount, + PolicySkippedCount: evalResult.skippedCount, + PolicyPassedCount: evalResult.passedCount, PolicyHasGatedViolations: evalResult.hasGatedViolations, PolicyCheckBlockingStrategy: policyCheckBlockingStrategy, PolicyBlockBypassEnabled: r.att.GetBypassPolicyCheck(), @@ -311,6 +317,8 @@ type evaluationsResult struct { hasGatedViolations bool evaluationsCount int violationsCount int + skippedCount int + passedCount int } func groupEvaluations(evals []*v1.PolicyEvaluation) (*evaluationsResult, error) { @@ -329,12 +337,17 @@ func groupEvaluations(evals []*v1.PolicyEvaluation) (*evaluationsResult, error) return nil, err } - if len(ev.Violations) > 0 { + switch { + case len(ev.Violations) > 0: res.hasViolations = true res.violationsCount += len(ev.Violations) if ev.Gate { res.hasGatedViolations = true } + case ev.Skipped: + res.skippedCount++ + default: + res.passedCount++ } res.evaluations[keyName] = append(res.evaluations[keyName], ev) @@ -471,6 +484,8 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationSt HasGatedViolations: p.PolicyHasGatedViolations, EvaluationsCount: p.PolicyEvaluationsCount, ViolationsCount: p.PolicyViolationsCount, + SkippedCount: p.PolicySkippedCount, + PassedCount: p.PolicyPassedCount, } } diff --git a/pkg/attestation/renderer/chainloop/v02_test.go b/pkg/attestation/renderer/chainloop/v02_test.go index f7ccfdb3d..e7f50fd96 100644 --- a/pkg/attestation/renderer/chainloop/v02_test.go +++ b/pkg/attestation/renderer/chainloop/v02_test.go @@ -698,11 +698,15 @@ func TestPolicyEvaluationStatusCounts(t *testing.T) { PolicyEvaluationsCount: 5, PolicyViolationsCount: 2, PolicyHasViolations: true, + PolicySkippedCount: 1, + PolicyPassedCount: 2, } status := p.GetPolicyEvaluationStatus() assert.Equal(t, 5, status.EvaluationsCount) assert.Equal(t, 2, status.ViolationsCount) + assert.Equal(t, 1, status.SkippedCount) + assert.Equal(t, 2, status.PassedCount) assert.True(t, status.HasViolations) } From c1b5757736bb54598f33f37488fd6e43b55800d7 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sat, 18 Apr 2026 12:41:22 +0200 Subject: [PATCH 2/4] feat(api): mark has_policy_violations on WorkflowRunItem as deprecated Superseded by policy_summary.status (and policy_summary.violated > 0 for the coarse boolean case). Signed-off-by: Miguel Martinez Trivino --- .../api/controlplane/v1/response_messages.pb.go | 12 +++++++++--- .../api/controlplane/v1/response_messages.proto | 5 ++++- .../frontend/controlplane/v1/response_messages.ts | 9 ++++++++- .../controlplane.v1.WorkflowRunItem.jsonschema.json | 4 ++-- .../controlplane.v1.WorkflowRunItem.schema.json | 4 ++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index 5f2d651b5..44a1897e7 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -838,6 +838,11 @@ type WorkflowRunItem struct { // The version of the project the attestation was initiated with Version *ProjectVersion `protobuf:"bytes,13,opt,name=version,proto3" json:"version,omitempty"` // Whether the run has policy violations (null if no policies were evaluated) + // Deprecated: use policy_summary.violated > 0, or the richer + // policy_summary.status, which also distinguishes skipped / warning / + // blocked / bypassed outcomes. + // + // Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. HasPolicyViolations *bool `protobuf:"varint,14,opt,name=has_policy_violations,json=hasPolicyViolations,proto3,oneof" json:"has_policy_violations,omitempty"` // Canonical policy status summary for this run (null if no policies were // evaluated). Carries both the categorical PolicyStatus and per-evaluation @@ -969,6 +974,7 @@ func (x *WorkflowRunItem) GetVersion() *ProjectVersion { return nil } +// Deprecated: Marked as deprecated in controlplane/v1/response_messages.proto. func (x *WorkflowRunItem) GetHasPolicyViolations() bool { if x != nil && x.HasPolicyViolations != nil { return *x.HasPolicyViolations @@ -2930,7 +2936,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x18contract_revision_latest\x18\v \x01(\x05R\x16contractRevisionLatest\x12\x16\n" + "\x06public\x18\t \x01(\bR\x06public\x12 \n" + "\vdescription\x18\n" + - " \x01(\tR\vdescription\"\xcf\x06\n" + + " \x01(\tR\vdescription\"\xd3\x06\n" + "\x0fWorkflowRunItem\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x129\n" + "\n" + @@ -2948,8 +2954,8 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x16contract_revision_used\x18\n" + " \x01(\x05R\x14contractRevisionUsed\x128\n" + "\x18contract_revision_latest\x18\v \x01(\x05R\x16contractRevisionLatest\x129\n" + - "\aversion\x18\r \x01(\v2\x1f.controlplane.v1.ProjectVersionR\aversion\x127\n" + - "\x15has_policy_violations\x18\x0e \x01(\bH\x00R\x13hasPolicyViolations\x88\x01\x01\x12K\n" + + "\aversion\x18\r \x01(\v2\x1f.controlplane.v1.ProjectVersionR\aversion\x12;\n" + + "\x15has_policy_violations\x18\x0e \x01(\bB\x02\x18\x01H\x00R\x13hasPolicyViolations\x88\x01\x01\x12K\n" + "\x0epolicy_summary\x18\x0f \x01(\v2$.controlplane.v1.PolicyStatusSummaryR\rpolicySummaryB\x18\n" + "\x16_has_policy_violations\"\xd2\x01\n" + "\x0eProjectVersion\x12\x0e\n" + diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index 928bd936d..1475af6a3 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -68,7 +68,10 @@ message WorkflowRunItem { ProjectVersion version = 13; // Whether the run has policy violations (null if no policies were evaluated) - optional bool has_policy_violations = 14; + // Deprecated: use policy_summary.violated > 0, or the richer + // policy_summary.status, which also distinguishes skipped / warning / + // blocked / bypassed outcomes. + optional bool has_policy_violations = 14 [deprecated = true]; // Canonical policy status summary for this run (null if no policies were // evaluated). Carries both the categorical PolicyStatus and per-evaluation diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index 04d948e7f..bcddd0ec3 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -488,7 +488,14 @@ export interface WorkflowRunItem { contractRevisionLatest: number; /** The version of the project the attestation was initiated with */ version?: ProjectVersion; - /** Whether the run has policy violations (null if no policies were evaluated) */ + /** + * Whether the run has policy violations (null if no policies were evaluated) + * Deprecated: use policy_summary.violated > 0, or the richer + * policy_summary.status, which also distinguishes skipped / warning / + * blocked / bypassed outcomes. + * + * @deprecated + */ hasPolicyViolations?: | boolean | undefined; diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json index f9c3f1eb4..588a45c34 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.jsonschema.json @@ -25,7 +25,7 @@ "$ref": "google.protobuf.Timestamp.jsonschema.json" }, "^(has_policy_violations)$": { - "description": "Whether the run has policy violations (null if no policies were evaluated)", + "description": "Whether the run has policy violations (null if no policies were evaluated)\n Deprecated: use policy_summary.violated \u003e 0, or the richer\n policy_summary.status, which also distinguishes skipped / warning /\n blocked / bypassed outcomes.", "type": "boolean" }, "^(job_url)$": { @@ -84,7 +84,7 @@ "$ref": "google.protobuf.Timestamp.jsonschema.json" }, "hasPolicyViolations": { - "description": "Whether the run has policy violations (null if no policies were evaluated)", + "description": "Whether the run has policy violations (null if no policies were evaluated)\n Deprecated: use policy_summary.violated \u003e 0, or the richer\n policy_summary.status, which also distinguishes skipped / warning /\n blocked / bypassed outcomes.", "type": "boolean" }, "id": { diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json index a25191490..f0063d825 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunItem.schema.json @@ -25,7 +25,7 @@ "$ref": "google.protobuf.Timestamp.schema.json" }, "^(hasPolicyViolations)$": { - "description": "Whether the run has policy violations (null if no policies were evaluated)", + "description": "Whether the run has policy violations (null if no policies were evaluated)\n Deprecated: use policy_summary.violated \u003e 0, or the richer\n policy_summary.status, which also distinguishes skipped / warning /\n blocked / bypassed outcomes.", "type": "boolean" }, "^(jobUrl)$": { @@ -84,7 +84,7 @@ "$ref": "google.protobuf.Timestamp.schema.json" }, "has_policy_violations": { - "description": "Whether the run has policy violations (null if no policies were evaluated)", + "description": "Whether the run has policy violations (null if no policies were evaluated)\n Deprecated: use policy_summary.violated \u003e 0, or the richer\n policy_summary.status, which also distinguishes skipped / warning /\n blocked / bypassed outcomes.", "type": "boolean" }, "id": { From e1590b7c77818930b6a34d404bf20bf7a7f6acd4 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sat, 18 Apr 2026 12:48:05 +0200 Subject: [PATCH 3/4] fix(renderer): backfill skipped/passed counts for pre-existing attestations Attestations signed before the skipped/passed counters were added to the predicate decode them as zero. When inline evaluations are available, recompute at describe time so DerivePolicyStatusSummary doesn't misclassify skipped-only runs as PASSED. Identified by cubic. Signed-off-by: Miguel Martinez Trivino --- pkg/attestation/renderer/chainloop/v02.go | 36 +++++++++++++++- .../renderer/chainloop/v02_test.go | 43 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index f53d60422..ba3240df0 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -476,6 +476,22 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluationsRef() *intoto.ResourceDescr } func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationStatus { + skipped, passed := p.PolicySkippedCount, p.PolicyPassedCount + // Attestations signed before the skipped/passed counters were added to + // the predicate decode them as zero. When the inline evaluations are + // available, recompute to keep the canonical PolicyStatus derivation + // correct for historic envelopes (e.g. skipped-only runs would otherwise + // be misclassified as PASSED). + if skipped == 0 && passed == 0 && p.PolicyEvaluationsCount > 0 { + evals := p.PolicyEvaluations + if len(evals) == 0 { + evals = p.PolicyEvaluationsFallback + } + if len(evals) > 0 { + skipped, passed = countSkippedAndPassed(evals) + } + } + return &PolicyEvaluationStatus{ Strategy: p.PolicyCheckBlockingStrategy, Bypassed: p.PolicyBlockBypassEnabled, @@ -484,9 +500,25 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationSt HasGatedViolations: p.PolicyHasGatedViolations, EvaluationsCount: p.PolicyEvaluationsCount, ViolationsCount: p.PolicyViolationsCount, - SkippedCount: p.PolicySkippedCount, - PassedCount: p.PolicyPassedCount, + SkippedCount: skipped, + PassedCount: passed, + } +} + +func countSkippedAndPassed(evals map[string][]*PolicyEvaluation) (skipped, passed int) { + for _, list := range evals { + for _, ev := range list { + switch { + case len(ev.Violations) > 0: + // violation-bearing evaluations are neither passed nor skipped + case ev.Skipped: + skipped++ + default: + passed++ + } + } } + return } // Translate a ResourceDescriptor to a NormalizedMaterial diff --git a/pkg/attestation/renderer/chainloop/v02_test.go b/pkg/attestation/renderer/chainloop/v02_test.go index e7f50fd96..f6feaebcf 100644 --- a/pkg/attestation/renderer/chainloop/v02_test.go +++ b/pkg/attestation/renderer/chainloop/v02_test.go @@ -710,6 +710,49 @@ func TestPolicyEvaluationStatusCounts(t *testing.T) { assert.True(t, status.HasViolations) } +// Attestations predating the skipped/passed counters must still surface them +// correctly at describe time — otherwise a run where every evaluation was +// skipped would be misclassified as PASSED by DerivePolicyStatusSummary. +func TestPolicyEvaluationStatusBackfill(t *testing.T) { + evals := map[string][]*PolicyEvaluation{ + "material-a": { + {Skipped: true}, + {Skipped: true}, + }, + "material-b": { + {}, // passed + {Violations: []*PolicyViolation{{Message: "boom"}}}, + }, + } + p := &ProvenancePredicateV02{ + PolicyEvaluations: evals, + PolicyEvaluationsCount: 4, + PolicyViolationsCount: 1, + PolicyHasViolations: true, + } + + status := p.GetPolicyEvaluationStatus() + assert.Equal(t, 2, status.SkippedCount) + assert.Equal(t, 1, status.PassedCount) +} + +// When the predicate already carries counters, the inline evaluations are +// ignored — the stored values are authoritative for new attestations. +func TestPolicyEvaluationStatusDoesNotOverrideStoredCounters(t *testing.T) { + p := &ProvenancePredicateV02{ + PolicyEvaluations: map[string][]*PolicyEvaluation{ + "m": {{Skipped: true}}, + }, + PolicyEvaluationsCount: 3, + PolicySkippedCount: 0, + PolicyPassedCount: 3, + } + + status := p.GetPolicyEvaluationStatus() + assert.Equal(t, 0, status.SkippedCount) + assert.Equal(t, 3, status.PassedCount) +} + func TestPolicyEvaluationsField(t *testing.T) { raw, err := os.ReadFile("testdata/attestation-pe-snake.json") require.NoError(t, err) From 58e3deb69ec3cc29af46e5a7028aceaa4160b82a Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Tue, 21 Apr 2026 14:53:28 +0200 Subject: [PATCH 4/4] feat(api): add has_gates flag and PolicyGatesFilter to workflow run list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaces whether a run had gates in effect (any policy marked gate:true or contract using ENFORCED strategy) on both PolicyStatusSummary and WorkflowRunServiceListRequest. Independent of policy status — a PASSED run can still have has_gates=true. Materialized on workflow_run via a partial index so the list filter is cheap. Also refactored the renderer backfill for historic envelopes to a single pass that derives skipped/passed counts and gate presence together. Signed-off-by: Miguel Martinez Trivino --- .../controlplane/v1/response_messages.pb.go | 319 +++++++++++------- .../controlplane/v1/response_messages.proto | 11 + .../api/controlplane/v1/workflow_run.pb.go | 105 +++--- .../api/controlplane/v1/workflow_run.proto | 3 + .../controlplane/v1/response_messages.ts | 61 +++- .../frontend/controlplane/v1/workflow_run.ts | 22 ++ ...ane.v1.PolicyStatusSummary.jsonschema.json | 10 + ...olplane.v1.PolicyStatusSummary.schema.json | 10 + ...kflowRunServiceListRequest.jsonschema.json | 38 +++ ....WorkflowRunServiceListRequest.schema.json | 38 +++ .../internal/service/workflowrun.go | 6 + app/controlplane/pkg/biz/workflowrun.go | 3 + .../ent/migrate/migrations/20260418100730.sql | 4 +- .../pkg/data/ent/migrate/migrations/atlas.sum | 4 +- .../pkg/data/ent/migrate/schema.go | 23 +- app/controlplane/pkg/data/ent/mutation.go | 75 +++- .../pkg/data/ent/schema/workflowrun.go | 5 + app/controlplane/pkg/data/ent/workflowrun.go | 16 +- .../pkg/data/ent/workflowrun/where.go | 25 ++ .../pkg/data/ent/workflowrun/workflowrun.go | 8 + .../pkg/data/ent/workflowrun_create.go | 78 +++++ .../pkg/data/ent/workflowrun_update.go | 52 +++ app/controlplane/pkg/data/workflowrun.go | 10 +- .../renderer/chainloop/chainloop.go | 3 + .../renderer/chainloop/policy_status.go | 4 + .../renderer/chainloop/policy_status_test.go | 160 +++++++++ pkg/attestation/renderer/chainloop/v02.go | 41 ++- .../renderer/chainloop/v02_test.go | 4 + 28 files changed, 942 insertions(+), 196 deletions(-) diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index 44a1897e7..c81b0c385 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -224,6 +224,56 @@ func (PolicyStatus) EnumDescriptor() ([]byte, []int) { return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{2} } +// Server-side filter aligned with PolicyStatusSummary.has_gates. +type PolicyGatesFilter int32 + +const ( + PolicyGatesFilter_POLICY_GATES_FILTER_UNSPECIFIED PolicyGatesFilter = 0 + PolicyGatesFilter_POLICY_GATES_FILTER_WITH_GATES PolicyGatesFilter = 1 + PolicyGatesFilter_POLICY_GATES_FILTER_WITHOUT_GATES PolicyGatesFilter = 2 +) + +// Enum value maps for PolicyGatesFilter. +var ( + PolicyGatesFilter_name = map[int32]string{ + 0: "POLICY_GATES_FILTER_UNSPECIFIED", + 1: "POLICY_GATES_FILTER_WITH_GATES", + 2: "POLICY_GATES_FILTER_WITHOUT_GATES", + } + PolicyGatesFilter_value = map[string]int32{ + "POLICY_GATES_FILTER_UNSPECIFIED": 0, + "POLICY_GATES_FILTER_WITH_GATES": 1, + "POLICY_GATES_FILTER_WITHOUT_GATES": 2, + } +) + +func (x PolicyGatesFilter) Enum() *PolicyGatesFilter { + p := new(PolicyGatesFilter) + *p = x + return p +} + +func (x PolicyGatesFilter) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PolicyGatesFilter) Descriptor() protoreflect.EnumDescriptor { + return file_controlplane_v1_response_messages_proto_enumTypes[3].Descriptor() +} + +func (PolicyGatesFilter) Type() protoreflect.EnumType { + return &file_controlplane_v1_response_messages_proto_enumTypes[3] +} + +func (x PolicyGatesFilter) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PolicyGatesFilter.Descriptor instead. +func (PolicyGatesFilter) EnumDescriptor() ([]byte, []int) { + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3} +} + // Server-side filter aligned 1:1 with PolicyStatus values. type PolicyStatusFilter int32 @@ -270,11 +320,11 @@ func (x PolicyStatusFilter) String() string { } func (PolicyStatusFilter) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[3].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[4].Descriptor() } func (PolicyStatusFilter) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[3] + return &file_controlplane_v1_response_messages_proto_enumTypes[4] } func (x PolicyStatusFilter) Number() protoreflect.EnumNumber { @@ -283,7 +333,7 @@ func (x PolicyStatusFilter) Number() protoreflect.EnumNumber { // Deprecated: Use PolicyStatusFilter.Descriptor instead. func (PolicyStatusFilter) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{3} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4} } type MembershipRole int32 @@ -328,11 +378,11 @@ func (x MembershipRole) String() string { } func (MembershipRole) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[4].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[5].Descriptor() } func (MembershipRole) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[4] + return &file_controlplane_v1_response_messages_proto_enumTypes[5] } func (x MembershipRole) Number() protoreflect.EnumNumber { @@ -341,7 +391,7 @@ func (x MembershipRole) Number() protoreflect.EnumNumber { // Deprecated: Use MembershipRole.Descriptor instead. func (MembershipRole) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{4} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} } type AllowListError int32 @@ -374,11 +424,11 @@ func (x AllowListError) String() string { } func (AllowListError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[5].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[6].Descriptor() } func (AllowListError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[5] + return &file_controlplane_v1_response_messages_proto_enumTypes[6] } func (x AllowListError) Number() protoreflect.EnumNumber { @@ -387,7 +437,7 @@ func (x AllowListError) Number() protoreflect.EnumNumber { // Deprecated: Use AllowListError.Descriptor instead. func (AllowListError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{5} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} } type FederatedAuthError int32 @@ -420,11 +470,11 @@ func (x FederatedAuthError) String() string { } func (FederatedAuthError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[6].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[7].Descriptor() } func (FederatedAuthError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[6] + return &file_controlplane_v1_response_messages_proto_enumTypes[7] } func (x FederatedAuthError) Number() protoreflect.EnumNumber { @@ -433,7 +483,7 @@ func (x FederatedAuthError) Number() protoreflect.EnumNumber { // Deprecated: Use FederatedAuthError.Descriptor instead. func (FederatedAuthError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{6} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} } type UserWithNoMembershipError int32 @@ -466,11 +516,11 @@ func (x UserWithNoMembershipError) String() string { } func (UserWithNoMembershipError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[7].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[8].Descriptor() } func (UserWithNoMembershipError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[7] + return &file_controlplane_v1_response_messages_proto_enumTypes[8] } func (x UserWithNoMembershipError) Number() protoreflect.EnumNumber { @@ -479,7 +529,7 @@ func (x UserWithNoMembershipError) Number() protoreflect.EnumNumber { // Deprecated: Use UserWithNoMembershipError.Descriptor instead. func (UserWithNoMembershipError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{7} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} } type UserNotMemberOfOrgError int32 @@ -512,11 +562,11 @@ func (x UserNotMemberOfOrgError) String() string { } func (UserNotMemberOfOrgError) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[8].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[9].Descriptor() } func (UserNotMemberOfOrgError) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[8] + return &file_controlplane_v1_response_messages_proto_enumTypes[9] } func (x UserNotMemberOfOrgError) Number() protoreflect.EnumNumber { @@ -525,7 +575,7 @@ func (x UserNotMemberOfOrgError) Number() protoreflect.EnumNumber { // Deprecated: Use UserNotMemberOfOrgError.Descriptor instead. func (UserNotMemberOfOrgError) EnumDescriptor() ([]byte, []int) { - return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{8} + return file_controlplane_v1_response_messages_proto_rawDescGZIP(), []int{9} } type WorkflowContractVersionItem_RawBody_Format int32 @@ -564,11 +614,11 @@ func (x WorkflowContractVersionItem_RawBody_Format) String() string { } func (WorkflowContractVersionItem_RawBody_Format) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[9].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[10].Descriptor() } func (WorkflowContractVersionItem_RawBody_Format) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[9] + return &file_controlplane_v1_response_messages_proto_enumTypes[10] } func (x WorkflowContractVersionItem_RawBody_Format) Number() protoreflect.EnumNumber { @@ -613,11 +663,11 @@ func (x OrgItem_PolicyViolationBlockingStrategy) String() string { } func (OrgItem_PolicyViolationBlockingStrategy) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[10].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[11].Descriptor() } func (OrgItem_PolicyViolationBlockingStrategy) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[10] + return &file_controlplane_v1_response_messages_proto_enumTypes[11] } func (x OrgItem_PolicyViolationBlockingStrategy) Number() protoreflect.EnumNumber { @@ -662,11 +712,11 @@ func (x CASBackendItem_ValidationStatus) String() string { } func (CASBackendItem_ValidationStatus) Descriptor() protoreflect.EnumDescriptor { - return file_controlplane_v1_response_messages_proto_enumTypes[11].Descriptor() + return file_controlplane_v1_response_messages_proto_enumTypes[12].Descriptor() } func (CASBackendItem_ValidationStatus) Type() protoreflect.EnumType { - return &file_controlplane_v1_response_messages_proto_enumTypes[11] + return &file_controlplane_v1_response_messages_proto_enumTypes[12] } func (x CASBackendItem_ValidationStatus) Number() protoreflect.EnumNumber { @@ -1080,7 +1130,11 @@ type PolicyStatusSummary struct { // Number of evaluations that were skipped Skipped int32 `protobuf:"varint,4,opt,name=skipped,proto3" json:"skipped,omitempty"` // Total number of violations across all evaluations - Violated int32 `protobuf:"varint,5,opt,name=violated,proto3" json:"violated,omitempty"` + Violated int32 `protobuf:"varint,5,opt,name=violated,proto3" json:"violated,omitempty"` + // Whether this run had gates in effect — any policy marked gate:true or + // the contract using the ENFORCED blocking strategy. Independent of status: + // a PASSED run can still have has_gates=true. + HasGates bool `protobuf:"varint,6,opt,name=has_gates,json=hasGates,proto3" json:"has_gates,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1150,6 +1204,13 @@ func (x *PolicyStatusSummary) GetViolated() int32 { return 0 } +func (x *PolicyStatusSummary) GetHasGates() bool { + if x != nil { + return x.HasGates + } + return false +} + type AttestationItem struct { state protoimpl.MessageState `protogen:"open.v1"` // encoded DSEE envelope @@ -2967,13 +3028,14 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\n" + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + "\vreleased_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "releasedAt\"\xb0\x01\n" + + "releasedAt\"\xcd\x01\n" + "\x13PolicyStatusSummary\x125\n" + "\x06status\x18\x01 \x01(\x0e2\x1d.controlplane.v1.PolicyStatusR\x06status\x12\x14\n" + "\x05total\x18\x02 \x01(\x05R\x05total\x12\x16\n" + "\x06passed\x18\x03 \x01(\x05R\x06passed\x12\x18\n" + "\askipped\x18\x04 \x01(\x05R\askipped\x12\x1a\n" + - "\bviolated\x18\x05 \x01(\x05R\bviolated\"\xa4\f\n" + + "\bviolated\x18\x05 \x01(\x05R\bviolated\x12\x1b\n" + + "\thas_gates\x18\x06 \x01(\bR\bhasGates\"\xa4\f\n" + "\x0fAttestationItem\x12\x1e\n" + "\benvelope\x18\x03 \x01(\fB\x02\x18\x01R\benvelope\x12\x16\n" + "\x06bundle\x18\n" + @@ -3197,7 +3259,11 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x15POLICY_STATUS_SKIPPED\x10\x03\x12\x19\n" + "\x15POLICY_STATUS_WARNING\x10\x04\x12\x19\n" + "\x15POLICY_STATUS_BLOCKED\x10\x05\x12\x1a\n" + - "\x16POLICY_STATUS_BYPASSED\x10\x06*\x8d\x02\n" + + "\x16POLICY_STATUS_BYPASSED\x10\x06*\x83\x01\n" + + "\x11PolicyGatesFilter\x12#\n" + + "\x1fPOLICY_GATES_FILTER_UNSPECIFIED\x10\x00\x12\"\n" + + "\x1ePOLICY_GATES_FILTER_WITH_GATES\x10\x01\x12%\n" + + "!POLICY_GATES_FILTER_WITHOUT_GATES\x10\x02*\x8d\x02\n" + "\x12PolicyStatusFilter\x12$\n" + " POLICY_STATUS_FILTER_UNSPECIFIED\x10\x00\x12'\n" + "#POLICY_STATUS_FILTER_NOT_APPLICABLE\x10\x01\x12\x1f\n" + @@ -3238,112 +3304,113 @@ func file_controlplane_v1_response_messages_proto_rawDescGZIP() []byte { return file_controlplane_v1_response_messages_proto_rawDescData } -var file_controlplane_v1_response_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 12) +var file_controlplane_v1_response_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 13) var file_controlplane_v1_response_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_controlplane_v1_response_messages_proto_goTypes = []any{ (RunStatus)(0), // 0: controlplane.v1.RunStatus (PolicyViolationsFilter)(0), // 1: controlplane.v1.PolicyViolationsFilter (PolicyStatus)(0), // 2: controlplane.v1.PolicyStatus - (PolicyStatusFilter)(0), // 3: controlplane.v1.PolicyStatusFilter - (MembershipRole)(0), // 4: controlplane.v1.MembershipRole - (AllowListError)(0), // 5: controlplane.v1.AllowListError - (FederatedAuthError)(0), // 6: controlplane.v1.FederatedAuthError - (UserWithNoMembershipError)(0), // 7: controlplane.v1.UserWithNoMembershipError - (UserNotMemberOfOrgError)(0), // 8: controlplane.v1.UserNotMemberOfOrgError - (WorkflowContractVersionItem_RawBody_Format)(0), // 9: controlplane.v1.WorkflowContractVersionItem.RawBody.Format - (OrgItem_PolicyViolationBlockingStrategy)(0), // 10: controlplane.v1.OrgItem.PolicyViolationBlockingStrategy - (CASBackendItem_ValidationStatus)(0), // 11: controlplane.v1.CASBackendItem.ValidationStatus - (*WorkflowItem)(nil), // 12: controlplane.v1.WorkflowItem - (*WorkflowRunItem)(nil), // 13: controlplane.v1.WorkflowRunItem - (*ProjectVersion)(nil), // 14: controlplane.v1.ProjectVersion - (*PolicyStatusSummary)(nil), // 15: controlplane.v1.PolicyStatusSummary - (*AttestationItem)(nil), // 16: controlplane.v1.AttestationItem - (*PolicyEvaluations)(nil), // 17: controlplane.v1.PolicyEvaluations - (*PolicyEvaluation)(nil), // 18: controlplane.v1.PolicyEvaluation - (*PolicyViolation)(nil), // 19: controlplane.v1.PolicyViolation - (*PolicyReference)(nil), // 20: controlplane.v1.PolicyReference - (*WorkflowContractItem)(nil), // 21: controlplane.v1.WorkflowContractItem - (*ScopedEntity)(nil), // 22: controlplane.v1.ScopedEntity - (*WorkflowRef)(nil), // 23: controlplane.v1.WorkflowRef - (*WorkflowContractVersionItem)(nil), // 24: controlplane.v1.WorkflowContractVersionItem - (*User)(nil), // 25: controlplane.v1.User - (*OrgMembershipItem)(nil), // 26: controlplane.v1.OrgMembershipItem - (*OrgItem)(nil), // 27: controlplane.v1.OrgItem - (*CASBackendItem)(nil), // 28: controlplane.v1.CASBackendItem - (*APITokenItem)(nil), // 29: controlplane.v1.APITokenItem - nil, // 30: controlplane.v1.AttestationItem.AnnotationsEntry - nil, // 31: controlplane.v1.AttestationItem.PolicyEvaluationsEntry - (*AttestationItem_PolicyEvaluationStatus)(nil), // 32: controlplane.v1.AttestationItem.PolicyEvaluationStatus - (*AttestationItem_EnvVariable)(nil), // 33: controlplane.v1.AttestationItem.EnvVariable - (*AttestationItem_Material)(nil), // 34: controlplane.v1.AttestationItem.Material - nil, // 35: controlplane.v1.AttestationItem.Material.AnnotationsEntry - nil, // 36: controlplane.v1.PolicyEvaluation.AnnotationsEntry - nil, // 37: controlplane.v1.PolicyEvaluation.WithEntry - nil, // 38: controlplane.v1.PolicyReference.DigestEntry - (*WorkflowContractVersionItem_RawBody)(nil), // 39: controlplane.v1.WorkflowContractVersionItem.RawBody - (*CASBackendItem_Limits)(nil), // 40: controlplane.v1.CASBackendItem.Limits - (*timestamppb.Timestamp)(nil), // 41: google.protobuf.Timestamp - (v1.CraftingSchema_Runner_RunnerType)(0), // 42: workflowcontract.v1.CraftingSchema.Runner.RunnerType - (*v1.CraftingSchema)(nil), // 43: workflowcontract.v1.CraftingSchema + (PolicyGatesFilter)(0), // 3: controlplane.v1.PolicyGatesFilter + (PolicyStatusFilter)(0), // 4: controlplane.v1.PolicyStatusFilter + (MembershipRole)(0), // 5: controlplane.v1.MembershipRole + (AllowListError)(0), // 6: controlplane.v1.AllowListError + (FederatedAuthError)(0), // 7: controlplane.v1.FederatedAuthError + (UserWithNoMembershipError)(0), // 8: controlplane.v1.UserWithNoMembershipError + (UserNotMemberOfOrgError)(0), // 9: controlplane.v1.UserNotMemberOfOrgError + (WorkflowContractVersionItem_RawBody_Format)(0), // 10: controlplane.v1.WorkflowContractVersionItem.RawBody.Format + (OrgItem_PolicyViolationBlockingStrategy)(0), // 11: controlplane.v1.OrgItem.PolicyViolationBlockingStrategy + (CASBackendItem_ValidationStatus)(0), // 12: controlplane.v1.CASBackendItem.ValidationStatus + (*WorkflowItem)(nil), // 13: controlplane.v1.WorkflowItem + (*WorkflowRunItem)(nil), // 14: controlplane.v1.WorkflowRunItem + (*ProjectVersion)(nil), // 15: controlplane.v1.ProjectVersion + (*PolicyStatusSummary)(nil), // 16: controlplane.v1.PolicyStatusSummary + (*AttestationItem)(nil), // 17: controlplane.v1.AttestationItem + (*PolicyEvaluations)(nil), // 18: controlplane.v1.PolicyEvaluations + (*PolicyEvaluation)(nil), // 19: controlplane.v1.PolicyEvaluation + (*PolicyViolation)(nil), // 20: controlplane.v1.PolicyViolation + (*PolicyReference)(nil), // 21: controlplane.v1.PolicyReference + (*WorkflowContractItem)(nil), // 22: controlplane.v1.WorkflowContractItem + (*ScopedEntity)(nil), // 23: controlplane.v1.ScopedEntity + (*WorkflowRef)(nil), // 24: controlplane.v1.WorkflowRef + (*WorkflowContractVersionItem)(nil), // 25: controlplane.v1.WorkflowContractVersionItem + (*User)(nil), // 26: controlplane.v1.User + (*OrgMembershipItem)(nil), // 27: controlplane.v1.OrgMembershipItem + (*OrgItem)(nil), // 28: controlplane.v1.OrgItem + (*CASBackendItem)(nil), // 29: controlplane.v1.CASBackendItem + (*APITokenItem)(nil), // 30: controlplane.v1.APITokenItem + nil, // 31: controlplane.v1.AttestationItem.AnnotationsEntry + nil, // 32: controlplane.v1.AttestationItem.PolicyEvaluationsEntry + (*AttestationItem_PolicyEvaluationStatus)(nil), // 33: controlplane.v1.AttestationItem.PolicyEvaluationStatus + (*AttestationItem_EnvVariable)(nil), // 34: controlplane.v1.AttestationItem.EnvVariable + (*AttestationItem_Material)(nil), // 35: controlplane.v1.AttestationItem.Material + nil, // 36: controlplane.v1.AttestationItem.Material.AnnotationsEntry + nil, // 37: controlplane.v1.PolicyEvaluation.AnnotationsEntry + nil, // 38: controlplane.v1.PolicyEvaluation.WithEntry + nil, // 39: controlplane.v1.PolicyReference.DigestEntry + (*WorkflowContractVersionItem_RawBody)(nil), // 40: controlplane.v1.WorkflowContractVersionItem.RawBody + (*CASBackendItem_Limits)(nil), // 41: controlplane.v1.CASBackendItem.Limits + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp + (v1.CraftingSchema_Runner_RunnerType)(0), // 43: workflowcontract.v1.CraftingSchema.Runner.RunnerType + (*v1.CraftingSchema)(nil), // 44: workflowcontract.v1.CraftingSchema } var file_controlplane_v1_response_messages_proto_depIdxs = []int32{ - 41, // 0: controlplane.v1.WorkflowItem.created_at:type_name -> google.protobuf.Timestamp - 13, // 1: controlplane.v1.WorkflowItem.last_run:type_name -> controlplane.v1.WorkflowRunItem - 41, // 2: controlplane.v1.WorkflowRunItem.created_at:type_name -> google.protobuf.Timestamp - 41, // 3: controlplane.v1.WorkflowRunItem.finished_at:type_name -> google.protobuf.Timestamp + 42, // 0: controlplane.v1.WorkflowItem.created_at:type_name -> google.protobuf.Timestamp + 14, // 1: controlplane.v1.WorkflowItem.last_run:type_name -> controlplane.v1.WorkflowRunItem + 42, // 2: controlplane.v1.WorkflowRunItem.created_at:type_name -> google.protobuf.Timestamp + 42, // 3: controlplane.v1.WorkflowRunItem.finished_at:type_name -> google.protobuf.Timestamp 0, // 4: controlplane.v1.WorkflowRunItem.status:type_name -> controlplane.v1.RunStatus - 12, // 5: controlplane.v1.WorkflowRunItem.workflow:type_name -> controlplane.v1.WorkflowItem - 42, // 6: controlplane.v1.WorkflowRunItem.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType - 24, // 7: controlplane.v1.WorkflowRunItem.contract_version:type_name -> controlplane.v1.WorkflowContractVersionItem - 14, // 8: controlplane.v1.WorkflowRunItem.version:type_name -> controlplane.v1.ProjectVersion - 15, // 9: controlplane.v1.WorkflowRunItem.policy_summary:type_name -> controlplane.v1.PolicyStatusSummary - 41, // 10: controlplane.v1.ProjectVersion.created_at:type_name -> google.protobuf.Timestamp - 41, // 11: controlplane.v1.ProjectVersion.released_at:type_name -> google.protobuf.Timestamp + 13, // 5: controlplane.v1.WorkflowRunItem.workflow:type_name -> controlplane.v1.WorkflowItem + 43, // 6: controlplane.v1.WorkflowRunItem.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType + 25, // 7: controlplane.v1.WorkflowRunItem.contract_version:type_name -> controlplane.v1.WorkflowContractVersionItem + 15, // 8: controlplane.v1.WorkflowRunItem.version:type_name -> controlplane.v1.ProjectVersion + 16, // 9: controlplane.v1.WorkflowRunItem.policy_summary:type_name -> controlplane.v1.PolicyStatusSummary + 42, // 10: controlplane.v1.ProjectVersion.created_at:type_name -> google.protobuf.Timestamp + 42, // 11: controlplane.v1.ProjectVersion.released_at:type_name -> google.protobuf.Timestamp 2, // 12: controlplane.v1.PolicyStatusSummary.status:type_name -> controlplane.v1.PolicyStatus - 33, // 13: controlplane.v1.AttestationItem.env_vars:type_name -> controlplane.v1.AttestationItem.EnvVariable - 34, // 14: controlplane.v1.AttestationItem.materials:type_name -> controlplane.v1.AttestationItem.Material - 30, // 15: controlplane.v1.AttestationItem.annotations:type_name -> controlplane.v1.AttestationItem.AnnotationsEntry - 31, // 16: controlplane.v1.AttestationItem.policy_evaluations:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationsEntry - 32, // 17: controlplane.v1.AttestationItem.policy_evaluation_status:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationStatus - 18, // 18: controlplane.v1.PolicyEvaluations.evaluations:type_name -> controlplane.v1.PolicyEvaluation - 36, // 19: controlplane.v1.PolicyEvaluation.annotations:type_name -> controlplane.v1.PolicyEvaluation.AnnotationsEntry - 37, // 20: controlplane.v1.PolicyEvaluation.with:type_name -> controlplane.v1.PolicyEvaluation.WithEntry - 19, // 21: controlplane.v1.PolicyEvaluation.violations:type_name -> controlplane.v1.PolicyViolation - 20, // 22: controlplane.v1.PolicyEvaluation.policy_reference:type_name -> controlplane.v1.PolicyReference - 20, // 23: controlplane.v1.PolicyEvaluation.group_reference:type_name -> controlplane.v1.PolicyReference - 38, // 24: controlplane.v1.PolicyReference.digest:type_name -> controlplane.v1.PolicyReference.DigestEntry - 41, // 25: controlplane.v1.WorkflowContractItem.created_at:type_name -> google.protobuf.Timestamp - 41, // 26: controlplane.v1.WorkflowContractItem.updated_at:type_name -> google.protobuf.Timestamp - 41, // 27: controlplane.v1.WorkflowContractItem.latest_revision_created_at:type_name -> google.protobuf.Timestamp - 23, // 28: controlplane.v1.WorkflowContractItem.workflow_refs:type_name -> controlplane.v1.WorkflowRef - 22, // 29: controlplane.v1.WorkflowContractItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity - 41, // 30: controlplane.v1.WorkflowContractVersionItem.created_at:type_name -> google.protobuf.Timestamp - 43, // 31: controlplane.v1.WorkflowContractVersionItem.v1:type_name -> workflowcontract.v1.CraftingSchema - 39, // 32: controlplane.v1.WorkflowContractVersionItem.raw_contract:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody - 41, // 33: controlplane.v1.User.created_at:type_name -> google.protobuf.Timestamp - 41, // 34: controlplane.v1.User.updated_at:type_name -> google.protobuf.Timestamp - 27, // 35: controlplane.v1.OrgMembershipItem.org:type_name -> controlplane.v1.OrgItem - 25, // 36: controlplane.v1.OrgMembershipItem.user:type_name -> controlplane.v1.User - 41, // 37: controlplane.v1.OrgMembershipItem.created_at:type_name -> google.protobuf.Timestamp - 41, // 38: controlplane.v1.OrgMembershipItem.updated_at:type_name -> google.protobuf.Timestamp - 4, // 39: controlplane.v1.OrgMembershipItem.role:type_name -> controlplane.v1.MembershipRole - 41, // 40: controlplane.v1.OrgItem.created_at:type_name -> google.protobuf.Timestamp - 41, // 41: controlplane.v1.OrgItem.updated_at:type_name -> google.protobuf.Timestamp - 10, // 42: controlplane.v1.OrgItem.default_policy_violation_strategy:type_name -> controlplane.v1.OrgItem.PolicyViolationBlockingStrategy - 41, // 43: controlplane.v1.CASBackendItem.created_at:type_name -> google.protobuf.Timestamp - 41, // 44: controlplane.v1.CASBackendItem.validated_at:type_name -> google.protobuf.Timestamp - 11, // 45: controlplane.v1.CASBackendItem.validation_status:type_name -> controlplane.v1.CASBackendItem.ValidationStatus - 40, // 46: controlplane.v1.CASBackendItem.limits:type_name -> controlplane.v1.CASBackendItem.Limits - 41, // 47: controlplane.v1.CASBackendItem.updated_at:type_name -> google.protobuf.Timestamp - 22, // 48: controlplane.v1.APITokenItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity - 41, // 49: controlplane.v1.APITokenItem.created_at:type_name -> google.protobuf.Timestamp - 41, // 50: controlplane.v1.APITokenItem.revoked_at:type_name -> google.protobuf.Timestamp - 41, // 51: controlplane.v1.APITokenItem.expires_at:type_name -> google.protobuf.Timestamp - 41, // 52: controlplane.v1.APITokenItem.last_used_at:type_name -> google.protobuf.Timestamp - 17, // 53: controlplane.v1.AttestationItem.PolicyEvaluationsEntry.value:type_name -> controlplane.v1.PolicyEvaluations - 15, // 54: controlplane.v1.AttestationItem.PolicyEvaluationStatus.summary:type_name -> controlplane.v1.PolicyStatusSummary - 35, // 55: controlplane.v1.AttestationItem.Material.annotations:type_name -> controlplane.v1.AttestationItem.Material.AnnotationsEntry - 9, // 56: controlplane.v1.WorkflowContractVersionItem.RawBody.format:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody.Format + 34, // 13: controlplane.v1.AttestationItem.env_vars:type_name -> controlplane.v1.AttestationItem.EnvVariable + 35, // 14: controlplane.v1.AttestationItem.materials:type_name -> controlplane.v1.AttestationItem.Material + 31, // 15: controlplane.v1.AttestationItem.annotations:type_name -> controlplane.v1.AttestationItem.AnnotationsEntry + 32, // 16: controlplane.v1.AttestationItem.policy_evaluations:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationsEntry + 33, // 17: controlplane.v1.AttestationItem.policy_evaluation_status:type_name -> controlplane.v1.AttestationItem.PolicyEvaluationStatus + 19, // 18: controlplane.v1.PolicyEvaluations.evaluations:type_name -> controlplane.v1.PolicyEvaluation + 37, // 19: controlplane.v1.PolicyEvaluation.annotations:type_name -> controlplane.v1.PolicyEvaluation.AnnotationsEntry + 38, // 20: controlplane.v1.PolicyEvaluation.with:type_name -> controlplane.v1.PolicyEvaluation.WithEntry + 20, // 21: controlplane.v1.PolicyEvaluation.violations:type_name -> controlplane.v1.PolicyViolation + 21, // 22: controlplane.v1.PolicyEvaluation.policy_reference:type_name -> controlplane.v1.PolicyReference + 21, // 23: controlplane.v1.PolicyEvaluation.group_reference:type_name -> controlplane.v1.PolicyReference + 39, // 24: controlplane.v1.PolicyReference.digest:type_name -> controlplane.v1.PolicyReference.DigestEntry + 42, // 25: controlplane.v1.WorkflowContractItem.created_at:type_name -> google.protobuf.Timestamp + 42, // 26: controlplane.v1.WorkflowContractItem.updated_at:type_name -> google.protobuf.Timestamp + 42, // 27: controlplane.v1.WorkflowContractItem.latest_revision_created_at:type_name -> google.protobuf.Timestamp + 24, // 28: controlplane.v1.WorkflowContractItem.workflow_refs:type_name -> controlplane.v1.WorkflowRef + 23, // 29: controlplane.v1.WorkflowContractItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity + 42, // 30: controlplane.v1.WorkflowContractVersionItem.created_at:type_name -> google.protobuf.Timestamp + 44, // 31: controlplane.v1.WorkflowContractVersionItem.v1:type_name -> workflowcontract.v1.CraftingSchema + 40, // 32: controlplane.v1.WorkflowContractVersionItem.raw_contract:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody + 42, // 33: controlplane.v1.User.created_at:type_name -> google.protobuf.Timestamp + 42, // 34: controlplane.v1.User.updated_at:type_name -> google.protobuf.Timestamp + 28, // 35: controlplane.v1.OrgMembershipItem.org:type_name -> controlplane.v1.OrgItem + 26, // 36: controlplane.v1.OrgMembershipItem.user:type_name -> controlplane.v1.User + 42, // 37: controlplane.v1.OrgMembershipItem.created_at:type_name -> google.protobuf.Timestamp + 42, // 38: controlplane.v1.OrgMembershipItem.updated_at:type_name -> google.protobuf.Timestamp + 5, // 39: controlplane.v1.OrgMembershipItem.role:type_name -> controlplane.v1.MembershipRole + 42, // 40: controlplane.v1.OrgItem.created_at:type_name -> google.protobuf.Timestamp + 42, // 41: controlplane.v1.OrgItem.updated_at:type_name -> google.protobuf.Timestamp + 11, // 42: controlplane.v1.OrgItem.default_policy_violation_strategy:type_name -> controlplane.v1.OrgItem.PolicyViolationBlockingStrategy + 42, // 43: controlplane.v1.CASBackendItem.created_at:type_name -> google.protobuf.Timestamp + 42, // 44: controlplane.v1.CASBackendItem.validated_at:type_name -> google.protobuf.Timestamp + 12, // 45: controlplane.v1.CASBackendItem.validation_status:type_name -> controlplane.v1.CASBackendItem.ValidationStatus + 41, // 46: controlplane.v1.CASBackendItem.limits:type_name -> controlplane.v1.CASBackendItem.Limits + 42, // 47: controlplane.v1.CASBackendItem.updated_at:type_name -> google.protobuf.Timestamp + 23, // 48: controlplane.v1.APITokenItem.scoped_entity:type_name -> controlplane.v1.ScopedEntity + 42, // 49: controlplane.v1.APITokenItem.created_at:type_name -> google.protobuf.Timestamp + 42, // 50: controlplane.v1.APITokenItem.revoked_at:type_name -> google.protobuf.Timestamp + 42, // 51: controlplane.v1.APITokenItem.expires_at:type_name -> google.protobuf.Timestamp + 42, // 52: controlplane.v1.APITokenItem.last_used_at:type_name -> google.protobuf.Timestamp + 18, // 53: controlplane.v1.AttestationItem.PolicyEvaluationsEntry.value:type_name -> controlplane.v1.PolicyEvaluations + 16, // 54: controlplane.v1.AttestationItem.PolicyEvaluationStatus.summary:type_name -> controlplane.v1.PolicyStatusSummary + 36, // 55: controlplane.v1.AttestationItem.Material.annotations:type_name -> controlplane.v1.AttestationItem.Material.AnnotationsEntry + 10, // 56: controlplane.v1.WorkflowContractVersionItem.RawBody.format:type_name -> controlplane.v1.WorkflowContractVersionItem.RawBody.Format 57, // [57:57] is the sub-list for method output_type 57, // [57:57] is the sub-list for method input_type 57, // [57:57] is the sub-list for extension type_name @@ -3367,7 +3434,7 @@ func file_controlplane_v1_response_messages_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_controlplane_v1_response_messages_proto_rawDesc), len(file_controlplane_v1_response_messages_proto_rawDesc)), - NumEnums: 12, + NumEnums: 13, NumMessages: 29, NumExtensions: 0, NumServices: 0, diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index 1475af6a3..2bafd0c24 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -143,6 +143,17 @@ message PolicyStatusSummary { int32 skipped = 4; // Total number of violations across all evaluations int32 violated = 5; + // Whether this run had gates in effect — any policy marked gate:true or + // the contract using the ENFORCED blocking strategy. Independent of status: + // a PASSED run can still have has_gates=true. + bool has_gates = 6; +} + +// Server-side filter aligned with PolicyStatusSummary.has_gates. +enum PolicyGatesFilter { + POLICY_GATES_FILTER_UNSPECIFIED = 0; + POLICY_GATES_FILTER_WITH_GATES = 1; + POLICY_GATES_FILTER_WITHOUT_GATES = 2; } // Server-side filter aligned 1:1 with PolicyStatus values. diff --git a/app/controlplane/api/controlplane/v1/workflow_run.pb.go b/app/controlplane/api/controlplane/v1/workflow_run.pb.go index cb385924c..7c19e9dff 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.pb.go +++ b/app/controlplane/api/controlplane/v1/workflow_run.pb.go @@ -958,6 +958,9 @@ type WorkflowRunServiceListRequest struct { PolicyViolations PolicyViolationsFilter `protobuf:"varint,6,opt,name=policy_violations,json=policyViolations,proto3,enum=controlplane.v1.PolicyViolationsFilter" json:"policy_violations,omitempty"` // by canonical policy status PolicyStatus PolicyStatusFilter `protobuf:"varint,7,opt,name=policy_status,json=policyStatus,proto3,enum=controlplane.v1.PolicyStatusFilter" json:"policy_status,omitempty"` + // by whether the run had gates in effect (either a policy marked gate:true + // or the contract using the ENFORCED blocking strategy) + PolicyGates PolicyGatesFilter `protobuf:"varint,8,opt,name=policy_gates,json=policyGates,proto3,enum=controlplane.v1.PolicyGatesFilter" json:"policy_gates,omitempty"` // pagination options Pagination *CursorPaginationRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields @@ -1037,6 +1040,13 @@ func (x *WorkflowRunServiceListRequest) GetPolicyStatus() PolicyStatusFilter { return PolicyStatusFilter_POLICY_STATUS_FILTER_UNSPECIFIED } +func (x *WorkflowRunServiceListRequest) GetPolicyGates() PolicyGatesFilter { + if x != nil { + return x.PolicyGates + } + return PolicyGatesFilter_POLICY_GATES_FILTER_UNSPECIFIED +} + func (x *WorkflowRunServiceListRequest) GetPagination() *CursorPaginationRequest { if x != nil { return x.Pagination @@ -1825,7 +1835,7 @@ const file_controlplane_v1_workflow_run_proto_rawDesc = "" + "\x18TRIGGER_TYPE_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14TRIGGER_TYPE_FAILURE\x10\x01\x12\x1d\n" + "\x19TRIGGER_TYPE_CANCELLATION\x10\x02\"\"\n" + - " AttestationServiceCancelResponse\"\xda\x05\n" + + " AttestationServiceCancelResponse\"\xa1\x06\n" + "\x1dWorkflowRunServiceListRequest\x12\xac\x01\n" + "\rworkflow_name\x18\x01 \x01(\tB\x86\x01\xbaH\x82\x01\xba\x01|\n" + "\rname.dns-1123\x12:must contain only lowercase letters, numbers, and hyphens.\x1a/this.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')\xd8\x01\x01R\fworkflowName\x12!\n" + @@ -1833,7 +1843,8 @@ const file_controlplane_v1_workflow_run_proto_rawDesc = "" + "\x06status\x18\x03 \x01(\x0e2\x1a.controlplane.v1.RunStatusR\x06status\x124\n" + "\x0fproject_version\x18\x05 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\x0eprojectVersion\x12X\n" + "\x11policy_violations\x18\x06 \x01(\x0e2'.controlplane.v1.PolicyViolationsFilterB\x02\x18\x01R\x10policyViolations\x12H\n" + - "\rpolicy_status\x18\a \x01(\x0e2#.controlplane.v1.PolicyStatusFilterR\fpolicyStatus\x12H\n" + + "\rpolicy_status\x18\a \x01(\x0e2#.controlplane.v1.PolicyStatusFilterR\fpolicyStatus\x12E\n" + + "\fpolicy_gates\x18\b \x01(\x0e2\".controlplane.v1.PolicyGatesFilterR\vpolicyGates\x12H\n" + "\n" + "pagination\x18\x02 \x01(\v2(.controlplane.v1.CursorPaginationRequestR\n" + "pagination:\x8e\x01\xbaH\x8a\x01\x1a\x87\x01\n" + @@ -1929,12 +1940,13 @@ var file_controlplane_v1_workflow_run_proto_goTypes = []any{ (RunStatus)(0), // 33: controlplane.v1.RunStatus (PolicyViolationsFilter)(0), // 34: controlplane.v1.PolicyViolationsFilter (PolicyStatusFilter)(0), // 35: controlplane.v1.PolicyStatusFilter - (*CursorPaginationRequest)(nil), // 36: controlplane.v1.CursorPaginationRequest - (*WorkflowRunItem)(nil), // 37: controlplane.v1.WorkflowRunItem - (*CursorPaginationResponse)(nil), // 38: controlplane.v1.CursorPaginationResponse - (*WorkflowContractVersionItem)(nil), // 39: controlplane.v1.WorkflowContractVersionItem - (*AttestationItem)(nil), // 40: controlplane.v1.AttestationItem - (*CASBackendItem)(nil), // 41: controlplane.v1.CASBackendItem + (PolicyGatesFilter)(0), // 36: controlplane.v1.PolicyGatesFilter + (*CursorPaginationRequest)(nil), // 37: controlplane.v1.CursorPaginationRequest + (*WorkflowRunItem)(nil), // 38: controlplane.v1.WorkflowRunItem + (*CursorPaginationResponse)(nil), // 39: controlplane.v1.CursorPaginationResponse + (*WorkflowContractVersionItem)(nil), // 40: controlplane.v1.WorkflowContractVersionItem + (*AttestationItem)(nil), // 41: controlplane.v1.AttestationItem + (*CASBackendItem)(nil), // 42: controlplane.v1.CASBackendItem } var file_controlplane_v1_workflow_run_proto_depIdxs = []int32{ 29, // 0: controlplane.v1.FindOrCreateWorkflowResponse.result:type_name -> controlplane.v1.WorkflowItem @@ -1950,44 +1962,45 @@ var file_controlplane_v1_workflow_run_proto_depIdxs = []int32{ 33, // 10: controlplane.v1.WorkflowRunServiceListRequest.status:type_name -> controlplane.v1.RunStatus 34, // 11: controlplane.v1.WorkflowRunServiceListRequest.policy_violations:type_name -> controlplane.v1.PolicyViolationsFilter 35, // 12: controlplane.v1.WorkflowRunServiceListRequest.policy_status:type_name -> controlplane.v1.PolicyStatusFilter - 36, // 13: controlplane.v1.WorkflowRunServiceListRequest.pagination:type_name -> controlplane.v1.CursorPaginationRequest - 37, // 14: controlplane.v1.WorkflowRunServiceListResponse.result:type_name -> controlplane.v1.WorkflowRunItem - 38, // 15: controlplane.v1.WorkflowRunServiceListResponse.pagination:type_name -> controlplane.v1.CursorPaginationResponse - 26, // 16: controlplane.v1.WorkflowRunServiceViewResponse.result:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.Result - 28, // 17: controlplane.v1.AttestationServiceGetUploadCredsResponse.result:type_name -> controlplane.v1.AttestationServiceGetUploadCredsResponse.Result - 29, // 18: controlplane.v1.AttestationServiceGetContractResponse.Result.workflow:type_name -> controlplane.v1.WorkflowItem - 39, // 19: controlplane.v1.AttestationServiceGetContractResponse.Result.contract:type_name -> controlplane.v1.WorkflowContractVersionItem - 37, // 20: controlplane.v1.AttestationServiceInitResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem - 24, // 21: controlplane.v1.AttestationServiceInitResponse.Result.signing_options:type_name -> controlplane.v1.AttestationServiceInitResponse.SigningOptions - 37, // 22: controlplane.v1.WorkflowRunServiceViewResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem - 40, // 23: controlplane.v1.WorkflowRunServiceViewResponse.Result.attestation:type_name -> controlplane.v1.AttestationItem - 27, // 24: controlplane.v1.WorkflowRunServiceViewResponse.Result.verification:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.VerificationResult - 41, // 25: controlplane.v1.AttestationServiceGetUploadCredsResponse.Result.backend:type_name -> controlplane.v1.CASBackendItem - 1, // 26: controlplane.v1.AttestationService.FindOrCreateWorkflow:input_type -> controlplane.v1.FindOrCreateWorkflowRequest - 8, // 27: controlplane.v1.AttestationService.GetContract:input_type -> controlplane.v1.AttestationServiceGetContractRequest - 10, // 28: controlplane.v1.AttestationService.Init:input_type -> controlplane.v1.AttestationServiceInitRequest - 12, // 29: controlplane.v1.AttestationService.Store:input_type -> controlplane.v1.AttestationServiceStoreRequest - 20, // 30: controlplane.v1.AttestationService.GetUploadCreds:input_type -> controlplane.v1.AttestationServiceGetUploadCredsRequest - 14, // 31: controlplane.v1.AttestationService.Cancel:input_type -> controlplane.v1.AttestationServiceCancelRequest - 3, // 32: controlplane.v1.AttestationService.GetPolicy:input_type -> controlplane.v1.AttestationServiceGetPolicyRequest - 6, // 33: controlplane.v1.AttestationService.GetPolicyGroup:input_type -> controlplane.v1.AttestationServiceGetPolicyGroupRequest - 16, // 34: controlplane.v1.WorkflowRunService.List:input_type -> controlplane.v1.WorkflowRunServiceListRequest - 18, // 35: controlplane.v1.WorkflowRunService.View:input_type -> controlplane.v1.WorkflowRunServiceViewRequest - 2, // 36: controlplane.v1.AttestationService.FindOrCreateWorkflow:output_type -> controlplane.v1.FindOrCreateWorkflowResponse - 9, // 37: controlplane.v1.AttestationService.GetContract:output_type -> controlplane.v1.AttestationServiceGetContractResponse - 11, // 38: controlplane.v1.AttestationService.Init:output_type -> controlplane.v1.AttestationServiceInitResponse - 13, // 39: controlplane.v1.AttestationService.Store:output_type -> controlplane.v1.AttestationServiceStoreResponse - 21, // 40: controlplane.v1.AttestationService.GetUploadCreds:output_type -> controlplane.v1.AttestationServiceGetUploadCredsResponse - 15, // 41: controlplane.v1.AttestationService.Cancel:output_type -> controlplane.v1.AttestationServiceCancelResponse - 4, // 42: controlplane.v1.AttestationService.GetPolicy:output_type -> controlplane.v1.AttestationServiceGetPolicyResponse - 7, // 43: controlplane.v1.AttestationService.GetPolicyGroup:output_type -> controlplane.v1.AttestationServiceGetPolicyGroupResponse - 17, // 44: controlplane.v1.WorkflowRunService.List:output_type -> controlplane.v1.WorkflowRunServiceListResponse - 19, // 45: controlplane.v1.WorkflowRunService.View:output_type -> controlplane.v1.WorkflowRunServiceViewResponse - 36, // [36:46] is the sub-list for method output_type - 26, // [26:36] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 36, // 13: controlplane.v1.WorkflowRunServiceListRequest.policy_gates:type_name -> controlplane.v1.PolicyGatesFilter + 37, // 14: controlplane.v1.WorkflowRunServiceListRequest.pagination:type_name -> controlplane.v1.CursorPaginationRequest + 38, // 15: controlplane.v1.WorkflowRunServiceListResponse.result:type_name -> controlplane.v1.WorkflowRunItem + 39, // 16: controlplane.v1.WorkflowRunServiceListResponse.pagination:type_name -> controlplane.v1.CursorPaginationResponse + 26, // 17: controlplane.v1.WorkflowRunServiceViewResponse.result:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.Result + 28, // 18: controlplane.v1.AttestationServiceGetUploadCredsResponse.result:type_name -> controlplane.v1.AttestationServiceGetUploadCredsResponse.Result + 29, // 19: controlplane.v1.AttestationServiceGetContractResponse.Result.workflow:type_name -> controlplane.v1.WorkflowItem + 40, // 20: controlplane.v1.AttestationServiceGetContractResponse.Result.contract:type_name -> controlplane.v1.WorkflowContractVersionItem + 38, // 21: controlplane.v1.AttestationServiceInitResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem + 24, // 22: controlplane.v1.AttestationServiceInitResponse.Result.signing_options:type_name -> controlplane.v1.AttestationServiceInitResponse.SigningOptions + 38, // 23: controlplane.v1.WorkflowRunServiceViewResponse.Result.workflow_run:type_name -> controlplane.v1.WorkflowRunItem + 41, // 24: controlplane.v1.WorkflowRunServiceViewResponse.Result.attestation:type_name -> controlplane.v1.AttestationItem + 27, // 25: controlplane.v1.WorkflowRunServiceViewResponse.Result.verification:type_name -> controlplane.v1.WorkflowRunServiceViewResponse.VerificationResult + 42, // 26: controlplane.v1.AttestationServiceGetUploadCredsResponse.Result.backend:type_name -> controlplane.v1.CASBackendItem + 1, // 27: controlplane.v1.AttestationService.FindOrCreateWorkflow:input_type -> controlplane.v1.FindOrCreateWorkflowRequest + 8, // 28: controlplane.v1.AttestationService.GetContract:input_type -> controlplane.v1.AttestationServiceGetContractRequest + 10, // 29: controlplane.v1.AttestationService.Init:input_type -> controlplane.v1.AttestationServiceInitRequest + 12, // 30: controlplane.v1.AttestationService.Store:input_type -> controlplane.v1.AttestationServiceStoreRequest + 20, // 31: controlplane.v1.AttestationService.GetUploadCreds:input_type -> controlplane.v1.AttestationServiceGetUploadCredsRequest + 14, // 32: controlplane.v1.AttestationService.Cancel:input_type -> controlplane.v1.AttestationServiceCancelRequest + 3, // 33: controlplane.v1.AttestationService.GetPolicy:input_type -> controlplane.v1.AttestationServiceGetPolicyRequest + 6, // 34: controlplane.v1.AttestationService.GetPolicyGroup:input_type -> controlplane.v1.AttestationServiceGetPolicyGroupRequest + 16, // 35: controlplane.v1.WorkflowRunService.List:input_type -> controlplane.v1.WorkflowRunServiceListRequest + 18, // 36: controlplane.v1.WorkflowRunService.View:input_type -> controlplane.v1.WorkflowRunServiceViewRequest + 2, // 37: controlplane.v1.AttestationService.FindOrCreateWorkflow:output_type -> controlplane.v1.FindOrCreateWorkflowResponse + 9, // 38: controlplane.v1.AttestationService.GetContract:output_type -> controlplane.v1.AttestationServiceGetContractResponse + 11, // 39: controlplane.v1.AttestationService.Init:output_type -> controlplane.v1.AttestationServiceInitResponse + 13, // 40: controlplane.v1.AttestationService.Store:output_type -> controlplane.v1.AttestationServiceStoreResponse + 21, // 41: controlplane.v1.AttestationService.GetUploadCreds:output_type -> controlplane.v1.AttestationServiceGetUploadCredsResponse + 15, // 42: controlplane.v1.AttestationService.Cancel:output_type -> controlplane.v1.AttestationServiceCancelResponse + 4, // 43: controlplane.v1.AttestationService.GetPolicy:output_type -> controlplane.v1.AttestationServiceGetPolicyResponse + 7, // 44: controlplane.v1.AttestationService.GetPolicyGroup:output_type -> controlplane.v1.AttestationServiceGetPolicyGroupResponse + 17, // 45: controlplane.v1.WorkflowRunService.List:output_type -> controlplane.v1.WorkflowRunServiceListResponse + 19, // 46: controlplane.v1.WorkflowRunService.View:output_type -> controlplane.v1.WorkflowRunServiceViewResponse + 37, // [37:47] is the sub-list for method output_type + 27, // [27:37] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name } func init() { file_controlplane_v1_workflow_run_proto_init() } diff --git a/app/controlplane/api/controlplane/v1/workflow_run.proto b/app/controlplane/api/controlplane/v1/workflow_run.proto index 8abcd1ffb..2c8e76127 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.proto +++ b/app/controlplane/api/controlplane/v1/workflow_run.proto @@ -220,6 +220,9 @@ message WorkflowRunServiceListRequest { PolicyViolationsFilter policy_violations = 6 [deprecated = true]; // by canonical policy status PolicyStatusFilter policy_status = 7; + // by whether the run had gates in effect (either a policy marked gate:true + // or the contract using the ENFORCED blocking strategy) + PolicyGatesFilter policy_gates = 8; // pagination options CursorPaginationRequest pagination = 2; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index bcddd0ec3..0b9fb7a0d 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -190,6 +190,46 @@ export function policyStatusToJSON(object: PolicyStatus): string { } } +/** Server-side filter aligned with PolicyStatusSummary.has_gates. */ +export enum PolicyGatesFilter { + POLICY_GATES_FILTER_UNSPECIFIED = 0, + POLICY_GATES_FILTER_WITH_GATES = 1, + POLICY_GATES_FILTER_WITHOUT_GATES = 2, + UNRECOGNIZED = -1, +} + +export function policyGatesFilterFromJSON(object: any): PolicyGatesFilter { + switch (object) { + case 0: + case "POLICY_GATES_FILTER_UNSPECIFIED": + return PolicyGatesFilter.POLICY_GATES_FILTER_UNSPECIFIED; + case 1: + case "POLICY_GATES_FILTER_WITH_GATES": + return PolicyGatesFilter.POLICY_GATES_FILTER_WITH_GATES; + case 2: + case "POLICY_GATES_FILTER_WITHOUT_GATES": + return PolicyGatesFilter.POLICY_GATES_FILTER_WITHOUT_GATES; + case -1: + case "UNRECOGNIZED": + default: + return PolicyGatesFilter.UNRECOGNIZED; + } +} + +export function policyGatesFilterToJSON(object: PolicyGatesFilter): string { + switch (object) { + case PolicyGatesFilter.POLICY_GATES_FILTER_UNSPECIFIED: + return "POLICY_GATES_FILTER_UNSPECIFIED"; + case PolicyGatesFilter.POLICY_GATES_FILTER_WITH_GATES: + return "POLICY_GATES_FILTER_WITH_GATES"; + case PolicyGatesFilter.POLICY_GATES_FILTER_WITHOUT_GATES: + return "POLICY_GATES_FILTER_WITHOUT_GATES"; + case PolicyGatesFilter.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + /** Server-side filter aligned 1:1 with PolicyStatus values. */ export enum PolicyStatusFilter { POLICY_STATUS_FILTER_UNSPECIFIED = 0, @@ -532,6 +572,12 @@ export interface PolicyStatusSummary { skipped: number; /** Total number of violations across all evaluations */ violated: number; + /** + * Whether this run had gates in effect — any policy marked gate:true or + * the contract using the ENFORCED blocking strategy. Independent of status: + * a PASSED run can still have has_gates=true. + */ + hasGates: boolean; } export interface AttestationItem { @@ -1556,7 +1602,7 @@ export const ProjectVersion = { }; function createBasePolicyStatusSummary(): PolicyStatusSummary { - return { status: 0, total: 0, passed: 0, skipped: 0, violated: 0 }; + return { status: 0, total: 0, passed: 0, skipped: 0, violated: 0, hasGates: false }; } export const PolicyStatusSummary = { @@ -1576,6 +1622,9 @@ export const PolicyStatusSummary = { if (message.violated !== 0) { writer.uint32(40).int32(message.violated); } + if (message.hasGates === true) { + writer.uint32(48).bool(message.hasGates); + } return writer; }, @@ -1621,6 +1670,13 @@ export const PolicyStatusSummary = { message.violated = reader.int32(); continue; + case 6: + if (tag !== 48) { + break; + } + + message.hasGates = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1637,6 +1693,7 @@ export const PolicyStatusSummary = { passed: isSet(object.passed) ? Number(object.passed) : 0, skipped: isSet(object.skipped) ? Number(object.skipped) : 0, violated: isSet(object.violated) ? Number(object.violated) : 0, + hasGates: isSet(object.hasGates) ? Boolean(object.hasGates) : false, }; }, @@ -1647,6 +1704,7 @@ export const PolicyStatusSummary = { message.passed !== undefined && (obj.passed = Math.round(message.passed)); message.skipped !== undefined && (obj.skipped = Math.round(message.skipped)); message.violated !== undefined && (obj.violated = Math.round(message.violated)); + message.hasGates !== undefined && (obj.hasGates = message.hasGates); return obj; }, @@ -1661,6 +1719,7 @@ export const PolicyStatusSummary = { message.passed = object.passed ?? 0; message.skipped = object.skipped ?? 0; message.violated = object.violated ?? 0; + message.hasGates = object.hasGates ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts index 55f8572d7..de1a394ec 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts @@ -13,6 +13,9 @@ import { CursorPaginationRequest, CursorPaginationResponse } from "./pagination" import { AttestationItem, CASBackendItem, + PolicyGatesFilter, + policyGatesFilterFromJSON, + policyGatesFilterToJSON, PolicyStatusFilter, policyStatusFilterFromJSON, policyStatusFilterToJSON, @@ -227,6 +230,11 @@ export interface WorkflowRunServiceListRequest { policyViolations: PolicyViolationsFilter; /** by canonical policy status */ policyStatus: PolicyStatusFilter; + /** + * by whether the run had gates in effect (either a policy marked gate:true + * or the contract using the ENFORCED blocking strategy) + */ + policyGates: PolicyGatesFilter; /** pagination options */ pagination?: CursorPaginationRequest; } @@ -1891,6 +1899,7 @@ function createBaseWorkflowRunServiceListRequest(): WorkflowRunServiceListReques projectVersion: "", policyViolations: 0, policyStatus: 0, + policyGates: 0, pagination: undefined, }; } @@ -1915,6 +1924,9 @@ export const WorkflowRunServiceListRequest = { if (message.policyStatus !== 0) { writer.uint32(56).int32(message.policyStatus); } + if (message.policyGates !== 0) { + writer.uint32(64).int32(message.policyGates); + } if (message.pagination !== undefined) { CursorPaginationRequest.encode(message.pagination, writer.uint32(18).fork()).ldelim(); } @@ -1970,6 +1982,13 @@ export const WorkflowRunServiceListRequest = { message.policyStatus = reader.int32() as any; continue; + case 8: + if (tag !== 64) { + break; + } + + message.policyGates = reader.int32() as any; + continue; case 2: if (tag !== 18) { break; @@ -1994,6 +2013,7 @@ export const WorkflowRunServiceListRequest = { projectVersion: isSet(object.projectVersion) ? String(object.projectVersion) : "", policyViolations: isSet(object.policyViolations) ? policyViolationsFilterFromJSON(object.policyViolations) : 0, policyStatus: isSet(object.policyStatus) ? policyStatusFilterFromJSON(object.policyStatus) : 0, + policyGates: isSet(object.policyGates) ? policyGatesFilterFromJSON(object.policyGates) : 0, pagination: isSet(object.pagination) ? CursorPaginationRequest.fromJSON(object.pagination) : undefined, }; }, @@ -2007,6 +2027,7 @@ export const WorkflowRunServiceListRequest = { message.policyViolations !== undefined && (obj.policyViolations = policyViolationsFilterToJSON(message.policyViolations)); message.policyStatus !== undefined && (obj.policyStatus = policyStatusFilterToJSON(message.policyStatus)); + message.policyGates !== undefined && (obj.policyGates = policyGatesFilterToJSON(message.policyGates)); message.pagination !== undefined && (obj.pagination = message.pagination ? CursorPaginationRequest.toJSON(message.pagination) : undefined); return obj; @@ -2026,6 +2047,7 @@ export const WorkflowRunServiceListRequest = { message.projectVersion = object.projectVersion ?? ""; message.policyViolations = object.policyViolations ?? 0; message.policyStatus = object.policyStatus ?? 0; + message.policyGates = object.policyGates ?? 0; message.pagination = (object.pagination !== undefined && object.pagination !== null) ? CursorPaginationRequest.fromPartial(object.pagination) : undefined; diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json index a8addf852..b601fc99c 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.jsonschema.json @@ -3,7 +3,17 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "description": "PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation\n counters. It is surfaced on both WorkflowRunItem (list response) and\n AttestationItem.PolicyEvaluationStatus (describe response) and is computed\n by a single backend helper so list and describe cannot drift.", + "patternProperties": { + "^(has_gates)$": { + "description": "Whether this run had gates in effect — any policy marked gate:true or\n the contract using the ENFORCED blocking strategy. Independent of status:\n a PASSED run can still have has_gates=true.", + "type": "boolean" + } + }, "properties": { + "hasGates": { + "description": "Whether this run had gates in effect — any policy marked gate:true or\n the contract using the ENFORCED blocking strategy. Independent of status:\n a PASSED run can still have has_gates=true.", + "type": "boolean" + }, "passed": { "description": "Number of evaluations with no violations and not skipped", "maximum": 2147483647, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json index 519a2bb0a..4cf3f2243 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyStatusSummary.schema.json @@ -3,7 +3,17 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "description": "PolicyStatusSummary bundles the canonical PolicyStatus with per-evaluation\n counters. It is surfaced on both WorkflowRunItem (list response) and\n AttestationItem.PolicyEvaluationStatus (describe response) and is computed\n by a single backend helper so list and describe cannot drift.", + "patternProperties": { + "^(hasGates)$": { + "description": "Whether this run had gates in effect — any policy marked gate:true or\n the contract using the ENFORCED blocking strategy. Independent of status:\n a PASSED run can still have has_gates=true.", + "type": "boolean" + } + }, "properties": { + "has_gates": { + "description": "Whether this run had gates in effect — any policy marked gate:true or\n the contract using the ENFORCED blocking strategy. Independent of status:\n a PASSED run can still have has_gates=true.", + "type": "boolean" + }, "passed": { "description": "Number of evaluations with no violations and not skipped", "maximum": 2147483647, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json index a963a8ec8..fddb11cbc 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json @@ -3,6 +3,25 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "patternProperties": { + "^(policy_gates)$": { + "anyOf": [ + { + "enum": [ + "POLICY_GATES_FILTER_UNSPECIFIED", + "POLICY_GATES_FILTER_WITH_GATES", + "POLICY_GATES_FILTER_WITHOUT_GATES" + ], + "title": "Policy Gates Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by whether the run had gates in effect (either a policy marked gate:true\n or the contract using the ENFORCED blocking strategy)" + }, "^(policy_status)$": { "anyOf": [ { @@ -64,6 +83,25 @@ "$ref": "controlplane.v1.CursorPaginationRequest.jsonschema.json", "description": "pagination options" }, + "policyGates": { + "anyOf": [ + { + "enum": [ + "POLICY_GATES_FILTER_UNSPECIFIED", + "POLICY_GATES_FILTER_WITH_GATES", + "POLICY_GATES_FILTER_WITHOUT_GATES" + ], + "title": "Policy Gates Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by whether the run had gates in effect (either a policy marked gate:true\n or the contract using the ENFORCED blocking strategy)" + }, "policyStatus": { "anyOf": [ { diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json index a198a9d61..085a1971e 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json @@ -3,6 +3,25 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "patternProperties": { + "^(policyGates)$": { + "anyOf": [ + { + "enum": [ + "POLICY_GATES_FILTER_UNSPECIFIED", + "POLICY_GATES_FILTER_WITH_GATES", + "POLICY_GATES_FILTER_WITHOUT_GATES" + ], + "title": "Policy Gates Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by whether the run had gates in effect (either a policy marked gate:true\n or the contract using the ENFORCED blocking strategy)" + }, "^(policyStatus)$": { "anyOf": [ { @@ -64,6 +83,25 @@ "$ref": "controlplane.v1.CursorPaginationRequest.schema.json", "description": "pagination options" }, + "policy_gates": { + "anyOf": [ + { + "enum": [ + "POLICY_GATES_FILTER_UNSPECIFIED", + "POLICY_GATES_FILTER_WITH_GATES", + "POLICY_GATES_FILTER_WITHOUT_GATES" + ], + "title": "Policy Gates Filter", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "by whether the run had gates in effect (either a policy marked gate:true\n or the contract using the ENFORCED blocking strategy)" + }, "policy_status": { "anyOf": [ { diff --git a/app/controlplane/internal/service/workflowrun.go b/app/controlplane/internal/service/workflowrun.go index ea9310b73..7d2a465d4 100644 --- a/app/controlplane/internal/service/workflowrun.go +++ b/app/controlplane/internal/service/workflowrun.go @@ -186,6 +186,11 @@ func (s *WorkflowRunService) List(ctx context.Context, req *pb.WorkflowRunServic filters.PolicyStatus = &s } + if req.GetPolicyGates() != pb.PolicyGatesFilter_POLICY_GATES_FILTER_UNSPECIFIED { + hasGates := req.GetPolicyGates() == pb.PolicyGatesFilter_POLICY_GATES_FILTER_WITH_GATES + filters.PolicyHasGates = &hasGates + } + p := req.GetPagination() paginationOpts, err := pagination.NewCursor(p.GetCursor(), int(p.GetLimit())) if err != nil { @@ -402,6 +407,7 @@ func bizPolicyStatusSummaryToPb(s *chainloop.PolicyStatusSummary) *pb.PolicyStat Passed: int32(s.Passed), Skipped: int32(s.Skipped), Violated: int32(s.Violated), + HasGates: s.HasGates, } } diff --git a/app/controlplane/pkg/biz/workflowrun.go b/app/controlplane/pkg/biz/workflowrun.go index 8436b0a44..7384d990b 100644 --- a/app/controlplane/pkg/biz/workflowrun.go +++ b/app/controlplane/pkg/biz/workflowrun.go @@ -404,6 +404,9 @@ type RunListFilters struct { // Filter by canonical policy status. When both PolicyStatus and // PolicyViolationsFilter are set, PolicyStatus takes precedence. PolicyStatus *chainloop.PolicyStatus + // Filter by whether the run had gates in effect. Orthogonal to + // PolicyStatus — a PASSED run can still have had gates. + PolicyHasGates *bool } // List the workflowruns associated with an org and optionally filtered by a workflow diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql b/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql index d6f448237..92295e795 100644 --- a/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql +++ b/app/controlplane/pkg/data/ent/migrate/migrations/20260418100730.sql @@ -1,6 +1,8 @@ -- atlas:txmode none -- Modify "workflow_runs" table -ALTER TABLE "workflow_runs" ADD COLUMN "policy_status" character varying NULL, ADD COLUMN "policy_evaluations_total" integer NULL, ADD COLUMN "policy_evaluations_passed" integer NULL, ADD COLUMN "policy_evaluations_skipped" integer NULL, ADD COLUMN "policy_violations_count" integer NULL; +ALTER TABLE "workflow_runs" ADD COLUMN "policy_status" character varying NULL, ADD COLUMN "policy_evaluations_total" integer NULL, ADD COLUMN "policy_evaluations_passed" integer NULL, ADD COLUMN "policy_evaluations_skipped" integer NULL, ADD COLUMN "policy_violations_count" integer NULL, ADD COLUMN "policy_has_gates" boolean NULL; -- Create index "workflowrun_policy_status" to table: "workflow_runs" CREATE INDEX CONCURRENTLY "workflowrun_policy_status" ON "workflow_runs" ("policy_status"); +-- Create partial index "workflowrun_policy_has_gates" on rows where gates were in effect +CREATE INDEX CONCURRENTLY "workflowrun_policy_has_gates" ON "workflow_runs" ("policy_has_gates") WHERE (policy_has_gates = true); diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum index b2d5a8af4..36d824e5b 100644 --- a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:51ubLMpxihVrDovBvFzCYWr5wW+dHyhs223p4O5uX2s= +h1:Mkh9OlAmAX9pbzA/X2mdwp/bouuIaDWHcTzH8DMHfOw= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -129,4 +129,4 @@ h1:51ubLMpxihVrDovBvFzCYWr5wW+dHyhs223p4O5uX2s= 20260318160301.sql h1:kH88s6pOi7Vprydb7xrzgY55JhMxfzY32txpQ8a1wEE= 20260408122048.sql h1:imfswpfmBlpP1l149/wCLN5HkN3/sGIQ3GnxaSnwOZE= 20260416153232.sql h1:xjEfZuMOo1lgZm3VUYGHpNOhpJixncVZuMRg0jiH+7A= -20260418100730.sql h1:oElg9D/TDuynW42As+caQ50ZiWvUePp1BS0fP4TW5H8= +20260418100730.sql h1:lLcPDneBlzyabUAOIEKqCJgtnklHGac4JUnnZAbyD1g= diff --git a/app/controlplane/pkg/data/ent/migrate/schema.go b/app/controlplane/pkg/data/ent/migrate/schema.go index e1a62a61a..b5fbc69d8 100644 --- a/app/controlplane/pkg/data/ent/migrate/schema.go +++ b/app/controlplane/pkg/data/ent/migrate/schema.go @@ -771,6 +771,7 @@ var ( {Name: "policy_evaluations_passed", Type: field.TypeInt32, Nullable: true}, {Name: "policy_evaluations_skipped", Type: field.TypeInt32, Nullable: true}, {Name: "policy_violations_count", Type: field.TypeInt32, Nullable: true}, + {Name: "policy_has_gates", Type: field.TypeBool, Nullable: true}, {Name: "version_id", Type: field.TypeUUID}, {Name: "workflow_id", Type: field.TypeUUID}, {Name: "workflow_run_contract_version", Type: field.TypeUUID, Nullable: true}, @@ -783,19 +784,19 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "workflow_runs_project_versions_runs", - Columns: []*schema.Column{WorkflowRunsColumns[18]}, + Columns: []*schema.Column{WorkflowRunsColumns[19]}, RefColumns: []*schema.Column{ProjectVersionsColumns[0]}, OnDelete: schema.NoAction, }, { Symbol: "workflow_runs_workflows_workflowruns", - Columns: []*schema.Column{WorkflowRunsColumns[19]}, + Columns: []*schema.Column{WorkflowRunsColumns[20]}, RefColumns: []*schema.Column{WorkflowsColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "workflow_runs_workflow_contract_versions_contract_version", - Columns: []*schema.Column{WorkflowRunsColumns[20]}, + Columns: []*schema.Column{WorkflowRunsColumns[21]}, RefColumns: []*schema.Column{WorkflowContractVersionsColumns[0]}, OnDelete: schema.Cascade, }, @@ -814,7 +815,7 @@ var ( { Name: "workflowrun_workflow_id_created_at", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[19], WorkflowRunsColumns[1]}, + Columns: []*schema.Column{WorkflowRunsColumns[20], WorkflowRunsColumns[1]}, Annotation: &entsql.IndexAnnotation{ DescColumns: map[string]bool{ WorkflowRunsColumns[1].Name: true, @@ -824,7 +825,7 @@ var ( { Name: "workflowrun_workflow_id_state_created_at", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[19], WorkflowRunsColumns[3], WorkflowRunsColumns[1]}, + Columns: []*schema.Column{WorkflowRunsColumns[20], WorkflowRunsColumns[3], WorkflowRunsColumns[1]}, Annotation: &entsql.IndexAnnotation{ DescColumns: map[string]bool{ WorkflowRunsColumns[1].Name: true, @@ -849,18 +850,26 @@ var ( { Name: "workflowrun_workflow_id", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[19]}, + Columns: []*schema.Column{WorkflowRunsColumns[20]}, }, { Name: "workflowrun_version_id_workflow_id", Unique: false, - Columns: []*schema.Column{WorkflowRunsColumns[18], WorkflowRunsColumns[19]}, + Columns: []*schema.Column{WorkflowRunsColumns[19], WorkflowRunsColumns[20]}, }, { Name: "workflowrun_policy_status", Unique: false, Columns: []*schema.Column{WorkflowRunsColumns[13]}, }, + { + Name: "workflowrun_policy_has_gates", + Unique: false, + Columns: []*schema.Column{WorkflowRunsColumns[18]}, + Annotation: &entsql.IndexAnnotation{ + Where: "policy_has_gates = true", + }, + }, }, } // ReferrerReferencesColumns holds the columns for the "referrer_references" table. diff --git a/app/controlplane/pkg/data/ent/mutation.go b/app/controlplane/pkg/data/ent/mutation.go index 5f19c83b1..d756b2b7b 100644 --- a/app/controlplane/pkg/data/ent/mutation.go +++ b/app/controlplane/pkg/data/ent/mutation.go @@ -17935,6 +17935,7 @@ type WorkflowRunMutation struct { addpolicy_evaluations_skipped *int32 policy_violations_count *int32 addpolicy_violations_count *int32 + policy_has_gates *bool clearedFields map[string]struct{} workflow *uuid.UUID clearedworkflow bool @@ -19033,6 +19034,55 @@ func (m *WorkflowRunMutation) ResetPolicyViolationsCount() { delete(m.clearedFields, workflowrun.FieldPolicyViolationsCount) } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (m *WorkflowRunMutation) SetPolicyHasGates(b bool) { + m.policy_has_gates = &b +} + +// PolicyHasGates returns the value of the "policy_has_gates" field in the mutation. +func (m *WorkflowRunMutation) PolicyHasGates() (r bool, exists bool) { + v := m.policy_has_gates + if v == nil { + return + } + return *v, true +} + +// OldPolicyHasGates returns the old "policy_has_gates" field's value of the WorkflowRun entity. +// If the WorkflowRun object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *WorkflowRunMutation) OldPolicyHasGates(ctx context.Context) (v *bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPolicyHasGates is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPolicyHasGates requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPolicyHasGates: %w", err) + } + return oldValue.PolicyHasGates, nil +} + +// ClearPolicyHasGates clears the value of the "policy_has_gates" field. +func (m *WorkflowRunMutation) ClearPolicyHasGates() { + m.policy_has_gates = nil + m.clearedFields[workflowrun.FieldPolicyHasGates] = struct{}{} +} + +// PolicyHasGatesCleared returns if the "policy_has_gates" field was cleared in this mutation. +func (m *WorkflowRunMutation) PolicyHasGatesCleared() bool { + _, ok := m.clearedFields[workflowrun.FieldPolicyHasGates] + return ok +} + +// ResetPolicyHasGates resets all changes to the "policy_has_gates" field. +func (m *WorkflowRunMutation) ResetPolicyHasGates() { + m.policy_has_gates = nil + delete(m.clearedFields, workflowrun.FieldPolicyHasGates) +} + // ClearWorkflow clears the "workflow" edge to the Workflow entity. func (m *WorkflowRunMutation) ClearWorkflow() { m.clearedworkflow = true @@ -19253,7 +19303,7 @@ func (m *WorkflowRunMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *WorkflowRunMutation) Fields() []string { - fields := make([]string, 0, 19) + fields := make([]string, 0, 20) if m.created_at != nil { fields = append(fields, workflowrun.FieldCreatedAt) } @@ -19311,6 +19361,9 @@ func (m *WorkflowRunMutation) Fields() []string { if m.policy_violations_count != nil { fields = append(fields, workflowrun.FieldPolicyViolationsCount) } + if m.policy_has_gates != nil { + fields = append(fields, workflowrun.FieldPolicyHasGates) + } return fields } @@ -19357,6 +19410,8 @@ func (m *WorkflowRunMutation) Field(name string) (ent.Value, bool) { return m.PolicyEvaluationsSkipped() case workflowrun.FieldPolicyViolationsCount: return m.PolicyViolationsCount() + case workflowrun.FieldPolicyHasGates: + return m.PolicyHasGates() } return nil, false } @@ -19404,6 +19459,8 @@ func (m *WorkflowRunMutation) OldField(ctx context.Context, name string) (ent.Va return m.OldPolicyEvaluationsSkipped(ctx) case workflowrun.FieldPolicyViolationsCount: return m.OldPolicyViolationsCount(ctx) + case workflowrun.FieldPolicyHasGates: + return m.OldPolicyHasGates(ctx) } return nil, fmt.Errorf("unknown WorkflowRun field %s", name) } @@ -19546,6 +19603,13 @@ func (m *WorkflowRunMutation) SetField(name string, value ent.Value) error { } m.SetPolicyViolationsCount(v) return nil + case workflowrun.FieldPolicyHasGates: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPolicyHasGates(v) + return nil } return fmt.Errorf("unknown WorkflowRun field %s", name) } @@ -19690,6 +19754,9 @@ func (m *WorkflowRunMutation) ClearedFields() []string { if m.FieldCleared(workflowrun.FieldPolicyViolationsCount) { fields = append(fields, workflowrun.FieldPolicyViolationsCount) } + if m.FieldCleared(workflowrun.FieldPolicyHasGates) { + fields = append(fields, workflowrun.FieldPolicyHasGates) + } return fields } @@ -19743,6 +19810,9 @@ func (m *WorkflowRunMutation) ClearField(name string) error { case workflowrun.FieldPolicyViolationsCount: m.ClearPolicyViolationsCount() return nil + case workflowrun.FieldPolicyHasGates: + m.ClearPolicyHasGates() + return nil } return fmt.Errorf("unknown WorkflowRun nullable field %s", name) } @@ -19808,6 +19878,9 @@ func (m *WorkflowRunMutation) ResetField(name string) error { case workflowrun.FieldPolicyViolationsCount: m.ResetPolicyViolationsCount() return nil + case workflowrun.FieldPolicyHasGates: + m.ResetPolicyHasGates() + return nil } return fmt.Errorf("unknown WorkflowRun field %s", name) } diff --git a/app/controlplane/pkg/data/ent/schema/workflowrun.go b/app/controlplane/pkg/data/ent/schema/workflowrun.go index a23c1b217..5e9d05ca2 100644 --- a/app/controlplane/pkg/data/ent/schema/workflowrun.go +++ b/app/controlplane/pkg/data/ent/schema/workflowrun.go @@ -71,6 +71,7 @@ func (WorkflowRun) Fields() []ent.Field { field.Int32("policy_evaluations_passed").Optional().Nillable(), field.Int32("policy_evaluations_skipped").Optional().Nillable(), field.Int32("policy_violations_count").Optional().Nillable(), + field.Bool("policy_has_gates").Optional().Nillable(), } } @@ -106,5 +107,9 @@ func (WorkflowRun) Indexes() []ent.Index { index.Fields("version_id", "workflow_id"), // List filtering on canonical policy status index.Fields("policy_status"), + // Partial index: most "gated" queries ask for true; NULL rows predate + // the column, and false is the high-cardinality majority where a + // seqscan is fine anyway. + index.Fields("policy_has_gates").Annotations(entsql.IndexWhere("policy_has_gates = true")), } } diff --git a/app/controlplane/pkg/data/ent/workflowrun.go b/app/controlplane/pkg/data/ent/workflowrun.go index d8708e038..4cfaf7537 100644 --- a/app/controlplane/pkg/data/ent/workflowrun.go +++ b/app/controlplane/pkg/data/ent/workflowrun.go @@ -63,6 +63,8 @@ type WorkflowRun struct { PolicyEvaluationsSkipped *int32 `json:"policy_evaluations_skipped,omitempty"` // PolicyViolationsCount holds the value of the "policy_violations_count" field. PolicyViolationsCount *int32 `json:"policy_violations_count,omitempty"` + // PolicyHasGates holds the value of the "policy_has_gates" field. + PolicyHasGates *bool `json:"policy_has_gates,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the WorkflowRunQuery when eager-loading is set. Edges WorkflowRunEdges `json:"edges"` @@ -147,7 +149,7 @@ func (*WorkflowRun) scanValues(columns []string) ([]any, error) { switch columns[i] { case workflowrun.FieldAttestation, workflowrun.FieldAttestationState: values[i] = new([]byte) - case workflowrun.FieldHasPolicyViolations: + case workflowrun.FieldHasPolicyViolations, workflowrun.FieldPolicyHasGates: values[i] = new(sql.NullBool) case workflowrun.FieldContractRevisionUsed, workflowrun.FieldContractRevisionLatest, workflowrun.FieldPolicyEvaluationsTotal, workflowrun.FieldPolicyEvaluationsPassed, workflowrun.FieldPolicyEvaluationsSkipped, workflowrun.FieldPolicyViolationsCount: values[i] = new(sql.NullInt64) @@ -302,6 +304,13 @@ func (_m *WorkflowRun) assignValues(columns []string, values []any) error { _m.PolicyViolationsCount = new(int32) *_m.PolicyViolationsCount = int32(value.Int64) } + case workflowrun.FieldPolicyHasGates: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field policy_has_gates", values[i]) + } else if value.Valid { + _m.PolicyHasGates = new(bool) + *_m.PolicyHasGates = value.Bool + } case workflowrun.ForeignKeys[0]: if value, ok := values[i].(*sql.NullScanner); !ok { return fmt.Errorf("unexpected type %T for field workflow_run_contract_version", values[i]) @@ -438,6 +447,11 @@ func (_m *WorkflowRun) String() string { builder.WriteString("policy_violations_count=") builder.WriteString(fmt.Sprintf("%v", *v)) } + builder.WriteString(", ") + if v := _m.PolicyHasGates; v != nil { + builder.WriteString("policy_has_gates=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } builder.WriteByte(')') return builder.String() } diff --git a/app/controlplane/pkg/data/ent/workflowrun/where.go b/app/controlplane/pkg/data/ent/workflowrun/where.go index 829c6ea59..56138293f 100644 --- a/app/controlplane/pkg/data/ent/workflowrun/where.go +++ b/app/controlplane/pkg/data/ent/workflowrun/where.go @@ -137,6 +137,11 @@ func PolicyViolationsCount(v int32) predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyViolationsCount, v)) } +// PolicyHasGates applies equality check predicate on the "policy_has_gates" field. It's identical to PolicyHasGatesEQ. +func PolicyHasGates(v bool) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyHasGates, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldEQ(FieldCreatedAt, v)) @@ -987,6 +992,26 @@ func PolicyViolationsCountNotNil() predicate.WorkflowRun { return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyViolationsCount)) } +// PolicyHasGatesEQ applies the EQ predicate on the "policy_has_gates" field. +func PolicyHasGatesEQ(v bool) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldEQ(FieldPolicyHasGates, v)) +} + +// PolicyHasGatesNEQ applies the NEQ predicate on the "policy_has_gates" field. +func PolicyHasGatesNEQ(v bool) predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNEQ(FieldPolicyHasGates, v)) +} + +// PolicyHasGatesIsNil applies the IsNil predicate on the "policy_has_gates" field. +func PolicyHasGatesIsNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldIsNull(FieldPolicyHasGates)) +} + +// PolicyHasGatesNotNil applies the NotNil predicate on the "policy_has_gates" field. +func PolicyHasGatesNotNil() predicate.WorkflowRun { + return predicate.WorkflowRun(sql.FieldNotNull(FieldPolicyHasGates)) +} + // HasWorkflow applies the HasEdge predicate on the "workflow" edge. func HasWorkflow() predicate.WorkflowRun { return predicate.WorkflowRun(func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go b/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go index 23bc23f9e..a99ca1425 100644 --- a/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go +++ b/app/controlplane/pkg/data/ent/workflowrun/workflowrun.go @@ -55,6 +55,8 @@ const ( FieldPolicyEvaluationsSkipped = "policy_evaluations_skipped" // FieldPolicyViolationsCount holds the string denoting the policy_violations_count field in the database. FieldPolicyViolationsCount = "policy_violations_count" + // FieldPolicyHasGates holds the string denoting the policy_has_gates field in the database. + FieldPolicyHasGates = "policy_has_gates" // EdgeWorkflow holds the string denoting the workflow edge name in mutations. EdgeWorkflow = "workflow" // EdgeContractVersion holds the string denoting the contract_version edge name in mutations. @@ -124,6 +126,7 @@ var Columns = []string{ FieldPolicyEvaluationsPassed, FieldPolicyEvaluationsSkipped, FieldPolicyViolationsCount, + FieldPolicyHasGates, } // ForeignKeys holds the SQL foreign-keys that are owned by the "workflow_runs" @@ -292,6 +295,11 @@ func ByPolicyViolationsCount(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPolicyViolationsCount, opts...).ToFunc() } +// ByPolicyHasGates orders the results by the policy_has_gates field. +func ByPolicyHasGates(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPolicyHasGates, opts...).ToFunc() +} + // ByWorkflowField orders the results by workflow field. func ByWorkflowField(field string, opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/workflowrun_create.go b/app/controlplane/pkg/data/ent/workflowrun_create.go index 252344fc0..e6add655f 100644 --- a/app/controlplane/pkg/data/ent/workflowrun_create.go +++ b/app/controlplane/pkg/data/ent/workflowrun_create.go @@ -249,6 +249,20 @@ func (_c *WorkflowRunCreate) SetNillablePolicyViolationsCount(v *int32) *Workflo return _c } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (_c *WorkflowRunCreate) SetPolicyHasGates(v bool) *WorkflowRunCreate { + _c.mutation.SetPolicyHasGates(v) + return _c +} + +// SetNillablePolicyHasGates sets the "policy_has_gates" field if the given value is not nil. +func (_c *WorkflowRunCreate) SetNillablePolicyHasGates(v *bool) *WorkflowRunCreate { + if v != nil { + _c.SetPolicyHasGates(*v) + } + return _c +} + // SetID sets the "id" field. func (_c *WorkflowRunCreate) SetID(v uuid.UUID) *WorkflowRunCreate { _c.mutation.SetID(v) @@ -515,6 +529,10 @@ func (_c *WorkflowRunCreate) createSpec() (*WorkflowRun, *sqlgraph.CreateSpec) { _spec.SetField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32, value) _node.PolicyViolationsCount = &value } + if value, ok := _c.mutation.PolicyHasGates(); ok { + _spec.SetField(workflowrun.FieldPolicyHasGates, field.TypeBool, value) + _node.PolicyHasGates = &value + } if nodes := _c.mutation.WorkflowIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -968,6 +986,24 @@ func (u *WorkflowRunUpsert) ClearPolicyViolationsCount() *WorkflowRunUpsert { return u } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (u *WorkflowRunUpsert) SetPolicyHasGates(v bool) *WorkflowRunUpsert { + u.Set(workflowrun.FieldPolicyHasGates, v) + return u +} + +// UpdatePolicyHasGates sets the "policy_has_gates" field to the value that was provided on create. +func (u *WorkflowRunUpsert) UpdatePolicyHasGates() *WorkflowRunUpsert { + u.SetExcluded(workflowrun.FieldPolicyHasGates) + return u +} + +// ClearPolicyHasGates clears the value of the "policy_has_gates" field. +func (u *WorkflowRunUpsert) ClearPolicyHasGates() *WorkflowRunUpsert { + u.SetNull(workflowrun.FieldPolicyHasGates) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. // Using this option is equivalent to using: // @@ -1393,6 +1429,27 @@ func (u *WorkflowRunUpsertOne) ClearPolicyViolationsCount() *WorkflowRunUpsertOn }) } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (u *WorkflowRunUpsertOne) SetPolicyHasGates(v bool) *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyHasGates(v) + }) +} + +// UpdatePolicyHasGates sets the "policy_has_gates" field to the value that was provided on create. +func (u *WorkflowRunUpsertOne) UpdatePolicyHasGates() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyHasGates() + }) +} + +// ClearPolicyHasGates clears the value of the "policy_has_gates" field. +func (u *WorkflowRunUpsertOne) ClearPolicyHasGates() *WorkflowRunUpsertOne { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyHasGates() + }) +} + // Exec executes the query. func (u *WorkflowRunUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -1985,6 +2042,27 @@ func (u *WorkflowRunUpsertBulk) ClearPolicyViolationsCount() *WorkflowRunUpsertB }) } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (u *WorkflowRunUpsertBulk) SetPolicyHasGates(v bool) *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.SetPolicyHasGates(v) + }) +} + +// UpdatePolicyHasGates sets the "policy_has_gates" field to the value that was provided on create. +func (u *WorkflowRunUpsertBulk) UpdatePolicyHasGates() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.UpdatePolicyHasGates() + }) +} + +// ClearPolicyHasGates clears the value of the "policy_has_gates" field. +func (u *WorkflowRunUpsertBulk) ClearPolicyHasGates() *WorkflowRunUpsertBulk { + return u.Update(func(s *WorkflowRunUpsert) { + s.ClearPolicyHasGates() + }) +} + // Exec executes the query. func (u *WorkflowRunUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/app/controlplane/pkg/data/ent/workflowrun_update.go b/app/controlplane/pkg/data/ent/workflowrun_update.go index 8423af64a..4c7572736 100644 --- a/app/controlplane/pkg/data/ent/workflowrun_update.go +++ b/app/controlplane/pkg/data/ent/workflowrun_update.go @@ -378,6 +378,26 @@ func (_u *WorkflowRunUpdate) ClearPolicyViolationsCount() *WorkflowRunUpdate { return _u } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (_u *WorkflowRunUpdate) SetPolicyHasGates(v bool) *WorkflowRunUpdate { + _u.mutation.SetPolicyHasGates(v) + return _u +} + +// SetNillablePolicyHasGates sets the "policy_has_gates" field if the given value is not nil. +func (_u *WorkflowRunUpdate) SetNillablePolicyHasGates(v *bool) *WorkflowRunUpdate { + if v != nil { + _u.SetPolicyHasGates(*v) + } + return _u +} + +// ClearPolicyHasGates clears the value of the "policy_has_gates" field. +func (_u *WorkflowRunUpdate) ClearPolicyHasGates() *WorkflowRunUpdate { + _u.mutation.ClearPolicyHasGates() + return _u +} + // SetContractVersionID sets the "contract_version" edge to the WorkflowContractVersion entity by ID. func (_u *WorkflowRunUpdate) SetContractVersionID(id uuid.UUID) *WorkflowRunUpdate { _u.mutation.SetContractVersionID(id) @@ -651,6 +671,12 @@ func (_u *WorkflowRunUpdate) sqlSave(ctx context.Context) (_node int, err error) if _u.mutation.PolicyViolationsCountCleared() { _spec.ClearField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32) } + if value, ok := _u.mutation.PolicyHasGates(); ok { + _spec.SetField(workflowrun.FieldPolicyHasGates, field.TypeBool, value) + } + if _u.mutation.PolicyHasGatesCleared() { + _spec.ClearField(workflowrun.FieldPolicyHasGates, field.TypeBool) + } if _u.mutation.ContractVersionCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -1147,6 +1173,26 @@ func (_u *WorkflowRunUpdateOne) ClearPolicyViolationsCount() *WorkflowRunUpdateO return _u } +// SetPolicyHasGates sets the "policy_has_gates" field. +func (_u *WorkflowRunUpdateOne) SetPolicyHasGates(v bool) *WorkflowRunUpdateOne { + _u.mutation.SetPolicyHasGates(v) + return _u +} + +// SetNillablePolicyHasGates sets the "policy_has_gates" field if the given value is not nil. +func (_u *WorkflowRunUpdateOne) SetNillablePolicyHasGates(v *bool) *WorkflowRunUpdateOne { + if v != nil { + _u.SetPolicyHasGates(*v) + } + return _u +} + +// ClearPolicyHasGates clears the value of the "policy_has_gates" field. +func (_u *WorkflowRunUpdateOne) ClearPolicyHasGates() *WorkflowRunUpdateOne { + _u.mutation.ClearPolicyHasGates() + return _u +} + // SetContractVersionID sets the "contract_version" edge to the WorkflowContractVersion entity by ID. func (_u *WorkflowRunUpdateOne) SetContractVersionID(id uuid.UUID) *WorkflowRunUpdateOne { _u.mutation.SetContractVersionID(id) @@ -1450,6 +1496,12 @@ func (_u *WorkflowRunUpdateOne) sqlSave(ctx context.Context) (_node *WorkflowRun if _u.mutation.PolicyViolationsCountCleared() { _spec.ClearField(workflowrun.FieldPolicyViolationsCount, field.TypeInt32) } + if value, ok := _u.mutation.PolicyHasGates(); ok { + _spec.SetField(workflowrun.FieldPolicyHasGates, field.TypeBool, value) + } + if _u.mutation.PolicyHasGatesCleared() { + _spec.ClearField(workflowrun.FieldPolicyHasGates, field.TypeBool) + } if _u.mutation.ContractVersionCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/app/controlplane/pkg/data/workflowrun.go b/app/controlplane/pkg/data/workflowrun.go index 41d4353fd..3fbddd01b 100644 --- a/app/controlplane/pkg/data/workflowrun.go +++ b/app/controlplane/pkg/data/workflowrun.go @@ -241,7 +241,8 @@ func (r *WorkflowRunRepo) UpdatePolicyStatus(ctx context.Context, id uuid.UUID, SetPolicyEvaluationsTotal(int32(summary.Total)). SetPolicyEvaluationsPassed(int32(summary.Passed)). SetPolicyEvaluationsSkipped(int32(summary.Skipped)). - SetPolicyViolationsCount(int32(summary.Violated)) + SetPolicyViolationsCount(int32(summary.Violated)). + SetPolicyHasGates(summary.HasGates) run, err := update.Save(ctx) if err != nil && !ent.IsNotFound(err) { @@ -384,6 +385,10 @@ func (r *WorkflowRunRepo) List(ctx context.Context, orgID uuid.UUID, filters *bi } } + if filters != nil && filters.PolicyHasGates != nil { + q = q.Where(workflowrun.PolicyHasGates(*filters.PolicyHasGates)) + } + if p.Cursor != nil { q = q.Where( func(s *sql.Selector) { @@ -518,5 +523,8 @@ func entWrPolicySummary(wr *ent.WorkflowRun) *chainloop.PolicyStatusSummary { if wr.PolicyViolationsCount != nil { s.Violated = int(*wr.PolicyViolationsCount) } + if wr.PolicyHasGates != nil { + s.HasGates = *wr.PolicyHasGates + } return s } diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index 06a8ba617..653962f5e 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -57,6 +57,9 @@ type PolicyEvaluationStatus struct { HasViolations bool // Whether the attestation has gated policy violations HasGatedViolations bool + // HasGates is true when a gate fired or strategy is ENFORCED, independent + // of whether gated violations were recorded. + HasGates bool // Total number of policy evaluations EvaluationsCount int // Total number of policy violations across all evaluations diff --git a/pkg/attestation/renderer/chainloop/policy_status.go b/pkg/attestation/renderer/chainloop/policy_status.go index d66fe8ad3..ef2bc6a29 100644 --- a/pkg/attestation/renderer/chainloop/policy_status.go +++ b/pkg/attestation/renderer/chainloop/policy_status.go @@ -40,6 +40,9 @@ type PolicyStatusSummary struct { Passed int Skipped int Violated int + // See PolicyEvaluationStatus.HasGates. Independent of Status — a PASSED + // run can still have HasGates=true. + HasGates bool } // DerivePolicyStatusSummary is the single source of truth for computing the @@ -66,6 +69,7 @@ func DerivePolicyStatusSummary(s *PolicyEvaluationStatus) PolicyStatusSummary { Passed: s.PassedCount, Skipped: s.SkippedCount, Violated: s.ViolationsCount, + HasGates: s.HasGates, } enforced := s.HasGatedViolations || s.Strategy == PolicyViolationBlockingStrategyEnforced diff --git a/pkg/attestation/renderer/chainloop/policy_status_test.go b/pkg/attestation/renderer/chainloop/policy_status_test.go index 2237b98a5..8df7d3e2e 100644 --- a/pkg/attestation/renderer/chainloop/policy_status_test.go +++ b/pkg/attestation/renderer/chainloop/policy_status_test.go @@ -181,6 +181,21 @@ func TestDerivePolicyStatusSummary(t *testing.T) { Passed: 2, }, }, + { + name: "has_gates propagates from status", + status: &PolicyEvaluationStatus{ + Strategy: advisory, + EvaluationsCount: 2, + PassedCount: 2, + HasGates: true, + }, + want: PolicyStatusSummary{ + Status: PolicyStatusPassed, + Total: 2, + Passed: 2, + HasGates: true, + }, + }, { name: "warning + skipped mix => warning (violations dominate skips)", status: &PolicyEvaluationStatus{ @@ -208,3 +223,148 @@ func TestDerivePolicyStatusSummary(t *testing.T) { }) } } + +// TestPredicateToPolicyStatusSummary exercises the attestation→summary chain +// used in the attestation-ingest path (biz.WorkflowRunUseCase.SaveAttestation): +// ProvenancePredicateV02.GetPolicyEvaluationStatus() → DerivePolicyStatusSummary. +// The two steps have their own unit tests but are never combined there, which +// is the exact translation the production code performs. +func TestPredicateToPolicyStatusSummary(t *testing.T) { + testCases := []struct { + name string + predicate *ProvenancePredicateV02 + want PolicyStatusSummary + }{ + { + name: "predicate with no policy evaluations => not applicable", + predicate: &ProvenancePredicateV02{}, + want: PolicyStatusSummary{Status: PolicyStatusNotApplicable}, + }, + { + name: "stored counters => passed", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 3, + PolicyPassedCount: 3, + }, + want: PolicyStatusSummary{Status: PolicyStatusPassed, Total: 3, Passed: 3}, + }, + { + name: "stored counters => skipped", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 3, + PolicyPassedCount: 2, + PolicySkippedCount: 1, + }, + want: PolicyStatusSummary{Status: PolicyStatusSkipped, Total: 3, Passed: 2, Skipped: 1}, + }, + { + name: "advisory violation => warning", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyPassedCount: 1, + PolicyViolationsCount: 1, + PolicyHasViolations: true, + }, + want: PolicyStatusSummary{Status: PolicyStatusWarning, Total: 2, Passed: 1, Violated: 1}, + }, + { + name: "gated violation, not bypassed => blocked", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyPassedCount: 1, + PolicyViolationsCount: 1, + PolicyHasViolations: true, + PolicyHasGatedViolations: true, + PolicyAttBlocked: true, + }, + want: PolicyStatusSummary{Status: PolicyStatusBlocked, Total: 2, Passed: 1, Violated: 1}, + }, + { + name: "gated violation bypassed => bypassed", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyPassedCount: 1, + PolicyViolationsCount: 1, + PolicyHasViolations: true, + PolicyHasGatedViolations: true, + PolicyBlockBypassEnabled: true, + }, + want: PolicyStatusSummary{Status: PolicyStatusBypassed, Total: 2, Passed: 1, Violated: 1}, + }, + { + // Attestations signed before the skipped/passed counters existed + // decode as zero. The predicate backfills from inline evaluations + // before the summary is derived, so a skipped-only run must not + // be misclassified as PASSED. + name: "historic envelope with only inline skipped evaluations => skipped", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyEvaluations: map[string][]*PolicyEvaluation{ + "material-a": {{Skipped: true}, {Skipped: true}}, + }, + }, + want: PolicyStatusSummary{Status: PolicyStatusSkipped, Total: 2, Skipped: 2}, + }, + { + // Historic envelopes can carry inline evaluations in the + // fallback-shaped field; the backfill must read from there too. + name: "historic envelope backfills from fallback evaluations => passed", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyEvaluationsFallback: map[string][]*PolicyEvaluation{ + "material-a": {{}, {}}, + }, + }, + want: PolicyStatusSummary{Status: PolicyStatusPassed, Total: 2, Passed: 2}, + }, + { + name: "stored has_gates flag propagates to summary", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyPassedCount: 2, + PolicyHasGates: true, + }, + want: PolicyStatusSummary{Status: PolicyStatusPassed, Total: 2, Passed: 2, HasGates: true}, + }, + { + // Historic envelope with no stored has_gates flag: derive from + // inline evaluations. + name: "historic envelope derives has_gates from inline gate:true", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyAdvisory, + PolicyEvaluationsCount: 2, + PolicyPassedCount: 2, + PolicyEvaluations: map[string][]*PolicyEvaluation{ + "material-a": {{Gate: true}, {}}, + }, + }, + want: PolicyStatusSummary{Status: PolicyStatusPassed, Total: 2, Passed: 2, HasGates: true}, + }, + { + // Enforced strategy implies gating even without explicit gate:true + // and even when the stored has_gates flag is missing (historic). + name: "historic envelope with ENFORCED strategy derives has_gates=true", + predicate: &ProvenancePredicateV02{ + PolicyCheckBlockingStrategy: PolicyViolationBlockingStrategyEnforced, + PolicyEvaluationsCount: 1, + PolicyPassedCount: 1, + }, + want: PolicyStatusSummary{Status: PolicyStatusPassed, Total: 1, Passed: 1, HasGates: true}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := DerivePolicyStatusSummary(tc.predicate.GetPolicyEvaluationStatus()) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index ba3240df0..a711e06c5 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -62,6 +62,7 @@ type ProvenancePredicateV02 struct { PolicyPassedCount int `json:"policyPassedCount,omitempty"` // Whether the attestation has policy violations in gated policies PolicyHasGatedViolations bool `json:"policyHasGatedViolations,omitempty"` + PolicyHasGates bool `json:"policyHasGates,omitempty"` // Whether we want to block the attestation on policy violations PolicyCheckBlockingStrategy PolicyViolationBlockingStrategy `json:"policyCheckBlockingStrategy"` // Whether the policy check was bypassed @@ -258,6 +259,8 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) { // In all cases, if the bypass flag is set, the attestation is not blocked blocked := !r.att.GetBypassPolicyCheck() && (evalResult.hasGatedViolations || (evalResult.hasViolations && r.att.GetBlockOnPolicyViolation())) + hasGates := evalResult.hasGates || policyCheckBlockingStrategy == PolicyViolationBlockingStrategyEnforced + p := ProvenancePredicateV02{ ProvenancePredicateCommon: predicateCommon(r.builder, r.att), Materials: normalizedMaterials, @@ -269,6 +272,7 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) { PolicySkippedCount: evalResult.skippedCount, PolicyPassedCount: evalResult.passedCount, PolicyHasGatedViolations: evalResult.hasGatedViolations, + PolicyHasGates: hasGates, PolicyCheckBlockingStrategy: policyCheckBlockingStrategy, PolicyBlockBypassEnabled: r.att.GetBypassPolicyCheck(), PolicyAttBlocked: blocked, @@ -315,6 +319,7 @@ type evaluationsResult struct { evaluations map[string][]*PolicyEvaluation hasViolations bool hasGatedViolations bool + hasGates bool evaluationsCount int violationsCount int skippedCount int @@ -337,6 +342,10 @@ func groupEvaluations(evals []*v1.PolicyEvaluation) (*evaluationsResult, error) return nil, err } + if ev.Gate { + res.hasGates = true + } + switch { case len(ev.Violations) > 0: res.hasViolations = true @@ -476,21 +485,30 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluationsRef() *intoto.ResourceDescr } func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationStatus { - skipped, passed := p.PolicySkippedCount, p.PolicyPassedCount - // Attestations signed before the skipped/passed counters were added to - // the predicate decode them as zero. When the inline evaluations are - // available, recompute to keep the canonical PolicyStatus derivation - // correct for historic envelopes (e.g. skipped-only runs would otherwise - // be misclassified as PASSED). - if skipped == 0 && passed == 0 && p.PolicyEvaluationsCount > 0 { + skipped, passed, hasGates := p.PolicySkippedCount, p.PolicyPassedCount, p.PolicyHasGates + + // Attestations signed before these counters/flags were added to the + // predicate decode as zero. Backfill from the inline evaluations when + // available so DerivePolicyStatusSummary stays correct for historic + // envelopes (e.g. skipped-only runs misclassified as PASSED). + countersMissing := skipped == 0 && passed == 0 && p.PolicyEvaluationsCount > 0 + if countersMissing || !hasGates { evals := p.PolicyEvaluations if len(evals) == 0 { evals = p.PolicyEvaluationsFallback } if len(evals) > 0 { - skipped, passed = countSkippedAndPassed(evals) + sc, pc, hg := summarizeInlineEvaluations(evals) + if countersMissing { + skipped, passed = sc, pc + } + hasGates = hasGates || hg } } + // Strategy-level enforcement implies gating even without inline evaluations. + if !hasGates && p.PolicyCheckBlockingStrategy == PolicyViolationBlockingStrategyEnforced { + hasGates = true + } return &PolicyEvaluationStatus{ Strategy: p.PolicyCheckBlockingStrategy, @@ -498,6 +516,7 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationSt Blocked: p.PolicyAttBlocked, HasViolations: p.PolicyHasViolations, HasGatedViolations: p.PolicyHasGatedViolations, + HasGates: hasGates, EvaluationsCount: p.PolicyEvaluationsCount, ViolationsCount: p.PolicyViolationsCount, SkippedCount: skipped, @@ -505,12 +524,14 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationSt } } -func countSkippedAndPassed(evals map[string][]*PolicyEvaluation) (skipped, passed int) { +func summarizeInlineEvaluations(evals map[string][]*PolicyEvaluation) (skipped, passed int, hasGates bool) { for _, list := range evals { for _, ev := range list { + if ev.Gate { + hasGates = true + } switch { case len(ev.Violations) > 0: - // violation-bearing evaluations are neither passed nor skipped case ev.Skipped: skipped++ default: diff --git a/pkg/attestation/renderer/chainloop/v02_test.go b/pkg/attestation/renderer/chainloop/v02_test.go index f6feaebcf..fd259187d 100644 --- a/pkg/attestation/renderer/chainloop/v02_test.go +++ b/pkg/attestation/renderer/chainloop/v02_test.go @@ -596,6 +596,7 @@ func TestGroupEvaluationsCounts(t *testing.T) { wantViolCount int wantViolations bool wantGatedViolations bool + wantGates bool }{ { name: "no evaluations", @@ -670,6 +671,7 @@ func TestGroupEvaluationsCounts(t *testing.T) { wantViolCount: 1, wantViolations: true, wantGatedViolations: true, + wantGates: true, }, { name: "gated evaluation without violations", @@ -678,6 +680,7 @@ func TestGroupEvaluationsCounts(t *testing.T) { }, wantEvalCount: 1, wantViolCount: 0, + wantGates: true, }, } @@ -689,6 +692,7 @@ func TestGroupEvaluationsCounts(t *testing.T) { assert.Equal(t, tc.wantViolCount, res.violationsCount) assert.Equal(t, tc.wantViolations, res.hasViolations) assert.Equal(t, tc.wantGatedViolations, res.hasGatedViolations) + assert.Equal(t, tc.wantGates, res.hasGates) }) } }