← All privacy impact assessments
PIA: child accounts, the guardian invariant and the child-safe variant
Status: Built — WS8 shipped 2026-09-08 (20260908043933); revised against the code the
same day. Sections marked as built describe what the database and the server now do; §5.2
says which rows are structural, which are conventional, and which two are NOT built and stand in
§11 as residual risk for the owner. §12A grades every Code requirement met / partly / deferred.
Written: 2026-09-07 · Issues: #282 §4 (with #219 consent ledger, #220 child policy,
#221 this register) · Workstreams: WS5 (schema, shipped), WS8 (behaviour, shipped), WS9
(the child-directed policy, shipped 2026-09-08)
Nothing here is legal advice. Children's Code citations are to the exposure draft;
section numbers may move on registration (due 10 Dec 2026).
Read 2026-09-ban-self-assessment.md first. This design is
only lawful if the conclusion there holds: the Online Safety Act Part 4A has no
parental-consent exception, so if GroupApp were an age-restricted social media platform, an
under-16 account would be prohibited however carefully it was built.
1. Purpose and scope
What it is for. Children live in the groups this app organises. Today they appear only as subjects of records — a name on a task, a face in a photo, a baby with a feed log — and the privacy policy says in terms that they do not hold accounts. That is a real limitation: a thirteen-year-old with their own chores, their own place in the calendar and their own things to say in the family chat has no way to use the app, and the workaround is a shared adult login, which is worse for everyone.
The feature gives a child their own account, inside their own family's group, with a version of the app that is smaller by construction rather than by configuration.
In scope: how a child account comes into existence; what it can and cannot reach; who consents to what; what happens when guardians leave, are erased, or separate; and how a child ages out.
Out of scope, assessed elsewhere: the ban analysis
(2026-09-ban-self-assessment.md); anything crossing a
group boundary (cross-group-events.md); the baby tracker's own
data (baby-tracker.md) and the photo wall's
(photo-wall.md); the consent ledger's mechanics (#219, WS2); encryption
(#223).
Not in scope and worth saying: this design does not make GroupApp a moderated platform. A group with a guardian in it is family, supervised — it is not platform, moderated. Adults in the group can post whatever adults post, and the app does not police it. That is what keeps the app out of the content-moderation business, and it is a limitation a parent should understand rather than a gap to be closed later.
2. Data involved
2.1 New collection
| Kind | Whose | Children's? | Sensitive? | Free text? |
|---|---|---|---|---|
user_birth_months.birth_month — a month and year, never a date of birth; a ROW of its own, not a users column, because every co-member reads the whole users row (WS5; AGENTS.md → A Birth Month Is A ROW) |
the account holder's | yes, for a child | no | no |
allowed_emails.child_of + allowed_emails.birth_month — the guardian who created the entry, and the month they declared, at the gate (as built: written by invite_child) |
the child's, linking to an adult | yes | no | no |
child_guardians rows — child ↔ guardian, plural |
both | yes | no | no |
groups.founder_assured_at / founder_assured_by — when and by what method a group's founder was assured to be an adult |
the founder's | no | no | no |
groups.children_allowed — tri-state (undecided / yes / no) (as built) |
the group's | no | no | no |
A consent ledger row per act, with given_by_kind (self / guardian / group_admin); as built two purposes: child_account (the guardian's) and child_account_assent (the child's) |
the consenting person's | sometimes | no | no |
Birth month, not date of birth. A month is enough to derive every threshold this design uses — 13, 15, 16, 18 — and a full date of birth is a stronger identifier for no gain. This is minimisation with a concrete payoff rather than a gesture: a leaked birth month is close to useless for impersonation, and a leaked date of birth is not.
No age verification data of any kind. No government identification, no Digital ID, no
document upload, no biometric estimate, no third-party assurance provider. Two reasons: it
would be disproportionate to the risk (§8), and it is precisely the class of collection the
Act's 63DA/63DB singled out as not required of even an age-restricted platform.
2.2 What a child account then holds, that any account holds
Chat messages, task completions, calendar entries, photos they post and photos they appear in, game state, their display name and avatar colour, push tokens, and — where they use the parts of the app that have them — read receipts and reactions. All of it is a child's personal information under the Code (a child is anyone under 18, not under 16).
2.3 What is deliberately not collected
- No date of birth, no address, no phone number, no school, no location of any kind. The app has no geolocation feature at all and this design adds none.
- No behavioural profile of a child. The one counted metric that exists for ban-risk monitoring (child accounts and active guardians per group — ban self-assessment §9) is a count per group, not per person.
- No child's data reaches a model at all, because a child account has no AI surface (§5). That removes the cross-border disclosure question (s 26) and the automated-decision-making disclosure (s 28(2)(g)) for children in one move, rather than answering them.
3. Flows and locations
| Data | Device | Supabase (Sydney, ap-southeast-2) | GCP (australia-southeast1) | Model provider |
|---|---|---|---|---|
| Birth month, guardian links, child flag | entered by the guardian | stored; read inside RLS predicates | no | no |
| A child's chat, tasks, photos, games | written and read on the child's device | stored, RLS-scoped to the group | no | no — the child's own surfaces have no AI |
| Founder assurance (Door A, planned) | web, not the app | groups.founder_assured_* |
no | no |
| Card details for the $0 SetupIntent (planned) | web | never — Stripe holds them | no | no |
| Consent ledger rows | — | stored | no | no |
No new boundary crossing. Everything this feature adds stays inside Supabase's Sydney project. The adults in a child's group continue to use the assistant, the insight and the allocator, and those surfaces continue to send group data to a model under the existing consents; what changes is that the child's own account never does, and that the group's task and calendar data — which is about children by nature — is being moved to Sydney inference under WS20 (planned).
One flow to state plainly because it is easy to miss. A child's data still reaches a model when an adult asks the assistant about the group: a task titled "Ada's dentist, 3pm" is a child's personal information in an adult's request. Removing AI from the child account does not remove children from AI. WS20's rule — group data is processed in Australia — is what addresses that, and the transparency copy must not imply otherwise.
4. How an account comes into existence: assurance once, declaration per child
An earlier draft put an age question at every join. That conflated two different things, and separating them is most of this design.
| Assurance | Declaration | |
|---|---|---|
| Question | Is there a real adult responsible for this group? | Which members are children, so the right rules apply to them? |
| When | Once, at group founding | When an adult creates a member's entry |
| How | Today: the owner manually allowlists someone he has met and knows to be an adult. Planned (Door A, #282 §2, growth-triggered): a $0 Stripe SetupIntent plus a birth-month declaration on the web. Either way recorded on groups.founder_assured_at / _by. |
One checkbox: "This person is under 16. I am their parent or guardian." No verification. |
| Serves | The ban: a child cannot found a group, and a founder was assured | The Code: guardian consent (s 13), the child policy (s 23), the child-safe variant, PIA scope |
| Fails how | A card is possession, not age — some Australian banks issue debit cards from 14. Reasonable steps, not proof. | A guardian declares an age wrongly. Bounded by: children cannot invite anyone, and every inviter is inside an assured group. |
Why not per-member checks. They would age-verify every member of every family to catch a rare misuse; that fails minimisation, it puts an identity check between a parent and their family calendar, and it is what the Code's s 8(5) posture explicitly lets a service skip by applying child-level protections to everyone instead. The failure mode being defended against is a group founded for the wrong purpose, so the group is the right place to check. If teen-only groups appear as a real pattern, the escalation is a stronger assurance method at founding — never per-member checks (ban self-assessment §9).
The existing choke point does the work. enforce_email_allowlist_on_signup
(20260808150000) already means nobody enters without an existing member creating their entry.
Child status becomes a property of that entry, stamped at the door, unbypassable by any code
path — which is why child accounts can ship under the current invite-only regime and do not
wait on open sign-up. Never add a bypass to that trigger while this is in flight; the
project depends on it being the only door.
5. Age bands and the child-safe variant
5.1 Bands
Three legal thresholds apply to the same person and they do not line up. Storing a birth month
and deriving flags at read time — is_under_16(uid), is_under_18(uid), usable inside RLS
predicates — means nobody ages out by cron job.
| Age | What they are | What that means |
|---|---|---|
| 0–12 | Profile, no login. Modelled the way group_babies is today: a name and an avatar the group holds. Chores and screen time can be assigned to them by a parent. |
No account, so no policy to show them, no consent to obtain from them, no ban question to answer. This is deliberately the default for young children rather than a lesser account. |
| 13–15 | Child account. The guardian creates the entry and consents; the child assents. The child-safe variant applies, enforced by the database. | Under 15 the guardian holds every consent (s 13). At 15 the child may hold their own — the OAIC's presumption of capacity — but the variant does not change, because the variant is about the ban and the Code's protections, not about who clicks "I agree". |
| 16–17 | Young person. Outside the ban, inside the Code. The full app, but AI and voice off by default and enabled only by the young person's own consent with the cross-border notice. Guardian link retained to 18. | The child-directed policy is still shown. Every Code obligation runs to 18. |
| 18+ | Adult. | On the eighteenth birthday month the guardian link lapses and the account is ordinary. |
13 is a floor chosen, not derived. No Australian instrument sets it; it is the conventional minimum across comparable services and it keeps the app clear of the youngest cohort, for whom the profile model is the better answer anyway. It is a decision the owner can move, and if it moves the reasoning here moves with it.
5.2 The variant, by surface — as built (2026-09-08)
The column that matters is the last one. Structural means the database or the server refuses it whatever the client does; conventional means the app hides or declines to offer it and a different client would not; not built means exactly that, and the row is carried into §11.
| Surface | Under 16 gets | Enforced by | Structural or conventional |
|---|---|---|---|
| Assistant, dashboard insight, task allocation | Nothing. Fails closed. The dashboard shows the deterministic fallback brief; allocation is the deterministic floor. | agent-server reads is_child_account through the caller's own client on every request to /api/agent, /api/baby/summary and /api/ceremonies/facilitate (the ceremony still runs — deterministic script) and refuses before anything is built or billed; an RPC error is a refusal (agent-server/src/childAccount.ts, pinned by checks/childAccount.check.ts). generate-dashboard-insight and ai-task-allocator answer 403 child_account the same way. Restrictive policies on chat_sessions and chat_messages refuse the transcript rows the typed screen would write. The Assistant tab is ALSO hidden (href: null), which is the courtesy and not the enforcement. |
Structural |
| Voice grants | Cannot be created; a grant paired before the month was recorded stops working on the next turn | A restrictive INSERT policy on voice_grants; /api/voice/grants refuses the pairing call; /api/voice re-reads the predicate for the grant's owner on every turn and records the refusal as a turn. The Voice row is hidden in Settings. |
Structural |
| Baby tracker | Unchanged — the child reads and writes it as any active member does. | Nothing. Not in WS8's brief; no policy on group_babies, baby_logs, baby_feed_sessions keys on the child predicate. |
NOT BUILT — §11 |
| Public plan reviews, dish photos | Unchanged — a child can submit a review and a dish photo. | Nothing. submit-review and moderate-photo do not read the predicate. |
NOT BUILT — §11 |
| Photo wall | Yes. No likes at all — no heart, no names, no count — and that is true for every user, not a child restriction (WS21, shipped 2026-09-08). | Shipped | — |
| Chat, tasks, calendar, games, ceremonies, intentions, thanks | Full. This is the app for them. Proved: a child in the same group posts to the chat, creates a task and an event, and writes a plan nobody else can read. | The guardian invariant is the safety property, not a feature restriction. The question bank and check-ins are deterministic for everybody (WS22), so the child's motivation layer is the same surface, not a reduced one. | Structural (the invariant), and no restriction to be conventional about |
| Group founding, invites, member admin | None | create_group_secure refuses an under-18 founder (GRP08); generate_group_invite, approve_join_request and reject_join_request refuse an under-18 (GRP10); invite_child is adult-only. Three admin RPCs the client calls do not exist at all (AGENTS.md → Known Issues, 2026-09-08) — a gap in the app, not in the gate. |
Structural |
| Cross-group event invitations (WS6, shipped) | Cannot send, preview or redeem. Sees the event on their own group's calendar once an adult accepts. | Every RPC refuses is_under_18 with code: 'under_18' (20260908022755); the tables are RPC-only with no client grant, so there is no table path to add a policy to. The event-code chip is hidden on a child account. |
Structural |
| Consents | A child under 15 holds none of their own except their assent | A restrictive INSERT policy on consents: no self row from an under-15 for any purpose but child_account_assent. At 15 the presumption of capacity applies and the policy stops matching; the variant above does not move. |
Structural |
What this variant is for, corrected 2026-09-08 against eSafety's guidance. It is a privacy-Code measure — strictly-necessary-by-default collection (s 9), best interests (ss 10–11) — and a harm-reduction measure. It is not part of the argument that GroupApp is outside the under-16 ban: the guidance's Step 7 says age-based restriction of social features "is not to be given any meaningful weight" in the purpose test. The ban question is answered by how every member uses the service.
Updated 2026-09-08. That paragraph's last sentence used to read "and the ban self-assessment's §7A stocktake is the gate on this feature". It is not, any more. The app is outside the under-16 rule at the features step — no recommender feature and no logged-in feature — so s 63D does not bind it and the stocktake gates nothing (ban self-assessment §§5.5, 7). Everything else here is unchanged: the variant is still justified on Code and harm-reduction grounds alone, and it still contributes nothing to the ban argument.
Two implementation rules that are the whole point:
- "Hidden tabs" is not enforcement. Every row above needs a database-side refusal, proven
in a test with a child account in the same group. A stranger is excluded by
is_active_group_memberno matter what the policy says, so a test written against one passes even when the policy is inert (AGENTS.md → Check RLS Is ENABLED Before Writing Policies). - Restrictive policies, ANDed with the existing permissive ones. One predicate per table, additive, rather than rewriting the group-membership policies — which keeps the change reviewable and keeps the child rule in one place.
An open question this document raises and does not answer. ANSWERED 2026-09-08, and not the
way this section framed it. The question was whether a child account should lose attributed
likes, message reactions and read receipts — each a "feedback feature" within Rules s 4A(5)(a) —
which would have made the child experience free of every feature the Rules name. It noted the
cost: a thirteen-year-old's messages silently not showing as read while everyone else's do, a
strange unexplained asymmetry inside one family chat.
The owner removed all three for EVERY member instead (WS21 — ban self-assessment §5.3). Two things follow, and the second is the more useful lesson:
- There is no asymmetry, because there is no child-specific rule. Nobody in the app can see who read a message, who liked a photo, or who reacted to anything. The cost this paragraph worried about does not arise.
- The framing was wrong, and it is the framing eSafety's guidance warns against. Asking "what should a CHILD account lose" treats an age-gated restriction as though it moved the app's position, and Step 7 says an age-restricted experience carries no weight. The question that actually mattered was whether the SERVICE has a feedback feature at all — and once it is put that way, a child-only removal is obviously the wrong answer: it leaves the feature in front of everybody else while buying nothing. §5.2's table row for the photo wall now reads the same for a child as for an adult, which is the shape to aim for whenever a row here is tempted to differ for reasons that are really about the app rather than about the child.
6. The guardian invariant, and the edges where it hurts
The invariant: a child account is active only in a group where at least one of their
guardians is active. A group_members trigger, not a client check. This is the one guarantee
in the design that is absolute, and it is why child_guardians is a table rather than a column
— separated parents are the ordinary case, not an edge, and a child is active in any group
where any of their guardians is active. Co-parenting across two homes is a child in two
standing groups with one guardian in each.
The initial schema had a child member role, migrated away on 2026-07-25. Do not resurrect
it. Child-ness is a property of the person, not of one membership.
Departures are the hard part, and they are where a bug hurts a child. Every one of these is
a fail-closed refusal, in the same family as delete-account failing closed on the erasure
ledger:
- A guardian leaving a group that would strand a child there is refused — "remove [child] first". The alternative is a child account sitting in a group with no responsible adult, which is the exact state the invariant exists to make unreachable.
- Guardian account erasure checks for dependants and refuses until they are re-homed or removed. This is the awkward one: an adult exercising a deletion right is told they must do something else first. It is the right answer — the alternative is honouring one person's erasure by stranding a child — but the refusal message has to explain, not just refuse.
- A child's own erasure is requestable by the child or by a guardian (s 32) and lands in the same off-provider erasure ledger as any other, so it survives a database restore.
- A group's last guardian being removed by someone else is the same refusal from the other side.
- Graduation at 18 lapses the guardian link automatically. It is not a deletion: the account continues, the child_guardians row stops having effect. The young person is told.
What the invariant does not do. It does not make the group safe. It does not review what adults post. It does not mean a guardian is watching. It means there is always a responsible adult present who chose to be there and can act.
As built (2026-09-08). enforce_guardian_presence on group_members (20260908043933):
a guardian's row cannot be deleted or moved off active while a dependant under 18 in that
group would be left with no other active guardian (GRP06, "Remove GRP07); request_join_group returns no_guardian /
children_not_allowed before writing anything, uncharged. delete-account refuses a guardian
with a live dependant (409, in words, before the storage sweep) and is the one place a LAPSED
link — a child now 18 — is removed, because the FK cannot tell lapsed from live. The lapse itself
is a predicate (is_under_18), never a sweep; the child_guardians row stays as a record. "The
young person is told" at graduation is not built: nothing notifies anybody when a month rolls
somebody past 18 (§11). Separated parents work as designed: the child's join request into a
second group succeeds once any guardian is active there (proved in
__tests__/child_accounts_rls.test.ts).
7. Groups, not households — and children_allowed
Say "group", never "household". The app is about groups: co-parents, polycules, blended families, share houses, carer teams. "Household" imports one dwelling and one family shape, and it conflates three things the schema keeps apart:
| Axis | Column | Rule |
|---|---|---|
| What a group calls itself | groups.type (exists: Family, Couple, Extended Family, Share House, Housemates, Team, Group) |
Self-description and copy only, rendered through groupNoun() (lib/groupTypes.ts). Never an eligibility rule, never sent to a model, and never extended with labels that encode relationship structure — a stored "polycule" is sensitive information the app has no need to hold. "Group" stays the catch-all so nobody must name their arrangement. |
| What a group is for, and how long it lasts | groups.kind (planned: standing, event) |
Behaviour, never a label. See cross-group-events.md. |
| Who founded it | founded_by, founder_assured_at / _by (planned) |
A property of any standing group |
Children per group is a switch, not a type: groups.children_allowed, tri-state (undecided /
yes / no). A consent-class group setting: admin-only by trigger, announced in the group chat,
and refused if turned off while a child is a member. The group's type suggests the
creation default only — on for Family, Extended Family, Couple and Group; off for Share House,
Housemates and Team — and a guardian adding the first child to an undecided group is offered the
switch in the same flow, so "the group allows children" and "this guardian is adding one" are
one screen rather than two.
Why a switch rather than a type. A share house that is also three friends co-parenting is a real arrangement, and a type-based rule would refuse it while a family group that has become a flat-share would silently keep permission it no longer wants. The switch is a decision someone made, attributable and announced; a type is a label someone picked at signup.
As built (2026-09-08). groups.children_allowed is the fourth consent-class group setting
in lib/settingsScope.ts: owner/admin only by trigger (GRP04, with the app.consent_sweep
arm on the admin check), announced by announce_setting_change in three sentences ("decided
that children can be in this group"), and refused with GRP05 naming the child when set to
no while an under-18 is a member — that refusal is NOT armed, so no process can flip it over a
child's head. The type suggests the creation default in lib/childAccounts.ts only. The
"same screen" is Manage Group → Children (components/Group/ChildrenSection.tsx): the three
pills, the children and their guardians, and the add-a-child sheet, which offers the switch to
an admin when invite_child answers children_not_allowed.
The privacy policy's "children do not hold accounts" became false the day this shipped, and
the one sentence was corrected in the same PR (constants/legalTexts.ts §4 now describes child
accounts and points at the child's page). The child-DIRECTED policy (s 23) shipped as WS9
(2026-09-08): CHILD_PRIVACY_POLICY, agreeing word for word with the child's page wherever they
overlap (both are built from childPrivacySections); §12A now records that as met.
8. Risks and mitigations
| Risk | Who is harmed | Mitigation | Structural or conventional | Residual |
|---|---|---|---|---|
| A guardian declares a 14-year-old as an adult, deliberately or carelessly, and the child gets the full app including the assistant | the child | The declaration is made by the person who knows; children cannot invite anyone, so a misdeclaration cannot propagate; every inviter sits inside an assured group | Conventional — a checkbox | Accepted. §11 |
| A group is founded by a 15-year-old using a debit card | children in that group; the app's ban position | Founding assurance (§4); today, manual allowlisting by someone who has met the person | Assurance is structural (a recorded column); what it proves is conventional | Accepted; escalation is stronger assurance at founding |
| A teen-only group forms and the app becomes what the ban was written about | the app, and the children in it | The child cannot found or invite; the guardian invariant; the per-group count metric (ban §9) | Invariant: structural. Detection: conventional | Accepted, with monitoring |
| A child's data reaches a model | the child | The child account has no AI surface, failing closed, re-checked server-side each turn | Structural once WS8 ships | An adult's request about the group can still carry a child's name (§3). Addressed by WS20's Australia rule, not eliminated |
| A guardian leaves and strands a child | the child | Trigger-enforced refusal (§6) | Structural | — |
| Guardian erasure orphans a child | the child, and the departing adult | Fail-closed dependant check | Structural | An adult's erasure is delayed by a step. Accepted deliberately |
| A separated parent uses the group to monitor the other parent through the child | an adult; potentially the child | Nothing in this design. The app has no location, no activity feed, and no per-member behavioural surface, so the material to monitor with is thin | Conventional (an absence) | Accepted, and named. The app should never gain a per-member activity view |
| A shared, unlocked phone means the child account is used by an adult or a sibling | the child | Nothing. No app fixes this | — | Accepted. §11 |
| An adult in the group posts something a child should not see | the child | Nothing — this is family, supervised, not platform, moderated (§1) | — | Accepted and stated to parents |
| A child's record outlives their interest in it — years of chores, chat and photographs | the child, later | Per-item deletion exists; erasure by the child or a guardian (s 32); the retention sweeps that exist | Partly structural | Accepted. §10 has the reasoning |
9. Legal basis and consents
The consent ledger (#219, WS2) is planned. Until it ships there is no record of a consent
act for anything except the terms acceptance already on users; the current state lives in
switch columns. This feature must not ship before the ledger, because a guardian consenting on
a child's behalf is exactly what the ledger's given_by column exists to record.
| Purpose | Who gives it for a child | Notes |
|---|---|---|
child_account (as built) |
The guardian: subject the child, given_by_kind = 'guardian', admitted by policy only where child_guardians links the two and the child is under 18. Twelve months; the existing sweep expires it. |
Recorded from app/child-consent.tsx, a screen the guardian went looking for. The s 13(2)(b) reasoning is on the screen, and it says "we have not verified". A guardian may READ a dependant's consents (a new SELECT policy) — otherwise the row they wrote would be invisible to them. |
child_account_assent (as built) |
The child, as their own self row — the ONE self purpose an under-15 may write. Separate purpose rather than a new action, so the latest-row view cannot let one mask the other. |
Recorded from the child's page by "I have read this". It gates nothing and says so. |
terms |
13–15: the guardian's child_account stands for it; the child's own terms row at signup is refused by the under-15 policy and tolerated by the signup screen. 16–17: the young person. |
The child-directed policy (#220, WS9) shipped 2026-09-08 as CHILD_PRIVACY_POLICY — §12A. |
assistant, voice, on_device_brief |
Not available under 16 at all — there is nothing to consent to. 16–17: the young person's own, off by default. | Removing the surface is better than obtaining consent for it. |
baby_summary_to_model |
The group's admins, who are the parents by construction | Group-scoped, admin-only. Not a child-account consent. |
read_receipts |
— | No writer since 2026-09-08. Read receipts were removed for every member (WS21); the purpose stays in the closed list because a purpose that was consented to is a fact and the list mirrors a CHECK constraint. Nothing asks for it, and the expiry sweep flips a column nothing reads. |
overseas_processing (planned, WS16/WS20) |
Where a child account is present, one of the child's registered guardians must be among the two confirmers, enforced in the approval RPC | The ledger row records that it was given on the child's behalf |
cross_group_events (planned, #331) |
Never a child — under-18s cannot redeem | See cross-group-events.md |
Consent must be voluntary, informed, current, specific and unambiguous, must not be bundled (s 14(3)(b)), and expires at twelve months (ss 15–16). Withdrawal is one tap (s 17). A personal stance preset may set preferences and never consents — a consent is always an individual prompt on a screen a person went looking for.
What the child is told (the s 33 posture) — as built. app/child-privacy.tsx, "What your
parent can see", the first row of Settings on an under-16 account and reachable whenever they
want it: what is kept, why, who sees it, what is only theirs (their plan; the absent assistant
conversation; the chat being the group's), what a guardian can do (take the yes back, remove
them, ask for deletion; the admin's children_allowed, announced to the whole group; and that
the app does not tell a parent what they looked at), how long, and the s 32/36 route they may use
themselves. Two sections are GENERATED from lib/settingsScope.ts so the page cannot promise a
privacy the switches do not keep. Written for a ten-to-twelve-year-old and scanned for
overclaims. It is not shown at setup — there is no child setup flow yet; the deviation from
this paragraph's last sentence is recorded in §12A (s 33 "as soon as in use": partly met).
10. Best interests of the child (exposure draft s 10)
What a child gets. Their own place in their family's organisation: their own chores, their own view of the calendar, their own voice in the group chat, their own account rather than borrowing a parent's. That is not a trivial good. A thirteen-year-old who can see that Thursday is their turn, and tick it off themselves, is being treated as a participant in the household rather than a subject of it. The games hub's per-user presentation and the ledger-visible parent adjustments already fit that shape.
What it costs them. An account is a record. Chat messages, completions, photographs and game history accumulate under their name from thirteen, and they did not choose the retention policy. They are in a space their parents administer and can read. And the app is being made attractive to exactly the age group a national law has just decided should not be on social platforms — which is a reason to be careful, not a reason the app is one.
What the safer design would have been, and why it was not chosen. The safest design is the one that exists today: no child accounts, children as profiles only. It was not chosen because it is not actually safer for the child — it produces shared logins, which are worse for the child's privacy from their siblings and their parents' contacts than an account of their own, and it excludes teenagers from a tool their family runs on. The second-safest is a read-only child account, which was not chosen because a child who can see the chores but not tick them is being shown their obligations without any of the agency, which is the wrong lesson.
Where the child's interests and the parent's diverge, and who won.
- The guardian can read the group. The child cannot have a private space inside their family's group. The parent's interest won, and openly: this is a family organiser, the guardian invariant is the safety property, and a private channel for a child inside a guardian-supervised app would be a worse thing to build than not building it. The child is told, plainly, rather than left to discover it.
- The guardian declares the age. The parental-responsibility model won. The alternative is age-verifying children, which is worse for them.
- AI is removed rather than made safe. The child's interest won against the parent's
convenience. An open-ended generative surface for a minor is an AI companion, which is its own
regulatory and safety category, and locality does not fix it. If a child assistant is ever
built it is a command interface with a fixed vocabulary and template responses — the model
classifies, it never composes prose to a child (
docs/privacy_posture.md§5). The moment a model composes sentences to a child, this is a different product with different rules. - The like count is gone for everyone. The child's interest won at no cost to the parent.
The graduation design is a best-interests decision too. A child ages into more of the app rather than being handed all of it at 16 or having a switch flipped by a parent. Sixteen is outside the ban but still inside the Code, so the young person gets the full app with AI off by default and their own consent to turn it on — their decision, informed, and reversible.
11. Residual risk accepted by the owner
- A guardian can misdeclare a child's age. There is no verification and there will not be one. This is the parental-responsibility model working as designed, and the alternative — verifying children's ages — is worse for children. Bounded by children being unable to invite anyone, and by every inviter being inside an assured group.
- A shared, unlocked phone defeats every account boundary in the app. No app fixes this and this one does not pretend to. It is worth stating to parents rather than leaving implied.
- A guardian-present group does not sanitise what adults post. The model is family, supervised, not platform, moderated. This is what keeps the app out of content moderation, and it is a real limitation.
- The whole design rests on the ban self-assessment being right. If GroupApp is an age-restricted platform, child accounts are prohibited outright and this feature is not merely risky but unlawful. Accepted on the reasoning in that document, with its review triggers as the early warning.
- A child's record accumulates from thirteen. Per-item deletion and erasure rights exist; no automatic expiry of a child's own content does. Accepted; a retention design for a graduating account is a candidate for the roadmap rather than this release.
The feedback-feature question is unresolved.Resolved 2026-09-08: likes, reactions and read receipts were removed for every member, not only for children (§5.2). No longer a residual risk here; the app-level residual risks that replaced it are in the ban self-assessment's §12, and the one that matters is the recommender reading rather than anything about children.
Added as built, 2026-09-08 — each is a thing the owner is accepting by merging, written plainly:
- A child account can read and write the baby tracker. §5.2 said "hidden — a sibling's
health record is not a child's to browse"; nothing was built. A thirteen-year-old in a group
with a baby sees the feed log, the medicine doses and the notes, and can log a nappy, exactly as
any active member can. The fix is one restrictive policy per baby table keyed on
is_under_16(auth.uid())plus hiding the tool, a small migration; it was not in WS8's brief. Recommended before the first child account exists in a group that has a baby. - A child account can submit a public review and a dish photo. §5.2 said "refused — the
app's only cross-group surface";
submit-reviewandmoderate-photodo not read the predicate. A child's review, moderated by the same model gate as an adult's, would be visible to every account. Two edge-function checks in the shape of the ones added togenerate-dashboard-insight. Also not in the brief. - Nobody is told at 18. The link lapses correctly and silently. The young person's account gains nothing visible — the variant is under 16 and they left it two years earlier — so the cost is small, but "the young person is told" (§6) is an unmet sentence.
- A child made admin by an adult admin is an admin. "Admins can update roles"
(20260729030000) has no age check. The three member-admin RPCs a child admin could reach are
refused (
GRP10), and the three the client calls do not exist (Known Issues), so the practical reach today is the role policy itself and the group-settings rows. A restrictive policy ongroup_membersUPDATE ofroleis the fix if the owner wants the role closed too. - The child's page is reachable, not shown. s 33 asks that the child be told "as soon as the mechanism is in use"; the page exists from the moment they sign in but nothing presents it. A first-sign-in interstitial for an under-16 is the fix, one screen.
The child-directed policy (s 23) is not written.Resolved 2026-09-08 (WS9, issue #220):CHILD_PRIVACY_POLICY(constants/legalTexts.ts) is the document — reachable from the child's page, the guardian's consent screen and Settings' Legal section, and published on the public site at/privacy/children. The adult policy's §4 now also states the guardian's own view of what they can see and do for a dependant, rather than only pointing at the child's page.- The variant refuses; it does not hide everywhere. The Assistant tab, the Voice row and the event-code chip are hidden; the baby tool and the review button are not (above). A child who finds a hidden surface by deep link is refused by the server, so the risk is confusion rather than disclosure.
12. Review trigger
Before WS8 starts — this document is the gate.WS8 started and shipped on 2026-09-08; the document was revised against the code in the same PR (this revision).- Before the first child account is created in a group that has a baby, or in a group whose members write public reviews — the two "not built" rows in §5.2 become live exposures at that moment, not before. Build them, or accept them again with that fact in view.
When WS9 lands the child-directed policy — §9'sWS9 landed 2026-09-08; §9'stermsrow and §12A's s 23 line change.termsrow and §12A's s 23(6) and s 23 lines are updated in this revision.- Any change to the variant table, the age bands, the guardian invariant, or
children_allowed. - When sign-up opens (#282 §2) — Door A replaces manual allowlisting as the assurance, and §4 is re-argued.
- If a child account ever gains an AI surface, cross-group reach, or a private channel.
12A. Requirements confirmed against the exposure draft (read 2026-09-08) — graded as built
Read from docs/sources/oaic-childrens-online-privacy-code-exposure-draft.pdf. Each is a
concrete obligation the child-accounts work must carry, with the section it comes from, and —
after WS8 — whether it is met, partly met or deferred, and where.
- s 13(2)(b): reasonable steps to confirm the consenting person has parental responsibility.
Met, on the terms the design chose. The guardian is self-declared and the consent screen
says "we have not verified"; what it relies on — an adult member of an assured group, who
created the entry themselves, named on the ledger row — is written on the screen
(
childConsentParagraphs, "WHAT WE RELY ON"), and the copy test asserts the word "verified" appears once and only to deny it. Whether that IS reasonable steps is the residual in §11. - s 13(3): after a guardian consents, the child gets an age-appropriate notice listing the
seven items. Met:
app/child-privacy.tsxcarries what is kept, why, who sees it, how long (12 months, stated), the consequences (nothing is removed by withdrawal; the account exists because they were invited), how to withdraw and the route, how it is used, and the recipients (the group, nobody outside it), in a 10–12-year-old's register, scanned for overclaims. The two settings sections are generated from the scope map. - s 15(4)/s 16: the consent period is stated and never exceeds 12 months. Met: the
guardian's
child_accountrow carriesexpires_attwelve months out; the existing sweep expires it; both screens state the period; the guardian's screen shows when it lapses. - s 20: a child under 15 gives assent, separately. Partly met. The child's own
child_account_assentrow, written by "I have read this" on their page, is a separate act by a separate person under a separate purpose. What it is assent TO is the whole account, not the three specific things s 20 names (sensitive information, secondary uses, contacting the guardian) — the app collects no sensitive information about a child account, has no secondary use, and does not contact guardians, so there is nothing more specific to assent to today. If any of those three appears, this becomes a per-thing assent. - s 9(2): a child must be able to control non-necessary collection, use and disclosure. Met, by absence more than by switches. A child account has no AI, no voice, no cross-group reach, so the two collections that were "non-necessary" for an adult do not exist for a child. Their personal switches remain theirs (typing indicator, on-lock-screen answers, the two lock-screen rows settled for every under-16 and named as such on their page).
- s 23(6): the child-directed policy must explain anonymity / pseudonymity. Met (WS9,
2026-09-08).
CHILD_PRIVACY_POLICY's "You use your own name" section says plainly there is no anonymous or made-up way to use the service — a name and a picture are required to join a group — and names the one exception: a complaint may be sent without a name, the same fact the child's own page already stated. - s 33: a child must be told, as soon as the mechanism is in use, of any guardian monitoring or
control mechanism, easily accessible. Partly met. The page names every such mechanism
(the guardian can take the yes back, remove them, ask for deletion, change the month; the
admin's
children_allowed, announced) and is the first row of their Settings — easily accessible. It is NOT presented at first sign-in ("as soon as"); §11. - s 32(1)(b), s 36(1)(c): a person with parental responsibility may request destruction or
access for a child under 15; a child of any age for themselves. Met:
docs/privacy/requests.mdis updated for accounts, the child's page gives the address and says they may write themselves and need no name for a complaint, the guardian's screen gives the same address. A guardian's own erasure is refused while a dependant names them (409, in words); a child's erasure goes through the ordinary path.
13. Sources
Primary: exposure draft of the Children's Online Privacy Code (ss 8, 10, 13, 14, 15–17, 23,
26, 28, 32, 33, 38–39) as summarised in docs/privacy_posture.md §4.2 — the exposure draft
itself has not been re-read for this document, so every section number here is as good as that
summary and no better. Online Safety Act 2021 Part 4A and the Rules, verified — see
2026-09-ban-self-assessment.md §14.
In repo: supabase/migrations/20260808150000 (the signup allowlist trigger);
constants/legalTexts.ts (the sentence that changes); lib/groupTypes.ts (groupNoun);
AGENTS.md → Invite-Only Is Enforced In The Database, Check RLS Is ENABLED Before Writing
Policies, Ship The Retention JOB Before The Retention CLAIM; docs/privacy_posture.md §5;
issues #282 §4, #219, #220, #221.
Built (2026-09-08): supabase/migrations/20260908043933_ws8_child_accounts.sql;
__tests__/child_accounts_rls.test.ts; lib/childAccounts.ts, lib/childVisibility.ts,
lib/consents.ts; app/child-privacy.tsx, app/child-consent.tsx,
components/Group/ChildrenSection.tsx; agent-server/src/childAccount.ts;
supabase/functions/delete-account, generate-dashboard-insight, ai-task-allocator.
Also built (WS9, 2026-09-08): CHILD_PRIVACY_POLICY and the guardian's-view paragraph in
constants/legalTexts.ts; lib/__tests__/childPrivacyPolicy.test.ts; the Legal section in
app/settings.tsx; the policy links in app/child-privacy.tsx and app/child-consent.tsx;
site/build.mjs's /privacy/children page.
Not built: the two §5.2 rows marked so, and everything in §11's "added as built" list.