SamwiseCampus runs institutional administration, teaching,
attendance, finance, scheduling, records and certificates from a
single application — with every school's data resolved on the
server, never chosen in the browser.
It is an owner, administrators, a bursary, teachers, guardians and
learners, each seeing a different slice of the same records — and
several schools running side by side in one deployment without ever
touching each other's data. That shape is what the system is built
around.
Built for institutions
A tenant is an institution with its own campuses, tiers,
timezone, currency and branding. A person's relationship to it
is a membership — not a column on their user record — so one
account can hold several at once and switch between them.
Authorised on the server
Role and institution are resolved from the signed-in session on
every protected request. The client never sends a tenant or a
privilege level, so it has nothing to tamper with. Eighteen
named capabilities decide what each workspace may do.
The whole school day
Admissions through to a signed certificate. Enrolment records,
attendance registers, gradebooks, timetables, fee structures,
invoices, payment review, documents, messaging and awards all
live in one schema and share one audit trail.
Runs without a babysitter
Deterministic builds, migrations applied during deploy, a
rehearsed backup and restore drill, health checks and a handover
workspace that proves the deployment is ready before anyone
depends on it.
Tenancy and safety
The browser does not get a vote.
The central rule of the system, and the one everything else is
arranged around: an authenticated person never picks their school or
their privilege level. The server works it out and refuses the rest.
A person signs in
Authentication establishes a session. It does not establish
a role — nothing in the credential names a school or a
permission.
The server resolves the active membership
For every protected request, the session is resolved to an
active membership, its institution, its access role and its
membership type. Suspended, invited, archived and
unauthorised contexts are denied or shown a restricted
state.
A capability is checked before any data moves
Each procedure asserts the capability it needs against that
resolved membership. Reading the institution, managing
membership, writing grades, reviewing payments and signing
certificates are separate grants — holding one never implies
another.
Queries are scoped to the institution
Repositories filter by the institution that came from the
membership. A record belonging to another school is not
merely hidden from the response — it is never selected.
Client-side role gates are navigation, not
security.
The workspace routes check the role so a teacher is not
offered the bursary screens, which keeps the interface honest
and the navigation short. They are a courtesy. Every one of
those screens is independently protected on the server, so
hiding a link is never the thing standing between a person and
another school's records.
Workspaces
Eighteen domains, one application.
The product is organised as workspaces rather than screens. Each is
a domain of the API with its own capability boundary, its own
records and its own audience — and each appears in the navigation
only for the people who have the capability to use it.
Foundation
foundation
The institutional dashboard and first-run experience. It
answers what a signed-in person may do next, and it is where a
new deployment is brought to life.
Institution dashboard scoped to the active membership
Guided onboarding steps, tracked per person and resumable
Access assertions that fail closed for the wrong institution
Route performance dashboard with a tunable alert policy
Preferred workspace, so a returning user lands where they
work
Resilience
resilience
Institutional recovery treated as a measurable practice rather
than a promise. A quarterly review is scheduled, activated and
evidenced.
Recovery posture overview for the institution
Quarterly review created, activated and updated on a cadence
Evidence recorded against a review, so the claim is
auditable
Analytics
analytics
Attendance is the operational heartbeat: rosters, an
acceptance preview before anything is committed, single and
bulk marking, and a branded register document.
Institution summary and attendance acceptance preview
Attendance rosters by class, session and date
Single and bulk attendance marking
Attendance document branding resolved per institution
Reports
reports
The export surface. Reporting data leaves the application as a
file a school can keep, mail or hand to an inspector — scoped
to the institution that asked.
Institution-scoped CSV export of reporting data
Finance and bursary
finance · guardian
Fee structures become invoices, invoices become payment
proofs, and proofs are reviewed by a person with the authority
to accept them.
Fee structures per session and tier
Invoice generation against a student and a structure
Payment proof submitted by a guardian
Pending payments queue with a review decision
Receipt reference issued on approval
Guardian's own invoice list, isolated to their learners
People and access
membership · profile
Who is in the institution, what they may do, and the
invitation that got them there — including teaching
assignments that narrow a teacher to specific classes.
Invitations issued, listed and revoked before acceptance
Invitation accepted by the invited person, not on their
behalf
Managed memberships with access role changes
Deactivation and reactivation, recorded as lifecycle events
Per-teacher teaching assignments and their summaries
Switching between the contexts one account holds
Account profile, avatar and password management
Documents
documents
Files live in managed object storage, not in the database. The
application holds the metadata, the ownership and the access
decision; the file itself is reached through a short-lived
signed reference.
Upload, rename and archive against the institution
Opening a document via a signed, expiring URL
Sharing with a named recipient and listing active shares
Revoking a share, which takes effect immediately
Recipients opening what was shared with them
Branding
branding
One institution's identity governs every document it produces.
A register, a roster or a certificate must not carry another
school's marks.
Brand profile read and saved per institution
Logo uploaded to managed storage
Brand resolved into attendance, roster and certificate
documents
Attendance
Marked once, counted everywhere.
Attendance is recorded against a roster rather than a free-form
list, so the register a teacher marks and the register a head
teacher prints are the same object.
Register behaviour. Rosters are resolved per institution,
class and session, and a roster without the capability to mark
it is a read, not a write.
Step
What happens
Who
Roster
The learners expected in a class for a session and date,
resolved for the institution.
Teaching staff, administrators
Preview
An acceptance preview shows what the mark set will do
before it is committed.
Teaching staff
Mark
A single learner is marked, or the whole register is
submitted in bulk.
attendance:manage
Brand
The institution's brand profile is applied to the register
document.
Resolved automatically
Export
The reporting data leaves as a CSV scoped to the
institution.
Administrators
Teaching and grading
From enrolment to a grade that means something.
Courses, assignments, submissions and grading schemes, for
institutions that grade by continuous assessment and for those that
grade on a tertiary scale.
Courses and assignments
Courses are created for an academic session, students are
enrolled against them, and assignments are created inside a
course. The eligible-student list is resolved per course rather
than offered as a directory dump, so a teacher only ever picks
from the people who are actually in front of them.
The gradebook
A gradebook is requested for a course and session, submissions
are graded against it, and the grading scheme that produced the
total is stored alongside the result. A mark without its scheme
is a number nobody can defend a year later.
Tertiary grading
Institutions on a tertiary tier can save a grading scheme of
their own, which the gradebook and the grading bridge then use.
The default scheme is not assumed for them and not forced on
anyone else.
Grading bridge
A bridge from recorded grades to issued awards, so a certificate
reflects the gradebook rather than a transcription of it. Grades
carry grades:manage; courses carry
courses:manage. They are separate grants.
Timetable and classes
Rooms, staff, classes — and the learners inside them.
The timetable is built from resources that exist: physical spaces,
the staff who teach in them, and the class groups that move between
them.
Resources and entries
Timetable resources are registered once and entries are created,
updated or cancelled against them. Staff resources and staff
profiles let a teacher's own load be described without exposing
anyone else's.
Class groups
A class group is a cohort with a roster. Learners are enrolled
into it, withdrawn from it or transferred between groups — each
transition recorded, so a learner's movement through the
institution is reconstructable.
The learner's own view
A learner sees their timetable and nothing else. It is the
narrowest workspace in the system by design: the smaller the
surface, the smaller the thing that can be got wrong.
Academic reminders
Reminders are activated as an explicit, recorded action rather
than a background timer that quietly starts existing. Scheduled
work is a declared job, so it survives a restart and can be
inspected.
Fees and payments
Two people, two halves of one transaction.
The school sets what is owed; the guardian says what they paid; the
bursary decides whether it counts. Three separate grants, so no one
of them can complete the loop alone.
Step one
The school defines what is owed
A fee structure is created for an academic session, and an
invoice is generated against a student from it. Structures
carry fees:manage — a teacher does not have it.
Step two
The guardian declares a payment
A guardian sees only their own learners' invoices and submits
proof of payment against one, under
payments:submit. They cannot mark it approved,
because they do not hold that grant.
Step three
The bursary decides
Pending payments are reviewed and confirmed or rejected under
payments:review. An approved payment yields a
receipt reference. Reviewing and submitting are deliberately
not the same capability.
Certificates and awards
An award you can check without trusting the paper.
Certificates are issued from recorded results, signed by someone
with the authority to sign, released to a recipient, and
independently verifiable afterwards. A certificate is worth what its
verification is worth.
Issuing
A recipient and an award are chosen, suggestions come from the
gradebook, and the certificate is previewed before it is issued.
Single and batch issue both exist, so a graduating class does
not become an evening of clicking.
Correction without erasure
A certificate is voided, not edited, and a corrected one is
issued as a separate document. The history of what was issued
remains intact, which is the only behaviour that survives an
audit.
Release and delivery
A certificate is released to the recipient and can be sent to
them. Being eligible to see an award and having it delivered are
separate moments, so a document is never in someone's hands
before the school meant it to be.
Verification and delegation
A verification path confirms an award against the record it was
issued from. Signature authority can be delegated and revoked,
and the delegation list is itself visible — so it is knowable
who could sign for the institution, and when.
Four capabilities sit here rather than one: issuing, signing,
verifying and delegating. An institution can let a registrar
issue awards, a head of school sign them, and a verifier check
them, without any of those three being able to do the other two.
Records and enrolment
A learner enters the school once, and every step is recorded.
The intake workspace carries a learner from a document on someone's
desk to an enrolled person with a guardian attached and a history
nobody has to reconstruct.
Intake options resolve
The tiers, sessions and classes this institution may
actually admit into are resolved for the caller — not read
from a fixed list.
Documents are attached
Supporting documents are uploaded and attached to the
record, and reached later through a signed reference rather
than a public path.
The learner is admitted
Admission creates the enrolment record, with an admission
number and a tier, against the institution and campus.
A guardian is assigned
The relationship between a guardian and a learner is an
explicit assignment, which is what a guardian's access is
derived from — not a guess from a surname.
The record stays inspectable
Roster history and an audit log are queryable for the
institution, so the state of the register at any point can
be explained rather than remembered.
Documents and sharing
The file is never the thing being protected. The reference is.
Nothing is served from a guessable path. A document is opened by
asking the application, which checks the capability, checks the
institution, and only then produces a signed reference that expires.
Storage, not database
Files live in S3-compatible object storage. The database holds
the metadata: what it is, who it belongs to, which institution
it is scoped to, and whether it has been archived.
Signed and short-lived
Every read produces a signed URL with an expiry. Copies of a
link differ from one another and none of them works
indefinitely, so a link that leaks in a group chat is of limited
use.
Sharing is a grant with a name on it
A share names its recipient. It can be listed, and it can be
revoked — after which the reference stops resolving. Archiving
is separate from deleting, so a document leaves the working set
without leaving the record.
Documents that build themselves
Attendance registers, enrolment rosters and certificates are
produced as PDFs from the same records the screens read, under
the institution's own brand. There is no second copy of the
truth in a template.
Communication
Messages that stay with the institution.
A message centre inside the application, governed by
notifications:manage. Messages are sent, listed,
archived and unarchived — the archive being how a school keeps a
working view short without losing anything.
Transactional email — password resets and account notices — is
delivered through a dedicated sending domain with its own SPF
and DKIM records, so school correspondence is not sent from an
address that anyone else can claim.
Institutional branding
The institution's identity governs its paperwork.
A school's register should not go home bearing a default logo. Brand
is stored per institution and resolved into every document that
institution produces — in a multi-tenant deployment, that is a
correctness requirement, not a cosmetic one.
Register documentsEnrolment rostersCertificatesLogo in managed storageResolved per institutionGoverned by documents:write
People and access
Two questions, answered separately.
Who someone is to the school, and how much authority they hold
there. A membership type and an access role are different fields on
purpose — a bursar and a head teacher are both staff, and they
should not be the same grant.
Membership types
What someone is in the institution. This is what determines which
workspaces are available to them at all.
Seven membership types, from the institution's own schema.
Type
Who it is for
Typical surface
Webmaster
The institution's technical owner.
Branding, handover, resilience, everything below
Admin
School administration.
People, enrolment records, reports, timetable
Bursary
The finance office.
Fee structures, pending payments, payment review
Teacher
Teaching staff.
Attendance, gradebook, courses, own classes
Staff
Non-teaching members of staff.
Schedules and resources within their role
Student
Learners.
Own timetable
Guardian
Parents and guardians.
Own learners' invoices and certificates
Access roles
How much authority a membership carries. Owner, admin, staff and
read-only — applied on top of the membership type, and evaluated
on the server for every request.
OWNERADMINSTAFFREAD_ONLY
Capabilities
Eighteen named grants. A procedure asserts the one it needs;
nothing is implied by holding a neighbouring grant.
The complete capability vocabulary. Each is asserted by the
procedures that need it and resolved from the caller's active
membership.
Capability
What it authorises
institution:read
Reading the institution's own profile and settings.
membership:manage
Inviting, changing, deactivating and reactivating
memberships.
attendance:manage
Recording and amending attendance.
courses:manage
Creating courses and assigning students to them.
grades:manage
Recording and amending grades and grading schemes.
schedule:manage
Timetable resources, entries and class groups.
timetable:manage
Building and changing the timetable itself.
fees:manage
Fee structures and invoice generation.
payments:submit
Declaring a payment against an invoice.
payments:review
Accepting or rejecting a declared payment.
documents:write
Uploading, renaming, archiving and sharing documents.
certificates:issue
Issuing and voiding certificates.
certificates:signature
Signing a certificate on the institution's behalf.
certificates:verify
Verifying an issued certificate.
certificates:delegate
Granting or revoking signature authority.
notifications:manage
The institution's message centre.
resilience:manage
Recovery posture and the quarterly review.
deployment:handover
Evidence that the deployment is ready to be depended on.
Membership lifecycle
People arrive, move and leave. The record should too.
A membership is a state machine, not a flag. Every transition is
written to a lifecycle event, so the answer to "why can this person
not see that?" is a record rather than a conversation.
Membership states and the lifecycle events recorded when they
change.
State
Meaning
Recorded event
Invited
An invitation was issued and has not been accepted.
INVITED
Active
The person has access at the assigned role. Only this
state grants access.
ACCEPTED
Suspended
Access withdrawn without ending the relationship.
DEACTIVATED
Archived
The membership is closed.
DEACTIVATED
Role changed
The access role was raised or lowered, recorded with both
values.
ROLE_CHANGED
Restored
A suspended membership is returned to active.
REACTIVATED
Invitations are for one person
An invitation is addressed to a specific email, carries an
expiry, stores only a hash of its token, and is accepted by
the invited person. It can be revoked before that happens.
Staff number, job title, admission number and tier travel with
it, so accepting it creates the right record rather than a
blank one.
Authority to change authority
Not everyone who can manage memberships can promote anyone.
Who may assign which role to whom is itself a policy — an
administrator cannot create an owner, and an owner cannot be
demoted by an administrator. The rule lives on the server
beside the capability it qualifies.
One account, several schools
A person who belongs to more than one institution keeps one
identity and switches between contexts. The active context is
resolved per request, so a stale browser tab cannot be used to
act in a school the person has since left.
Teaching assignments narrow a teacher
A teacher can be scoped to specific classes rather than the
whole institution. Assignments are set, summarised and
removed, which is how a school employs a teacher for one
subject without handing them the register for the school.
Architecture
One process, four layers, no surprises.
A React client, an Express server speaking tRPC, a typed data layer
on MySQL, and managed object storage. Each layer has one job and
does not reach into the next one's.
The layers, and where each one lives in the repository.
Layer
Responsibility
Where
Web client
Role-aware workspaces, accessibility, lazy routes, error
recovery, light and dark.
client/src/
Application server
One Node process serving the built client, the protected
tRPC API, the sign-in and reset routes, security headers.
server/, api/
Data layer
Typed multi-tenant schema, migrations, repositories,
transactional school operations.
Object references, signed URLs, sharing and lifecycle for
documents.
server/storage.ts,
server/document*
Operations
Deterministic build, migrations on deploy, backup and
restore drill, deployment handover, CI.
scripts/, deploy/,
.github/workflows/
How a request is answered
The client asks
A typed tRPC call from a workspace screen. It carries a
session cookie and the arguments of the call — no tenant,
no role.
The session is authenticated
The signed session is verified. Sessions carry the moment
the account's password last changed, and a session issued
before that moment is refused — so changing a password
ends the old sessions rather than leaving them alive.
The membership is resolved
The active membership, institution, access role and status
are established on the server, from the database, for this
request.
The capability is asserted
The procedure checks the grant it requires. A missing
grant is a refusal, and the refusal is what the client
receives.
The data is read or written, scoped
The repository filters by the resolved institution. Input
is validated by the schema on the way in, so a malformed
or hostile payload never reaches a query.
The typed result returns
The workspace renders a response whose shape it already
knew. If the request fails, the client recovers rather
than showing a broken screen.
Data model, in outline
Institutions own campuses, memberships, invitations and their
lifecycle events. Enrolment records attach learners to
institutions, campuses and tiers. Everything else — attendance,
grades, fees, documents, certificates — hangs off those.
InstitutionsCampusesMembershipsInvitationsLifecycle eventsTeaching assignmentsEnrolment recordsAttendanceCourses and gradesTimetable entriesClass groupsFee structures and invoicesDocuments and sharesCertificates and delegations
Security
What the system assumes about its users.
That some of them will be curious, and one of them might be hostile.
Everything below follows from that rather than from optimism.
No client-supplied authority
Institution and role are never accepted as input. They are
derived, per request, from the authenticated session and the
membership row behind it. There is no parameter to tamper with
because there is no parameter.
Passwords are not stored
Passwords are held as derived hashes with per-account salt,
verified in constant time. Reset tokens are stored only as
hashes, expire, and are single-use — a second attempt with the
same token is refused.
A session knows when it was made
Each session records the password epoch it was issued under.
Changing a password moves the epoch forward and invalidates
every session issued before it, on every device, without a
session table to keep in step.
Input is validated at the boundary
Every procedure declares the shape it accepts. Values are parsed
before they reach a repository, and the data layer uses
parameterised queries, so a shape the schema does not describe
never travels further than the edge.
Files are never public
Object storage is private. Reads are issued as signed, expiring
URLs produced only after the capability and institution have
been checked, so possession of a path is not possession of a
document.
Secrets stay out of the image
Credentials and connection strings are environment
configuration, never source. The application carries no secret
it could leak through a response, a log line or a client bundle.
Technology
Chosen for the property that it fails loudly.
TypeScript end to end, a schema that is the source of truth for both
the database and the API contract, and a small number of moving
parts. A school system is not a place for cleverness nobody can
debug at term start.
The stack, and what each piece is responsible for.
Concern
Technology
Why it is here
Interface
React 19, Vite, Tailwind 4
Fast builds, a component model that scales past fifty
screens, and a real design system.
Design system
Material Design 3, Radix, shadcn
Accessible primitives underneath a token-based theme, so
light and dark are one implementation.
Routing
Wouter
A small router with lazy route loading and no ceremony.
API
Express 5, tRPC 11, Zod
Typed calls with no hand-written client, and validation that
is the same code on both sides.
Data
Drizzle ORM, MySQL / TiDB
A typed schema that generates migrations and the query types
from one definition.
Files
S3-compatible object storage
Documents out of the database, reached only through signed
references.
Documents
React PDF
Registers, rosters and certificates rendered from the same
records the screens use.
State and data
TanStack Query
Caching, retries and request lifecycle handled once rather
than per screen.
Quality
Vitest, TypeScript, Prettier, CI
Tests that run the same way locally and in the pipeline,
with a type check gating the deploy.
Hosting
Vercel
Production deploys from the repository, functions placed in
the region nearest the data.
Operations
School data is part of the release, not follow-up work.
A deployment is not finished when the page loads. It is finished
when the migrations have run, the restore has been rehearsed, and
someone has recorded that it was.
Migrations run with the deploy
Schema changes are applied as part of the production build, and
the deployment is stopped if the configuration or the migration
is not valid. A release that would run against the wrong schema
does not reach the users.
Restore is rehearsed, not assumed
Recovery is a workspace with a quarterly review and recorded
evidence, so the institution can show when it last proved it
could come back. The drill is a routine, not a paragraph in a
policy.
Handover is evidenced
The deployment handover workspace collects the proof that the
installation is ready to be depended on — configuration, access,
recovery and operations — under
deployment:handover, so readiness is a state
someone can check.
Functions follow the data
The API runs in the same region as the database, so a query does
not cross an ocean to be answered. Region placement is a
deployment setting that travels with the project, not a
per-environment hope.
Mail is sent from a domain the school owns
Password resets and account notices go out through a dedicated
sending domain with SPF, DKIM and DMARC published, so the
school's own mail is verifiable and not refused for looking like
someone else's.
Quality gates before production
The type check, the test suite and the format check run in CI. A
change that breaks a gate does not deploy, and the gate is the
same command that runs locally, so there is nothing to disagree
about.
FAQ
Questions a school actually asks.
Can one deployment serve several schools?
Yes, and that is the point of the tenancy model. Each
institution is a tenant with its own campuses, tiers,
branding, memberships and records. A person's access is a
membership in one institution, and the server resolves which
institution a request is acting in before any data is
returned.
The isolation is enforced in the data layer, not in the
interface. Hiding a navigation link is a convenience; the
query filter is the boundary.
What happens when a teacher leaves?
Their membership is deactivated rather than deleted. Access
ends immediately, the lifecycle event records who did it and
when, and everything they recorded stays attached to the
institution. Reactivating them later restores the same
membership rather than creating a second one.
Can a guardian see other children's records?
No. A guardian's access is derived from explicit guardian
assignments, and the guardian workspaces are built on those
relationships. There is no code path that returns another
learner's record to a guardian, so this is a property of the
queries rather than a setting someone could get wrong.
Do different school tiers behave differently?
Yes. An institution enables the tiers it operates — creche,
nursery, primary, secondary, tertiary — and the application
adapts to them. Tertiary institutions, for example, can define
their own grading scheme rather than being given a default
that does not describe how they assess.
What does "self-contained" mean for a deployment?
The application does not depend on a service the school cannot
see or control. One process serves the client and the API; the
database and the object storage are the only external
dependencies, and both are configuration the operator owns.
There is no hidden third-party call in the request path.
How are documents protected?
Files live in private object storage and are never served from
a public path. Opening a document asks the application, which
checks the capability and the institution, then issues a
signed reference that expires. Shares name their recipient and
can be revoked, after which the reference stops resolving.
What happens to a certificate issued in error?
It is voided, and a corrected one is issued as a new document.
Nothing is edited in place and nothing is deleted, so the
record of what was issued remains complete — which is the
behaviour that survives an inspection.
Does it work on a phone?
Yes. The client is responsive throughout, with mobile-specific
interaction work — quick actions, drawer gestures and touch
targets — and it installs to a home screen as a web app. For a
teacher marking a register in a classroom, that is the case
that matters.
Is there an accessibility story?
Material Design 3 places a floor under contrast, focus states
and type scale, and the application honours it in both light
and dark schemes: visible focus rings, real landmarks and
headings, a skip link, and motion that yields to a
reduced-motion preference.