Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions app/controlplane/api/controlplane/v1/response_messages.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/controlplane/api/controlplane/v1/response_messages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/controlplane/internal/service/workflowcontract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
14 changes: 14 additions & 0 deletions app/controlplane/pkg/biz/workflowcontract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Add tests for the new managed-contract guards in Update and Delete. These are new authorization/validation branches and currently have no matching test coverage.

(Based on your team's feedback about adding or updating tests for new paths.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/controlplane/pkg/biz/workflowcontract.go, line 416:

<comment>Add tests for the new managed-contract guards in Update and Delete. These are new authorization/validation branches and currently have no matching test coverage.

(Based on your team's feedback about adding or updating tests for new paths.) </comment>

<file context>
@@ -408,6 +412,11 @@ func (uc *WorkflowContractUseCase) Update(ctx context.Context, orgID, name strin
 	}
 
+	// 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")
+	}
</file context>
Fix with Cubic

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 {
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Modify "workflow_contracts" table
ALTER TABLE "workflow_contracts" ADD COLUMN "managed" boolean NOT NULL DEFAULT false;
3 changes: 2 additions & 1 deletion app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down Expand Up @@ -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=
5 changes: 3 additions & 2 deletions app/controlplane/pkg/data/ent/migrate/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
},
Expand All @@ -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",
},
Expand Down
56 changes: 55 additions & 1 deletion app/controlplane/pkg/data/ent/mutation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions app/controlplane/pkg/data/ent/runtime.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion app/controlplane/pkg/data/ent/schema/workflowcontract.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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),
}
}

Expand Down
Loading