Boarding life.
The school's other half. The teacher's classroom shapes the academic side; the warden's hostel shapes everything else — sleep, meals, behaviour after lights-out, the moment a boarder leaves for the weekend, the moment she comes back. YESS treats Boarding as a system, not a folder: roll-call writes attendance, leave requests notify parents, curfew breaches ping phones, complaints escalate to admin, maintenance resolutions post to the ledger. Every operation a warden performs has a downstream consequence the rest of the platform can see — and the platform has fixed every silent failure it shipped with.
- 13tables
- 7cross-cluster fanouts
- 3production bugs caught + fixed
- EN/FR/ARevery surface
Prologue
What a warden's week actually looks like#
Sunday 16:00, the warden opens her dashboard at /dashboard/hostel. Six leave requests came in over the weekend; she approves four, rejects two — the trigger fans a notification to each parent within the transaction. A new boarder's room assignment was created Friday; the gender-match trigger accepted it (mixed-gender hostels are configured per-school). By Monday 06:30 the morning roll-call runs on the warden's phone: she taps "absent" for one boarder; the bridge writes a `student_attendance` row so the form tutor sees it before first period.
21:30 — curfew. The mobile RPC marks one boarder late, two absent. The notify trigger walks each affected parent's profile and fires a notification ("CURFEW ABSENT:" with high priority). That trigger was silently broken from September 2024 to May 2026 — wrong column names on the notifications insert AND wrong column in the parent join. Migration 00653 fixed it; parents now actually get the messages.
Wednesday — a complaint. A boarder flags broken Wi-Fi as 'maintenance'. The warden marks it 'escalated' (the school's ISP is the real culprit); the trigger pings every staff member with `hostel.manage` permission. By Thursday the fault is fixed and the bursar marks the request resolved with an actual_cost. The maintenance→finance trigger posts the journal entry (DR 5500 Hostel maintenance / CR 1100 Cash) within the same transaction the resolve happened in.
Thirteen chapters. One residential life. Read this one first.
Chapter 1
The thirteen tables#
Boarding's schema covers every step from the warden's first building setup to the boarder's daily curfew check. Thirteen tables across four migrations (00080, 00570, 00573, 00580, 00583), all RLS-scoped to public.get_school_id().
- Physical infrastructure —
hostels(per-school dormitory, gender + warden + capacity),hostel_rooms(floor + room number + capacity),hostel_beds(per-physical bed). - Assignments + fees —
hostel_assignments(student → bed, with gender-match trigger from 00570),hostel_fee_structures(per-hostel fee + frequency). - Daily ops —
hostel_roll_calls(morning + evening + lights-out roll, per-student JSONB array),hostel_curfew_settings+hostel_curfew_checks(per-student-per-night curfew tracking with cron seed),hostel_meal_plans(links to canteen). - Lifecycle events —
hostel_leave_requests(pending → approved / rejected → returned with parent comm fanout),hostel_visitors+hostel_visitor_preapprovals(parent pre-approves before the visit so the guard knows who's coming). - Quality + grievance —
hostel_inspections(periodic room/dorm inspection with score),hostel_complaints(student grievance log with escalation routing),hostel_maintenance_requests(broken bed / tap / bulb with auto-post to finance ledger on resolve). - Config —
hostel_settings(per-school config).
Chapter 2
Gender match at the assignment#
Schools that run gendered hostels (most do) need a guarantee that no student ends up in the wrong dorm by data-entry error. The schema enforces it at the database, not the UI.
On every INSERT or UPDATE of hostel_assignments where either hostel_id or student_id changes, the BEFORE trigger enforce_hostel_gender_match (00570) reads the hostel's gender setting and the student's gender. If they conflict, the trigger raises an exception and the assignment never commits. Mixed-gender hostels are configurable — schools that don't enforce gender separation set hostels.gender='mixed' and the trigger short-circuits.
Leave-request lifecycle
Boarder asks for weekend leave
- pending — student submits
- Student opens the portal, picks dates + leave_type (weekend / holiday / emergency / medical / family / other) + reason + pickup person.
- Row INSERTed with status='pending', parent_consent=NULL.
- approved — warden signs off
- Warden taps Approve. Status flips to 'approved', approved_by + approved_at stamped.
- BEFORE UPDATE trigger trg_hostel_leave_request_notify (00655 Phase B) fans a parent notification: title 'Leave approved' + body with dates + leave_type.
- Multi-hop join: parent_student_links → parents → user_profile_id.
- rejected — warden declines
- Status flips to 'rejected'.
- Same trigger fires: parent notification with 'declined' message + pointer to warden contact.
- returned — boarder back at hostel
- Warden taps Returned; actual_return_date stamped.
- Trigger fires: parent notification 'Returned to hostel on <date>'.
- parent_notified + parent_notified_at idempotency stamp ensures only one notification per status flip.
Parent sees return confirmation
Chapter 4
Roll-call feeds attendance#
The marketing claim "Hostel roll call feeds attendance — boarders never go missing" was false in production from launch through May 2026. No trigger existed. The hostel_roll_calls table stored per-student absences in a JSONB array; nothing wrote to student_attendance. The form tutor saw a missing boarder for the first time in first period — too late.
Migration 00655 Phase A fixed it. The trigger hostel_roll_call_feed_attendance fires AFTER INSERT or UPDATE OF completed_at / records on hostel_roll_calls. It:
- Skips non-morning periods (evening + lights_out are residential, not academic).
- Skips incomplete roll-calls (no
completed_at). - Walks the records JSONB array; for each entry with
status='absent', resolves the student's currentsection_idfromstudents.section_id. - INSERTs
student_attendance (status='absent')ON CONFLICT (student_id, date) DO NOTHING.
The ON CONFLICT clause matters: if the class teacher beat the warden to it (rare — morning roll runs before first period), the teacher's mark wins. If the warden marks first, the teacher's later mark replaces ours via the UNIQUE constraint update path. Either way: a missing boarder appears on the attendance officer's morning queue in real time, not in the afternoon when somebody finally notices.
Chapter 5
Curfew (with the trigger that actually fires#
Configured at /dashboard/hostel/settings: weeknight_time, weekend_time, grace_minutes, auto_create_checks, notify_parent_on_late, notify_parent_on_absent. The cron run_hostel_curfew_seed auto-creates one hostel_curfew_checks row per assigned boarder per night.
At curfew the warden taps each student on the mobile RPC hostel_curfew_mark(p_check_id, p_status, p_notes, p_excuse_reason). Status flips to on_time / late / absent / excused.
Chapter 6
Complaints + escalation routing#
A boarder lodges a complaint with category maintenance / plumbing / electrical / pest / noise / theft / bullying / food / other, severity low/medium/high/critical, and a free-text description. Status starts at open; warden transitions to in_progress / resolved / closed / escalated.
Migration 00655 Phase C ships the comm fanouts. On escalated: hostel_complaint_notify trigger walks the role chain (user_role_assignments → roles → role_permissions → permissions) and writes a notification to every user with hostel.manage permission. On resolved with a student_id attached, the complainant student gets a "Your complaint was resolved" ping in their portal.
Chapter 7
Maintenance posts to the ledger#
A maintenance request hits /dashboard/hostel/maintenance with category + severity + photos. Bursar assigns staff, tracks estimated_cost, and on resolution stamps actual_cost + resolution_notes.
Migration 00655 Phase D wires the finance journal. The trigger hostel_maintenance_post_on_resolve fires AFTER UPDATE OF status, actual_cost on hostel_maintenance_requests. When status flips to 'resolved' AND actual_cost > 0 AND expense_id IS NULL, it calls the SECURITY DEFINER RPC post_hostel_maintenance_expense:
- Find-or-create the
HOSTEL-MAINTexpense category. - INSERT
expensesrow scoped to the request. - Post journal entry — debit
5500 Hostel maintenance, credit1100 Cash— via the standardc4_post_journal_entryhelper. - Stamp
expense_idback on the maintenance request row.
Idempotent: re-resolve doesn't double-post (the trigger guards on expense_id IS NULL; the RPC also re-checks). Mirror of post_transport_fuel_expense (00564) and post_library_acquisition_expense (00569) — the cluster's third atomic ledger fanout.
Chapter 8
Visitor pre-approvals at the gate#
The pre-approval flow (migration 00580) lets parents register intended visits before they arrive. The guard at /dashboard/hostel/visitor-preapprovals checks the pre-approval list when a visitor arrives; recognised visitors get expedited check-in, unknown ones require a warden call before entry.
Walk-in visitors (no pre-approval) get logged in /dashboard/hostel/visitors. The two surfaces share the same recipient join. Overstay escalation (visitor stayed past expected_check_out) is schema-supported but the trigger isn't wired yet — flagged for a future fix-batch.
Chapter 9 · prologue
The canteen runs on the same contracts#
Boarding doesn't end at the bed. The canteen feeds every boarder three times a day plus the day-students who buy lunch, runs an inventory that's a real warehouse, posts revenue + COGS to the ledger in the same transaction the POS tap commits, and (since migration 00656) cross-checks every order against the student's health profile. The canteen shares the boarding cluster's three discipline gates: every state change writes a downstream signal, every count on a screen is a trigger-maintained denorm, and every flagged incident pings the right person within the transaction.
Open the canteen surface at /dashboard/canteen. Five sub-pages cover the operational day: menu (/dashboard/canteen/menu), daily menus, orders, transactions, and recipes. Two more handle administration: meal plans + subscriptions. Three cover finance + reporting: inventory, transactions, reports.
Chapter 10
The canteen's thirteen tables#
Canteen's schema covers everything from menu config to the journal entry. Thirteen tables across three migrations (00081, 00568, 00585). Note the symmetry with Hostel — another thirteen-table sub-domain. Both are residential.
- Menu + recipe —
canteens(per-school outlet),canteen_menu_items(per-item with allergens + dietary_tags as JSONB on the item itself),canteen_daily_menus(what's being served today),canteen_recipes+canteen_recipe_ingredients(dish spec with cost-per-serving + produce RPC that decrements inventory). - Subscription + POS —
canteen_meal_plans(plan definitions),canteen_meal_subscriptions(per-student enrolment with auto-renewal cron),canteen_orders(the POS log with items JSONB + payment_method + dietary_flag JSONB columns from 00656),canteen_transactions(financial ledger sibling). - Inventory + reports —
canteen_inventory(raw stock),canteen_inventory_movements(in/out with auto-COGS trigger),canteen_operator_reports(daily summary),canteen_settings(per-school config).
Chapter 11
Dietary cross-check at the database#
The marketing eliteCloser "Canteen dietary cross-check (allergies, halal, vegetarian) flags incompatible meals" was false from launch through May 2026. No trigger existed. Schools that had configured student_health_profile.food_allergens + dietary_restrictions got nothing back — orders went through unflagged.
Migration 00656 fixed it. Three phases in one transaction:
- Phase A — schema columns.
canteen_orders.dietary_flag JSONB+dietary_flagged_at TIMESTAMPTZ+ partial index on flagged orders. - Phase B — BEFORE INSERT trigger
canteen_order_dietary_check. Skips staff/guest orders (only students have a health profile). Walksitems[], looks up eachcanteen_menu_items.allergens+dietary_tags(flattened from JSONB toTEXT[]viajsonb_array_elements_text), intersects withstudent_health_profile.food_allergensvia SET INTERSECT, computes restriction conflicts via SET EXCEPT (student has 'halal' restriction but item lacks 'halal' tag → conflict). Stampsdietary_flagJSONB with per-item breakdowns when any conflict found. Does NOT block the order — operator decides after talking to the student. - Phase C — AFTER INSERT trigger
canteen_order_flag_notify. Fires whendietary_flagged_atis set. Multi-hop parent join (the sameparent_student_links → parents → user_profile_idwalk that powers Transport, Hostel, Infirmary). Writes one notification per parent. EXCEPTION-wrapped so a comm-hub failure cannot poison the order insert.
Chapter 12
The canteen money flow#
Three triggers from migration 00568 wire the canteen into the school's ledger:
- Order → Finance.
canteen_order_revenue_post_triggerfires AFTER INSERT oncanteen_orders; posts the total_amount to revenue viac4_post_journal_entry. Whether the payment method is cash, mobile money, card, wallet, meal_plan, or invoice, the journal entry lands within the same transaction the POS tap committed in. - Inventory movement → COGS.
canteen_inventory_movement_posttrigger callspost_canteen_inventory_cogson every stock-out (recipe production, waste, transfer). Posts a journal entry that increments COGS by the movement's value. - Hostel assignment → meal plan auto-enrol.
auto_enrol_boarder_meal_plantrigger fires onhostel_assignmentsINSERT — every new boarder gets enrolled in the school's default meal plan automatically. The bursar doesn't double-enter.
A fourth piece — the subscription renewal cron run_canteen_subscription_renewal — walks every expiring subscription nightly and creates the next period's row + the next invoice line. The bursar wakes up with tomorrow's billing already done.
Chapter 13
Recipes + the produce RPC#
A recipe is a dish spec (cost-per-serving, allergens, dietary tags, prep time) plus a list of ingredients with per-serving quantities. The chef opens /dashboard/canteen/recipes, defines the recipe, attaches ingredients from the inventory catalogue.
When the kitchen produces N servings, the chef calls the RPC canteen_recipe_produce(p_recipe_id, p_servings, p_notes). The RPC walks the recipe's ingredients, INSERTs one canteen_inventory_movements row per ingredient with quantity = qty_per_serving × N. The inventory-COGS trigger then posts each ingredient's value to the ledger. One tap = real-time stock decrement + real ledger entry.
Permissions
Who can do what#
Hostel side
hostel.view— read every surface. Default for school admins, ops managers, wardens, and (scoped) students + parents for their own records.hostel.create— assign students to beds, log visitors, file maintenance requests. Default for wardens.hostel.edit— update assignments, approve leave, mark curfew, resolve complaints, update maintenance. Default for wardens.hostel.manage— configure settings, manage fee structures, schedule curfew, run manual seed. Default for school admins. Used by the complaint escalation trigger to identify recipients of escalated issues.hostel.delete— hard delete buildings / rooms / beds. Default for school admins only.
Canteen side
canteen.view— read every surface. Default for ops, canteen managers, and (scoped) students + parents for their own orders + transactions.canteen.create— place orders, run recipe production (which fires the COGS journal trigger). Default for canteen operators.canteen.edit— cancel orders, refund, update inventory counts. Default for canteen operators.canteen.manage— manage recipes + ingredient catalogue, configure meal plans + subscriptions, edit canteen settings. Default for canteen managers.canteen.delete— hard delete menu items + recipes. Default for school admins.
What makes this elite
- 01
Gender match enforced at the database database
Schools with gendered hostels can't accidentally assign a girl to the boys' wing. enforce_hostel_gender_match trigger reads hostels.gender + students.gender and raises before the row commits. Mixed-gender hostels opt-in via hostels.gender='mixed'.
- 02
Roll-call feeds attendance — for real for real
Morning roll-call writes student_attendance for every absent boarder. Form tutor sees the gap before first period. ON CONFLICT respects the teacher's later in-class mark. Was missing entirely until 00655 — now correct.
- 03
Leave-request fanouts parents — for real for real
approved / rejected / returned each writes a notification per parent via the canonical multi-hop join. parent_notified + parent_notified_at stamped idempotently. Was missing entirely until 00655.
- 04
Curfew notifies parents — for real for real
late / absent transitions ping every parent. Was silently broken from 00583 (Sep 2024) through 00653 (May 2026) — wrong column names on notifications + wrong parent join. 20 months of silent failures fixed.
- 05
Complaint escalation routes to hostel.manage holders routes
On 'escalated', the trigger walks user_role_assignments → roles → role_permissions → permissions to find every staff with hostel.manage. Resolve sends a thanks to the complainant student.
- 06
Maintenance resolve posts the journal entry posts
post_hostel_maintenance_expense RPC + AFTER UPDATE trigger. On resolve with actual_cost > 0: HOSTEL-MAINT category, expense row, journal entry (DR 5500 / CR 1100). Idempotent. Mirror of transport fuel + library acquisition.
- 07
Shop RLS — parents see their child's orders see
Was silently broken from 00563 (wrong column on parent_student_links). Staff with shop.view always saw everything; parents saw their own orders only. 00653 rewrote the RLS with the canonical parent walk. Parents now see their child's orders.
- 08
Canteen dietary cross-check — at the trigger, not the form trigger
BEFORE INSERT trigger walks items[], intersects canteen_menu_items.allergens with student_health_profile.food_allergens, computes restriction conflicts via SET EXCEPT. Stamps dietary_flag + dietary_flagged_at, fires parent notification in the same transaction. Was marketed but never wired until 00656; now real.
- 09
Hostel boarder → canteen meal plan auto-enrolment auto
auto_enrol_boarder_meal_plan trigger from 00568 fires on hostel_assignments INSERT — every new boarder lands on the school's default meal plan. Bursar doesn't double-enter; subscription renewal cron picks them up nightly.
- 10
Canteen order → revenue + COGS in the same transaction same transaction
canteen_order_revenue_post + canteen_inventory_movement_post triggers wire the POS tap straight to the ledger. Revenue lands when the order INSERTs; COGS lands when the recipe production INSERTs the inventory_movements row. No nightly batch.