Each of these is called by a route registered in the marketing app’s own app.ts, behind a tenant wall and a registrar-only or student-data-denied gate. “Reachable” is a source-level claim: a handler calls it and app.ts registers that handler. It is not a claim that a real district has run it against production data today.
Public intake, no login
The public intake lane — a token resolves the school; the caller never states it
A family reaches a registration form at a link the school published. The school is resolved server-side from the opaque token, under a system scope, before anything else happens — the caller never supplies a school id, so there is nothing to spoof. A missing, expired, inactive, or out-of-window token returns the exact same uniform 404 as a token that never existed, so the endpoint cannot be used to enumerate which schools have a link open. The submission lands as submitted and does nothing else: it does not touch the roster, and it cannot until a records official reviews it.
The registrar's link manager
Mint, list, rotate, deactivate — a registrar-only surface, not a shared secret in an email thread
A registration link is created, listed, rotated, and deactivated behind an affirmative allowlist: only school_admin or district_admin is admitted. A masthead adviser title is explicitly not on that list, and a minor-held role is denied before the allowlist is even consulted. Rotating a link mints a brand-new token and the old one stops resolving immediately — there is no grace window where both work, because a link a family bookmarked from a leaked email is exactly the case rotation exists for.
The applicant payload, bounded
A closed shape for a child’s intake record, not an open text box
Name, date of birth, grade, home address, up to ten guardians with a relationship and a contact — every field is length-bounded and type-checked at the boundary, on both the authenticated and the anonymous lane, from the same shared shape. Name fields are NFC-normalized on the way in, and normalized only — never folded or stripped, because collapsing diacritics or casing on a minor’s legal name is a real harm, not a cosmetic one.
Consent captured at intake
The guardian’s consent choice rides the form, and lands on the SAME rows every gate reads
A guardian’s per-purpose consent decision is captured on the registration itself. At finalize — and only at finalize — those explicit granted / denied choices are mapped onto the same canonical consent rows every downstream gate in this platform reads. A pending choice is never written as a grant; it falls through to the fail-closed substrate default. So a finalized student is consent-resolved the instant they are enrolled, and intake can never fabricate a grant the family did not make.
McKinney-Vento and MIC3 flags
Self-declared, surfaced to a human — never auto-discharged
A guardian may self-declare a McKinney-Vento housing-instability flag or a MIC3 military-family transfer flag on the form. Both are stored and copied onto the enrollment row so a homeless liaison or registrar sees them on sight. Neither flag triggers anything automatic: the §722 liaison duties (immediate enrollment, transport, dispute resolution) and the MIC3 interstate-compact protections are legal duties that need a person, not code, and the module’s own header says so — this surface names the duty and stops.
The registration review state machine
Submitted to finalized, and nowhere else — a records officer runs it, not a status field a form can fake
A registration moves through a closed set of states — submitted → in_review → needs_info → approved → finalized, with rejected and withdrawn as the two other exits. Every transition is validated against the current state; an edge the machine does not recognize is refused, not attempted. finalized is the ONLY state that has ever mutated the roster — every earlier state is entirely inert with respect to the student record, no matter how far along the review looks.
The enrollment lifecycle
Six states, five closed transitions, and an exit reason the machine will not let you skip
Once a student is on the roster, the lifecycle is its own closed machine: pre_registered → enrolled, and from there to withdrawn, transferred_out, graduated, or no_show. Every transition that exits enrollment requires an exit reason — there is no silent withdrawal. And an exit date is rejected outright if it falls before the recorded enrollment date, which is the ordinary way a records office would otherwise discover a data-entry error three years later during an audit instead of at the moment it was typed.
Lottery: commit, then freeze the roster
The seed is committed before entries close. The entrant roster is frozen before the seed is revealed.
Before a district accepts entries, it commits a SHA-256 hash of the seed and the full, ordered priority-tier rules — sibling, in-zone, FRL set-aside, open, each with an explicit rank and label. Nothing about the tiers is hidden; all of it rides into the commitment, so a tier ranking changed after commit breaks the hash and the offline verifier catches it. Separately, before the seed is ever revealed, the entrant set itself is frozen — its own fingerprint anchored on the ledger — so an applicant cannot be quietly added, dropped, re-tiered, or re-weighted in the gap between commit and draw. That gap is exactly where an earlier design of this feature had a real hole; the freeze step exists because of it.
Lottery: the consent admission gate
Three arms, fail-closed, and a fourth one that was deliberately removed
Being placed in a publicly verifiable draw pseudonymously discloses that a child applied — so admission is gated the same way any external disclosure is: opt-in directory_info consent, default-deny. Three arms, checked in order: no resolvable consent context withholds; the platform-wide do-not-publish kill switch withholds; no live opt-in grant withholds. A fourth arm existed in an earlier draft — a declined or lapsed school-photo consent also withheld a child from the draw — and it was removed on purpose. A picture-day permission form and a place in an admissions lottery are not the same consent, and letting one gate the other would have quietly denied an educational opportunity to a family that simply never returned a photo form.
Lottery: the deterministic draw
The same seed, the same roster, the same rules, always the same result
The draw orders every admitted entrant with a seeded keyed hash — no Math.random anywhere in the module, node:crypto SHA-256 only. Tier rank is a first-class sort key, applied above the random ordering: a lower-priority tier can never draw ahead of a higher one no matter what the random key says, and the random key only ever breaks ties within one tier. Run it again with the same three inputs — seed, entrant set, rules — and it produces the identical placement ordering, position for position. That reproducibility is not a side effect; it is the entire mechanism the offline verifier checks against.
Lottery: the append-only ledger
A SHA-256 hash chain over four artifacts, because a database trigger alone does not survive a privileged edit
Every anchor — the seed commitment, the canonicalized rules, the pre-reveal entrant freeze, and the final result — is chained: each row’s hash binds its own payload to the previous row’s hash, so a back-dated edit, a deletion, or a reorder breaks every link after it. The table underneath already carries a database trigger that refuses an UPDATE or DELETE outright, and the module says plainly why that is not enough on its own: a trigger stops the application from mutating a row, but a privileged out-of-band write — a DBA, a restored backup that predates the trigger — leaves the trigger no evidence to point to. The hash chain does. A verifier walks the chain and reports the first index where it breaks, not just a pass/fail bit.
Lottery: the offline verifier
No login, no network call, no phone-home — a family re-runs the math itself
The verify endpoint reads only the JSON record it is handed. No school scope, no database read, no PII anywhere in the request or the response. It runs five independent checks and reports exactly which one failed if any did: does the revealed seed and salt re-derive the published commitment; do the published rules re-derive the committed rules digest; does re-running the draw from the revealed seed reproduce the anchored result digest; is the published placement ordering byte-exact against the re-derived one; and does the published entrant set re-derive the frozen roster fingerprint. A single family can also check just their own opaque token’s placement — offered or waitlist, at what position — without ever exposing another applicant.
The FERPA disclosure recorder
A 99.32-shaped disclosure log, wired for a parent-facing release, reused (not duplicated) where it applies
Releasing a student’s education record is a FERPA disclosure, and this platform has one recorder for that fact rather than one per feature that needs it. The route wired today calls it for a parent recipient class. It is the same function the (currently unwired) school-to-school transfer workflow below is written to reuse rather than reimplement — which is the honest reason that workflow is a small gap to close rather than a rebuild.