Book a demo

Tutus · school safeguarding · described field for field

A student can file a report here without signing in. Here is exactly what that report carries, and what it does not.

A reporting line that overstates itself is not a small mistake — it is a child deciding whether it is safe to say something, on the strength of a sentence somebody wrote in a hurry. So this page does the opposite of a promise: it names the route, the fields, the four layers of request plumbing that would otherwise have captured the reporter, the four build checks that hold those closures shut, and the three storage questions that are still open. Every capability sentence here is a branch of what the serving code actually does today.

One reporting route, no sign-in on it. A separate, sign-in-gated console for the staff who read reports. A follow-up path that does not exist, said so plainly. Money honest-off.

What happens when a student opens the link

A reporter arriving on a link that carries their school can file a report today, with no sign-in, at any school whose safety module is on — which is the default.

  1. The link carries the school. A reporter opens /report-a-concern/<school> — the school part is the public storefront link, the same one that appears in a school’s own web address. It is not the internal record id, and this surface never handles one. The page reads it straight out of the address bar.
  2. They fill in two things. a concern category from a list of six, and the tip text itself. There is no name field, no mail field, no phone field, and no field for an internal school or student record. Nothing about who is reporting rides along, because there is nowhere on the form for it to go.
  3. The school rides along as institutional context. which school (the public school link from the poster, never a reporter identity). It says which school, never who is reporting.
  4. The session is dropped on the way out. A reporter may be a student who is also signed in to the school platform in the same browser — and the whole point is that this must not matter. The reporting page strips that session before the message is sent: the submit call sets a hard override that stops the shared request helper attaching the bearer token it would otherwise default to, so the Authorization header is not sent. That override is held by its own test at the real header-building boundary, and by a build check of its own.
  5. The server takes it with no sign-in requirement. The submit handler carries no session check, no bearer requirement and no staff role. Its own header records why the obvious module guard is not used here: that guard throws an unauthorised error on a null context, which would have made this intake unreachable for a logged-out reporter — forcing exactly the sign-in this line exists to avoid. The module check is done a different way instead (see the module latch below).
  6. The answer is a receipt, not a token. A successful report comes back as a created status with three keys: the report id, its triage state, and the lane it was routed to. The page shows the id back. It is a receipt that the report was written and routed. It opens nothing.

Two things this line is honest about in the other direction. The plain link with no school on the end of it cannot file: the client refuses in the browser, before anything is sent, and says so — This link is missing its school. Nothing was sent. That is a real gap, and it fails early rather than after a child has written everything out. And a school that has switched the safety module off answers with the same not-found the platform gives an unknown school, on purpose; that is covered below too.

The reporting page itself opens with an emergency notice before the form, not after it: in an emergency, call your local emergency number first. Nothing here claims monitoring around the clock, and this page will not imply it.

Field for field: what a report carries

The most useful thing a safeguarding vendor can publish is not a promise, it is a list. This is the entire wire body a report sends, and the entire set of things that are structurally not in it. Three layers hold the second list: the field does not exist on the input type, the schema rejects it if it arrives anyway, and a runtime belt throws if it somehow reaches the draft.

Carried

  • A concern category
    One of six, from a closed list. Anything outside the list is refused by the schema, and the routing layer independently falls back to general triage on an unrecognised value rather than dropping the report.
  • The report text
    Up to twenty thousand characters. Content, not identity. It reaches triage byte for byte as written — including a student’s account of a named adult.
  • The public school link
    Institutional. Which school, never who. Resolved server-side to an internal record under a system scope, because a reporter has no tenancy of their own and a link lookup must not depend on the caller’s.

Absent

  • A name, a mail address, a phone
    Not optional-and-blank. The input type the form fills in has no such field, so there is nothing to leave empty and nothing to redact later.
  • A session, a bearer token, a user id
    The submit call drops the Authorization header explicitly, even for a reporter who is signed in elsewhere in the same browser.
  • The caller’s network address, in any log or key
    The framework’s own request log is silenced on this route; the general limiter uses a salted bucket for it instead of the raw address; the route’s own bucket is salted, rotating and truncated.
  • An internal school or student record id from the reporter
    The public link is the only school addressing this surface knows. A subject student, if one is named, is verified against the school and dropped if it does not match — the report is still filed.
  • Any unexpected key at all
    The body schema is strict: an unknown field is a hard rejection, not a silent drop. Underneath, the shared engine runs a refuse-list belt that throws on a dozen reporter-handle spellings, so a caller that spread a raw request body into the draft fails loudly.

Two shape rules on the server are worth stating because they are what makes the walk-up call legal without a sign-in: exactly one school addressing field is required (never both, never neither), and exactly one narrative field. The walk-up client supplies the public link and the text, which satisfies both. Reports default to heightened confidentiality rather than the other way round.

A clean route is not a clean request

A reporting route can have no reporter field, write no reporter column, and stamp a null actor on its own audit row — and still sit underneath plumbing that captures the reporter on every single call. That is what an audit of this surface found, and it is the part of this work that is not visible from the outside. Three always-on layers were writing the reporter down one level up, and a fourth lived in the route’s own limiter.

A clean route is not a clean requestFour layers sit above the handler. Each is closed separately; none of them is the same fix.1Framework request logwould carry: caller address, every requestclosed at the route: log level raised, no request or response line2General rate limiterwould carry: raw address written to shared storageclosed by the shielded-intake registry: salted bucket instead3Observability bindwould carry: signed-in user id on the access lineclosed by the same registry: no user id bound4The route's own bucketwould carry: raw address in a route-local keyclosed at the route: salted, rotating, truncated key
Request plumbing The four layers above the handler, and where each is closed. A registry entry closes two of them; the other two are closed at the route itself.

The correction that matters most here is not any single closure — it is what happened when one of them was asserted rather than enforced. A route file’s own header claimed registry membership for two public paths from the day it landed. The entries were never added. The claim was a comment, not a control, and two layers stayed open in production while the file said they were shut. A build check now fails on exactly that: a route whose comments claim membership it does not have.

And the check that found the most was the one that asked the inverse question. Two of the checks take the registry itself as their population, which makes a route missing from the registry invisible to both by construction — a measurement that cannot enter its adverse state while the fault is present is not an alarm. A third check takes the other population (routes that shielded themselves at the log layer) and asserts set equality in both directions. Run bare, it exited non-zero on four orphans, one of them a public route that had been writing the raw caller address into shared storage on every request.

The link cannot be used to ask questions about children

An open route that varies its answer with a submitted identifier is a lookup service for whoever holds the link. That is the failure mode a public reporting line invites, and every branch below was designed against it rather than discovered afterwards.

The rule that produces all of it: a status or a shape that varies with a submitted id is an oracle, so nothing here varies with one. A school link that does not resolve and a school that has the safety module switched off take the identical not-found, with the same message and the same body. A named subject student who belongs to a different school and a student id that does not exist converge on the same outcome — and, importantly, that outcome is not a refusal. The structured reference is dropped and the report is filed anyway, with the narrative untouched, because refusing would have thrown away a child’s report to protect a database column.

How each identifier is answered on the open route
What is submittedA plain online formThis line
An unknown school linkdistinguishable from a real oneuniform not-found
A real school with the safety module offdistinguishablethe same uniform not-found
A subject student who belongs to another schoolrejected, so the answer variesreference dropped, report still filed
A staff id a stranger guessedstored, and it arms a walldropped; the narrative is untouched
The success bodyechoes what was sentthe same three keys every time

The staff row deserves its own sentence, because it is the one where a protection could itself become the weapon. The structured staff-id field is the key to a recusal wall with four enforcement points. On an open route, that means a stranger who knew or guessed a serving administrator’s id could file a fabricated account naming them and, in the same call, wall that administrator off from ever seeing it. That was measured at the wire, not inferred: before the fix, a test persisted a victim’s real id and then returned a refusal to the victim on the report accusing them. Rejecting the field outright would have deprived a child naming a real abuser of filing at all. So the field is dropped, the words are kept, and the routing survives. The wall itself is untouched and still fail-closed — what was removed is a stranger’s ability to arm it.

The one switch that decides whether your link takes a report

The safety module is on by default on the base plans, so for most schools the answer is that the link works. If a school has switched it off, the link answers with the same not-found an unknown school gets — deliberately identical, so the public address cannot be used to work out which schools are on the platform and which have the module dark.

That means the honest instruction to a school is a short one. Send the link to yourself and use it once before you print it. A report you file lands on your own triage list; a not-found tells you the module is off, and switching it on is a setting, not a build. There is no status page that would tell you this from outside, because a status page for this would be the enumeration oracle the design just spent all that effort closing.

This page does not know your school’s module state and does not claim to. It states the default, states what an off module looks like, and tells you the one-minute check that answers it for your school specifically.

The reason the module is checked this way rather than the ordinary way is worth recording, because the ordinary way had a defect in it. The platform’s standard module guard throws an unauthorised error when there is no session context — which, on a route designed for a logged-out student, meant the module check itself was forcing a sign-in. The check was moved to a system-scoped read of the same setting, so a logged-out reporter and a signed-in one get a byte-identical response.

Where a report goes after the receipt

Routing is a pure function of the category. No reporter identity is consulted, because none exists to consult. Priority is derived the same way: four of the six categories a reporter can pick are urgent.

The six categories the form offers, and where each one lands
What the reporter picksPriorityLane
Bullying or harassmentStandardsafety_triage
A threat of violenceUrgentthreat_assessment
Self-harm or suicide riskUrgentthreat_assessment
A weapon on campusUrgentthreat_assessment
Shared intimate images without consentUrgentncii_trust_safety
Something elseStandardsafety_triage

The underlying record vocabulary is wider than the form — the database also accepts a substance category and a general abuse category, which reach triage through staff-side entry rather than the public form. The form deliberately offers the smaller list, in a reporter’s words rather than a schema’s.

Threat assessment

A threat of violence, a weapon on campus, or self-harm and suicide risk route to the behavioural threat-assessment lane and are marked urgent on arrival, which means an immediate look rather than a queue position. A report that is referred onward can attach to a threat case with its own state machine — referred, screening, assessing, managing, monitoring, closed — and its own risk levels and action vocabulary, including a recorded distinction between an assessment, a parent contact, a safety plan, a mental-health referral and a law-enforcement action.

Image-based abuse

A report of intimate images shared without consent routes to the trust-and-safety lane, marked urgent, and it is the same handler the public takedown channel feeds. That matters because the two arrive from opposite directions: a bystander who does not want to be identified files through the school line, and the person depicted files through the public channel with a statutory clock attached. Both end up in front of the same people rather than in two queues that do not know about each other.

General safety triage

Bullying and harassment, and anything the reporter classified as something else, go to the school’s safety-staff triage queue, marked standard. The routing function takes a raw category string and falls back to this lane on anything it does not recognise, on purpose: an unknown value must land somewhere a person will look, not be dropped as invalid. A report moves new, triaged, referred to a team, closed — or marked duplicate.

Staff misconduct escalation

A report that names a member of staff escalates to a separate lane regardless of its category, because an account of an adult must not sit in the in-house queue that adult helps run. Said plainly and in the present tense: this lane is real in the routing layer, and no report filed through the public link can currently reach it, because the structured staff-id field that triggers it is dropped on that route. Off-site delivery to an external or district authority is a provider seam that is not wired. What this lane owns today is the routing and the access-control decision.


Two things that happen to every report, whichever lane it lands in

It is chained. Intake appends an entry to an append-only ledger, hashed to the school’s previous entry, so a replay detects an alteration or a removal from the middle of the chain. The entry is deliberately census-neutral: the category, the state, the lane, and booleans recording whether a subject or a staff member was named — never the report text, never an identifier. And the verify endpoint says what it cannot prove: its completeness verdict is null, meaning not measured, because an omission at the tip of the chain leaves the chain intact and there is no independent expectation to compare against yet. A consumer checking that verdict alarms rather than passes, and quieting it by narrowing the check back is the repair we will not make.

It is held for the district by default. A walk-up report arrives with no school grant recorded, so policy holds it at the district until a district actor releases it. A held report stays in the school’s list, stripped, with a flag saying so — not hidden. Hiding it would hide that a report about this school exists, and a school that cannot see a report exists cannot ask for it. The existence is the actionable part; the content is what is withheld. On the single-report read, a held report returns a truthful two hundred with the narrative and the subject stripped rather than a refusal, because a refusal there is mapped by the console to a sentence telling a legitimate administrator they have no access to triage at all — which would be false, and they would stop looking.

The staff side is confidential and does require a sign-in

There is one submit route and it has no sign-in on it. Everything on the READING side does, and that asymmetry is the design rather than an oversight: the person reporting is not the person being audited, and a staff read of a report about a child is exactly the event an access log should hold.

The list and the single-report read both require the safety module, a records-staff role, and a school scope, and every identity field on the way out routes through the same consent chokepoint the rest of the platform enforces — a suppressed student’s name is masked while the row still counts, so a redaction never silently changes a total. A report marked heightened-confidentiality has its body withheld on the LIST for every role, whatever their seniority; only the single-record read can reveal it, and reports default to that heightened setting rather than the other way round.

The recusal wall runs before any body or subject is assembled. A staff member named as the subject of a report is omitted from the list entirely — the accused must not even learn such a report exists — and refused on the direct read with no body leaked. It is fail-closed in the sharpest way: an unidentifiable viewer on a report that names someone is treated as recused rather than allowed. Reports that name nobody are untouched.

The same conflict rule is applied to a second, harder case: a member of staff who performed or recorded a use-of-force incident cannot be the one to close the documentation of it. Seniority is not independence. An unidentifiable actor is recused, and an unresolvable participant set is recused too, because the closer’s independence cannot be established — the wall errs toward exclusion rather than falling open.

Live today, behind a sign-in, scoped to one school, consent-gated on the way out.

What this is not: an emergency channel

In an emergency, call your local emergency number. This software records and routes; it does not dispatch, and nobody here is watching a screen around the clock.

The reporting page carries that notice above the form, not below it, and this page repeats it because a hallway poster is read by people in trouble. The related panic-alert surface is honest in the same direction, and the honesty is structural rather than a disclaimer.

A panic alert is recorded, given a map reference and a lifecycle, and fanned out to an internal responder audience as counts — never a roster. What it is not is delivered. The planner that builds the alert is a pure function: no network call, no clock, no provider underneath it. The public-safety integration is unset at the call site, so the external verdict comes back as unconfigured with a reason naming the missing integration. With no mail-provider key, each responder message is planned as queued and never marked sent. With no verified sending identity for the school, it is suppressed rather than faked.

The part worth reading twice: a lifecycle stamp that wrote a durable “the notification went out at this time” column was removed from this path. It fired on a plan, not a result — and an incident review or a subpoena reads that column as a delivery. Worse, the honesty of it was being held only by the absence of a provider key, so it would have started making a true-looking false claim silently on the day a key was provisioned. The stamp is now reachable only when a human asserts it under their own identity, behind a forward-only transition guard. The panic hardware and the emergency routing belong to a hardware partner; we are the record and notification layer, and this page will not blur that.

The rest of the safeguarding console the line sits inside

A reporting line on its own is a mailbox. What makes it useful is that the report lands in the same console as the drill log, the visitor record, the custody directive and the crisis headcount — and under the same consent and access walls. Each of these is shipped engineering with its own tests, not a roadmap slide; each is described here by what it does and by the constraint it was built against.

Visitor watchlist and front-office check-in

A kiosk checks a visitor in at the front office and screens against a watchlist the school manages directly, with a visitor log behind the same staff gate. It is the one safeguarding surface where the subject is an adult rather than a child, which is why it is a separate console rather than a tab on the student record.

Crisis command: accountability and reunification

During an incident, a live headcount of who is accounted for, missing, or off site, plus reunification progress. Counts only, never a who-is-where roster. That constraint is the whole design: a screen that shows where every child is, shown in a crisis to whoever is holding the tablet, is a different and worse artefact than a number that tells you how many are still unaccounted for.

Drills, sensor feeds and alert lifecycles

Drill scheduling and logging, and normalised sensor-detection events with an explicit notification lifecycle so an alert cannot sit unacknowledged with nobody owning it. The detection hardware is a device partner’s; the record, the lifecycle and the audit trail are ours.

Use-of-force documentation with an independence rule

Restraint and seclusion documentation carries a self-review recusal: the staff member who performed the incident, or who recorded it, is excluded from closing its documentation. Fail-closed on an unidentifiable actor and on an unresolvable participant set. Admin seniority does not substitute for independence.

Custody directives, hall passes, dismissal and bus ridership

The front-office safeguarding surfaces that share the same module and the same walls: recorded custody directives that the reunification release and the dismissal desk read server-side, hall passes, carline dismissal with a flat refusal for a barred guardian, and bus ridership. Staff-gated, office-gated, and scoped by the same representation wall as the student record.

What is deliberately not here

No face matching, on this page or anywhere on a K-12 surface of ours — not on a visitor, not on a student, not on an image attached to a report. No outside-model analysis of a child’s report. The staff console has no safeguarding navigation group: the pages exist and are reachable, and a named menu group is not something this page will claim exists when it does not. And no adoption figures, school counts, testimonials or partner logos anywhere on this site, because we do not have honest ones to publish and an invented one on a child-safety page is the worst possible place to start.

The image takedown channel, and the feature we decided not to build

A platform that hosts uploaded images of minors carries a federal notice-and-removal duty: provide a clear process for a person depicted in an intimate image shared without their consent to request removal, and remove it — along with reasonable efforts at identical copies — inside forty-eight hours of a valid request. Our answer is a public channel with no sign-in, because the duty runs to any person depicted rather than to account holders. It is metered by a token bucket and bot-gated, and the bot gate is honest-off without a secret rather than blocking a real victim out.

The two halves fail in opposite directions on purpose. Intake fails open: a request must be hard to lose, so it is accepted, stored with its deadline, and answered with a ticket reference the requester keeps. Removal fails closed: an unresolved sweep escalates to a human operator inside the deadline rather than quietly closing itself. The request row is trust-and-safety read only — a school or an adviser never reads other people’s takedown requests — and the acknowledgement echoes only the public reference and the deadline, never whether a given image exists on the platform.

The part that is worth publishing is the feature we refused. A self-service lookup, where a requester could type their ticket reference and see their case, was designed and then declined, and the reasoning is short: the row holds the requester-supplied pointer to where the imagery appears, so returning it to whoever holds the reference would hand an abuser the location of the image; a removed-copy count is an existence oracle through the back door; and the image-matching fields are a matching oracle for the image itself. Intersect what would be genuinely new to the victim with what is safe if an abuser reads it and the set is close to empty — the requester already holds the timestamp and the deadline from the acknowledgement. So there is no self-service lookup, and the reference is worth keeping only to quote to a human operator. A response that is safe was not worth building; a response worth building was not safe.

Putting it on a wall

The link on a poster has to be the one that works. That means it has to carry your school — the plain address with nothing after it refuses in the browser and tells the reporter their link is missing its school, which is honest but is not what you want a student to meet in a corridor at the moment they have decided to say something.

Per-school link: https://app.homeroom.software/report-a-concern/<your-school-link>

That is the address to put behind a QR code. The school part is your public school link — the same one that already appears in your school’s own web address on this platform — never an internal record id, so the printed address exposes nothing.

What we do not do: there is no poster generator in this product. A layout was written, and nothing in the shipped application calls it — no route, no job, no export — so no poster is produced for a real school by any code path here today. This page is a page that shows you the address; the printing is yours. Said plainly rather than implied, because a school that believes a poster is coming from us is a school with a blank wall.

Before you print: use the link once yourself. A report you file lands on your own triage list and tells you three things at once — that the module is on, that the school link resolves, and what your staff will actually see. It takes a minute and it is the only check that is specific to your school.

What we are not claiming, and why

This is the section a safeguarding vendor is least likely to publish, which is exactly why it is here rather than in a footnote. Each item is open, named, and not dressed up.

How long a report is kept, and whether it is encrypted at rest

A report body is stored as plain text with no expiry, and there is no managed-key encryption at rest for it. That is the current state, and it is the single biggest reason this page describes the reporting line in narrow, checkable terms rather than a broad one. A binding retention period is one of the two items marked pending counsel below — not because it is hard to write, but because writing one we do not enforce would be worse than saying this.

What the surrounding infrastructure might hold

The four request-plumbing layers inside the application are closed and checked. What a proxy, a content network, a log shipper or a disk might incidentally retain is not something we have audited end to end, and it is not something we are going to assert from the application side. It is named here as open rather than left for someone else to discover.

There is no way to check back on a report

No follow-up route exists on this platform. The page that would consume one has no link pointing at it from anywhere in the product, and the credential such a page would need could only have been minted by a route that does not exist. The id shown after a successful report is a receipt that the report was written and routed. It is not a credential and it opens nothing.

One word we will not use

This page never uses the broad privacy word that would normally head a section like this, on either surface. The route-level facts are verified and stated above; the storage-level questions in the first two items are open. A word that covered both would be covering those too. A build check in this repository enforces that refusal against this page’s own rendered text, so it cannot quietly come back in a copy edit.

Student data and consent, said plainly

The reporting line has no reporter-identity field by absence rather than by policy: the shape it sends has no such field, so there is structurally nothing to redact or leak about who sent it. Policies change; a field that does not exist does not.

On the reading side, a student named in a report is protected by the same consent chokepoint the rest of the platform enforces, at the route rather than in a screen filter a client request could bypass: a suppressed student’s identifying fields are stripped while the row still counts, so a redaction cannot silently change a total. Access is scoped to one school by the same wall the student record uses. A district-held report’s subject identity is never loaded at all, rather than loaded and then withheld at the last moment.

Nothing produced by this surface is used for advertising, sold, or handed to a data broker. There is no face matching here or anywhere on a K-12 surface of ours, no photo claim, and no outside-model analysis of a child’s report — and none of the platform’s other postures on those subjects are being implied by their absence from this page.

How the money works, honestly

This is honest-off money: no pricing table, no checkout, no live charge, and no payment processor wired to any surface on this site — nothing here can complete a purchase, by design rather than by omission. A conversation with a school works out what, if anything, applies. We are a for-profit vendor and this page is not going to pretend to be a charity; it is also not going to invent a price it cannot take.

There is one place where money and safeguarding touch, and it is worth being exact about it because the answer decides how much of the rest of this page you can lean on. The reporting line is not metered, priced or seat-counted. It is gated by a single module setting, and that setting is on by default on the base plans — it is not a paid add-on.

And then a distinction we are going to state precisely rather than round up in our own favour. Two capabilities on this platform — the crisis-command view and the mass-notification channel — are bound as always-on at the plan layer, which means they cannot be darkened by a downgrade, an unpaid invoice or a deny flip, on the reasoning that a mandated life-safety channel is the wrong thing to hold as leverage. The safety module that gates this reporting line is deliberately NOT in that bound set. It is a broad records module wider than the mandated channel, so it was left out of the always-on carve-out on purpose. The honest summary is therefore: on by default, not a paid extra, and not structurally undarkenable either. Rounding that up to a guarantee would be exactly the kind of sentence this page exists to avoid.

One consequence follows and we would rather say it than let you infer it: because there is no live charge anywhere on this site, there is nothing here that can lapse today and quietly take a safeguarding surface down with it. If we ever wire money to any of this, the honest description of what a lapse does belongs on this page before the switch is thrown, not after.

Pending counsel

Two things are deliberately unwritten on this page, because they would be binding legal claims and we are not going to fabricate them. Both are marked PENDING COUNSEL and land as their own reviewed sections once cleared, and not before.

Mandatory-reporting statute language. Who must report, what triggers the duty, how fast, and to whom differ by state, and in several states they differ by role within the same building. A vendor page that summarised all of that into one confident paragraph would be handing a safeguarding lead a version of the law that is wrong for their jurisdiction, in the one place they are least able to check it. So this page describes what the software does with a report and leaves the duty to the people qualified to state it. If your counsel wants the mechanics in order to write your own policy, the routing, the lanes, the priorities and the audit behaviour are all described above in enough detail to map against.

A binding retention period. Report bodies are held as plain text with no expiry today, which is named in the open-questions section above rather than hidden here. Publishing a retention commitment before the deletion path exists to enforce it would be a promise the storage layer cannot keep, and a retention promise a system does not honour is worse than none: a school would answer a records request on the strength of it.

One statute this page does describe concretely is the federal notice-and-removal duty behind the image takedown channel, and it is described as what our code does — a public intake, a stored deadline, a ticket reference, an escalation to a human inside the window — not as legal advice to a school about its own obligations.

Common questions

These are the questions a safeguarding lead actually asks, answered at the level a safeguarding lead actually needs.

Can a student file a report right now, without signing in?

Yes, on a link that carries their school. The reporting page at /report-a-concern/<school> sends the report to a route that has no sign-in requirement in it at all: no session check, no bearer token, no staff role. The submit call sets a hard override that drops the Authorization header even for a reporter who happens to also be signed in to the school platform in the same browser, so a signed-in student’s session cannot ride along and identify them. The server answers 201 with a receipt id, and the page shows that id back. The one thing that stops a report is a link with no school in it — see the next answer.

What happens on the plain /report-a-concern link with no school on the end?

It refuses, in the browser, before anything is sent. The server requires exactly one school addressing field — either the internal id or the public school link — and this walk-up surface never handles the internal id, so a report with no school could only ever come back as a validation failure after the reporter had already written out the worst thing that ever happened to them. So the client checks first and says: This link is missing its school. Nothing was sent. Use the exact link or QR code from your school’s poster, which includes the school. Nothing is silently lost, and nothing is fabricated. It is a real gap in the product and it fails loudly and early rather than quietly and late.

Exactly which fields does a report carry?

Three, and the shape cannot grow a fourth by accident. A concern category from a closed list of six, the report text, and the public school link the reporter arrived on. The input type the form fills in has no reporter field to fill, so there is nothing to leave blank. On the server the body schema is strict, which means an unexpected key is a hard rejection rather than a silent drop — a stray contact field cannot be quietly accepted and stored. Underneath, the shared engine also runs a refuse-list belt that throws on any key that looks like a reporter handle: name, contact, phone, submitter id, user id, mail address, IP address. Three layers, and the first one is that the field does not exist.

Your form does not ask who I am. Does the rest of the system quietly capture it anyway?

That is the right question, and it is the one that produced the most work here. A route can be clean and still sit under request plumbing that is not. Three always-on layers were found capturing the reporter anyway: the web framework’s built-in request logger, which is a separate logging instance from the platform’s redacting one and writes the caller’s address on every request; the general rate limiter, which writes a bucket key containing the raw address into shared storage, timestamp-correlatable with the stored report; and the observability layer, which binds a signed-in user id onto the access-log line. The first is closed at the route by dropping its log level so the request and response lines are never written. The second and third are closed by a single registry of shielded intake routes that both layers consult. A fourth layer — a route’s own token bucket — is closed at the route with a salted key, never a raw address. Four layers, four separate closures, and none of them is the same fix.

How do you know the shield did not quietly come off?

Because four separate checks run in the build and each one is keyed on a different population, which is the only reason they can see each other’s blind spots. check:anon-intake-registry fails the build when a route’s own comments claim membership of the shielded registry that it does not actually have — a claim in a comment is not a control, and that exact gap once left two public routes exposed for weeks while the file asserted they were closed. check:anon-intake-kv-shape opens the files that own a registered route and asserts their storage keys are salted, not raw. check:anon-intake-orphan asks the inverse question that neither of the other two can: does a route shield itself at the log layer and then lack the registry membership that closes the others? Run bare, it exited non-zero on four such orphans. check:anon-intake-client-noauth holds the browser side of the wall. A gate whose population is the registry itself is blind by construction to a route missing from the registry; that is why the third one exists.

Why do you not use one broader privacy word for all of this?

Because the route-level facts and the storage-level facts are different questions, and only the first set is settled. What is true and verified: there is no reporter field on the wire, none on the stored row, no sign-in on the submit path, no bearer token sent, no raw address in the rate-limit key, no user id on the access line, and no request or response log line for the call. What is still open: how long a report body is retained — today it is stored as plain text with no expiry — whether it is encrypted at rest under a managed key, and what a proxy, a CDN, a log shipper or a disk might incidentally hold. Three open items, named. A word that covers all of it would be covering those three too, so this page states the narrower thing it can stand behind and stops there. The claim register in this repository still carries that family as gated for exactly these three reasons, and a build check enforces it against this page’s own text.

Can I check back on a report I filed?

No. There is no follow-up route on this platform — the check-back path the reporting page was built against does not exist on the server, and the page that would consume it has no link pointing at it from anywhere in the product. The id shown after a successful report is a RECEIPT: it proves the report was written and routed, and it is not a credential that opens anything. It is worth keeping only so a person can quote it to a human being at the school. Saying otherwise would be inventing a door.

Can the link be used to find out which schools are on your platform?

No, and the design is specific about it. A school link that does not resolve and a school that has switched the safety module off both take the same uniform 404 with the same message and the same body, so the two are not distinguishable from outside. The report’s optional subject student is treated the same way: an unknown student id and a student who belongs to a different school converge on the same result — the structured reference is dropped and the report is still filed, with the narrative untouched. A status code that varied with a submitted id would be an enumeration oracle on an open route, so it does not vary. The success body is the same three keys every time.

What if a student reports a member of staff?

The narrative reaches triage exactly as written — a student’s account of a named adult is never edited, dropped or held back. What is dropped is the structured staff id field, on this open route only. That field is the key to a recusal wall with four enforcement points, so a stranger who guessed a serving administrator’s id could otherwise have filed a fabricated report naming them and walled that administrator off from ever seeing it. Rejecting the whole report would have been the other harm: it would deprive a child of filing. So the report is filed, the words are untouched, the routing is preserved, and only the id is dropped. The recusal wall itself is untouched and still fail-closed — what was removed is a stranger’s ability to arm it. And stated plainly: because no signed-in producer for that field exists today, the wall currently has nothing to act on in production.

What happens if your audit ledger write fails after the report is stored?

The reporter is told the truth, which is that the report was filed. The report row commits first and is already on the staff triage list; the hash-chained ledger entry is appended afterwards, outside that transaction. If that append throws, the old behaviour was a 500, and the reporting page renders every non-honest-off 500 as Nothing was recorded — a confident falsehood about the one fact the reporter needs, told to someone who will then stop escalating. So the append failure is caught and the 201 still goes back, because the 201 is true. The alarm gets louder rather than quieter: an error-level log line fires and names the specific report whose ledger entry is missing, and the response itself carries a flag saying the audit entry was not recorded, so the gap is visible to the caller and to the tests, not only in a log nobody reads. Rolling the report back to keep the ledger tidy would have destroyed a child’s report to protect a bookkeeping invariant.

Does your audit ledger prove nothing was tampered with?

It proves no entry that IS there was altered, and it does not prove that none is missing. The ledger is append-only and each entry is chained by hash to the school’s previous one, so a replay detects an edit or a removal from the middle of the chain. But an omission at the tip — a safety row committed while its ledger append failed — leaves the chain perfectly intact, because there is no hole to find. The verify endpoint therefore returns a completeness verdict of null, meaning NOT MEASURED, rather than true: establishing completeness needs an expectation derived from outside the ledger, and no such per-school read exists yet. A consumer checking that verdict now alarms instead of passing. Narrowing it back to a bare pass to quiet that alarm is the one repair we will not make.

Is this an emergency line? Does someone watch it around the clock?

No, and the reporting page says so above the form, before anything else: in an emergency, call your local emergency number first. Nothing here claims monitoring around the clock and nothing here dispatches. The related panic-alert surface is honest in the same direction — a panic alert is RECORDED, mapped and given a lifecycle, and it is not delivered to a responder by this software. The planner that builds the alert is a pure function with no outbound call underneath it, the public-safety integration is unset, so the external verdict comes back as unconfigured, and with no mail provider key each responder message is planned as queued, never marked sent. A lifecycle stamp that would have written “the alert went out” was removed, because it would have made that claim the day a key was provisioned, silently. The panic device and the emergency routing are a hardware partner’s, not ours.

Our district wants to hold reports before a school sees them. Is that possible?

Yes, and it is the default for a walk-up report. A report arrives with no school grant recorded, so district policy holds it until a district actor releases it. What a held report looks like to a school matters as much as the hold itself: the row STAYS in the school’s list, stripped rather than hidden, with a flag marking it held. Hiding it would hide that a report about this school exists, and a school that cannot see a report exists cannot ask for it to be released — the existence is the actionable part; the content is what is withheld. On the single-report read the same choice is made deliberately in the opposite direction from a refusal: a 403 there would be mapped by the console to a sentence telling a legitimate administrator they have no access to triage at all, which would be false, and they would stop looking. A held report’s subject identity is never even loaded.

What does a member of staff see, and what do they have to sign in for?

Everything on the reading side needs a sign-in, and that is on purpose: a staff read of a report about a child is exactly the event an access log should hold. The list and the single-report read both require the safety module and a records-staff role, are scoped to one school, and route every identity field through the consent chokepoint the rest of the platform uses — a suppressed student’s name is masked, and the row still counts. A report marked heightened-confidentiality is redacted on the LIST for every role; only the single-record read can reveal it. Reports default to heightened-confidentiality, not the other way round. The submit side requires none of this, which is the whole design: the person reporting is not the person being audited.

We already have an online form. What does this do that a form does not?

A generic form collects a submission. The difference is everything that happens on either side of the collection: an input type with no reporter field so there is nothing to redact; a strict schema that rejects an unexpected key rather than storing it; a session strip so a signed-in reporter’s own bearer token is left off the submission entirely; four request-plumbing layers closed separately and four build checks that fail the build when one comes off; a uniform not-found so the link cannot be used to enumerate schools; category-driven routing into a threat-assessment lane, an image-abuse lane or general triage; a recusal wall; a district custody hold that keeps the row visible while withholding the content; a hash-chained audit entry; and a failure path that will not tell a child nothing was recorded when the row is already on the triage list. A form gets you the first hour of that.

Does this cost anything?

Money is honest-off on this page: there is no pricing table, no checkout, and no payment processor wired to any surface on this site — nothing here can complete a purchase. A conversation with a school works out what, if anything, applies.

Is any legal or mandatory-reporting language on this page binding?

No. Jurisdiction-specific mandatory-reporting statute language and a binding retention period are marked PENDING COUNSEL below and are deliberately unwritten rather than fabricated. Mandatory-reporting duties differ by state and a wrong version is worse than none. The one statute this page does describe concretely — the federal notice-and-removal duty behind the image takedown channel — is described as what the code does, not as legal advice.

What this page is, and is not, claiming

One reporting route, and it has no sign-in on it: a reporter on a link carrying their school can file today at any school whose safety module is on, which is the default. The plain link with no school cannot file and refuses in the browser before anything is sent, which is a real gap stated as one. The reading side does require a sign-in, is scoped to one school, is consent-gated on the way out, and carries a recusal wall that is fail-closed. There is no follow-up route and the id a reporter is shown is a receipt, not a credential. A panic alert is recorded and is not delivered; there is no public-safety integration and no dispatcher underneath it. No poster is produced by any code path here. Three storage questions — retention, encryption at rest, and what surrounding infrastructure holds — are open and named rather than covered by a bigger word, and this page uses no such word about either surface. The wider safeguarding console is shipped engineering. Money is honest-off: no pricing table, no checkout, no processor wired anywhere on this site. Two items are marked pending counsel rather than invented. No adoption figures, testimonials or partners appear here, and no competitor is named.

A previous revision of this page told schools that the reporting route had not been built and that the live line required a sign-in. Both were false, both discouraged reporting, and both were corrected here after measuring the client call, the route registration, the schema and the module default at their source. A correction is a claim and gets the same audit as the thing it corrects.