feat(workspaces): fork + push/pull#5210
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview The outbox processor cron now also reaps stale fork background-work rows stuck in Manage Forks and Sync workspace modals in the sidebar expose fork creation (optional resource copy), mapping wizard with dependent-field reconfiguration, overwrite confirmation, rollback, and an Activity tab backed by Workflow copy/sync logic gains shared subblock remapping (variables, conditions, cross-workflow references with optional A small editor fix wraps tool-input params in Reviewed by Cursor Bugbot for commit d5b4bd5. Configure here. |
Greptile SummaryThis PR introduces workspace forking — the ability to create a child workspace from a parent and bi-directionally sync (push/pull) deployed workflows and mapped resources between them. It is a large, well-structured addition spanning new DB tables, advisory-locked promote/rollback transactions, a credential-access propagation layer, and a full set of UI components.
Confidence Score: 5/5Safe to merge — the promote and rollback paths are carefully locked and the only new finding is a performance gap in the mapping GET endpoint. The core transaction paths (promote, rollback, fork creation) are well-guarded with advisory locks, deterministic lock ordering, locked re-confirmation of undo points, and durable outbox side-effects. The sole new finding is that getForkMappingView reads deployed workflow states one at a time in a sequential loop with no workflow count cap, while the sibling diff and promote paths already use bounded-concurrency batching and throw at 1000 workflows. This is a latency concern on the GET /mapping endpoint for large workspaces, not a correctness issue. apps/sim/lib/workspaces/fork/mapping/mapping-service.ts — the getForkMappingView sequential-read loop (lines 94–107) should adopt the same bounded-concurrency and count-cap pattern used by loadSourceDeployedStates. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant ForkRoute as POST /fork
participant PromoteRoute as POST /fork/promote
participant RollbackRoute as POST /fork/rollback
participant Auth as assertCanPromote/Fork
participant DB as PostgreSQL (advisory locks)
participant Outbox as Deployment Outbox
Client->>ForkRoute: fork workspace
ForkRoute->>Auth: assertCanFork (admin + plan gate)
Auth-->>ForkRoute: source + policy
ForkRoute->>DB: tx(lock) — copy workflows, seed mappings
DB-->>ForkRoute: new workspace id
ForkRoute->>Outbox: schedule background content copy
ForkRoute-->>Client: 201 workspaceId
Client->>PromoteRoute: push/pull with dependentValues
PromoteRoute->>Auth: assertCanPromote (admin on both)
Auth->>DB: resolveForkEdge (2 queries)
PromoteRoute->>DB: loadSourceDeployedStates (pre-tx)
PromoteRoute->>DB: tx(acquireTargetLock + acquireEdgeLock)
DB->>DB: computePromotePlan
DB->>DB: copyWorkflowStateIntoTarget (each item)
DB->>DB: reconcileBlockPairs + upsertIdentityMappings
DB->>DB: propagateCredentialAccess
DB->>DB: upsertPromoteRun (snapshot for rollback)
DB-->>PromoteRoute: promoteRunId + counts
PromoteRoute->>Outbox: process undeploy events (post-commit)
PromoteRoute->>DB: performFullDeploy (per target, unlocked)
PromoteRoute-->>Client: updated/created/archived/redeployed
Client->>RollbackRoute: rollback (otherWorkspaceId)
RollbackRoute->>Auth: assertCanRollback (admin on target)
RollbackRoute->>DB: getLatestPromoteRunForTarget (pre-tx)
RollbackRoute->>DB: tx(acquireTargetLock + acquireEdgeLock)
DB->>DB: re-confirm run is still newest
DB->>DB: un-archive + reactivate / undeploy + archive
DB->>DB: deleteAllPromoteRunsForTarget
DB-->>RollbackRoute: outbox event ids
RollbackRoute->>Outbox: process side-effects (post-commit)
RollbackRoute-->>Client: restored/archived/unarchived/skipped
%%{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 Client
participant ForkRoute as POST /fork
participant PromoteRoute as POST /fork/promote
participant RollbackRoute as POST /fork/rollback
participant Auth as assertCanPromote/Fork
participant DB as PostgreSQL (advisory locks)
participant Outbox as Deployment Outbox
Client->>ForkRoute: fork workspace
ForkRoute->>Auth: assertCanFork (admin + plan gate)
Auth-->>ForkRoute: source + policy
ForkRoute->>DB: tx(lock) — copy workflows, seed mappings
DB-->>ForkRoute: new workspace id
ForkRoute->>Outbox: schedule background content copy
ForkRoute-->>Client: 201 workspaceId
Client->>PromoteRoute: push/pull with dependentValues
PromoteRoute->>Auth: assertCanPromote (admin on both)
Auth->>DB: resolveForkEdge (2 queries)
PromoteRoute->>DB: loadSourceDeployedStates (pre-tx)
PromoteRoute->>DB: tx(acquireTargetLock + acquireEdgeLock)
DB->>DB: computePromotePlan
DB->>DB: copyWorkflowStateIntoTarget (each item)
DB->>DB: reconcileBlockPairs + upsertIdentityMappings
DB->>DB: propagateCredentialAccess
DB->>DB: upsertPromoteRun (snapshot for rollback)
DB-->>PromoteRoute: promoteRunId + counts
PromoteRoute->>Outbox: process undeploy events (post-commit)
PromoteRoute->>DB: performFullDeploy (per target, unlocked)
PromoteRoute-->>Client: updated/created/archived/redeployed
Client->>RollbackRoute: rollback (otherWorkspaceId)
RollbackRoute->>Auth: assertCanRollback (admin on target)
RollbackRoute->>DB: getLatestPromoteRunForTarget (pre-tx)
RollbackRoute->>DB: tx(acquireTargetLock + acquireEdgeLock)
DB->>DB: re-confirm run is still newest
DB->>DB: un-archive + reactivate / undeploy + archive
DB->>DB: deleteAllPromoteRunsForTarget
DB-->>RollbackRoute: outbox event ids
RollbackRoute->>Outbox: process side-effects (post-commit)
RollbackRoute-->>Client: restored/archived/unarchived/skipped
Reviews (6): Last reviewed commit: "fix required + dependsOn combo" | Re-trigger Greptile |
|
@greptile |
|
bugbot run |
|
bugbot run |
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 304a7bb. Configure here.
|
bugbot run |
|
bugbot run |
…guard first-sync fallback, perf + cleanup)
|
bugbot run |
|
bugbot run |
|
bugbot run |
|
bugbot run |
|
bugbot run |
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 d5b4bd5. Configure here.
|
@greptile |
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