Guard / foldkit-guard

Deciding reads and writes

A server asks the Policy which rows a principal may read. Every principal guard is decided by its body (once, however many rules name it); what is left is a Condition over the row, which an interpreter runs:

ts
import { Condition } from 'foldkit-guard'

const ada = Option.some({ id: 'ada', role: 'member' as const, projectIds: ['p1'] })
const readable = Effect.runSync(Policy.readable(Todo, ada))
// isAdmin was false, so: isOwner or inProject, over each row

const t1 = { id: 't1', ownerId: 'ada', projectId: 'p9', title: 'Write the README' }
Condition.holds(readable, t1, Policy.inputOf(ada), () => false)
// true
  • Condition.always and Condition.never are a read settled without looking at rows: an admin, or a visitor whose rules all read the principal.
  • Condition.holds is how an interpreter that has the rows runs it; a database compiles it instead. Its last argument answers an inherited Read, which only the caller can look up.
  • Policy.readMembers(Todo, principal, members) decides each member's own Read rule, with the refusal it shows (Omit or Redact).
  • Writes are decided alike, each principal guard's body told the interaction: Policy.writable(Todo, principal, 'Update') is the row's rule for it, { condition, deny } (Forbidden, or Explain with its sentence); Policy.writeMembers(Todo, principal, members, 'Update') each member's; Policy.linkable(Todo, principal, 'project', 'Link') a relation's, when it has one.