Moorwell
Pages
Look
Open Moorwell

Every commitment on this page is a property of the code.

Each line below names the mechanism that makes it true and the test that goes red the day it stops being true. Where something is a rule rather than a guard, it says so.

The invariants

A route to help is on screen before you have committed to anything.

Not behind an interaction, not after onboarding, not once an account exists. The rule is carried by a type rather than by a paragraph, and the test derives the set of first-run screens from the app's own entry point — so a screen added tomorrow is covered without anyone remembering that this rule exists.

Automated checkA test in the project’s own suite fails the build the day this stops being true.

No model writes a sentence anyone reads.

This is arithmetic rather than policy. Where a model is consulted at all, it is asked one multiple-choice question and allowed to answer with one token. A letter is mapped back to an identifier in ordinary code, and the reply is assembled from the written bank. An answer that was not on the list it was offered is not rejected — it is unrepresentable.

The test asserts“every user-visible string came from the slate, never from the model”

A model may raise an assessed distress level. It may never lower one.

The asymmetry is deliberate and it is the safe direction: a model that is wrong upward offers someone steadier content than they needed, and a model that is wrong downward does the opposite. A reply that lowers the level is discarded whole rather than corrected.

The test assertsmayMoveDistress(from: 2, to: 0) is false

“a response that lowers distress is discarded whole” — proving the composition path routes through that one function rather than reimplementing it

Prose cannot be smuggled in where an identifier belongs.

The guard is adversarial by construction: it is tested against well-formed replies that are wrong, not only against malformed ones. A card type that does not exist, a card that exists but was not offered this turn, a valid card naming a slot value that is not in the bank — each is discarded whole rather than repaired, because a repaired reply is one nobody reviewed.

The test asserts“prose where an id belongs is discarded whole”

The crisis pathway cannot be reached by a model, however hard it is pushed.

Risk is assessed before anything is recommended, and a risk signal interrupts to the crisis pathway with nothing to choose from. In the other direction a slate refuses crisis cards even when a reply asks for them, and a reply that names the crisis level at all is discarded. Two independent mechanisms, because one has already proved too narrow somewhere in this project.

The test asserts“a slate refuses crisis cards however hard they are pushed”

A number that reaches a threshold check is a number that has been sanitised.

Every model reading passes one guard first. Not defensively: this app once shipped a classifier that summed multi-label probabilities to 2.889 and compared them against a 0.55 threshold for months. The value was not corrupt — it was a different quantity than the code believed.

Automated checkA test in the project’s own suite fails the build the day this stops being true.

What the app cannot do

Decisions with reasons, and several are swept for by test so that a well-meaning addition goes red rather than shipping.

No streaks, points, badges, levels or unlockables.

Recognition here is retrospective and unannounced. A named threshold is a contract, and the day it breaks is the day someone who is already struggling has one more thing they have failed at.

The test assertsthe banned vocabulary as shared, cited rules, applied by sixteen test files to their own surfaces

the same sweep over generated recognition notes

No progress bars, percentages or denominators. Anywhere.

Show what was done, never what remains. A denominator turns an evening you managed into an evening you did not finish, and the rule holds even where the denominator would be decorative.

The test assertsAGENTS.md §3 — a rule, and an owner ruling is recorded as owed on the evidence behind it

No location, no microphone, no camera, no contacts.

Moorwell's own Android manifest declares three permissions, and the iOS build declares no privacy-sensitive capability at all — there is not one usage-description string in it. The set is pinned by test in both directions, so a permission added to that manifest fails the build and so does one quietly dropped. Six more arrive from merged libraries at build time, which no unit test can see because flutter test never runs a build; those were read by hand and listed on the privacy page, which says so rather than implying a coverage it does not have.

The test assertsthe declared set is pinned exactly

every declared permission must be named in PRIVACY.md

No appearance, weight or body targets, and nothing that scores you.

The app never evaluates a person's performance and never tells anyone to calm down before something they are being assessed on — because “ignore the stress” was the control condition the trials measured against.

The test assertsSee the register — the finding is printed there with the replication that did not reproduce it

The crisis library is capped at five entries and is never grown.

Every other library in the app has a minimum depth, because repetition is what makes support feel mechanical. This one runs the other way. Variety in the highest-stakes moment the app has would mean an unreviewed sentence in it, so the cap is the safety property and the sameness is the point.

The test asserts“capped at five, not floored”

The whole permission list

Three, in Moorwell's own manifest — and the fourth entry is the interesting one, because it is a permission the app takes away.

  • 3 permissions requested no contacts, no location, no microphone, no camera
  • 0 third-party scripts in the app and on this site — nothing is fetched from another origin
  • 13 crisis services shipped across 9 countries; eleven sourced to an official publication, two not yet
  • Internet INTERNET Used on every launch, whether or not anything optional is switched on. The privacy page names that call and does not describe it as optional
  • Reminders at the time you chose SCHEDULE_EXACT_ALARM Without it, a reminder set for 9pm arrives at some point after 9pm
  • Reminders that survive a restart RECEIVE_BOOT_COMPLETED A reminder you set stays set after the phone reboots
  • Vibration, removed VIBRATE  node=remove A notifications library declares this in its own manifest, so the merger would add it to the shipped package whether or not Moorwell mentioned it. This line subtracts it at merge time

Moorwell's haptics go through the platform call that needs no permission and honours your own system setting, and the reminder channel sets no vibration pattern — so the permission bought nothing, and would have appeared on the store listing of a mental-health app asking for something it does not use. A test asserts it stays stripped.

Still absent, and each one a product decision rather than an oversight: location, microphone, camera, contacts, calendar, storage, phone state, and any form of background sensor or wakelock. Nothing in the reminder feature polls, holds a wakelock, or wakes the app up to look at anything.

One question, one letter

A model composes. It never writes.

That sentence is the first line of the file that enforces it. Moorwell is deterministic rules over a hand-written bank, and a model's contribution is a choice between things already written. That is why a clinician can read everything the app is able to say, and it is why review is possible here at all.

How is that enforced Close

An earlier design handed the model a grammar that made an off-menu answer structurally impossible to sample. It was replaced by something narrower, and the difference matters: a grammar made a wrong answer unsamplable, and this makes it unrepresentable. There is no parser to confuse and no partial output to salvage.

What the model emits
One token, decoded greedily. Never an identifier, never JSON, never a word anyone reads
Where the identifier comes from
A lookup in ordinary code, over the options that question offered
What happens to an unrecognised answer
The composition ends and the deterministic result stands
What a reader sees in either case
Sentences from the written bank, and nothing else

Neither of the levels that use a model is switched on in the build anyone can install — a decision about measurement rather than a limitation, stated in full on the About page.

Thirteen helplines, each taken from an official source

Thirteen services across nine countries, each taken from an official publication and dated. The last column records exactly what that check covers — and the difference between a number we sourced and a number someone dialled is not a detail.

  • Tele-MANAS India14416Sourced from the Ministry of Health & Family Welfare, checked 27 July 2026
  • KIRAN Mental Health Helpline India1800-599-0019Sourced from the Ministry of Social Justice and Empowerment, checked 27 July 2026
  • CHILDLINE India1098Sourced from the Ministry of Women & Child Development, checked 13 August 2026
  • Women Helpline India181Sourced from the Ministry of Women & Child Development, checked 13 August 2026
  • 988 Suicide & Crisis Lifeline United States988Sourced from SAMHSA, checked 26 July 2026
  • 9-8-8 Suicide Crisis Helpline Canada988Sourced from CAMH, checked 26 July 2026
  • Samaritans United Kingdom116 123Sourced from Samaritans, checked 26 July 2026
  • Shout United Kingdomtext serviceListed from Mental Health Innovations, checked 26 July 2026 — one of the two not yet sourced to an official publication
  • Samaritans Ireland Ireland116 123Sourced from Samaritans, checked 26 July 2026
  • Lifeline Australia13 11 14Sourced from Lifeline Australia, checked 26 July 2026
  • Need to Talk? 1737 New Zealand1737Sourced from Whakarongorau Aotearoa, checked 26 July 2026
  • SADAG Suicide Crisis Helpline South Africa0800 567 567Sourced from the South African Depression and Anxiety Group, checked 26 July 2026
  • Samaritans of Singapore Singapore1767Listed from Samaritans of Singapore, checked 26 July 2026 — the second of the two not yet sourced to an official publication

Eleven of the thirteen are sourced to an official publication, with the URL and the date recorded beside them. Nobody on this project has dialled any of them. That is why none is described here as verified, why the flag in the app is false on all thirteen, and why a test refuses to let that flag change — an agent once set it true on two of these numbers with a note that itself said “not phone-verified”, and the suite stayed green until the test was written. Dialling them is human work, and it is one of exactly two things nobody here may mark done on someone else's behalf.

Counted on 16 August 2026, and the counts are not pinned by a test — only the flag is. The file declares fifteen regions, not nine: six carry no service yet, and each of those six carries a note saying what was searched for and not found. That is the honest shape of it, and filling them is work that should be done rather than a cap that should hold — which is the opposite of the rule governing the crisis library above, and deliberately so.

What has been checked, and by whom

A psychologist has read the content, and the wording changed where she asked for it. Every line is also checked against the clinical rationale it was written from, by a tool that runs on every build — 2,227 entries across 232 groups, every one gated groups.