|
1 | | -# Project creation |
| 1 | +# Projects |
2 | 2 |
|
3 | 3 | 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. |
4 | 4 |
|
5 | 5 | Project APIs return HTTP 503 until the `projects` feature flag is on (AppConfig on hosted deployments; the `PROJECT_API_ENABLED` secret elsewhere). The flag defaults off and gates only the Project APIs: workspace creation always assigns a Project atomically, and assigned Projects keep their lifecycle protections (fork inheritance, disconnect) either way. |
6 | 6 |
|
| 7 | +## Storage and consistency |
| 8 | + |
| 9 | +Each workspace belongs to exactly one Project through the required `workspace.project_id` |
| 10 | +foreign key. One Project can contain many workspaces, including disconnected roots; there |
| 11 | +is no membership connector table. Referenced Projects cannot be deleted while their |
| 12 | +workspaces remain. `project.updated_at` is Project-record metadata, not a workspace |
| 13 | +activity counter or a concurrency version. |
| 14 | + |
| 15 | +Native composite foreign keys enforce two structural invariants: |
| 16 | + |
| 17 | +- Workspace and Project organizations agree, including personal (`NULL`) scope. Both |
| 18 | + tables have a stored generated `organization_scope_key`: `personal` for NULL and |
| 19 | + `organization:` followed by the organization ID otherwise. Writers cannot supply this |
| 20 | + key independently. A unique Project `(id, organization_scope_key)` constraint supports |
| 21 | + the workspace `(project_id, organization_scope_key)` foreign key. |
| 22 | +- A connected fork and its parent share a Project. A unique workspace `(id, project_id)` |
| 23 | + constraint supports `(forked_from_workspace_id, project_id)` referencing that pair. |
| 24 | + The parent-ID foreign key uses `ON DELETE SET NULL`, clearing only the parent pointer. |
| 25 | + The composite foreign key uses `NO ACTION`; Project membership never cascades automatically. |
| 26 | + |
| 27 | +Both composite foreign keys are `DEFERRABLE INITIALLY DEFERRED`, allowing atomic |
| 28 | +organization transfers and subtree moves before validation. Drizzle records the columns, |
| 29 | +indexes, and relationships; the shared schema finalizer sets deferred timing after a fresh |
| 30 | +schema push because Drizzle does not represent that timing. |
| 31 | + |
| 32 | +Application transactions own creation with the first workspace, nonempty Projects, |
| 33 | +archive lifecycle, workflow admission/restoration, and subtree disconnection. Their |
| 34 | +Project, lineage, edge, and workspace locks protect these business operations. PostgreSQL |
| 35 | +owns referential-integrity concurrency through the native constraints; no custom Project |
| 36 | +integrity triggers or artificial Project row touches are required. |
| 37 | + |
7 | 38 | ## Choose the creation flow |
8 | 39 |
|
9 | 40 | | Caller | Flow | Result | |
10 | 41 | | --- | --- | --- | |
11 | 42 | | New Project onboarding | `POST /api/projects` | Creates a Project and its first environment together, with independently supplied names. | |
12 | 43 | | Existing workspace creation UI or caller | `POST /api/workspaces` | Creates a workspace and 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. | |
| 44 | +| Create another environment by forking | Existing workspace fork operation | Inherits the source workspace's Project. | |
14 | 45 |
|
15 | 46 | 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. Existing workspace creation endpoints remain supported. |
16 | 47 |
|
|
0 commit comments