Role-based access covers the normal case. Teachers see their classes, heads of year see their year group, admin sees everything. Then a safeguarding concern is raised about a student whose parent works at the school.
Deny has to beat allow
Ordinary permission models are additive: collect the user’s roles, union the permissions, allow if anything matches. Safeguarding needs the opposite — an explicit denial that no role can override, including the one usually called admin.
access_restrictions
subject_id -- the student
restricted_actor -- specific person, or a group
scope -- all | safeguarding_notes | contact_details
reason_ref -- the case, not the detail
set_by, set_at
review_on
Evaluation is: gather allows, gather denies, deny wins. That ordering is the entire feature, and it has to live in the one function every read path uses — a restriction honoured in nine places and missed in the tenth is worth nothing.
Absence must not be informative
If a restricted record renders as “you do not have permission to view this student”, you have told the restricted person that a concern exists. In a small school that is enough to identify the case.
Restricted records are absent, not blocked. They do not appear in lists, searches, or counts for that actor. The interface behaves as though the record is not there.
That has an uncomfortable consequence worth stating plainly: two users legitimately see different totals. Accept it and design the reports accordingly, or the first person to compare screens will file a data-integrity bug.
Notification is part of the model
Safeguarding is not only about hiding. Certain events must reach the designated lead within a defined window regardless of who is on duty. That is a routing rule attached to the concern type, with an escalation if unacknowledged — not an email to a shared inbox and hope.
Review dates, not permanent state
Restrictions are set during a live situation and rarely revisited. A review_on date forces the question back onto a list. A restriction nobody has looked at in two years is either stale or evidence that the process stopped, and both are worth surfacing.