Skip to content

feat(contracts): convert a contract between organization and project level - #3794

Open
jiparis wants to merge 2 commits into
chainloop-dev:mainfrom
jiparis:jiparis/pfm-7886-contract-scope-conversion
Open

jiparis wants to merge 2 commits into
chainloop-dev:mainfrom
jiparis:jiparis/pfm-7886-contract-scope-conversion

Conversation

@jiparis

@jiparis jiparis commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Summary

Lets an organization admin convert an existing workflow contract between the organization level and a project level, in both directions. The contract keeps its ID, name, revisions, and workflows. This implements spec issue-3644.

  • API: the contract update request gets an optional level field, either a project reference or organization_scoped. When it is not set, the level does not change (R-001, R-011, R-009).
  • CLI: chainloop workflow contract update gets --project <name> and --org-level, which cannot be used together.
  • Rules: a conversion creates no revision (R-003, R-007). A request for the current level changes nothing, and a move from one project to a different project is rejected (R-006). A conversion to a project is rejected when workflows of other projects use the contract, and the error lists them (R-005).
  • Authorization: only organization owners and admins, and organization-level API tokens, can convert a contract (R-004).
  • Audit: a new WorkflowContractScopeChanged event records the old and the new level, as read under the contract lock (R-008, D-010).
  • Workflows: workflow update now rejects a contract of a different project, as workflow create already did. Workflow create also checks the existing default contract of the workflow (R-010). Conversions and workflow changes lock the contract, so they cannot race (D-008).

Spec changes in this PR: D-009 supersedes D-003 (the flag is --org-level, because a local --org hides the global organization flag), R-011 supersedes R-002 (the same requirement with the new flag), and D-010 is new.

Closes #3644

AI disclosure: this change was implemented with the assistance of Claude Code.

…level

Adds an optional level to the contract update API and the --project and --org
flags to 'chainloop workflow contract update', so organization admins can move a
contract between the organization and a project while it keeps its revisions
and workflows. Workflow updates now reject a contract of a different project,
and conversions and workflow changes lock the contract.

Implements spec issue-3644 (R-001 to R-010, D-008).

Closes chainloop-dev#3644

Assisted-by: Claude Code
Signed-off-by: Jose Ignacio Paris Prieto <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: bc6fbb4d-3bf8-4fd0-b81e-10aa03bf1e58
@jiparis
jiparis requested a review from a team November 6, 2026 11:58
@chainloop-platform

chainloop-platform Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

PR validation — ⚠️ 1 failing

Status Policy Material Messages
⚠️ Failed pr-min-approvals pr-info PR/MR #3794 has 0 approving reviews, 1 required.
✅ Passed pr-description-required pr-info -
✅ Passed pr-user-story-linked pr-info -

View attestation ↗

AI Session Checks — ⚠️ no AI session found

Missing AI Coding Sessions

This organization requires every PR to be backed by a Chainloop Trace AI coding session, and none was found for this one.

Please make sure the AI coding session evidence has been sent by the Chainloop CLI, or add the skip-ai-session label to this PR to bypass this check.

Learn more about Chainloop Trace.

Security Checks — ✅ 6 passing

✅ Reviewed this change against the project's security context (23 concerns hunted) — no introduced risk found.

View in Chainloop ↗ · How this works ↗

✅ secret-scan

Status Policy Messages
✅ Passed secrets-detection -

✅ sast-scan

Status Policy Messages
✅ Passed owasp-top10-2025 -
✅ Passed sast -
✅ Passed cwe-top25 -
✅ Passed cwe-top26-40-cusp -

✅ iac-scan

Status Policy Messages
✅ Passed iac-misconfiguration -
Scans not applied (2)
Scan Reason
vulnerability-scan no manifest/lockfile changed
github-actions-scan no workflow files changed

View attestation ↗


Powered by Chainloop and Chainloop Trace

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 23 files

This PR changes authentication, authorization, or input validation. Ultrareviews find 2.4x more serious bugs than standard reviews. Comment @cubic-dev-ai ultrareview to run one.

View guided diff | Re-trigger cubic

Comment thread app/cli/cmd/workflow_contract_update.go Resolved
// Converts the contract to a project-level contract of this project
IdentityReference project_reference = 6;
// Converts the contract to an organization-level contract. It must be true.
bool organization_scoped = 7 [(buf.validate.field).bool.const = true];

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

what if the value is false or absent, will it fail the validation?

- Rename the CLI flag --org to --org-level. The local --org hid the global
  --org flag that selects the organization. Add a test that no command
  flag hides a global flag.
- Take the old level of the audit event from the conversion, which reads
  it under the contract lock, and write the event only when the level
  changed.
- Add validation tests for the level field of the update request.
- Spec issue-3644: D-009 supersedes D-003, R-011 supersedes R-002, add D-010.

Refs chainloop-dev#3644

Assisted-by: Claude Code
Signed-off-by: Jose Ignacio Paris Prieto <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: bc6fbb4d-3bf8-4fd0-b81e-10aa03bf1e58

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 9 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/controlplane/pkg/biz/workflowcontract.go">

<violation number="1" location="app/controlplane/pkg/biz/workflowcontract.go:487">
P2: `NewScope` is read from a post-commit query, so a concurrent conversion can make this event describe the next operation instead of this one. Capture the post-conversion scope while the conversion transaction holds the lock and use that snapshot for the audit event.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

uc.auditorUC.Dispatch(ctx, &events.WorkflowContractScopeChanged{
WorkflowContractBase: eventPayload.WorkflowContractBase,
OldScope: contractScopeForEvent(c.PreviousScopedEntity),
NewScope: contractScopeForEvent(c.Contract.ScopedEntity),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: NewScope is read from a post-commit query, so a concurrent conversion can make this event describe the next operation instead of this one. Capture the post-conversion scope while the conversion transaction holds the lock and use that snapshot for the audit event.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At app/controlplane/pkg/biz/workflowcontract.go, line 487:

<comment>`NewScope` is read from a post-commit query, so a concurrent conversion can make this event describe the next operation instead of this one. Capture the post-conversion scope while the conversion transaction holds the lock and use that snapshot for the audit event.</comment>

<file context>
@@ -473,12 +478,13 @@ func (uc *WorkflowContractUseCase) Update(ctx context.Context, orgID, name strin
-			OldScope:             contractScopeForEvent(wfContractPreUpdate),
-			NewScope:             contractScopeForEvent(c.Contract),
+			OldScope:             contractScopeForEvent(c.PreviousScopedEntity),
+			NewScope:             contractScopeForEvent(c.Contract.ScopedEntity),
 		}, &orgUUID)
 	}
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(contracts): allow converting a contract between organization-level and project-level

1 participant