6.10.4 Org-scoped services + access gating — org membership gates workspace access; org owner/admin extends 6.4 roles
Estimate: 70m · Depends on: 6.10.3
Build the org service layer + the ACCESS GATING that makes the org tier real: org membership gates workspace access, and the org owner/admin role extends (sits above) the 6.4 workspace role model. This is the business logic the org admin API/UI (6.10.5) and the platform stories later sit on.
organizationsService (business logic + transactions — 4-layer):
- Org CRUD (create/rename/update an org), membership management (invite/add a user to the org with an
OrganizationRole, remove, change role), and "list members ACROSS the org’s workspaces" (the cross-workspace roster — PAGINATED, never load-all, the at-scale rule). Each write-flow is ONEprisma.$transaction; returns DTOs vialib/mappers/*(never raw Prisma); throws typed errors fromlib/organizations/errors.tsthe route maps to HTTP. - Membership DIRECTION (6.10.2 §5, asymmetric — enforce in the service, NOT scattered): adding a user to the ORG creates ONLY an
OrganizationMembership— NO workspace membership (org-only members in zero workspaces are valid; a plain org member reaches only workspaces they’re explicitly added to). Conversely the add-to-WORKSPACE flow (extending the existingWorkspaceMembershipcreate) MUST auto-create the user’sOrganizationMembership(rolemember) in that workspace’s org if absent — the UPWARD invariant (you cannot be in a workspace without being in its org), in the SAME transaction. Removing from the org cascades loss of workspace access (gate); removing from a workspace leaves the org membership intact. - Resolving the ACTIVE org for a session (for the switcher) + listing the orgs a user belongs to.
- Auto-provision on signup (the progressive-disclosure principle, 6.10.2 §6). A
provisionForNewUser-style flow that creates an org + a default workspace + the owner memberships for a brand-new account, in ONE transaction, wired into the existing signup/onboarding path — so every account is an org of one (OPC) from day one and there is never a tier-less user. Mirror the shape of the 6.10.3 backfill (which does the same for pre-existing workspaces); the org name defaults from the user/company and is renameable. - (Make the create-workspace path org-aware so a new workspace is created under the active org with the creator as a member. The copy-on-create deep-copy of the source workspace’s config — the "looks-inherited" behaviour, 6.10.2 §6e — is its OWN subtask 6.10.9, which extends this flow; do not inline it here.)
The access gate (the load-bearing change). Extend the existing workspace access check so that reaching a workspace requires the session user to be a member of the workspace’s ORG (org membership gates workspace access — 6.10.2). Compose the roles per 6.10.2’s precedence: an org OWNER/ADMIN is granted admin-equivalent access to EVERY workspace under the org (extending the 6.4 MemberRole); an org MEMBER falls back to their per-workspace MemberRole. Preserve the 404-not-403 cross-tenant posture (a non-member of the org sees the workspace as not-found, not forbidden — the standing guard).
Where it threads. The gate is a single authorization helper the existing workspace-scoped services/route guards call (do NOT scatter org checks across N routes); the 6.4 permission helper is extended, not duplicated. Reads that guard a write take tx + SELECT FOR UPDATE where a concurrent membership change could race (the lock-before-read-derived-update rule).
Acceptance criteria
organizationsServiceowns org CRUD + membership management + the cross-workspace member listing (paginated), each write-flow in ONE transaction, returning DTOs (never raw Prisma), throwing typed errors; routes (added in 6.10.5) call exactly one service method.- The access gate denies a user who is a member of a workspace but NOT of its org (org membership gates workspace access), and a non-org-member gets 404-not-403 cross-tenant.
- An org owner/admin is granted admin-equivalent access to every workspace under the org (the role composes ABOVE the 6.4
MemberRole); an org member falls back to their per-workspace role. - The gate is a single shared helper (not scattered per route); the 6.4 permission check is extended, not duplicated.
- 4-layer respected throughout (service owns transactions; repos are single-op with required
txon writes).
Context refs
- 6.10.2 — the gating + role-precedence decision this implements.
- 6.10.3 — the
organizationRepository/organizationMembershipRepository+ the schema this orchestrates. motir-core/lib/services/— the workspace + membership services + the 6.4 permission/role helper the gate EXTENDS (mirror its shape).motir-core/CLAUDE.md§ 4-layer + the lock-before-read-derived-update rule + the 404-not-403 cross-tenant guard.