6.10.3 Schema — `Organization` + `OrganizationMembership` + `Workspace.organizationId` + migration + backfill
Estimate: 65m · Depends on: 6.10.2
Implement the schema for the org tier decided in 6.10.2, with a migration that BACKFILLS every existing workspace into a default org so no legacy data is orphaned. This is the data foundation the gating (6.10.4), the UI (6.10.5), and the seed (6.10.6) build on.
Schema (prisma/schema.prisma):
Organization— the root tenant:{ id, name, slug, createdAt, updatedAt, ... }withslugunique. The NEW top tier (do NOT touch Better-Auth’sAccount).OrganizationMembership— the membership join (mirrorsWorkspaceMembership):{ id, organizationId, userId, role (OrganizationRole owner|admin|member), createdAt }, unique on(organizationId, userId). Modelled as@relationto bothOrganizationandUser(back-relations on each).enum OrganizationRole—owner | admin | member(the org-scoped role from 6.10.2, distinct from the 6.4MemberRole).Workspace.organizationId— a NON-nullable FK toOrganization(after backfill), modelled as a Prisma@relation(Workspace.organization↔Organization.workspaces) with the matchingonDelete/onUpdate— NEVER a raw-SQL-only FK left as a bare scalar (the CLAUDE.md FK-as-@relationrule — a split would put the schema graph + migrate DB in permanent drift).
The migration + backfill (the load-bearing part). A single migration that: (1) creates Organization + OrganizationMembership + the enum; (2) adds Workspace.organizationId NULLABLE first; (3) BACKFILLS — for EACH existing workspace, create one default Organization (named/slugged from the workspace), point the workspace at it, and create an OrganizationMembership(owner) for the workspace’s owner/first-admin (so legacy data has an org owner); (4) makes Workspace.organizationId NON-nullable once every row is set. The backfill is idempotent / re-runnable-safe. Because the project uses prisma migrate + the shared dev DB, hand-author the data-backfill SQL in the migration (mirror the prodect-shared-db migrate pattern) so it is deterministic.
Repositories (single-op each — 4-layer). organizationRepository (find / create / update by id+slug) and organizationMembershipRepository (find-by-org+user, create [tx], list-by-org, delete [tx], update-role [tx]) — writes REQUIRE tx per CLAUDE.md. NO business logic here (that is 6.10.4’s service).
Acceptance criteria
prisma/schema.prismagainsOrganization,OrganizationMembership,enum OrganizationRole, andWorkspace.organizationId— every FK modelled as a Prisma@relationon BOTH sides (no raw-SQL-only FK);prisma migrate devreports "No difference detected" after the migration (no spurious DROP CONSTRAINT — the FK-drift rule).- The migration backfills exactly ONE default org per pre-existing workspace, points each workspace at it, and creates an owner
OrganizationMembershipfor each; after it, NO workspace has a nullorganizationIdand the column is NON-nullable. - The backfill is idempotent (re-running it creates no duplicate orgs/memberships).
organizationRepository+organizationMembershipRepositoryexist as single-op repos; write methods requiretx; Better-Auth’sAccountmodel is untouched.
Context refs
- 6.10.2 — the model + backfill decision this implements.
motir-core/prisma/schema.prisma—Workspace,WorkspaceMembership,MemberRole,User, and the existingAccount(Better-Auth — do NOT reuse); the patternsOrganizationMembershipmirrors.motir-core/lib/repositories/workspaceMembershipRepository.ts— the single-op + required-txrepo pattern to mirror.motir-core/CLAUDE.md§ FK-as-@relation(the FK-drift rule) + § 4-layer (repository layer).