Security & trust
The protections that matter live below the application.
The honest question a district’s IT office asks is “what happens when your app code has a bug?” Our answer is that the load-bearing walls aren’t in the app. A query bug can’t return another school’s rows because the database decides what’s visible; a bypassed consent check can’t publish a blocked student because the gate is a database trigger; and raw card data never reaches our server at all. This page is the posture, sourced from the real architecture.
Five walls, each below the app
Tenant isolation
Row-level security, forced
Postgres RLS with FORCE ROW LEVEL SECURITY decides row visibility — not the query. Coverage is gated in CI; the single-school wall is proven against real Postgres. Shipped
Consent gates
Fail-closed, with a DB backstop
The sensitive lanes are opt-in; do-not-publish denies everything; and a database trigger blocks a consent-unsafe publish even when the app is bypassed. Shipped
Provenance
Append-only history
The money and commission ledgers are append-only and refund-anchored — corrections are new rows. An attestation outlives the person who made it. Shipped
Payments
PCI SAQ-A — no card data here
The card is captured and tokenized client-side by Stripe’s hosted iframe; our server only ever sees opaque ids. A CI-enforced structural wall keeps it that way. Shipped
Student data
COPPA & FERPA walls
A single-school FERPA wall at the database, a rep PII wall that shows a studio zero student rows, and a COPPA under-13 block in the commerce path. Shipped
1 — Tenant isolation by row-level security
Multi-tenancy isn’t enforced by a WHERE school_id = ? a developer might forget. It’s enforced by Postgres row-level security with FORCE ROW LEVEL SECURITY and a tenant-isolation policy on every tenant-scoped table, so the database returns only the rows the current tenant is permitted to see — independent of the query. RLS coverage is checked in CI (a table that should be isolated and isn’t fails the build), and the single-school wall is exercised against real Postgres across the full student-data census: a cross-school read returns zero student rows even with both schools in scope. A bug in application SQL therefore cannot leak another school’s data — the wall is one layer down.
2 — Fail-closed consent gates
One pure resolver owns the consent policy, and every lane — commerce, publication, public-web publication, AI, biometric, directory — reads its decision before acting. The sensitive lanes are deny-unless-explicitly-granted; a do-not-publish kill-switch denies every purpose for a student at once; and the publish path has a database-trigger backstop (BEFORE INSERT OR UPDATE on the publish-state column) that rejects staging a student who lacks the required consent — so a worker or import job that skips the app gate still cannot publish consent-blocked content. The full mechanism is on the consent-as-substrate page.
3 — Append-only provenance
The money rails are designed so history is evidence, not mutable state. The commission and payout ledgers are append-only and refund-anchored: a correction is a new row that references what it corrects, never an in-place edit, so the ledger is the balance and the audit trail at once. Attestations (a school commerce attestation, a “no identifiable students” declaration, a consent grant) record who and when, and their references use SET NULL on deletion — the fact outlives the person who made it. Nothing load-bearing is silently rewritten after the fact.
4 — A PCI SAQ-A posture: raw card data never touches our server
Card numbers, CVC, and expiry are captured and tokenized entirely client-side by Stripe’s hosted PaymentElement iframe. Our server seam only ever receives Stripe-issued opaque ids (a PaymentIntent / charge reference) — never a primary account number. This keeps us in the SAQ-A scope (the lightest PCI self-assessment), and it is held by a CI-enforced structural wall — a test that fails the build if raw card fields could reach our code. The order ledger stores integer cents; Stripe holds the card and the card’s lifecycle. Payouts to studios and schools ride Stripe Connect on the same rail.
5 — COPPA & FERPA walls
Student-data protection is structural. The single-school FERPA wall is the RLS rule above: a member tied to one school cannot read another school’s student rows. A separate rep PII wall means a studio or sales rep sees aggregates, adult contacts, and reconciliation — and never a student row — which is what makes a genuinely resold product possible without anyone outside the school touching a child’s record. And COPPA is a hard under-13 block in the commerce path with an attestation lane of record, not a policy paragraph. The picture-day pipeline itself keeps student images in-VPC with no biometric template and no third-party egress — detailed on the privacy page.
What this page is — and isn’t
Every wall above is live today, and several are proven against real Postgres or held by a CI structural test. This page describes what the platform does; it is not a regulator’s certification or an attestation report. The binding artifacts — the data-flow and subprocessor map, the security-questionnaire responses, and the privacy policy — are the compliance documents of record, and we’ll walk your security team through them directly. We’d rather show you the mechanism than wave a badge.