Skip to content

Commit a14b448

Browse files
committed
fix(projects): stage activation after compatible writers deploy
1 parent 0980ad1 commit a14b448

15 files changed

Lines changed: 489 additions & 422 deletions

File tree

‎apps/sim/ee/workspace-forking/lib/create-fork.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,10 @@ export async function createFork(params: CreateForkParams): Promise<CreateForkRe
314314
updatedAt: now,
315315
})
316316

317-
await tx
318-
.insert(projectWorkspace)
319-
.values({ projectId: parentProject.id, workspaceId: childWorkspaceId })
317+
if (parentProject)
318+
await tx
319+
.insert(projectWorkspace)
320+
.values({ projectId: parentProject.id, workspaceId: childWorkspaceId })
320321

321322
const sourcePermissions = await tx
322323
.select({ userId: permissions.userId, permissionType: permissions.permissionType })

‎apps/sim/lib/billing/organizations/membership.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import {
6161
revokePersonalApiKeysTx,
6262
revokeUserSessionsTx,
6363
} from '@/lib/organizations/members/revocation'
64-
import { tryLockProject } from '@/lib/projects/membership'
64+
import { lockProjectBackfillWrites, tryLockProject } from '@/lib/projects/membership'
6565
import { removeWorkspaceSkillMembershipsTx } from '@/lib/skills/access'
6666
import {
6767
reassignWorkflowOwnershipForWorkspaceMemberRemovalTx,
@@ -543,7 +543,7 @@ async function reassignOwnedOrganizationResourcesTx({
543543
organizationId,
544544
workspaceIds,
545545
}: {
546-
tx: DbOrTx
546+
tx: DbTransaction
547547
userId: string
548548
organizationId: string
549549
workspaceIds: string[]
@@ -557,6 +557,7 @@ async function reassignOwnedOrganizationResourcesTx({
557557
const ownerId = ownerMembership?.userId
558558
if (!ownerId || ownerId === userId) return 0
559559

560+
await lockProjectBackfillWrites(tx)
560561
const ownedProjects = await tx
561562
.select({ id: project.id })
562563
.from(project)

‎apps/sim/lib/core/config/env.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,8 @@ export const env = createEnv({
669669

670670
// SSO Configuration (for script-based registration)
671671
SSO_ENABLED: z.boolean().optional(), // Enable SSO functionality
672+
PROJECT_WRITES_ENABLED: z.boolean().optional(), // Activate new Project assignments after old writers drain
673+
PROJECT_API_ENABLED: z.boolean().optional(), // Expose Projects after backfill and contract enforcement
672674
SCIM_ENABLED: z.boolean().optional(), // Enable SCIM directory provisioning
673675
USAGE_MONITORING_ENABLED: z.boolean().optional(), // Enable organization usage monitoring on self-hosted (bypasses hosted requirements)
674676
SSO_PROVIDER_TYPE: z.enum(['oidc', 'saml']).optional(), // [REQUIRED] SSO provider type

‎apps/sim/lib/projects/README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
A Project groups environments. An environment is an existing `workspace` record; there is no separate environment table. Every newly created Project starts with an environment.
44

5+
Project APIs return HTTP 503 until the deployment enables `PROJECT_API_ENABLED`. Automatic assignment on legacy workspace creation requires `PROJECT_WRITES_ENABLED`; both server controls default off. The API requires writers to be enabled. Existing assigned Projects always retain their lifecycle protections, including fork inheritance and disconnect behavior, even if activation is disabled.
6+
57
## Choose the creation flow
68

79
| Caller | Flow | Result |
810
| --- | --- | --- |
911
| New Project onboarding | `POST /api/projects` | Creates a Project and its first environment together, with independently supplied names. |
10-
| Existing workspace creation UI or caller | `POST /api/workspaces` | Creates a workspace and automatically creates its Project, preserving the existing workspace response. |
11-
| Create another environment by forking | Existing workspace fork operation | Creates a child workspace in the source workspace's Project. |
12+
| Existing workspace creation UI or caller | `POST /api/workspaces` | Creates a workspace and, when Project writers are enabled, automatically creates its Project, preserving the existing workspace response. |
13+
| Create another environment by forking | Existing workspace fork operation | Inherits the source workspace's Project; unassigned legacy families remain unassigned until backfilled. |
1214

1315
Both POST endpoints are internal, session-authenticated APIs. `POST /api/projects` is not a public `/api/v2` endpoint and does not accept API-key principals. This foundation does not remove or deprecate existing workspace creation endpoints.
1416

@@ -67,10 +69,10 @@ Existing callers can continue to use `createWorkspaceContract` and `POST /api/wo
6769

6870
`skipDefaultWorkflow` remains optional and defaults to `false`. The route uses the session's active organization and existing workspace creation policy to resolve ownership and billing. It does not take the explicit Project scope or independent Project name used by `POST /api/projects`.
6971

70-
The workspace and its Project are still created atomically. The generated Project name is `Support workspace - Project`; long names are bounded to 100 characters while retaining the suffix. The response remains `{ "workspace": ... }` with HTTP 200, without a new Project response wrapper. Call `GET /api/projects/by-workspace/[workspaceId]` when an existing workspace caller needs its authorized Project details.
72+
When Project writers are enabled, the workspace and its Project are created atomically. The generated Project name is `Support workspace - Project`; long names are bounded to 100 characters while retaining the suffix. The response remains `{ "workspace": ... }` with HTTP 200, without a new Project response wrapper. Call `GET /api/projects/by-workspace/[workspaceId]` when an existing workspace caller needs its authorized Project details.
7173

7274
## Server implementation
7375

7476
The Project route calls `createProject` in `application/create-project.ts`. Existing workspace callers continue through their current creation paths. Both use the shared transaction primitive in `lib/workspaces/create.ts`; surface adapters must not independently commit Project and workspace creation.
7577

76-
Project descriptions and Project-scoped files are not part of this creation contract. Project-scoped files and a designated Project brief are follow-up work documented in the foundation plan.
78+
Project descriptions and Project-scoped files are not part of this creation contract. Project-scoped files and a designated Project brief are follow-up work.

0 commit comments

Comments
 (0)