# Permission model — "the most specific setting wins" This document defines the authoritative semantics of roles and grants. The resolution algorithm is implemented **once** in `packages/shared/src/permissions/` and used by the API (guards), the collab server (token issuance), and the frontend (UI affordances only — the client never enforces security). ## Roles | Role | Scope of existence | Capabilities | | --- | --- | --- | | **Site Admin** | instance (user flag) | everything: instance settings, users, quotas, plugins (install + set `disabled`/`optional`/`required`), all ponds' administration, legal pages, backups | | **Pond Admin** | per pond (grant) | manage the pond: settings/fonts, labels, members and their grants, enable/disable optional plugins, trash, quotas view; implies Editor everywhere in the pond | | **Editor** | per pond/label/page (grant) | create/edit/delete pages within the granted scope; view history, restore versions, use trash for pages they may edit | | **Reader** | per pond/label/page (grant) | read pages within the granted scope; no history access | | **Public** | pseudo-subject | what non-authenticated visitors may read (never write) | Additional structural rules: - Personal ponds (self-signup) have exactly **one** Pond Admin — the owner; additional `pond_admin` grants are rejected there. Shared ponds may have several. - `pond_admin` grants exist only at pond scope. Editor/Reader grants exist at pond, label, or page scope. - Site Admin bypasses resolution entirely. ## Grants A grant is `(subject, role, scope, effect)` inside one pond (table `role_grants`, see `data-model.md`): - **subject**: a specific user, `authenticated` (any logged-in user), or `public` (everyone, including anonymous visitors). - **scope**: the whole pond, one label, or one page. - **effect**: `allow` or `deny`. `deny` expresses the vision's "all pages *except* label X" (pond-scope allow + label-scope deny). ## Resolution algorithm Question: *may user U perform action A (read / write) on page P?* 1. If U is Site Admin → **allow**. 2. If the page or its pond is in trash → only roles that could edit it may see it in trash views; regular access is denied. 3. Collect all grants in P's pond whose subject matches U (their user id; `authenticated` if logged in; `public` always). 4. Keep grants whose role covers action A (write needs `editor`/`pond_admin`; read is covered by any role). 5. Evaluate by **descending specificity**; the first level that contains any matching grant decides: 1. **Page scope** — grants on P itself. 2. **Label scope** — grants on any label assigned to P, **including inherited labels**: a grant on label L applies to L and all its descendants in the label hierarchy. 3. **Pond scope** — grants on the pond. 6. Within the deciding level: if any matching grant is `deny` → **deny**, else **allow**. (Deny wins ties at the same specificity; a more specific `allow` still beats a less specific `deny` — that is the point of "most specific wins".) 7. No matching grant at any level → **deny** (default-closed). ### Worked examples Pond "Handbook", user Uma has pond-scope `editor` (allow): | Extra grants | Uma edits page "Salaries"? | Why | | --- | --- | --- | | — | yes | pond-scope allow | | label `confidential` on the page + label-scope `editor` **deny** for Uma | no | label level is more specific than pond level | | additionally page-scope `editor` **allow** for Uma on "Salaries" | yes | page level beats label level | | page has labels `confidential` (deny Uma) and `hr` (allow Uma) | no | same level → deny wins | "Only pages with label Y" (vision) = no pond-scope editor grant + label-Y `editor` allow. "All except label X" = pond-scope allow + label-X deny. ### Non-page objects - **Attachments** inherit the permissions of their page (or pond for pond-level files): read requires read on the page, upload/delete requires write. - **Comments** (later milestone): reading follows page read; writing comments requires page read + the pond setting "who may comment" (readers-and-up or editors-only). - **History & trash**: require write permission on the affected page (ADR 0013). - **Search** results are filtered through the same resolution (ADR 0010). - **Plugin API** calls execute with the viewing user's permissions (ADR 0008) — the API enforces this server-side. ## Performance Resolution needs the page's labels (+ label ancestors) and the pond's grants — a handful of indexed queries, cacheable per (user, pond) with event-based invalidation on grant/label changes. The collab server resolves once at token issuance (token TTL ≤ 60 s) and re-checks on reconnect; revoking write access closes live sessions via a pond-level notification (`LISTEN/NOTIFY`). ## UI obligations - The frontend hides actions the user lacks (buttons, routes) but every denied server response renders a proper i18n-ed error — client checks are convenience, not security. - Pond Admin UI must make effective permissions inspectable: "show effective access for user X / for Public" per page — this transparency feature is a story of its own and load-bearing for admin trust.