Rustelo/resources/nickel/rbac/roles_contracts.ncl
Jesús Pérez f1010e9d07
chore: update
2026-07-18 20:16:16 +01:00

98 lines
3 KiB
Text

# Role Registry Type Contracts
#
# Schema for site/config/roles.ncl — the canonical role list that
# rbac.ncl groups, DB user_roles seeds, and the Rust Role enum must agree with.
let RustVariants = ["Admin", "Moderator", "User", "Guest", "Custom"] in
{
OneOf = fun allowed => std.contract.from_predicate (fun v => std.array.elem v allowed),
# Single role definition — one entry per role in the application
RoleEntry = {
name
| String
| doc m%"
Canonical role identifier.
Must match: JWT claims, DB user_roles.name, and rbac.ncl group names.
"%,
description
| String
| doc "Human-readable description for admin UI and documentation",
db_seed
| Bool
| doc m%"
Whether this role is inserted into user_roles on initial DB migration.
Set false for virtual roles (e.g. guest) that have no DB row.
"%
| default = true,
is_system
| Bool
| doc "System roles cannot be deleted or renamed via admin UI"
| default = false,
parent_roles
| Array String
| doc m%"
Role names inherited by this role (BFS expansion, same semantics as
rbac.ncl parent_groups). Names must resolve to other entries in this array.
"%
| default = [],
rust_variant
| OneOf RustVariants
| doc m%"
Corresponding Rust enum variant in rustelo_core_lib::auth::Role.
Use "Custom" for roles that have no dedicated variant.
"%
| default = "Custom",
db_level
| Number
| doc "Privilege level for UI ordering (higher = more privilege)"
| default = 0,
is_default
| Bool
| doc m%"
Role assigned automatically on new user registration.
Exactly one role in the registry must have is_default = true.
Must match registration_role in site/config/auth.toml.
"%
| default = false,
},
# Validates that exactly one role carries is_default = true.
ExactlyOneDefaultRole =
std.contract.custom (fun _label value =>
let defaults = value.roles |> std.array.filter (fun r => r.is_default) in
let n = std.array.length defaults in
if n == 1 then
'Ok value
else
'Error {
message =
"Exactly one role must have is_default = true, found "
++ std.string.from_number n,
}
),
# Root contract applied to site/config/roles.ncl
RolesConfig = {
roles
| Array RoleEntry
| doc m%"
Authoritative role registry.
Invariants enforced at eval time:
- Exactly one entry has is_default = true (enforced by ExactlyOneDefaultRole)
- Every rbac.ncl group name must appear here
- Every parent_role reference must resolve to another entry in this array
- DB migrations must seed all entries where db_seed = true
- registration_role in auth.toml must match the entry with is_default = true
"%,
},
}