Database constraints found four bugs the same afternoon
We moved the platform invariants into database constraints. Postgres rejected four writes the same afternoon that the services had been allowing.
Blog Product decisions
C6 passed as a build gate before anyone had written a line of BodyCommand. Nine months later we specified a body composition product that depends on precisely the numbers C6 forbids. One of the two had to give, and the interesting part is which.
The Ethical Gamification Charter has fourteen rules and all of them are assertions in a test file rather than paragraphs in a policy document. C6 was the shortest: no weight, no body-mass index, no energy figures, no appearance framing, at any age, in any mode, under any consent setting.
It was written for a specific reason. A product used by ten-year-olds that also shows a number labelled "body fat" is a product that will eventually hurt someone, and no consent checkbox makes that acceptable when the person consenting is twelve.
Adults kept asking for the body composition numbers C6 banned. Not vanity metrics — the ordinary, reasonable request of somebody in their fifties who wants to know whether what they are doing is working, in a number they can compare to last month.
We wrote up three options. Ship it as a separate product outside the platform, keeping the Charter untouched. Amend C6 and rewrite the test. Or scope the rule by audience.
Option A was the coward's answer: same company, same data, different logo, and the safeguarding argument evaporates the moment anyone looks closely. Option B meant weakening a rule because it had become commercially inconvenient, which is exactly how every safeguarding failure starts.
We took option C. C6 holds absolutely under 18 — body composition is not consent-gated, not parent-gated, absent from the interface. Above 18 it is opt-in, never competitive, never on a leaderboard, and never the first thing a person sees.
The implementation detail that makes it real: `bodySurfacePolicy` is a single function that both paths call, and below 18 it does not read the consent flag at all. There is no branch where a truthy value in the wrong field unlocks a child's weight. The database rejects the write as well, so a bug in the service layer cannot store one.
This cost us a procurement conversation with a schools buyer who wanted the metrics panel enabled for sixth-formers. Sixth-formers are seventeen. The answer was no.
The Charter is not a page on this site that describes our values. It is `charter.test.ts`, and a build that violates it does not deploy. That is the only version of an ethical commitment that survives a quarter where the numbers are bad.
A rule scoped by audience is only as good as the place the scoping happens. Put it in the interface and it is a display decision. Put it in each service and it is six display decisions, five of which will stay correct.
So there is one function. `bodySurfacePolicy` takes an age and a consent flag and returns what may be surfaced, and every path that could show a number calls it. Below 18 it returns the growth pathway with metrics null and does not read the consent argument at all — not "reads it and ignores it", does not read it. A reviewer can see that in eight lines.
Underneath, the database has a constraint that rejects any row storing a body metric against an account whose verified age is below 18. If the service layer ever grew a bug that let one through, the write would fail rather than succeed quietly, which is the difference between an incident and a near miss.
The API tells the same story. Send an assessment for a twelve-year-old with the consent flag deliberately set to true and you get back `CHILD_GROWTH` with `metrics: null`. It is one of the ten checks on our public console, and it is there so that anybody evaluating the platform can verify the claim in a browser rather than believing a page like this one.
No. C6 holds absolutely below 18 — not consent-gated, not parent-gated, absent from the interface. `bodySurfacePolicy` does not read the consent flag at all below 18, and the database rejects the write, so a bug in the service layer cannot store one.
Because weakening a safeguarding rule that has become commercially inconvenient is how safeguarding rules come apart. The rule was scoped by audience instead: absolute below 18, opt-in above it, never competitive and never the first thing a person sees.
In `charter.test.ts`. It is not a values page — a build that violates it does not deploy. That is the only version of an ethical commitment that survives a quarter where the numbers are bad.
This article sits in the weight-control cluster and links up to /body-balance. Clusters are how the editorial agent decides what to write next — the thinnest one wins.
Reading this page records the time it was visible and how far it was scrolled. No cookie, no identifier, and the connecting address is hashed with a salt that regenerates daily. How that works.
Other pages here that point at this one. Internal links are the part of “backlinks” a site actually controls — nothing on this platform buys or exchanges the other kind.
Read next
We moved the platform invariants into database constraints. Postgres rejected four writes the same afternoon that the services had been allowing.
Accessible interface design as a settings toggle produces a worse product for everyone. Six modes change register, density, mechanics and guardrails together.
Law 2 treats a notification fired into a moment you cannot move as a defect. We count silence as a successful outcome, and the held rate is a headline metric.
Everything described here is running. An account is free, takes about two minutes, and the writing stays free whether or not you make one.
Free to create. 150 AI credits a month for 2 months, then AI features need a plan — everything that is not AI carries on either way. Premium is £5.99 a month and cancels in one click. Under 18 a guardian confirms before the account opens.