The heart of the security model: one algorithm, implemented once, for API,
collab, and UI (permissions.md — authoritative).
- shared `permissions/`: pure resolution (`resolvePageCapability`) exactly per
permissions.md — specificity page > label (incl. ancestor labels) > pond,
deny wins within a level, default-closed, Site Admin bypass — plus the trash
rule (`canAccessPage` / `canAccessTrashedPage`, ADR 0013). `grantValidationError`
enforces the structural constraints. Documented, I/O-free signatures for
API/collab reuse.
- prisma: `RoleGrant` (+ grant enums) per data-model.md, unique on
(pond, subject, role, scope); migration adds a CHECK backstop that a
POND_ADMIN grant is pond-scope + user-subject.
- api `grants/`: `GrantsService.createGrant` validates before insert (structural
+ no extra admin on a personal pond), rejects duplicates; `grantsForPond`
returns the shared resolver model (what #52/#53 consume); enum mappers between
the DB and the shared model. Interim "who may manage grants" stays until #52.
- tests: exhaustive table-driven resolver suite — every worked example from
permissions.md §Resolution, edge cases (multi-label deny-wins, ancestor
inheritance, anonymous/public, most-specific-allow-beats-less-specific-deny,
trash) and a property test (a less-specific grant never overrides a
more-specific decision); validation unit tests; grants db test proving
write-time rejection of invalid grants.
- i18n: grant error codes (de + en).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY