Enroll.software · Built and wired · the residue is named on this page

The intake pipe, not the form builder.

Most enrollment products sell you a form. A family fills it in, a PDF lands in an inbox, and a registrar re-keys the parts that matter into the system of record by hand. That trade is fine for a district with time to spare — but nothing about it is verifiable, and nothing about it is a machine anyone can point to and say “this is why the seat went where it went.”

Enroll.software is the pipe underneath: a public intake lane that resolves the school from a token rather than trusting the caller, a consent choice captured on the form that lands on the exact rows every other gate in the platform reads, a closed state machine that will not let a registration skip a step, and a school-choice lottery built to be checked, not trusted — a commitment, a frozen roster, a deterministic draw, and an offline verifier that any family can run without logging in.

And where a piece of this is written but not wired — residency verification, records transfer, year-end promotion, an earlier registration design — this page names it by file, next to its own test count, rather than folding it into the pitch.

13reachable engines, each traced to a registered route — the intake lane, the consent chokepoint, two state machines, and the five-part verifiable lottery
6modules written and tested with no route calling them, named on this page by file rather than counted as shipped
0names published in the lottery’s public draw or verifier — opaque entrant tokens only, by design, not by redaction after the fact
84 / 134tests on the wired side versus the unwired side, across 10 and 5 files — stated here because a bigger number on the inert side is exactly the asymmetry a page like this is tempted to hide

The honest frame

What this is, and what it is not.

It is an intake-to-roster pipe. A public link resolves a school from an opaque token, a bounded applicant shape lands as a pending registration, a records official runs it through a closed review state machine, and exactly one transition — finalize — ever writes a roster row. Nothing upstream of finalize touches the student record.

It is a consent chokepoint. A guardian’s per-purpose consent choice is captured on the form and mapped, at finalize, onto the same canonical rows every downstream gate in this platform reads. There is exactly one path that writes those rows from an enrollment; this is it.

It is a verifiable lottery, where a district runs one. Commit the seed, freeze the roster, reveal and draw, and let anyone — not just a logged-in parent — re-run the math from the published record and see exactly which of five checks failed, if any did.

A school-to-school records-transfer workflow, a residency-verification lane, and a promotion advisor each carry zero production call sites today. Each is written and tested; none is reachable by a real request. All three are named on this page rather than quietly implied by proximity to the parts that do work — the honest version of this section is the one that costs us a cleaner pitch, and we ship it anyway.

And it is not a form builder wearing a school badge. The difference this page is built to make is that every claim above traces to a file, a route registration, and a test file — named, not implied.

Built and reachable — a registered handler calls it

The engines that are actually wired.

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.

routes/registration-link.ts No roster mutation on submit. The token is server-minted; rotate kills the old one at once.

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.

routes/enrollment.ts, routes/registration-link.ts (ApplicantSchema) A bounded shape at the boundary, shared by both lanes so they cannot silently drift apart.

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.

domain/enrollment-registration.ts (surfaceCounselDuties) Data plus a visible flag. No automatic clock, no automatic transport, no auto-enrollment.

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 — submittedin_reviewneeds_infoapprovedfinalized, 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.

domain/enrollment-registration.ts (applyRegistrationTransition) Pure state machine. The roster write happens once, at one named transition, and nowhere else.

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_registeredenrolled, 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.

domain/enrollment-registration.ts (applyEnrollmentTransition) A closed transition table. An illegal edge is a 400, never a state the roster silently accepts.

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.

domain/lottery-draw.ts (commitSeed, entrantsDigest) The seed stays hidden until the draw. The rules and the roster are both locked before it is revealed.

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.

domain/lottery-draw.ts (drawLottery) Pure function. No clock, no database, no network read anywhere in the draw itself.

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.

domain/lottery-ledger-hashchain.ts (verifyLotteryChain) Append-only, tamper-evident. A correction is a new chained entry, never an edit to an old one.

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.

domain/lottery-verifier.ts (verifyLotteryRecord, verifyEntrantReceipt) Pure, offline, no PII. This is the whole claim: fairness as physics, not a promise to trust.

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.

domain/sch-records-folder.ts (recordDisclosure), routes/records-ferpa-bridge.ts Append-only, basis-bearing disclosure log. Wired for one recipient class today.

Built, not switched on

Six modules with no production caller. Here they are by name.

This table is the part of the page we would remove if we were selling harder. Every module below is written, is covered by its own test file, and is imported by nothing outside its own siblings and its own tests. None of it is reachable from any registered API route.

Two of the six deserve a second look, because each one was deliberately superseded rather than merely unfinished. The pending-applicant registration design gave way to the simpler, wired registration-link lane before it ever shipped a route. And the seeded school-choice lottery gave way to the SHA-256 provable-fair system described below — a strictly harder engine to build, and the one we chose to wire. Leaving the earlier engines in the table, rather than deleting them quietly, is the same discipline as leaving them in the repository: the decision to prefer the wired system over the older one should be checkable, not just asserted.

ModuleWhat it doesWhy it is not reachableIts own tests
Pending-applicant registrationAn earlier design for a brand-new student’s intake: submitted → under_review → accepted, with the roster-create seam firing only on accept.Superseded before it was ever wired. The registration-link lane above shipped a different, simpler shape (submitted → in_review → needs_info → approved → finalized) and became the one a route actually calls.spr-online-new-student-registration.test.ts — 36 tests
Residency (proof-of-address) verificationDecides a residency claim from staff-verified proof documents, with a McKinney-Vento equity rule: a staff-verified affidavit alone can verify a homeless student without a lease.No route imports it. A real, working policy engine with zero production call sites in the API.spr-residency-verification.test.ts — 17 tests
Seeded school-choice lotteryA separate, simpler seeded Fisher-Yates draw over sibling / zone / open priority tiers, written before the SHA-256 provable-fair system above.Superseded. The provable-fair, commit-reveal, ledger-anchored, offline-verifiable lottery is the one that is wired; this earlier engine has no caller under apps/.spr-school-choice-lottery.test.ts — 19 tests
School-to-school records transferPackages and transmits a student’s cumulative record to a receiving school, logging the move as a FERPA disclosure through the same recorder the wired parent-release route uses.No route imports requestTransfer or importTransfer. The recorder it would reuse IS wired for a different recipient class; this specific workflow is not.sch-records-transfer.test.ts — 19 tests
Year-end promotion / retentionAn advisory engine that reads a registrar’s recorded decision for a cohort and never auto-retains a student without a recorded policy basis attached.Same file as the transfer workflow above; no route imports evaluatePromotion or promotionList.sch-records-transfer.test.ts — 19 tests (shared file)
Enrollment deposit holdAn exact-integer-cent engine for holding an enrollment deposit against a seat.No route imports it. Moot in practice as much as it is inert in fact: money stays honest-off across this whole estate regardless of wiring.cbuild-enrollment-deposit-hold.test.ts — 43 tests

Two closed state machines, not a status text field

A registration cannot skip review, and an exit cannot predate the enrollment it is exiting.

The registration-review machine and the enrollment-lifecycle machine are two separate, closed transition tables. Neither accepts an edge it does not recognize, and both reject the attempt rather than clamp it to the nearest legal state.

There is never a status you can set from the client; every legal transition is verified server-side against the table, not against a text field a form can spoof. That is an easy thing to claim and a cheap thing to fake, so this page states it as a checkable fact rather than a slogan — the honest form is naming the table, not describing it.

submittedin_reviewneeds_infoapprovedfinalized

The registration machine, with its two other exits — rejected and withdrawn — omitted from the strip above for space. finalized is the only state, out of all seven, that has ever mutated the roster.

pre_registeredenrolledwithdrawn / transferred_out / graduated / no_show

Every exiting transition requires an exit reason, and an exit date earlier than the recorded enrollment date is rejected at this layer, before anything is written — not caught later by a report someone has to remember to run.

The feature we believe no incumbent ships

A school-choice lottery a family can check without trusting anyone’s word for it.

Every oversubscribed choice or open-enrollment program eventually runs a lottery, and every family that does not get the seat they wanted has the same question: was it actually random, actually fair, actually run the way the rules said it would be? The ordinary answer is a portal that shows you a result and asks you to trust it. This is the other answer — the mechanism is public, and the check does not require trusting us at all.

  1. Commit

    Before entries close, the district commits a SHA-256 hash of the hidden seed and the full ranked tier rules — sibling, in-zone, FRL set-aside, open, each an explicit rank and label. Every one of those rides into the commitment. A tier ranking changed after commit breaks the hash, and the offline verifier will say so.

  2. Freeze

    Before the seed is revealed, the exact entrant roster — who is in the draw, at what tier, at what weight — is frozen and its own fingerprint anchored to the ledger. This closes the gap an earlier version of this feature had: a commitment that bound only the seed and the rules left the roster itself editable right up to the draw.

  3. Consent-gate

    Before the frozen roster is drawn, every candidate is checked against three fail-closed arms — a resolvable consent record, the platform-wide publication kill switch, and a live opt-in directory-information grant. Anyone who does not clear all three is withheld entirely; their opaque token never enters the public draw, and their name was never in it to begin with.

  4. Reveal and draw

    The seed is revealed and a deterministic keyed-hash ordering runs over the frozen, admitted roster. Tier rank sorts first, always; the seeded key only ever breaks a tie within one tier. Seats fill in that order up to capacity; the remainder is the waitlist, in the same order.

  5. Anchor and publish

    The commitment, the freeze, and the result are each chained onto an append-only SHA-256 ledger, one entry binding to the previous entry’s hash. The published record carries the revealed seed, the revealed salt, the rules, the frozen entrant list, and the result — opaque tokens throughout, never a name.

  6. Verify, offline, by anyone

    The verify endpoint takes only that published JSON. No school scope, no database read, no login. It re-derives the commitment from the revealed seed and salt, re-derives the rules digest, re-runs the entire draw and checks the digest, checks the published ordering byte for byte, and checks the entrant-set fingerprint against the freeze. It reports which of those five checks failed, if any did — not a bare yes or no.

Four anchor kinds, one chain per schoolSHA-256 hex
seed_committhe hash of the hidden seed, before entries closeentryHash[0]
rulesthe canonicalized, ranked priority tiers + seat countentryHash[1]
entrants_freezethe frozen roster fingerprint, before the seed is revealedentryHash[2]
resultthe final placement ordering, published and verifiableentryHash[3]

Each row’s hash binds its own canonical payload to the PRIOR row’s hash (genesis is 64 zero hex characters). Editing, deleting, or reordering any entry breaks every link that comes after it, and the chain verifier reports the first broken index rather than a bare true or false.

One scope bound stated plainly, because it is the honest edge of the claim: we provide the provable mechanism; the district owns the policy. Weighted set-asides, which tiers exist, how many seats are offered — all of that is the operator’s decision, documented and hashed into the commitment rather than hidden, but not something this engine invents or second-guesses. A district that runs an unfair policy through a perfectly verifiable mechanism has still run an unfair policy; verifiability proves the mechanism did what it committed to, not that the commitment itself was just.

Data, not automation

McKinney-Vento and MIC3: a flag a human sees, never a clock this code starts.

A guardian filling out the intake form can self-declare two things: that the family is experiencing housing instability under the McKinney-Vento Homeless Assistance Act, or that the student is transferring under a military-connected relocation the Military Interstate Children’s Compact Commission (MIC3) covers. Both are ordinary checkboxes on a form, and both are treated as exactly that — self-declared data, not a verified legal determination.

What the flag does: it is stored, and it is copied onto the enrollment record so a homeless liaison or a registrar sees it the moment they open the file. What the flag does not do: it does not start a clock on immediate enrollment, it does not dispatch a transportation request, it does not open a dispute-resolution case, and it does not automatically transfer academic or extracurricular eligibility credit under the interstate compact. Those are real legal duties — §722 of the McKinney-Vento Act and the MIC3 compact both create obligations a school has to discharge — and they need a trained person exercising judgment, not a background job. The module that carries this flag says so in its own header, and this page repeats it rather than letting the flag imply more than it does — the honest reading is data plus visibility, never automation.

Named honestly, not folded in

Residency, records transfer, and year-end promotion: real engines, zero production call sites.

Three adjacent capabilities are written and tested and are not part of the wired product today. We would rather a district read that here than discover it mid-pilot.

Residency verification. A policy engine that decides whether enough staff-verified proof documents establish that a student lives in a district or an attendance zone — it never auto-verifies an unreviewed upload, a human has to confirm each one. It carries a real equity rule: a single staff-verified McKinney-Vento affidavit can satisfy residency on its own, so a homeless student is never denied enrollment for lack of a lease. No route calls it.

School-to-school records transfer. A workflow that would package a student’s cumulative record and log the handoff to a receiving school as a FERPA disclosure, reusing the same disclosure recorder the platform already calls for a parent-facing release. Because it reuses rather than reimplements that recorder, wiring it is a smaller gap than the module count alone suggests — but it is still an unwired gap today, and this page is not going to round that up.

Year-end promotion and retention. An advisory engine, in the same file as the transfer workflow, that reads a registrar’s recorded per-student decision against a cohort policy and never invents a retention on its own — a “retained” advice is honored only when a recorded policy basis backs it, otherwise it falls back to pending. No route calls this one either.

Against the incumbent enrollment-management model

Eight rows. The incumbent model wins the first two, outright.

A vendor comparison table that sweeps every row is a table nobody should believe, so the first two rows here go to a district’s existing registrar’s office without qualification: a live phone line staffed by someone who has done this for twenty years, and a paper fallback for a family without reliable internet, are real advantages a piece of software behind an intake form does not replace on its own.

The six rows underneath describe what we can verify about our own source, stated as precisely as we can make it. Where the other column would require asserting a competitor’s internal behaviour, it says not established — because we have not read another vendor’s code and are not going to guess in the direction that flatters us.

The left column states our own measured behaviour. The middle column states only what the category openly advertises, or that we could not establish it.
What you are buyingAn enrollment-management incumbentEnroll.software
A live phone line and a person who has done this for twenty yearsYes. A registrar's office with an experienced staff is a real advantage a family can call.No. We are software behind the intake and the lottery. A district still staffs its own registrar's office.
A paper fallback for a family without reliable internetYes. A front-office intake process handles a walk-in or a phone application as a matter of course.Not built into this page's surface. The public intake lane assumes a link and a browser.
A school-choice lottery a family can mathematically re-derive, offline, without logging inNot that we have found openly documented. We make no claim about how any provider runs its draw internally.Yes. Commit, freeze, draw, and an offline verifier that reports which of five checks failed if any did.
The priority-tier rules committed and hashed before entries closeNot established. We have not read any provider's lottery implementation and will not characterise it.Yes. Sibling, zone, FRL and open tiers, each an explicit rank, all bound into the pre-draw commitment.
Tamper-evidence on the lottery record itself, not just an access logNot established as a public claim we could find.Yes. A SHA-256 hash chain over four anchor kinds; a back-dated edit breaks every link after it.
Consent resolved the moment a student is enrolled, on the same rows every downstream gate readsNot established. We cannot see how any provider's consent capture maps onto its own gates.Yes. Finalize maps the guardian's per-purpose choice onto the canonical consent rows; there is no second path.
An enrollment exit that cannot predate the enrollment date it is exitingNot established as a stated validation rule.Yes. The transition is rejected outright, at the state-machine layer, before anything is written.
What happens when a photo-day consent has lapsedNot established. We do not know whether or how a provider distinguishes this from other consents.It never gates the lottery. A lapsed picture-day form and a lottery seat are deliberately different consents.

One note on the category, since this page makes a claim about it. The enrollment-management and school-choice-lottery software market this product is measured against includes established K-12 SIS and enrollment vendors broadly. They are named here once, only as a factual category reference, and none of their code, copy, or design is used anywhere in this product. Nothing above is a claim about how any specific vendor is built internally — we have not read their source and cannot, so every row either cites our own measured behaviour or says outright that we could not establish theirs.

Fifteen test files, by name and by count

What is actually proven, and where the bigger number sits.

Ten test files sit on the reachable side of this product — the routes and domain modules a registered handler calls — carrying 84 tests between them. Five sit on the inert side, carrying 134. That is not a typo and it is not buried: the modules with no production caller are, on average, MORE thoroughly tested than the ones a district could actually hit today. A green suite proves a function computes the right answer; it says nothing about whether anything calls it, and this page would rather show you that asymmetry directly than let a big total imply reach it does not have.

Reachable side

R1 · Enrollment routes routes/enrollment.test.ts 5 tests
R2 · Enrollment route security routes/enrollment.security.test.ts 5 tests
R3 · Registration-link routes routes/registration-link.test.ts 8 tests
R4 · Registration-link security routes/registration-link.security.test.ts 5 tests
R5 · Lottery routes routes/enrollment-lottery.test.ts 12 tests
R6 · Lottery entrant-set freeze routes/enrollment-lottery-freeze.test.ts 6 tests
R7 · Lottery verifier, freeze case domain/lottery-verifier-entrants-freeze.test.ts 6 tests
R8 · Lottery consent gate domain/lottery-consent-gate.test.ts 11 tests
R9 · Lottery ledger hash chain domain/lottery-ledger-hashchain.test.ts 8 tests
R10 · Lottery domain (full) domain/comms-voice-enrollment-lottery.test.ts 18 tests

Inert side (built, not switched on)

I1 · Pending-applicant registration spr-online-new-student-registration.test.ts 36 tests Covers a module with no production caller
I2 · Residency verification spr-residency-verification.test.ts 17 tests Covers a module with no production caller
I3 · Seeded school-choice lottery spr-school-choice-lottery.test.ts 19 tests Covers a module with no production caller
I4 · Records transfer + promotion sch-records-transfer.test.ts 19 tests Covers a module with no production caller
I5 · Enrollment deposit hold cbuild-enrollment-deposit-hold.test.ts 43 tests Covers a module with no production caller

Data, walls, and what we hold

A minor’s intake record is an education record from the moment it is collected.

Every authenticated route in this surface runs the same shape of gate: student data is denied to a rep-class actor first, before anything else is checked, and then an affirmative allowlist admits only school_admin or district_admin — a masthead adviser title is explicitly not on that list, and any minor-held role is denied before the allowlist is even reached. The public intake lane runs anonymously by design, but resolves the school it is writing into strictly from a server-minted token, never from anything the caller states.

The lottery goes further than the intake lane has to, because its output is public by design. The published record and the offline verifier both operate on opaque, per-lottery entrant tokens only. The mapping from a real student to that token lives behind its own access wall and is never part of what gets published, drawn over, or verified — a family checking their own placement supplies their own token; there is no name field anywhere in the record to search by.

To be exact rather than flattering: this is not a claim that we hold no data. A registration record is a minor’s identifying information — name, date of birth, home address, guardian contacts — and it is treated as sensitive from the instant it is submitted, not from the instant it is finalized. The honest claim is narrower: it is walled at the tenant and at the role, resolved server-side rather than client-trusted, and where the design allows it entirely (the lottery’s public surface), it is built so a name never has to cross the wall at all.

Common questions

The questions a registrar’s office actually asks.

Is Enroll.software live at a real school district today?

We are not naming any district on this page, and we are not going to claim adoption numbers we cannot back. What we can say precisely: the public intake lane, the consent chokepoint, the enrollment lifecycle, and the provable-fair lottery are registered routes in the product, with real database migrations behind them, checked by their own test files. That is a claim about the source, not a claim about a specific school's daily use of it, and we are keeping those two claims separate on purpose.

What actually happens when a family submits the intake form?

The school is resolved from the link's token, server-side — the family never states which school they are applying to in a way the server trusts. The submission is validated against a bounded shape (name, DOB, grade, address, up to ten guardians), any consent choice they made is captured alongside it, and the record lands in a submitted state. Nothing about the roster changes yet. A records official has to move it through review before finalize — the one and only transition that writes a roster row.

How is the lottery actually verifiable? Walk through it.

Before entries close, the district commits a SHA-256 hash of the seed and the full, ranked tier rules. Before the seed is revealed, the exact entrant roster is frozen and its own fingerprint is anchored. At draw time the seed is revealed and a deterministic keyed-hash ordering runs over the frozen roster, tier rank first, the seeded key breaking ties within a tier. The commitment, the freeze, and the result are all chained on an append-only SHA-256 ledger. Anyone — not just the district, not just a logged-in parent — can take the published record and run the offline verifier, which re-derives the commitment, re-derives the rules digest, re-runs the draw, and checks the published ordering byte for byte. It reports exactly which of those checks failed if any did, rather than a bare pass or fail.

Does every applicant's name end up in that public lottery record?

No. The public draw and the public verify endpoint see only an opaque per-lottery token for each entrant — never a name, never a raw student id. The mapping from a real student to that opaque token lives behind a separate access wall and is never part of what gets published or verified. A family checking their own result supplies their own token; they cannot look up anyone else's by name, because no name is anywhere in the published record to look up.

What keeps a student out of the lottery, and is that fair?

A student is withheld — not redacted, withheld entirely, so there is no partial record to leak — in exactly three cases: no resolvable consent record for them at all, the platform's own do-not-publish kill switch, or no live opt-in directory-information consent on file. Notably absent is a fourth case an earlier draft of this feature had: a lapsed or declined school-photo consent. We removed that on purpose, because a family that never returned a picture-day form was quietly losing their child's shot at a lottery seat over an unrelated permission, and that is exactly the kind of consent-basis-reuse this estate's own privacy law forbids elsewhere.

What are McKinney-Vento and MIC3 flags, and what do they do?

They are self-declared checkboxes a guardian can mark on the form — McKinney-Vento for housing instability, MIC3 for a military-connected transfer. Marking one stores the flag and surfaces it on the enrollment record so a liaison or registrar sees it immediately. That is all it does automatically. The actual legal duties those statutes create — immediate enrollment, transportation, dispute resolution, interstate credit transfer — are not things software should silently discharge on a family's behalf, and this product does not try to. It surfaces the flag to a human who is trained to act on it.

Can I verify residency, transfer a student's records, or run year-end promotion through this product?

Not today, and we would rather tell you here than in a pilot. All three are written, and all three are tested — the residency engine even carries a McKinney-Vento equity rule so a homeless student is never denied for lack of a lease — but none of the three has a route that calls it. They are named on this page, by file, under “built, not switched on,” rather than counted as shipped features.

Is there more than one lottery engine in this codebase? Why?

Yes, honestly, there are two. An earlier, simpler seeded-shuffle design (sibling / zone / open tiers, one PRNG, no cryptographic commitment) was written first. The provable-fair, commit-reveal, ledger-anchored, offline-verifiable system described on this page was built afterward and is the one wired into a route. The earlier engine still exists in the repository, still has its own passing tests, and is not called by anything — we list it that way rather than pretend it was never built.

How is this different from a form-builder or a generic online-forms product?

A form builder collects fields into a submission and stops. This product ties intake to a state machine a school actually runs a registration through, to a consent chokepoint that resolves onto the same rows every other gate in the platform reads, and — where the district runs a lottery — to a cryptographic commitment a family can check without trusting anyone's word for it. We are not going to characterise how any particular form-builder or SIS handles its own intake internally, because we have not read its source and cannot know.

Does any of this touch children's data carelessly?

The intake payload is a minor's identifying information from the moment it is collected, and it is treated that way: bounded shapes, a records-office-only gate on every authenticated route, and a public lane that resolves the school from an opaque token rather than trusting the caller. The lottery specifically goes further — it is architected so a name never needs to appear in the public, verifiable part of the process at all. That is not a claim that we hold no data; it is a claim about where the wall sits and what crosses it.

What does it cost?

There is no price on this page and no checkout anywhere on this site. The honest next step is a conversation about which piece of this — the intake pipe, the consent chokepoint, or the verifiable lottery — matches what your registrar's office actually needs, and about the residue named above, which you should know about before a pilot, not during one.

Is Enroll.software a school, a district, or a nonprofit?

No. It is a for-profit software product built by Stanley Studios. It is not a school, not a district, and not a nonprofit, and nothing here is a charitable gift of any kind.

Related surfaces

Adjacent products, each its own thing.

Enroll.software is the front door to the roster. These are the neighbouring surfaces, kept as separate products rather than folded into a suite.

payroll.software

The same posture applied to a different back office: a calculation engine with its refusals in the first screen, not the footnotes.

tuition.software

The family-billing side of a school’s money. A finalized enrollment is often the event that starts a tuition record; we keep the two products separate on purpose.

What this page is and is not claiming

The whole posture, in one paragraph.

Enroll.software is built and wired for four real surfaces: a public, token-resolved intake lane that never mutates the roster before a records official finalizes it; a consent-at-intake chokepoint that writes the same rows every downstream gate reads; two closed state machines (registration review and enrollment lifecycle) that reject an edge they do not recognize; and a provable-fair, commit-reveal, SHA-256-ledger-anchored school-choice lottery with a free-standing offline verifier that reports which of five checks failed, if any did. Three adjacent, real capabilities — residency verification, school-to-school records transfer, and year-end promotion — and an earlier pending-applicant registration design it superseded each carry zero production call sites, all four named above by module and by their own test file rather than folded into the pitch. There is no name in the lottery’s public draw or verifier, by construction, not by after-the-fact redaction. There are no adoption counts, no district named, no testimonials, and no invented figures anywhere on this page; the only measurements quoted are the route registrations in app.ts and the test counts in each module’s own file, and the inert side’s test count is stated even though it is larger than the wired side’s, because that asymmetry is exactly the kind of fact a page like this is tempted to bury. Money stays honest-off. There is no pricing and no checkout on this site. The honest next step is a conversation.

Enroll.software's public intake lane, its consent-at-intake chokepoint, its enrollment lifecycle, and its provable-fair lottery are registered routes behind a registrar gate and a tenant wall, with real migrations under them -- not a roadmap slide. A dedicated residency-verification lane, a school-to-school records-transfer workflow, a year-end promotion advisor, and an earlier pending-applicant registration design each carry zero production call sites today -- named below by module and by test file, not folded quietly into a feature list.