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

JI
8h ago
+1387-105

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.