MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-38

1.3.2 Project RLS policies + active-project resolution in workspace context

Done
Description

Estimate: 16m · Depends on: 1.3.1

The structural multi-tenant gate for projects, plus the read path that resolves a member's active project. (1) Postgres RLS on the project table, keyed off the existing app.workspace_id session GUC that 1.2.3 established — a project row is visible/writable only when project.workspace_id = current_setting('app.workspace_id', true). Queries without the GUC see no rows (read AND write). This reuses the withWorkspaceContext machinery from 1.2.3 unchanged; projects just gain a policy. (2) Active-project resolution: a server helper getActiveProject() (analogue of getWorkspaceContext()) that reads the member's activeProjectId (or falls back to the workspace's first project, or null if the workspace has none) inside a workspace-scoped transaction.

Why RLS here too, not just app-layer filtering: defense-in-depth, identical to the rationale in 1.2.3. The service always filters by workspaceId, but RLS catches any future endpoint that forgets to. The Story-level "structurally impossible" claim requires the DB-layer gate, validated by 1.3.6's direct-DB test.

What you'll do: Add a migration (add_project_rls) enabling RLS + the workspace-match policy on project, granting the prodect_app role (from 1.2.3) the usual CRUD. Add projectsService.getActiveProject(userId, workspaceId) returning a DTO or null, reading via withWorkspaceContext. Wire it into lib/workspaces/index.ts or a sibling lib/projects/index.ts export so server components can read it the same way they read the workspace context. Note finding #5 (dev/CI connects as a BYPASSRLS superuser) — the RLS test in 1.3.6 must SET LOCAL ROLE prodect_app to make the policy bite; do NOT copy the direct-Prisma pattern from lib/workspaces/middleware.ts (finding #5/#7).

Acceptance criteria

  • Migration add_project_rls enables RLS on project and creates a policy matching workspace_id against current_setting('app.workspace_id', true); grants prodect_app CRUD.
  • projectsService.getActiveProject(userId, workspaceId) resolves the member's activeProjectId, falling back to the workspace's first project (createdAt asc) or null; runs inside withWorkspaceContext; returns a DTO.
  • A server-side getActiveProject() helper reads session + active workspace + active project, mirroring getWorkspaceContext()'s shape.
  • Existing 11 RLS tests stay green; no behavior change to workspace RLS. 4-layer rule respected. All quality gates green.

Context refs

  • prisma/migrations/…add_workspace_rls — the exact RLS migration pattern to mirror
  • lib/workspaces/context.ts (withWorkspaceContext) + lib/workspaces/index.ts (getWorkspaceContext) — the resolver shape to analogize
  • tests/workspace-rls.test.ts — the SET LOCAL ROLE prodect_app test harness
  • PRODECT_FINDINGS.md #5/#7 — RLS-inert-under-superuser + the middleware direct-Prisma anti-pattern to NOT copy
Status
Done
Type
Sub-task