Policies
Policies
Policies are how an operation controls who can see and change what. One policy, one file: the grants themselves (per collection, per action, with row `where` and field masks), the capabilities the holder may call, the rate limits, and the approval on a grant. Everything here is enforced on the server — permissions, not placeholders.
The policy model
Access is authored as reusable policies — never per-user rows:
- policy — one file: description,
grants—{ collection: { read?, history?, create?, update?, delete? } } - teams — the team map: which policies each named team holds
Team rows (name, parent, description) come from the dashboard; authority comes from
src/access/+teams.ts, bound by name
is object-keyed by collection and action: read, history, create, update, delete
Holders: a person through their team, an envoy, or an automation
. A team row the map does not name is inert. Org admins bypass policy evaluation.
Row-level conditions
A read or history grant takes where and fields ; the where uses tokens such as ${requestor.id} . Partial matches produce reduced access — row filters merged into the SQL WHERE clauses of every read and mutation, so a user simply cannot see or touch rows outside their grants.
Token vocabulary: ${requestor.id} ${requestor.team_scope_users} — the requestor along the whole path — and `$sql` for predicates a comparison cannot express. Approvals, step functions, and `authorize` are TS/Effect, never expression strings.
Field masks
- Attribute-level — omitted from reads, hidden in UI, and stripped from submitted payloads.
- Capabilities — apps, tools, MCP servers, and skills are granted on the policy; something not named is not reachable.
What a mutation can do
create | update | delete
│
▼
resolve the subject's grants for this collection/action
│
├── grant matches ─────────────────────► direct
├── grant narrows (where/fields) ──────► reduced (SQL + mask)
├── grant carries an approval ────────► gated (write-then-lock)
└── no grant ──────────────────────────► denied - Direct — grant matches; mutation proceeds
- Reduced — rows and fields narrowed to the grant’s `where` and `fields`
- Gated — the write applies immediately, the records are locked with the request id, and an approval request is raised (write-then-lock)
- Denied — an access-denied answer when no grant or approval path matches
Recommended order
- Name the teams in `src/access/+teams.ts`; teams are what approvers and holders use.
- Author one policy per surface, with grants per collection.
- Hold policies by team; envoys and automations name their own.
- Add approvals only on mutations that need review.
- Test each role with its own preview account before go live.