From 3f4f6ca147402567c1c52fee05e2f04bb7172644 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Tue, 12 May 2026 11:50:46 +0200 Subject: [PATCH] feat(contracts): add managed flag Adds a managed boolean to workflow contracts to mark resources provisioned and operated by Chainloop. Managed contracts cannot be updated or deleted by users. Mirrors the same flag added for CAS backends in #3092. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino --- .../controlplane/v1/response_messages.pb.go | 17 ++++- .../controlplane/v1/response_messages.proto | 2 + .../controlplane/v1/response_messages.ts | 16 +++++ ...ne.v1.WorkflowContractItem.jsonschema.json | 8 +++ ...lplane.v1.WorkflowContractItem.schema.json | 8 +++ .../internal/service/workflowcontract.go | 1 + app/controlplane/pkg/biz/workflowcontract.go | 14 ++++ .../ent/migrate/migrations/20260512094407.sql | 2 + .../pkg/data/ent/migrate/migrations/atlas.sum | 3 +- .../pkg/data/ent/migrate/schema.go | 5 +- app/controlplane/pkg/data/ent/mutation.go | 56 +++++++++++++++- app/controlplane/pkg/data/ent/runtime.go | 4 ++ .../pkg/data/ent/schema/workflowcontract.go | 4 +- .../pkg/data/ent/workflowcontract.go | 13 ++++ .../pkg/data/ent/workflowcontract/where.go | 15 +++++ .../ent/workflowcontract/workflowcontract.go | 10 +++ .../pkg/data/ent/workflowcontract_create.go | 65 +++++++++++++++++++ .../pkg/data/ent/workflowcontract_update.go | 34 ++++++++++ app/controlplane/pkg/data/workflowcontract.go | 6 +- 19 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 app/controlplane/pkg/data/ent/migrate/migrations/20260512094407.sql diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index 0fff67449..33a2e4477 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -1358,7 +1358,9 @@ type WorkflowContractItem struct { WorkflowNames []string `protobuf:"bytes,5,rep,name=workflow_names,json=workflowNames,proto3" json:"workflow_names,omitempty"` WorkflowRefs []*WorkflowRef `protobuf:"bytes,7,rep,name=workflow_refs,json=workflowRefs,proto3" json:"workflow_refs,omitempty"` // wether the contract is scoped to an entity in the organization - ScopedEntity *ScopedEntity `protobuf:"bytes,9,opt,name=scoped_entity,json=scopedEntity,proto3" json:"scoped_entity,omitempty"` + ScopedEntity *ScopedEntity `protobuf:"bytes,9,opt,name=scoped_entity,json=scopedEntity,proto3" json:"scoped_entity,omitempty"` + // Whether this contract is provisioned and operated by Chainloop + IsManaged bool `protobuf:"varint,11,opt,name=is_managed,json=isManaged,proto3" json:"is_managed,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1465,6 +1467,13 @@ func (x *WorkflowContractItem) GetScopedEntity() *ScopedEntity { return nil } +func (x *WorkflowContractItem) GetIsManaged() bool { + if x != nil { + return x.IsManaged + } + return false +} + type ScopedEntity struct { state protoimpl.MessageState `protogen:"open.v1"` // Type is the type of the scoped entity i.e project or org @@ -2771,7 +2780,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x03uri\x18\x04 \x01(\tR\x03uri\x1a9\n" + "\vDigestEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8a\x04\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa9\x04\n" + "\x14WorkflowContractItem\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12$\n" + @@ -2785,7 +2794,9 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x1alatest_revision_created_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\x17latestRevisionCreatedAt\x12)\n" + "\x0eworkflow_names\x18\x05 \x03(\tB\x02\x18\x01R\rworkflowNames\x12A\n" + "\rworkflow_refs\x18\a \x03(\v2\x1c.controlplane.v1.WorkflowRefR\fworkflowRefs\x12B\n" + - "\rscoped_entity\x18\t \x01(\v2\x1d.controlplane.v1.ScopedEntityR\fscopedEntity\"F\n" + + "\rscoped_entity\x18\t \x01(\v2\x1d.controlplane.v1.ScopedEntityR\fscopedEntity\x12\x1d\n" + + "\n" + + "is_managed\x18\v \x01(\bR\tisManaged\"F\n" + "\fScopedEntity\x12\x12\n" + "\x04type\x18\x01 \x01(\tR\x04type\x12\x0e\n" + "\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n" + diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index dc3e71779..7c5a649b2 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -195,6 +195,8 @@ message WorkflowContractItem { repeated WorkflowRef workflow_refs = 7; // wether the contract is scoped to an entity in the organization ScopedEntity scoped_entity = 9; + // Whether this contract is provisioned and operated by Chainloop + bool is_managed = 11; } message ScopedEntity { 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 51e86aa40..237a71756 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -498,6 +498,8 @@ export interface WorkflowContractItem { workflowRefs: WorkflowRef[]; /** wether the contract is scoped to an entity in the organization */ scopedEntity?: ScopedEntity; + /** Whether this contract is provisioned and operated by Chainloop */ + isManaged: boolean; } export interface ScopedEntity { @@ -2974,6 +2976,7 @@ function createBaseWorkflowContractItem(): WorkflowContractItem { workflowNames: [], workflowRefs: [], scopedEntity: undefined, + isManaged: false, }; } @@ -3009,6 +3012,9 @@ export const WorkflowContractItem = { if (message.scopedEntity !== undefined) { ScopedEntity.encode(message.scopedEntity, writer.uint32(74).fork()).ldelim(); } + if (message.isManaged === true) { + writer.uint32(88).bool(message.isManaged); + } return writer; }, @@ -3089,6 +3095,13 @@ export const WorkflowContractItem = { message.scopedEntity = ScopedEntity.decode(reader, reader.uint32()); continue; + case 11: + if (tag !== 88) { + break; + } + + message.isManaged = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -3114,6 +3127,7 @@ export const WorkflowContractItem = { ? object.workflowRefs.map((e: any) => WorkflowRef.fromJSON(e)) : [], scopedEntity: isSet(object.scopedEntity) ? ScopedEntity.fromJSON(object.scopedEntity) : undefined, + isManaged: isSet(object.isManaged) ? Boolean(object.isManaged) : false, }; }, @@ -3139,6 +3153,7 @@ export const WorkflowContractItem = { } message.scopedEntity !== undefined && (obj.scopedEntity = message.scopedEntity ? ScopedEntity.toJSON(message.scopedEntity) : undefined); + message.isManaged !== undefined && (obj.isManaged = message.isManaged); return obj; }, @@ -3160,6 +3175,7 @@ export const WorkflowContractItem = { message.scopedEntity = (object.scopedEntity !== undefined && object.scopedEntity !== null) ? ScopedEntity.fromPartial(object.scopedEntity) : undefined; + message.isManaged = object.isManaged ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.jsonschema.json index 3041d3023..f7c88554f 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.jsonschema.json @@ -6,6 +6,10 @@ "^(created_at)$": { "$ref": "google.protobuf.Timestamp.jsonschema.json" }, + "^(is_managed)$": { + "description": "Whether this contract is provisioned and operated by Chainloop", + "type": "boolean" + }, "^(latest_revision)$": { "maximum": 2147483647, "minimum": -2147483648, @@ -46,6 +50,10 @@ "id": { "type": "string" }, + "isManaged": { + "description": "Whether this contract is provisioned and operated by Chainloop", + "type": "boolean" + }, "latestRevision": { "maximum": 2147483647, "minimum": -2147483648, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.schema.json index a781d0d79..4ee3161b6 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowContractItem.schema.json @@ -6,6 +6,10 @@ "^(createdAt)$": { "$ref": "google.protobuf.Timestamp.schema.json" }, + "^(isManaged)$": { + "description": "Whether this contract is provisioned and operated by Chainloop", + "type": "boolean" + }, "^(latestRevision)$": { "maximum": 2147483647, "minimum": -2147483648, @@ -46,6 +50,10 @@ "id": { "type": "string" }, + "is_managed": { + "description": "Whether this contract is provisioned and operated by Chainloop", + "type": "boolean" + }, "latest_revision": { "maximum": 2147483647, "minimum": -2147483648, diff --git a/app/controlplane/internal/service/workflowcontract.go b/app/controlplane/internal/service/workflowcontract.go index b0f9f9596..71a7f5cdd 100644 --- a/app/controlplane/internal/service/workflowcontract.go +++ b/app/controlplane/internal/service/workflowcontract.go @@ -349,6 +349,7 @@ func bizWorkFlowContractToPb(schema *biz.WorkflowContract) *pb.WorkflowContractI LatestRevisionCreatedAt: timestamppb.New(*schema.LatestRevisionCreatedAt), WorkflowNames: workflowNames, WorkflowRefs: workflowRefs, + IsManaged: schema.Managed, //nolint:staticcheck Description: schema.Description, } diff --git a/app/controlplane/pkg/biz/workflowcontract.go b/app/controlplane/pkg/biz/workflowcontract.go index 0615678a9..ee5bde278 100644 --- a/app/controlplane/pkg/biz/workflowcontract.go +++ b/app/controlplane/pkg/biz/workflowcontract.go @@ -47,6 +47,8 @@ type WorkflowContract struct { WorkflowRefs []*WorkflowRef // entity the contract is scoped to, if not set it's scoped to the organization ScopedEntity *ScopedEntity + // Managed indicates this contract is provisioned and operated by Chainloop + Managed bool } type ScopedEntity struct { @@ -128,6 +130,8 @@ type ContractCreateOpts struct { Contract *Contract // ProjectID indicates the project to be scoped to ProjectID *uuid.UUID + // Managed indicates the contract is provisioned and operated by Chainloop + Managed *bool } type ContractUpdateOpts struct { @@ -408,6 +412,11 @@ func (uc *WorkflowContractUseCase) Update(ctx context.Context, orgID, name strin return nil, fmt.Errorf("failed to find contract %s in org %s: %w", name, orgUUID, err) } + // Managed contracts are owned and operated by Chainloop and cannot be modified by users. + if wfContractPreUpdate.Managed { + return nil, NewErrValidationStr("managed contracts cannot be modified") + } + args := &ContractUpdateOpts{Description: opts.Description, Contract: contract} c, err := uc.repo.Update(ctx, orgUUID, name, args) if err != nil { @@ -657,6 +666,11 @@ func (uc *WorkflowContractUseCase) Delete(ctx context.Context, orgID, contractID return NewErrValidation(errors.New("there are associated workflows with this contract, delete them first")) } + // Prevent deletion of managed contracts - they are owned and operated by Chainloop + if contract.Managed { + return NewErrValidation(errors.New("can't delete a managed contract")) + } + // Check that the workflow to delete belongs to the provided organization if err := uc.repo.SoftDelete(ctx, contractUUID); err != nil { return fmt.Errorf("failed to delete contract: %w", err) diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/20260512094407.sql b/app/controlplane/pkg/data/ent/migrate/migrations/20260512094407.sql new file mode 100644 index 000000000..4a58d7c30 --- /dev/null +++ b/app/controlplane/pkg/data/ent/migrate/migrations/20260512094407.sql @@ -0,0 +1,2 @@ +-- Modify "workflow_contracts" table +ALTER TABLE "workflow_contracts" ADD COLUMN "managed" boolean NOT NULL DEFAULT false; diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum index 7a9b904e4..e230a4d8c 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:POeLVZ5wn0luHTIuCxBdUZBNVPEq0gfgfoaNFgQzR4g= +h1:yhiwazV5IpUmc03WN2gsiovl7haggXBd1HJ6M2DawkM= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -127,3 +127,4 @@ h1:POeLVZ5wn0luHTIuCxBdUZBNVPEq0gfgfoaNFgQzR4g= 20260211225609.sql h1:DTkyg3oZSV99uPGl+vOuK9FSlEumXwoYCgchUhsg/P4= 20260303120000.sql h1:msXy2MRkzMOGxWbG1NOHh+PN5qjaBZcRzVT+7SFIwaA= 20260318160301.sql h1:kH88s6pOi7Vprydb7xrzgY55JhMxfzY32txpQ8a1wEE= +20260512094407.sql h1:y5MoNBye22Xnspdx76+zdvzJHLXddCzqYZHcj2rtIoY= diff --git a/app/controlplane/pkg/data/ent/migrate/schema.go b/app/controlplane/pkg/data/ent/migrate/schema.go index 3e59365a6..aac7f7c68 100644 --- a/app/controlplane/pkg/data/ent/migrate/schema.go +++ b/app/controlplane/pkg/data/ent/migrate/schema.go @@ -693,6 +693,7 @@ var ( {Name: "description", Type: field.TypeString, Nullable: true}, {Name: "scoped_resource_type", Type: field.TypeEnum, Nullable: true, Enums: []string{"project", "org"}}, {Name: "scoped_resource_id", Type: field.TypeUUID, Nullable: true}, + {Name: "managed", Type: field.TypeBool, Default: false}, {Name: "organization_workflow_contracts", Type: field.TypeUUID, Nullable: true}, } // WorkflowContractsTable holds the schema information for the "workflow_contracts" table. @@ -703,7 +704,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "workflow_contracts_organizations_workflow_contracts", - Columns: []*schema.Column{WorkflowContractsColumns[8]}, + Columns: []*schema.Column{WorkflowContractsColumns[9]}, RefColumns: []*schema.Column{OrganizationsColumns[0]}, OnDelete: schema.Cascade, }, @@ -712,7 +713,7 @@ var ( { Name: "workflowcontract_name_organization_workflow_contracts", Unique: true, - Columns: []*schema.Column{WorkflowContractsColumns[1], WorkflowContractsColumns[8]}, + Columns: []*schema.Column{WorkflowContractsColumns[1], WorkflowContractsColumns[9]}, Annotation: &entsql.IndexAnnotation{ Where: "deleted_at IS NULL", }, diff --git a/app/controlplane/pkg/data/ent/mutation.go b/app/controlplane/pkg/data/ent/mutation.go index 6bb19f4d0..929df4193 100644 --- a/app/controlplane/pkg/data/ent/mutation.go +++ b/app/controlplane/pkg/data/ent/mutation.go @@ -16222,6 +16222,7 @@ type WorkflowContractMutation struct { description *string scoped_resource_type *biz.ContractScope scoped_resource_id *uuid.UUID + managed *bool clearedFields map[string]struct{} versions map[uuid.UUID]struct{} removedversions map[uuid.UUID]struct{} @@ -16644,6 +16645,42 @@ func (m *WorkflowContractMutation) ResetScopedResourceID() { delete(m.clearedFields, workflowcontract.FieldScopedResourceID) } +// SetManaged sets the "managed" field. +func (m *WorkflowContractMutation) SetManaged(b bool) { + m.managed = &b +} + +// Managed returns the value of the "managed" field in the mutation. +func (m *WorkflowContractMutation) Managed() (r bool, exists bool) { + v := m.managed + if v == nil { + return + } + return *v, true +} + +// OldManaged returns the old "managed" field's value of the WorkflowContract entity. +// If the WorkflowContract 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 *WorkflowContractMutation) OldManaged(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldManaged is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldManaged requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldManaged: %w", err) + } + return oldValue.Managed, nil +} + +// ResetManaged resets all changes to the "managed" field. +func (m *WorkflowContractMutation) ResetManaged() { + m.managed = nil +} + // AddVersionIDs adds the "versions" edge to the WorkflowContractVersion entity by ids. func (m *WorkflowContractMutation) AddVersionIDs(ids ...uuid.UUID) { if m.versions == nil { @@ -16825,7 +16862,7 @@ func (m *WorkflowContractMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *WorkflowContractMutation) Fields() []string { - fields := make([]string, 0, 7) + fields := make([]string, 0, 8) if m.name != nil { fields = append(fields, workflowcontract.FieldName) } @@ -16847,6 +16884,9 @@ func (m *WorkflowContractMutation) Fields() []string { if m.scoped_resource_id != nil { fields = append(fields, workflowcontract.FieldScopedResourceID) } + if m.managed != nil { + fields = append(fields, workflowcontract.FieldManaged) + } return fields } @@ -16869,6 +16909,8 @@ func (m *WorkflowContractMutation) Field(name string) (ent.Value, bool) { return m.ScopedResourceType() case workflowcontract.FieldScopedResourceID: return m.ScopedResourceID() + case workflowcontract.FieldManaged: + return m.Managed() } return nil, false } @@ -16892,6 +16934,8 @@ func (m *WorkflowContractMutation) OldField(ctx context.Context, name string) (e return m.OldScopedResourceType(ctx) case workflowcontract.FieldScopedResourceID: return m.OldScopedResourceID(ctx) + case workflowcontract.FieldManaged: + return m.OldManaged(ctx) } return nil, fmt.Errorf("unknown WorkflowContract field %s", name) } @@ -16950,6 +16994,13 @@ func (m *WorkflowContractMutation) SetField(name string, value ent.Value) error } m.SetScopedResourceID(v) return nil + case workflowcontract.FieldManaged: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetManaged(v) + return nil } return fmt.Errorf("unknown WorkflowContract field %s", name) } @@ -17047,6 +17098,9 @@ func (m *WorkflowContractMutation) ResetField(name string) error { case workflowcontract.FieldScopedResourceID: m.ResetScopedResourceID() return nil + case workflowcontract.FieldManaged: + m.ResetManaged() + return nil } return fmt.Errorf("unknown WorkflowContract field %s", name) } diff --git a/app/controlplane/pkg/data/ent/runtime.go b/app/controlplane/pkg/data/ent/runtime.go index adb88e48d..7bb40d01e 100644 --- a/app/controlplane/pkg/data/ent/runtime.go +++ b/app/controlplane/pkg/data/ent/runtime.go @@ -341,6 +341,10 @@ func init() { workflowcontractDescUpdatedAt := workflowcontractFields[3].Descriptor() // workflowcontract.DefaultUpdatedAt holds the default value on creation for the updated_at field. workflowcontract.DefaultUpdatedAt = workflowcontractDescUpdatedAt.Default.(func() time.Time) + // workflowcontractDescManaged is the schema descriptor for managed field. + workflowcontractDescManaged := workflowcontractFields[8].Descriptor() + // workflowcontract.DefaultManaged holds the default value on creation for the managed field. + workflowcontract.DefaultManaged = workflowcontractDescManaged.Default.(bool) // workflowcontractDescID is the schema descriptor for id field. workflowcontractDescID := workflowcontractFields[0].Descriptor() // workflowcontract.DefaultID holds the default value on creation for the id field. diff --git a/app/controlplane/pkg/data/ent/schema/workflowcontract.go b/app/controlplane/pkg/data/ent/schema/workflowcontract.go index ea3c2d77d..9f398c544 100644 --- a/app/controlplane/pkg/data/ent/schema/workflowcontract.go +++ b/app/controlplane/pkg/data/ent/schema/workflowcontract.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. @@ -53,6 +53,8 @@ func (WorkflowContract) Fields() []ent.Field { // If this value is set, the contract is scoped to a resource field.Enum("scoped_resource_type").GoType(biz.ContractScope("")).Optional(), field.UUID("scoped_resource_id", uuid.UUID{}).Optional(), + // managed indicates this contract is provisioned and operated by Chainloop + field.Bool("managed").Default(false), } } diff --git a/app/controlplane/pkg/data/ent/workflowcontract.go b/app/controlplane/pkg/data/ent/workflowcontract.go index 078c8cac4..ca6638d6f 100644 --- a/app/controlplane/pkg/data/ent/workflowcontract.go +++ b/app/controlplane/pkg/data/ent/workflowcontract.go @@ -34,6 +34,8 @@ type WorkflowContract struct { ScopedResourceType biz.ContractScope `json:"scoped_resource_type,omitempty"` // ScopedResourceID holds the value of the "scoped_resource_id" field. ScopedResourceID uuid.UUID `json:"scoped_resource_id,omitempty"` + // Managed holds the value of the "managed" field. + Managed bool `json:"managed,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the WorkflowContractQuery when eager-loading is set. Edges WorkflowContractEdges `json:"edges"` @@ -88,6 +90,8 @@ func (*WorkflowContract) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { + case workflowcontract.FieldManaged: + values[i] = new(sql.NullBool) case workflowcontract.FieldName, workflowcontract.FieldDescription, workflowcontract.FieldScopedResourceType: values[i] = new(sql.NullString) case workflowcontract.FieldCreatedAt, workflowcontract.FieldUpdatedAt, workflowcontract.FieldDeletedAt: @@ -159,6 +163,12 @@ func (_m *WorkflowContract) assignValues(columns []string, values []any) error { } else if value != nil { _m.ScopedResourceID = *value } + case workflowcontract.FieldManaged: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field managed", values[i]) + } else if value.Valid { + _m.Managed = value.Bool + } case workflowcontract.ForeignKeys[0]: if value, ok := values[i].(*sql.NullScanner); !ok { return fmt.Errorf("unexpected type %T for field organization_workflow_contracts", values[i]) @@ -237,6 +247,9 @@ func (_m *WorkflowContract) String() string { builder.WriteString(", ") builder.WriteString("scoped_resource_id=") builder.WriteString(fmt.Sprintf("%v", _m.ScopedResourceID)) + builder.WriteString(", ") + builder.WriteString("managed=") + builder.WriteString(fmt.Sprintf("%v", _m.Managed)) builder.WriteByte(')') return builder.String() } diff --git a/app/controlplane/pkg/data/ent/workflowcontract/where.go b/app/controlplane/pkg/data/ent/workflowcontract/where.go index 3349309a0..ca420800c 100644 --- a/app/controlplane/pkg/data/ent/workflowcontract/where.go +++ b/app/controlplane/pkg/data/ent/workflowcontract/where.go @@ -87,6 +87,11 @@ func ScopedResourceID(v uuid.UUID) predicate.WorkflowContract { return predicate.WorkflowContract(sql.FieldEQ(FieldScopedResourceID, v)) } +// Managed applies equality check predicate on the "managed" field. It's identical to ManagedEQ. +func Managed(v bool) predicate.WorkflowContract { + return predicate.WorkflowContract(sql.FieldEQ(FieldManaged, v)) +} + // NameEQ applies the EQ predicate on the "name" field. func NameEQ(v string) predicate.WorkflowContract { return predicate.WorkflowContract(sql.FieldEQ(FieldName, v)) @@ -447,6 +452,16 @@ func ScopedResourceIDNotNil() predicate.WorkflowContract { return predicate.WorkflowContract(sql.FieldNotNull(FieldScopedResourceID)) } +// ManagedEQ applies the EQ predicate on the "managed" field. +func ManagedEQ(v bool) predicate.WorkflowContract { + return predicate.WorkflowContract(sql.FieldEQ(FieldManaged, v)) +} + +// ManagedNEQ applies the NEQ predicate on the "managed" field. +func ManagedNEQ(v bool) predicate.WorkflowContract { + return predicate.WorkflowContract(sql.FieldNEQ(FieldManaged, v)) +} + // HasVersions applies the HasEdge predicate on the "versions" edge. func HasVersions() predicate.WorkflowContract { return predicate.WorkflowContract(func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/workflowcontract/workflowcontract.go b/app/controlplane/pkg/data/ent/workflowcontract/workflowcontract.go index 5ba08a3ae..cab6a30b1 100644 --- a/app/controlplane/pkg/data/ent/workflowcontract/workflowcontract.go +++ b/app/controlplane/pkg/data/ent/workflowcontract/workflowcontract.go @@ -31,6 +31,8 @@ const ( FieldScopedResourceType = "scoped_resource_type" // FieldScopedResourceID holds the string denoting the scoped_resource_id field in the database. FieldScopedResourceID = "scoped_resource_id" + // FieldManaged holds the string denoting the managed field in the database. + FieldManaged = "managed" // EdgeVersions holds the string denoting the versions edge name in mutations. EdgeVersions = "versions" // EdgeOrganization holds the string denoting the organization edge name in mutations. @@ -72,6 +74,7 @@ var Columns = []string{ FieldDescription, FieldScopedResourceType, FieldScopedResourceID, + FieldManaged, } // ForeignKeys holds the SQL foreign-keys that are owned by the "workflow_contracts" @@ -100,6 +103,8 @@ var ( DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. DefaultUpdatedAt func() time.Time + // DefaultManaged holds the default value on creation for the "managed" field. + DefaultManaged bool // DefaultID holds the default value on creation for the "id" field. DefaultID func() uuid.UUID ) @@ -157,6 +162,11 @@ func ByScopedResourceID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldScopedResourceID, opts...).ToFunc() } +// ByManaged orders the results by the managed field. +func ByManaged(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldManaged, opts...).ToFunc() +} + // ByVersionsCount orders the results by versions count. func ByVersionsCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/app/controlplane/pkg/data/ent/workflowcontract_create.go b/app/controlplane/pkg/data/ent/workflowcontract_create.go index 60bbe77ee..f1e143bf2 100644 --- a/app/controlplane/pkg/data/ent/workflowcontract_create.go +++ b/app/controlplane/pkg/data/ent/workflowcontract_create.go @@ -118,6 +118,20 @@ func (_c *WorkflowContractCreate) SetNillableScopedResourceID(v *uuid.UUID) *Wor return _c } +// SetManaged sets the "managed" field. +func (_c *WorkflowContractCreate) SetManaged(v bool) *WorkflowContractCreate { + _c.mutation.SetManaged(v) + return _c +} + +// SetNillableManaged sets the "managed" field if the given value is not nil. +func (_c *WorkflowContractCreate) SetNillableManaged(v *bool) *WorkflowContractCreate { + if v != nil { + _c.SetManaged(*v) + } + return _c +} + // SetID sets the "id" field. func (_c *WorkflowContractCreate) SetID(v uuid.UUID) *WorkflowContractCreate { _c.mutation.SetID(v) @@ -224,6 +238,10 @@ func (_c *WorkflowContractCreate) defaults() { v := workflowcontract.DefaultUpdatedAt() _c.mutation.SetUpdatedAt(v) } + if _, ok := _c.mutation.Managed(); !ok { + v := workflowcontract.DefaultManaged + _c.mutation.SetManaged(v) + } if _, ok := _c.mutation.ID(); !ok { v := workflowcontract.DefaultID() _c.mutation.SetID(v) @@ -246,6 +264,9 @@ func (_c *WorkflowContractCreate) check() error { return &ValidationError{Name: "scoped_resource_type", err: fmt.Errorf(`ent: validator failed for field "WorkflowContract.scoped_resource_type": %w`, err)} } } + if _, ok := _c.mutation.Managed(); !ok { + return &ValidationError{Name: "managed", err: errors.New(`ent: missing required field "WorkflowContract.managed"`)} + } return nil } @@ -310,6 +331,10 @@ func (_c *WorkflowContractCreate) createSpec() (*WorkflowContract, *sqlgraph.Cre _spec.SetField(workflowcontract.FieldScopedResourceID, field.TypeUUID, value) _node.ScopedResourceID = value } + if value, ok := _c.mutation.Managed(); ok { + _spec.SetField(workflowcontract.FieldManaged, field.TypeBool, value) + _node.Managed = value + } if nodes := _c.mutation.VersionsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -495,6 +520,18 @@ func (u *WorkflowContractUpsert) ClearScopedResourceID() *WorkflowContractUpsert return u } +// SetManaged sets the "managed" field. +func (u *WorkflowContractUpsert) SetManaged(v bool) *WorkflowContractUpsert { + u.Set(workflowcontract.FieldManaged, v) + return u +} + +// UpdateManaged sets the "managed" field to the value that was provided on create. +func (u *WorkflowContractUpsert) UpdateManaged() *WorkflowContractUpsert { + u.SetExcluded(workflowcontract.FieldManaged) + 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: // @@ -647,6 +684,20 @@ func (u *WorkflowContractUpsertOne) ClearScopedResourceID() *WorkflowContractUps }) } +// SetManaged sets the "managed" field. +func (u *WorkflowContractUpsertOne) SetManaged(v bool) *WorkflowContractUpsertOne { + return u.Update(func(s *WorkflowContractUpsert) { + s.SetManaged(v) + }) +} + +// UpdateManaged sets the "managed" field to the value that was provided on create. +func (u *WorkflowContractUpsertOne) UpdateManaged() *WorkflowContractUpsertOne { + return u.Update(func(s *WorkflowContractUpsert) { + s.UpdateManaged() + }) +} + // Exec executes the query. func (u *WorkflowContractUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -966,6 +1017,20 @@ func (u *WorkflowContractUpsertBulk) ClearScopedResourceID() *WorkflowContractUp }) } +// SetManaged sets the "managed" field. +func (u *WorkflowContractUpsertBulk) SetManaged(v bool) *WorkflowContractUpsertBulk { + return u.Update(func(s *WorkflowContractUpsert) { + s.SetManaged(v) + }) +} + +// UpdateManaged sets the "managed" field to the value that was provided on create. +func (u *WorkflowContractUpsertBulk) UpdateManaged() *WorkflowContractUpsertBulk { + return u.Update(func(s *WorkflowContractUpsert) { + s.UpdateManaged() + }) +} + // Exec executes the query. func (u *WorkflowContractUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/app/controlplane/pkg/data/ent/workflowcontract_update.go b/app/controlplane/pkg/data/ent/workflowcontract_update.go index 61e369803..eeb9e4fc3 100644 --- a/app/controlplane/pkg/data/ent/workflowcontract_update.go +++ b/app/controlplane/pkg/data/ent/workflowcontract_update.go @@ -128,6 +128,20 @@ func (_u *WorkflowContractUpdate) ClearScopedResourceID() *WorkflowContractUpdat return _u } +// SetManaged sets the "managed" field. +func (_u *WorkflowContractUpdate) SetManaged(v bool) *WorkflowContractUpdate { + _u.mutation.SetManaged(v) + return _u +} + +// SetNillableManaged sets the "managed" field if the given value is not nil. +func (_u *WorkflowContractUpdate) SetNillableManaged(v *bool) *WorkflowContractUpdate { + if v != nil { + _u.SetManaged(*v) + } + return _u +} + // AddVersionIDs adds the "versions" edge to the WorkflowContractVersion entity by IDs. func (_u *WorkflowContractUpdate) AddVersionIDs(ids ...uuid.UUID) *WorkflowContractUpdate { _u.mutation.AddVersionIDs(ids...) @@ -312,6 +326,9 @@ func (_u *WorkflowContractUpdate) sqlSave(ctx context.Context) (_node int, err e if _u.mutation.ScopedResourceIDCleared() { _spec.ClearField(workflowcontract.FieldScopedResourceID, field.TypeUUID) } + if value, ok := _u.mutation.Managed(); ok { + _spec.SetField(workflowcontract.FieldManaged, field.TypeBool, value) + } if _u.mutation.VersionsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -547,6 +564,20 @@ func (_u *WorkflowContractUpdateOne) ClearScopedResourceID() *WorkflowContractUp return _u } +// SetManaged sets the "managed" field. +func (_u *WorkflowContractUpdateOne) SetManaged(v bool) *WorkflowContractUpdateOne { + _u.mutation.SetManaged(v) + return _u +} + +// SetNillableManaged sets the "managed" field if the given value is not nil. +func (_u *WorkflowContractUpdateOne) SetNillableManaged(v *bool) *WorkflowContractUpdateOne { + if v != nil { + _u.SetManaged(*v) + } + return _u +} + // AddVersionIDs adds the "versions" edge to the WorkflowContractVersion entity by IDs. func (_u *WorkflowContractUpdateOne) AddVersionIDs(ids ...uuid.UUID) *WorkflowContractUpdateOne { _u.mutation.AddVersionIDs(ids...) @@ -761,6 +792,9 @@ func (_u *WorkflowContractUpdateOne) sqlSave(ctx context.Context) (_node *Workfl if _u.mutation.ScopedResourceIDCleared() { _spec.ClearField(workflowcontract.FieldScopedResourceID, field.TypeUUID) } + if value, ok := _u.mutation.Managed(); ok { + _spec.SetField(workflowcontract.FieldManaged, field.TypeBool, value) + } if _u.mutation.VersionsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/app/controlplane/pkg/data/workflowcontract.go b/app/controlplane/pkg/data/workflowcontract.go index 511350456..e9d23e0ce 100644 --- a/app/controlplane/pkg/data/workflowcontract.go +++ b/app/controlplane/pkg/data/workflowcontract.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. @@ -122,7 +122,8 @@ func (r *WorkflowContractRepo) Create(ctx context.Context, opts *biz.ContractCre func (r *WorkflowContractRepo) addCreateToTx(ctx context.Context, tx *ent.Tx, opts *biz.ContractCreateOpts) (*ent.WorkflowContract, *ent.WorkflowContractVersion, error) { contractQuery := tx.WorkflowContract.Create(). SetName(opts.Name).SetOrganizationID(opts.OrgID). - SetNillableDescription(opts.Description) + SetNillableDescription(opts.Description). + SetNillableManaged(opts.Managed) if opts.ProjectID != nil { contractQuery = contractQuery.SetScopedResourceID(*opts.ProjectID).SetScopedResourceType(biz.ContractScopeProject) @@ -441,6 +442,7 @@ func (r *WorkflowContractRepo) entContractToBizContract(ctx context.Context, w * LatestRevisionCreatedAt: toTimePtr(version.CreatedAt), WorkflowRefs: workflowReferences, Description: w.Description, + Managed: w.Managed, } if w.ScopedResourceID != uuid.Nil {