feat(workspaces): fork + push/pull#5210
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Sync (push/pull) along the direct parent–child edge lets admins map credentials, env vars, and other resources, preview workflow create/update/archive changes (including drift), then promote deployed state into the target—with optional force on drift and rollback of the last promote. New APIs cover fork, lineage, resources, mapping, diff, promote, and rollback; the sidebar exposes fork, sync, and manage-forks modals when forking is available. Workflow copy/promote logic is centralized with deterministic block IDs, resource remapping, and stricter cross-workspace handling (unmapped workflow references cleared). Reviewed by Cursor Bugbot for commit 2739547. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2739547. Configure here.
Greptile SummaryThis PR adds workspace forking — creating a child workspace that inherits the parent's deployed workflows — along with push/pull promote and one-level rollback. The implementation covers DB schema, API routes, a credential-propagation path, deterministic block-ID remapping for stable webhook URLs, and background copy of heavy content (table rows, KB embeddings) via Trigger.dev.
Confidence Score: 3/5The core fork/promote/rollback logic is well-structured and the auth boundaries are correct, but the mapping save path can issue thousands of individual DB round-trips in a single transaction — a real timeout risk at the contract's allowed input size. Two findings affect the mapping update path in a compounding way: applyForkMappingEntries calls upsertEdgeMappings and deleteEdgeMappingByChildResource once per entry (up to 5,000), and the delete queries scan without a child_resource_id index. Either alone would be a latency concern; together they can exhaust the transaction timeout or the connection pool under realistic load. Additionally, workflow-type entries bypass target validation in validateForkMappingTargets, allowing arbitrary workflow identity rows to be inserted through the mapping editor. These issues are all confined to the mapping-save code path; the promote, rollback, and fork-creation flows read cleanly and the schema migration is careful. apps/sim/lib/workspaces/fork/mapping/mapping-service.ts and apps/sim/lib/workspaces/fork/mapping/mapping-store.ts for the serial-query pattern; packages/db/migrations/0250_workspace_forking.sql for the missing child_resource_id index; apps/sim/lib/workspaces/fork/copy/copy-resources.ts for the connectorId on KB copy. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant UI as Browser UI
participant API as API Route
participant Auth as authz.ts
participant Plan as promote-plan.ts
participant DB as PostgreSQL
UI->>API: POST /fork (create fork)
API->>Auth: assertCanFork(sourceId, userId)
Auth->>DB: checkWorkspaceAccess + plan policy
API->>DB: tx: insert workspace, permissions, copy workflows/resources, seedMappings
API-->>UI: "{ workspace, workflowsCopied }"
UI->>API: "GET /fork/diff?direction=push"
API->>Auth: assertCanPromote(id, otherId, direction, userId)
API->>Plan: computeForkPromotePlan(edge, source, target)
Plan->>DB: getEdgeMappingRows, listDeployedWorkflows, readDeployedState
API-->>UI: "{ willUpdate, willCreate, willArchive, unmappedRequired, drift }"
UI->>API: PUT /fork/mapping (save credential/env mappings)
API->>Auth: assertCanPromote
API->>DB: validateForkMappingTargets, tx: applyForkMappingEntries
UI->>API: POST /fork/promote
API->>Auth: assertCanPromote
API->>DB: tx: acquireEdgeLock, computePlan, copyWorkflows, upsertMappings, upsertPromoteRun
API->>DB: performFullDeploy (outside tx, per workflow)
API-->>UI: "{ promoteRunId, updated, created, archived, redeployed }"
UI->>API: POST /fork/rollback
API->>Auth: assertCanRollback (admin on target)
API->>DB: resolveForkEdge, getPromoteRunForRollback
API->>DB: performActivateVersion (prior versions)
API->>DB: tx: acquireEdgeLock, undeploy/archive created, deleteIdentityRows, deletePromoteRun
API-->>UI: "{ restored, archived, unarchived }"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant UI as Browser UI
participant API as API Route
participant Auth as authz.ts
participant Plan as promote-plan.ts
participant DB as PostgreSQL
UI->>API: POST /fork (create fork)
API->>Auth: assertCanFork(sourceId, userId)
Auth->>DB: checkWorkspaceAccess + plan policy
API->>DB: tx: insert workspace, permissions, copy workflows/resources, seedMappings
API-->>UI: "{ workspace, workflowsCopied }"
UI->>API: "GET /fork/diff?direction=push"
API->>Auth: assertCanPromote(id, otherId, direction, userId)
API->>Plan: computeForkPromotePlan(edge, source, target)
Plan->>DB: getEdgeMappingRows, listDeployedWorkflows, readDeployedState
API-->>UI: "{ willUpdate, willCreate, willArchive, unmappedRequired, drift }"
UI->>API: PUT /fork/mapping (save credential/env mappings)
API->>Auth: assertCanPromote
API->>DB: validateForkMappingTargets, tx: applyForkMappingEntries
UI->>API: POST /fork/promote
API->>Auth: assertCanPromote
API->>DB: tx: acquireEdgeLock, computePlan, copyWorkflows, upsertMappings, upsertPromoteRun
API->>DB: performFullDeploy (outside tx, per workflow)
API-->>UI: "{ promoteRunId, updated, created, archived, redeployed }"
UI->>API: POST /fork/rollback
API->>Auth: assertCanRollback (admin on target)
API->>DB: resolveForkEdge, getPromoteRunForRollback
API->>DB: performActivateVersion (prior versions)
API->>DB: tx: acquireEdgeLock, undeploy/archive created, deleteIdentityRows, deletePromoteRun
API-->>UI: "{ restored, archived, unarchived }"
Reviews (1): Last reviewed commit: "make rollback part of the footer" | Re-trigger Greptile |
|
@greptile |
|
bugbot run |

Summary
Be able to Fork Workspaces. And push/pull changes into or from parent workspaces. Allows one click promotion between environments and supports mapping credentials, secrets, and resources.
Type of Change
Testing
Tested manually
Checklist