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-352

6.4.3 Service enforcement — `projectAccess` browse/edit gate threaded into project/board/issue reads

Done
Description

Estimate: 34m · Depends on: 6.4.2

The permission policy + its enforcement, at the service layer (the retrofit of the deferred TODO(6.4) seams). A projectAccessService (or a guard in projectsService) computes, for a (user, project): canBrowseopen/limited → any workspace member; private → a ProjectMembership exists (or the user is workspace owner/admin, who always pass); canEditlimited → false for non-members (view+comment only); else gated by project role (viewer → false, member/admin → true). Thread canBrowse into the existing project read, the board projection (getBoard), and the issue list/detail reads so a non-member gets a typed ProjectAccessDeniedError (→ 403/404 at the route, the no-access state in the UI); thread canEdit into the write paths (create/move/assign/update) so a viewer’s writes are rejected. Workspace owner/admin bypass throughout.

Out of scope: the management API (6.4.4) + UI (6.4.5/6.4.6). This subtask is the policy + wiring it into reads/writes that already exist.

Acceptance criteria

  • A single canBrowse(project, ctx) + canEdit(project, ctx) policy (open/limited/private × role), with workspace owner/admin always passing; a typed ProjectAccessDeniedError mapped to 403 (existing project) / 404 (hidden).
  • canBrowse gates the project read + getBoard + the issue list/detail reads; canEdit gates the issue/board write paths (create/move/assign/update); no read or write path bypasses the gate.
  • Vitest (real Postgres) covers each access level × role for both browse + edit, and the owner/admin bypass.

Context refs

  • lib/services/projectsService.ts, boardsService.getBoard, workItemsService reads/writes — the seams to gate (search the TODO(6.4) notes, e.g. workflowsService 2.2.5, boardsService 3.3.3)
  • finding #26 (explicit app-layer workspace gate — this adds the project tier beneath it); motir-core/CLAUDE.md