HandbookFoundation37

Multi-campus federation.

Some schools are one building on one street. Others are a name over four sites in three towns, each with its own principal, its own bell, its own fee scale, its own way of doing things. YESS treats that second kind as a federation: one tenant, one student record, one set of books — but every campus scoped so a campus manager sees and acts only within their own gate, while the proprietor still reads the whole. This handbook is how you stand a campus up, decide who can do what where, govern how students move between sites, and roll five campuses into one comparison the owner reads on a Monday morning.

  • 9campus surfaces
  • 5campus role kits
  • 2-tierpermission gate
  • ≈12 minto read

Prologue

One school, many gates#

Every row in YESS already carries a school_id — that is how one school is walled off from the next. Multi-campus adds a second dimension inside a school: a campus_id on the operational tables, so the same tenant can be sliced by site. A campus manager in town B should not see town A's attendance, town A's invoices, or town A's staff. But the owner — sitting above both — should see everything, side by side.

That is the whole design problem, and YESS solves it with two ideas that recur on every screen in this book. First, a campus-scoped role: a grant that makes someone powerful on one campus and an ordinary member everywhere else. Second, a two-tier permission gate that checks both the school-wide permission and the campus grant before any action lands. Get those two right and federation is just bookkeeping.

A single school, federated — each campus run on its own, the whole one number the owner can read.

Chapter one

A morning across two campuses#

Greenwood runs two sites — the Main Campus in the city and the Annex ten kilometres out. Follow one morning and the rest of the book is detail.

07:50. The Annex principal logs in. Her topbar carries a campus switcher; it is already pinned to Annex. Every list she opens — attendance, invoices, staff — is filtered to her site. She could flip to All accessible, but her grants only reach the Annex, so the school-wide totals stay closed to her. The choice is remembered on her device.

08:05. A teacher who normally works the Main Campus is covering a class at the Annex today. The Annex principal opens /dashboard/attendance/campus-presence and there he is — the presence roster is built from the timetable, so it lists everyone teaching on this campus today, visitors included, not just the staff who are home-based here.

08:30. A family wants to move their daughter from the Annex to the Main Campus permanently. The registrar checks the rule: the school's mobility policy is Open, so a permanent transfer is allowed. The move is recorded; an append-only row lands in the student's campus history, and her campus_id follows her enrolment to the Main Campus automatically.

09:15. The proprietor opens /owner/campuses on his tablet. Two bars, ranked: enrolment, fee collection, and presence for the Main Campus against the Annex. The Annex is collecting at 81%, the Main at 68%. He forwards the screen to the Main bursar with one line.

Chapter two

The campus record#

A campus is a row in the campuses table, and it is the anchor everything else hangs from. You create and manage campuses at /dashboard/campuses — a roster with a KPI rail (campuses, students, staff, classes), a searchable table, a CSV export, and create/edit/delete dialogs gated by the campuses.create and campuses.edit permissions.

Each record carries an identity (name, address, phone, email), an assigned principal drawn from staff, a capacity, and — for schools that use it — a geofence: a GPS point with a radius, or a walked / drawn polygon, with the method chosen per campus (point-and-radius, polygon, or none). The geofence is what lets attendance and presence features know whether a phone is physically on this campus.

Every campus with its student and staff counts, mobility policy, and a CSV export — the federation at a glance.

Chapter three

How students move#

The most political question in a federation is who is allowed to move a student from one campus to another, and on what terms. YESS does not decide this for you — it gives you a policy you set. On a multi-campus school, the campuses page shows a mobility policy card (visible when you hold campuses.edit) with three settings:

Three mobility modes

  1. 1

    Open

    Both temporary and permanent moves are allowed. A student can be sent to another campus for a day (an exam, a sports fixture) or transferred for good. This is the default for schools that treat their campuses as one fluid community.

  2. 2

    Temporary only

    Day-visits between campuses are allowed, but a permanent transfer is blocked at the policy layer. Good for schools where each campus owns its roll and re-rostering is a deliberate, rare act.

  3. 3

    Locked

    Neither temporary nor permanent inter-campus movement is permitted. Each campus is sealed; the mobility check returns false for any cross-campus move.

Behind the card sits the campus_mobility_rules table. The policy you set on the page is the school-wide default — a single rule with no specific from/to campus. The schema also stores per-pair rules (Annex → Main allowed, Main → Annex blocked), and the mobility check honours them, but the current screen exposes only the school-wide default; per-pair overrides are a planned enhancement called out in the roadmap below. Every permanent class move and student transfer writes an append-only history row, so the trail of who moved where, when, and why is always reconstructable.

Chapter four

Powerful here, ordinary elsewhere#

A school-wide role makes you the same everywhere. A campus-scoped role makes you a principal on one campus and nobody special on the next. This is the heart of federation, and it lives in the campus_role_assignments table.

You grant a campus role in two places — both write the same row. Open a campus and go to its roles surface (the campus-roles admin under /dashboard/campuses/[id]/roles), or use the scope picker in the central roles page at /dashboard/roles. Pick the staff member, pick the role, add a rationale; YESS records the grant with an effective-from date and an optional allowance. Revoking does not delete the row — it sets it inactive and stamps an effective-to date, preserving the audit trail.

YESS seeds four campus-native role kits per school — campus principal, vice-principal, operations, and admissions — each pre-filled with a sensible permission set, alongside the school-wide campus manager role. The assign dialog sorts these campus-native roles to the top with a badge, but any custom role can be granted on a campus too.

On the client, the same logic is exposed as hasCampusPermission(action, campusId) and scopedCampusIds(), so a screen hides the buttons a campus manager cannot use and shows only the campuses they can reach — the UI and the database agree because they read the same grants.

Chapter five

Opening a campus mini-world#

Click a campus from the roster and you land on its own little world at /dashboard/campuses/[id]: an identity header, four KPIs (students, staff, classes, rooms), and a grid of cards that lead into everything that campus owns. Some cards open a dedicated admin surface; others are filtered deep-links that carry the campus into another module so you see only that site's slice.

What a campus owns

  1. 1

    Rooms

    The campus's physical spaces at /dashboard/campuses/[id]/rooms — classrooms, labs, the library desk — each typed and capacity-tagged, and each gated by campus-aware write rules. These rooms are what the timetable solver schedules into.

  2. 2

    Attendance blocks

    At /dashboard/campuses/[id]/blocks, sub-divide a campus into blocks for officer accountability and attach rooms and classes to each. Delete a block and its rooms and classes re-home to the campus-wide implicit block — nothing is orphaned.

  3. 3

    Campus-scoped roles

    The roles surface for this campus alone — grant a campus principal or operations lead here without touching anyone's reach on another site.

  4. 4

    Settings

    Per-campus overrides at /dashboard/campuses/[id]/settings — attendance grace, walk-in admissions, contacts, bell schedule, calendar overrides. Empty fields inherit the school defaults.

  5. 5

    Conduct

    A per-campus conduct lens at /dashboard/campuses/[id]/conduct — the staff and students on this campus needing attention, sorted by severity, for the principal or dean of one site.

  6. 6

    Inline announcements

    A composer that posts an announcement to this campus alone, so a site head can reach their own community without spamming the whole federation.

The detail page also carries quick-actions and cross-links into students, staff, classes, timetable, and transport — each pre-filtered to the campus — so the campus becomes a hub you steer the whole school from while never leaving its scope.

Chapter six

Each campus, its own rules#

A federation only works if a campus can be a little different. The campus_settings table — one row per campus — lets each site override the school defaults at /dashboard/campuses/[id]/settings. The overrides that matter most day to day:

The attendance lock grace (how many minutes after the bell a teacher can still mark the roll), whether the campus allows walk-in admissions, its own contact and emergency details, and — stored as validated JSON — its own bell schedule and calendar overrides. A campus can keep a later start, a different term-break, or a stricter attendance window than its siblings. Leave a field empty and the campus simply inherits the school-wide value; there is no need to restate what is already true.

The same pattern reaches finance and HR: a campus can carry its own fee scale and its own timetable template, and per-campus P&L and collection figures are computed from the campus_id stamped on invoices, payments, journal entries, and expenses — so "how is the Annex doing" is a real, isolated number, not an estimate.

Chapter seven

Who is here, and what they can see#

Two campus-aware features close the loop between "who is physically here" and "what each person is allowed to touch."

Campus presence

The presence roster at /dashboard/attendance/campus-presence answers a question single-site schools never have to ask: who is on this campus today? It is built from the timetable — a database function walks every period scheduled into a room on the chosen campus and returns every staff member teaching there, with a period count. A teacher who is home-based on the Main Campus but covering a class at the Annex shows up on the Annex roster, because presence follows the schedule, not the staff record.

Exam isolation

Some federations want a hard wall around assessment: a campus-assigned teacher should never see another campus's exam papers, mark sheets, or report cards. YESS ships an exam campus isolation toggle on the campuses page. Switch it on and a restrictive rule limits campus-scoped staff to their own campus's exams and gradebook — while school-wide leadership still sees everything. Off by default, so smaller federations are not boxed in.

Chapter eight

The owner reads the whole federation#

Everything above scopes people down to their campus. The owner portal does the opposite — it rolls every campus up into one comparison. At /owner/campuses the proprietor sees each campus ranked side by side on the three numbers that decide whether a site is healthy: enrolment, fee collection rate, and presence.

The comparison is computed by a single database snapshot that returns one row per campus — student count, staff count, attendance rate, billed, collected, and collection-rate percentage — so the ranking is live and the owner never waits for a report. This is the federated view multi-campus exists to produce: a campus manager runs their gate; the owner reads them all.

What makes it elite

What makes multi-campus elite#

Six things that make this elite

  1. 1

    Scope in the database, not the screen

    Campus isolation is enforced by row-level security and the two-tier has_campus_permission gate — so it holds identically on the web, the API, and every export. A campus manager cannot widen their reach by changing a URL.

  2. 2

    Roles that mean different things in different places

    A campus-scoped grant makes the same person a principal on one site and an ordinary member on the next, with an optional allowance attached. School-wide, campus-scoped, and program-scoped grants are three real ledgers, not one flattened list.

  3. 3

    A mobility policy the school owns

    Open, temporary-only, or locked — the school decides how students move between campuses, and the rule is checked at the policy layer, not left to whoever happens to have the transfer button.

  4. 4

    Presence that follows the schedule

    The campus presence roster is built from the timetable, so a teacher covering a class on another campus appears on that campus's roster — visitors counted, not just the home-based staff.

  5. 5

    Per-campus finance that is real, not estimated

    Invoices, payments, journal entries, and expenses each carry a campus_id, so per-campus P&L and collection rates are isolated figures the owner can trust at a board meeting.

  6. 6

    Federated up, scoped down, one tenant

    Every campus runs on its own gate; the owner reads them all in one ranked comparison — and it is still one school, one student record, one set of books underneath.

Planned features

What comes next#

Multi-campus federation is built as a multi-node initiative; identity and membership, scoped RBAC, campus-aware data and RLS, finance and HR scoping, timetable, presence, and the owner comparison are live today. The honest gaps below are tracked and will land in upcoming releases.

Five honest gaps

  1. 1

    Per-pair mobility overrides

    The schema already stores rule rows for specific campus pairs (Annex → Main allowed, Main → Annex blocked), and the mobility check honours them. The current screen exposes only the school-wide default; the per-pair override editor is the next step.

  2. 2

    Conduct and grades by column

    A few tables — some conduct and grade records — have no campus_id yet and are scoped by joining through the student or staff. That is why the per-campus conduct view filters in memory. Adding the column closes the last RLS gap.

  3. 3

    Mobile campus-awareness

    The web app carries the campus dimension end to end; the mobile app still largely runs school-wide. Bringing the campus switcher, scoped lists, and presence to mobile is planned.

  4. 4

    Per-chapter campus filter in analytics

    The owner's campus comparison is live, but the other owner analytics chapters (academics, attendance, finance) do not yet all carry a campus filter. A consistent campus selector across every owner report is on the way.

  5. 5

    Program × campus operations

    A coordinator who spans campuses for one programme should see exactly their programme's campuses and no more. The program-scoped and campus-scoped ledgers exist; the combined coordinator view is a planned node.

Adjacent modules

Where federation connects#

Tutorial

Do it step by step#

Stand up a second campus end to end — from the empty record to a campus principal who is powerful there and nowhere else, to the owner reading both sites on one screen. Six steps, in the order a federation actually grows.

  1. 1

    Create the campus

    Open /dashboard/campuses and click Add campus. Enter its name, address, contact, and assigned principal; set a capacity and — if you use geofenced attendance — its GPS point and radius or a walked polygon.

    You need the campuses.create permission to add a campus. The KPI rail and CSV export update the moment it is saved.

  2. 2

    Set the mobility policy

    Still on /dashboard/campuses — now that the school has more than one campus, the mobility-policy card appears. Choose Open, Temporary only, or Locked to decide how students move between sites. Next to it, decide whether to switch on exam isolation.
  3. 3

    Open the campus mini-world

    Click into the campus at /dashboard/campuses/[id] and set up its rooms and attendance blocks. Rooms are what the timetable schedules into; blocks give officer accountability a boundary.
  4. 4

    Tune the per-campus settings

    Go to /dashboard/campuses/[id]/settings and override only what differs — a later bell, a stricter attendance grace, a walk-in-admissions toggle, a different term-break. Leave the rest empty and the campus inherits the school defaults.

    Bell schedule and calendar overrides are stored as JSON and validated on save, so a malformed override never reaches the database.

  5. 5

    Grant a campus-scoped role

    At /dashboard/campuses/[id]/roles (or via the scope picker in /dashboard/roles) grant the site head a campus principal role. They become powerful on this campus and ordinary everywhere else — the two-tier gate enforces it in the database.
  6. 6

    Read both campuses as the owner

    Switch to the owner portal and open /owner/campuses. Both campuses are ranked side by side on enrolment, fee collection, and presence — live, no export, the federated view the whole module exists to produce.

Two campuses, one tenant — each run on its own gate, both read as one number by the owner.