feat(api): complete the v2 workflows resource with versions and CRUD - #6184
Conversation
Adds version listing/detail plus create, update, and delete to the v2 workflows surface, which previously covered only execution and deployment. - GET /api/v2/workflows/[id]/versions — cursor-paginated, newest first - GET /api/v2/workflows/[id]/versions/[version] — version + pinned state - POST /api/v2/workflows, PATCH and DELETE /api/v2/workflows/[id] All six delegate to the existing orchestration and persistence helpers; no new domain logic.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview New routes: Handlers are thin wrappers around existing Auth / errors: Rate-limit labels Reviewed by Cursor Bugbot for commit a155729. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThe PR completes the public v2 workflow-management surface and adds deployment-version inspection.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the prior folder-state disclosure is prevented by checking workspace containment before lock state, and malformed decoded version cursors are now rejected.
|
| Filename | Overview |
|---|---|
| apps/sim/app/api/v2/workflows/route.ts | Adds workflow creation with write authorization, containment-before-lock validation, orchestration delegation, and public response projection. |
| apps/sim/app/api/v2/workflows/[id]/route.ts | Adds metadata update and archive handlers with masked authorization failures and mutability enforcement. |
| apps/sim/app/api/v2/workflows/[id]/versions/route.ts | Adds bounded keyset pagination for deployment versions and rejects decoded cursors without a positive integer version. |
| apps/sim/app/api/v2/workflows/[id]/versions/[version]/route.ts | Adds authorized retrieval of a deployment version and its pinned workflow state. |
| apps/sim/lib/workflows/persistence/utils.ts | Extends deployment-version persistence reads with bounded version-keyset pagination. |
| apps/sim/lib/api/contracts/v2/workflows.ts | Defines request and response contracts for workflow CRUD and version resources. |
| apps/docs/openapi-v2-workflows.json | Documents the expanded v2 workflow CRUD and deployment-version API surface. |
Sequence Diagram
sequenceDiagram
participant Client
participant Route as v2 Workflow Route
participant Auth as Access Control
participant Domain as Workflow Orchestration
participant DB as Persistence
Client->>Route: CRUD or version request
Route->>Auth: Rate limit, feature gate, workspace access
Auth-->>Route: Authorized
alt Create or move into folder
Route->>Auth: Verify folder containment
Route->>Auth: Verify workflow/folder mutability
end
alt Workflow mutation
Route->>Domain: Create, update, or archive
Domain->>DB: Validate and persist
DB-->>Domain: Workflow result
Domain-->>Route: Public workflow projection
else Version inspection
Route->>DB: List versions or fetch pinned state
DB-->>Route: Deployment-version data
end
Route-->>Client: v2 response envelope
Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/imp..." | Re-trigger Greptile
…d version cursors assertFolderMutable walks a folder's ancestor chain without filtering on workspace, so inspecting it before containment let a caller tell a locked folder in someone else's workspace (423) from a nonexistent one (400). Create and update now assert containment first, matching the ordering import-workflow.ts already uses. A version cursor that decodes to JSON without a numeric version filtered every row out and returned an empty page with nextCursor null, which reads as a clean end-of-list. Malformed cursors are now a 400.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c86d2fc. Configure here.
listWorkflowVersions read every version row and the route filtered and sliced the result in memory, so the response was bounded but the query was not. It now takes optional limit/afterVersion, turning the cursor into a real keyset query; the route asks for limit + 1 and only trims the has-more probe. Both params are optional, so the internal, v1 admin, and copilot callers are unchanged. Also restores the untouched GET handler in [id]/route.ts to its original formatting — collapsing its signature had re-indented the whole body and buried the actual additions in whitespace churn.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a745600. Configure here.
…eat/v2-workflow-versions # Conflicts: # apps/sim/app/api/v2/workflows/route.test.ts # apps/sim/app/api/v2/workflows/route.ts # apps/sim/lib/api/contracts/v2/workflows.ts
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a155729. Configure here.
Summary
rollbackexisted with no way to enumerate what to roll back toGET /api/v2/workflows/[id]/versions(cursor-paginated, newest first) andGET .../versions/[version](version + the workflow state it pins)POST /api/v2/workflows,PATCH /api/v2/workflows/[id],DELETE /api/v2/workflows/[id]performCreateWorkflow/performUpdateWorkflow/performDeleteWorkflowandlistWorkflowVersions/getWorkflowDeploymentVersion. No new domain logicNotes on three judgment calls:
workflow_deployment_version— one row per deploy, forever. The cursor keys on the version number.listWorkflowVersionsitself is still unpaginated, so the route bounds the response, not the DB read; the rows carry nostateblob so they're small, but that helper wants alimitargument eventuallyassertWorkflowMutableis called in the routes.performUpdateWorkflow/performDeleteWorkflowdon't assert internally (unlike the deploy family), so this matches what the internalPUT/DELETEhandlers already do. Locked workflow returns 423[id]routes are masked as 404, matching the existingGET— the caller never names a workspace, so a 403 would confirm the workflow exists somewhere they can't reach. Create returns a real 403 since the caller supplied theworkspaceIddeployments/[version]/revertis deliberately not ported: it overwrites the editor draft, which is an editor concern whose public analogue is export/import.rollbackalready covers version activationPath is named
versions, notdeployments, to match the existingrollbackverb. Public projection drops the rawcreatedByuser id —deployedByalready carries the name.sortOrder/locked/forkSyncExcludedstay off the public surface.Type of Change
Testing
route.test.tsfiles, 69 tests passing acrossapp/api/v2/workflows+lib/workflows/orchestration— gate-off, invalid body, access denied, rate limited, happy path per route, plus locked-workflow 423 on PATCH and DELETE, 409 name collision, and last-workflow-in-workspace 400bun run check:api-validation:strict,check:openapi,type-check,lint:checkall passBASELINE.totalRoutes/zodRoutesbumped 1046 → 1048 for the two newroute.tsfilesChecklist