Skip to content

Permissions

Policy is deployed with each named schema and denies by default. Read rules decide which facts exist in a principal’s filtered database. Operation rules decide which exact operations the principal may attempt.

export const ProjectSchema = Ramose.Schema("project-v1", {
user: User,
task: Task,
})
ProjectSchema.applyPolicy(
{
principal: User.authId,
roles: ["member", "admin", "suspended"],
},
({ policy, session }) => {
policy.task.read.where(session.hasRole("member"))
policy.task.read.where(session.hasRole("admin"))
policy.task.read.denyWhere(session.hasRole("suspended"))
},
)

applyPolicy records inert schema-checked policy data. Deployment rejects unknown fields, invalid paths, incompatible targets, and unbounded structures.

policy.task.read.where(session.hasRole("member"))

Multiple allows for the same focus combine with OR. A deny overrides allows at that focus:

policy.task.read.denyWhere(session.hasRole("suspended"))
policy.task.read.where((task) => task.owner.eq(actor))
policy.task.read.where((task) => task.project.members.contains(actor))
policy.task.read.where(session.hasRole("admin"))

actor is the principal entity resolved through the configured unique field. session.subject is the JWT subject string. Resource relation traversal is bounded and must remain within the schema.

Prefer database-backed membership for rapidly changing access. A token is identity input, not a database selector.

policy.task.read.where(session.hasRole("member"))
policy.task.fields.privateNote.read.where(session.hasRole("admin"))

The field rule narrows its parent. A client asking for privateNote receives it only for admins; the field is absent for others. Hidden refs hide the whole edge rather than revealing an inaccessible target id.

policy.commentable.read.where(session.hasRole("member"))

Trait rules apply to the trait fields on readable composers. They do not reopen an entity hidden by its concrete rule. This makes reusable capability policy possible without weakening concrete domains.

policy.task.operations.createTask.where(session.hasRole("member"))
policy.task.operations.setDone.where(session.hasRole("member"))
policy.task.operations.delete.where(session.hasRole("admin"))

Operation expressions are session-only. They can use roles, the subject, and declared claims. They cannot read the target resource. A targeted operation also requires a visible compatible target.

For each role, test visible rows, fields, relations, trait roots, operations, history, counts, ordering, limits, errors, and revocation. Compare hidden and nonexistent cases. The security contract includes what callers cannot infer, not just which successful request returns 200.