diff --git a/src/lib/projects/authzQueries.ts b/src/app/repositories/projects/authzRepository.ts similarity index 87% rename from src/lib/projects/authzQueries.ts rename to src/app/repositories/projects/authzRepository.ts index fea89557..418f6e55 100644 --- a/src/lib/projects/authzQueries.ts +++ b/src/app/repositories/projects/authzRepository.ts @@ -1,8 +1,17 @@ /** - * DB-backed fact loaders feeding the pure guards in `./authz` (issue #408). + * Ara Projects authorization repository (issue #408). * - * Kept separate from the guards so those stay unit-testable without a database. - * Route handlers load facts here, then decide with `authz`. + * The persistence boundary for authorization: it owns the Drizzle queries and + * returns domain-shaped facts (`ProjectAuthzFacts`, `WorkOrderAuthzFacts`) + * rather than raw rows, so the domain layer never sees a table shape. The + * decisions themselves live in `@/lib/projects/authz`, which imports no + * database client and unit-tests without a connection. + * + * const [user, project] = await Promise.all([ + * loadAuthzUser(userId), + * loadProjectAuthzFacts(projectId), + * ]); + * if (!canViewProject(user, project)) return forbidden(); * * Every loader is a single round trip; none probes per-row inside a list. */ @@ -20,7 +29,7 @@ import type { AuthzUser, ProjectAuthzFacts, WorkOrderAuthzFacts, -} from "./authz"; +} from "@/lib/projects/authz"; /** * The organisations a user belongs to, via `team_members → team → org_id`. diff --git a/src/lib/projects/authz.ts b/src/lib/projects/authz.ts index a65b0588..7fbd15c4 100644 --- a/src/lib/projects/authz.ts +++ b/src/lib/projects/authz.ts @@ -3,16 +3,14 @@ * Ara Projects route handler (issue #408). No route may re-implement any of * this inline. * - * This module is deliberately **pure**: it imports no database client, so the - * guards unit-test without mocking a connection pool. The DB-backed resolvers - * that produce its inputs live next door in `./authzQueries` — a route handler - * loads facts there, then decides here. + * This is the domain layer: deliberately **pure**, importing no database + * client, so the guards unit-test without mocking a connection pool. It owns + * the vocabulary (`ProjectRole`, `ProjectAuthzFacts`) and the decisions. * - * const [user, project] = await Promise.all([ - * loadAuthzUser(userId), - * loadProjectAuthzFacts(projectId), - * ]); - * if (!canViewProject(user, project)) return forbidden(); + * Persistence lives behind the repository at + * `@/app/repositories/projects/authzRepository`, which loads those facts. A + * route handler loads there, then decides here — and the dependency only ever + * points that way, so the domain never learns a table shape. * * Scoping note: Ara Projects are **organisation**-scoped. The existing * `team_portfolio_permissions` machinery is portfolio-scoped and plays no part