A workspace contains projects. Each project has a name, slug, and identifier (e.g., PROD). A workspace holds many projects (the project switcher lists them); the app scopes to one active project at a time at the UI level, and the non-unique workspaceId FK accommodates as many projects as a workspace needs — no per-workspace cap. Cross-project views that span projects at once (unified search / reporting) belong to the Search, reporting & admin epic (Epic 6).
Prerequisites: Story 1.2 (Workspaces) must be complete — project FKs against Workspace with onDelete: Cascade, the project RLS policies key off the same app.workspace_id session GUC that 1.2.3 established, and the active-project selection rides on the existing WorkspaceMembership row. Story 1.0.5 (Design system) must be complete before 1.3.3 (mockups) and 1.3.4 (UI) — those compose the canonical Button / Input / Card / Modal / Popover primitives (Popover shipped in 1.2.6).
project table: id (cuid), workspaceId (FK, onDelete: Cascade), name, slug (workspace-unique), identifier (3-5 chars uppercase, workspace-unique), lastWorkItemNumber (int default 0 — the per-project key counter), createdAt, updatedAt, archivedAt (nullable — soft-delete/archive, never a hard delete that would orphan work-item history in 1.4+).PROD-42). Auto-generated from the name (uppercased, alphanumeric, 3-5 chars) with a numeric collision suffix; the user can override at creation.project.lastWorkItemNumber via UPDATE … RETURNING inside the caller's transaction (NOT a Postgres SEQUENCE — sequences are per-DB-object, leak on rollback, and would need one per project). Story 1.4 calls this when inserting a work item.WorkspaceMembership.activeProjectId (nullable FK, onDelete: SetNull) — determines what the member sees on landing. Switching projects updates this, mirroring the workspace_id cookie pattern from 1.2.6.project: a row is visible/writable only when its workspaceId matches the active app.workspace_id GUC — the same structural gate 1.2.3 applied to workspace-scoped tables. Cross-workspace access is structurally impossible at the DB layer, not just the app layer.activeProjectId and re-renders.motir-core/CLAUDE.md): route/Server-Action → service → repository → Prisma; writes go through repo methods requiring tx; services own transactions + DTO mapping. All quality gates green; multi-tenant isolation proven by E2E + direct-DB RLS test (1.3.6).