← All privacy impact assessments

PIA: Cloud inference — where a group's words are processed, and what stops the bill

Amended 1 November 2026 — Device-only retired (privacy-levels §3E). From the move date, a group that had Device-only runs its AI at Vertex global like every other group (§12). No level is left that keeps a group's words away from a model.

Status: Draft — written before we built this change, then revised against the finished implementation the same day (9 September 2026), and revised again on 10 September 2026. Written: 9 September 2026 · Last revised: 10 November 2026 (D164: account A$4/day and A$12/seven UTC days; five pausing slices A$5; the A$10 platform day remains binding); 31 October 2026 (§8.7: the two smallest slices raised to A$1.50, and the ledger refuses when it can't answer); 31 October 2026 (§6 and §8.7: the A$200 month, the kill switch at A$300 and a per-account cap; §14: the per-account daily spend record); 28 October 2026 (§12, amendment: photos and PDFs attached to the assistant); 4 October 2026 (§12: every group's AI now runs at Vertex global; this supersedes the Sydney routing in §1–§9 wherever they disagree) Nothing here is legal advice. Children's Code citations are to the exposure draft; section numbers may move on registration (due 10 December 2026).


1. Purpose and scope

Every generative call this app makes today goes to OpenRouter, which routes to whichever of Google's regions it likes. This change moves the calls that carry a group's own information to Google Cloud Vertex AI in australia-southeast1 (Sydney), and puts a layered ceiling under the spend so a runaway costs money for hours rather than for a month.

In scope: which surface talks to which provider in which region; what the request body carries when it crosses a border; the spend ledger, its ceilings and the kill switch. Out of scope and covered elsewhere: what the assistant is allowed to see (see our assessment on child accounts and the in-app transparency page), transcript encryption at rest (see our encryption plan), and the on-device dashboard brief, which does not leave the phone at all and is unaffected.

One sentence for why: a group's information should be processed in Australia, and a surface that holds none of it may stay global.

2. Data involved

Nothing new is collected. What changes is where the same request body is processed.

Surface What its request body carries Strictly necessary?
Assistant turn (router + sub-graphs) the member's message, the last few turns, group context: member names/roles, the group's date and time, and whatever the bound tools read back Yes — it is the question and the facts needed to answer it
Task/event allocation task titles, member names, effort levels, existing calendar rows Yes — the allocation is over exactly these
Baby recent summary a counted digest of one baby's day (5 feeds, never a characterisation) Yes, and only behind show_recent_summary
Voice baby-log extraction one spoken sentence about a baby Yes — there is no other way to read a free-text spoken log
Dashboard insight + expiry estimate the day's items and, behind consent, baby cues Yes
Home brief editions (planned, added 29 September 2026) one group's fact sheet — at most 30 short facts: task and event titles, times and assignees, linked preparation titles, meal and shopping state, and up to four recall facts; no chat, notes, places, private plans or baby lines; names replaced on the global route Yes — see our home brief assessment §2
Schedule time lookup (embeddings) ~150 of the group's own past event titles Yes
Review moderation a public recipe review the author is publishing to every user Yes — and it is not group information
Tool codegen our own database schema, at developer time Yes — no user data at all

The last two are the only ones that stay global, and the reason is the same for both: there is no group's information in the request. A review is written to be published to strangers; the codegen prompt is a database schema and a TypeScript file.

Baby records, a child's name and a child's day are the most sensitive things in any of these bodies, which is why the health-vocabulary pin in §3 exists.

3. Flows and locations

This table describes the state after the platform default is switched to INFERENCE_PROVIDER_DEFAULT=vertex — as at 9 September 2026, that switch had not yet happened:

Node Provider Model Region
supervisor / extraction, calendar, chore, meal_planner, pantry, admin, games, baby, insight, insight_summary, allocation Vertex AI gemini-2.5-flash australia-southeast1
voice baby-log extraction Vertex AI gemini-2.5-flash australia-southeast1
dashboard insight + expiry estimate (/api/insight, insight_summary node) Vertex AI gemini-2.5-flash australia-southeast1
task allocation (/api/allocate, allocation node) Vertex AI gemini-2.5-flash australia-southeast1
schedule time lookup Vertex AI gemini-embedding-001 australia-southeast1
organising domains at "Global, records here"; every domain but extraction at "Global, everything" (names replaced) — since 28 September 2026 Vertex AI gemini-3.8-flash global
capacity fallback for the gemini-3.8-flash rows — since 22 October 2026 (§12, amendment) Vertex AI gemini-3.7-flash global
Mail Sifter: the assessment, its pictures and a "Turn into cards" turn — from the release after 31 October 2026 (§12, amendment); the Flash rows above are its capacity fallback Vertex AI gemini-4-argon global
home brief editions (planned) — writer and editor, at Australia, and at a Global level whenever the fact sheet carries health vocabulary, a baby as a task's beneficiary or a baby's name Vertex AI gemini-2.5-flash australia-southeast1
home brief editions (planned) — writer and editor at either Global level, for a fact sheet with no health or baby content (names replaced) Vertex AI gemini-3.8-flash global
review moderation OpenRouter gemini-3.5-flash-lite global
tool codegen (developer time) OpenRouter gemini-3.5-flash-lite global

Moving the dashboard insight and the task allocator onto our assistant server closed both of the residuals this section used to carry (§8 residuals 1 and 2, below): they now run as /api/insight and /api/allocate rather than as Supabase edge functions with no runtime identity, so they follow INFERENCE_PROVIDER_DEFAULT exactly like every other node in the first row. The deprecated edge functions they replace (generate-dashboard-insight, ai-task-allocator) were kept deployed, on OpenRouter, global, so that an app version from before 9 September 2026 could still call them — that window, not a routing decision, is why they are not in this table. Their code was removed in October 2026, and the deployed copies are deleted by hand; until each is deleted, an app version that old can still reach it.

Sydney serves exactly one generation model — gemini-2.5-flash — plus gemini-embedding-001 (checked 7 September 2026). It does not serve 3.5 Flash, 3.5 Flash-Lite or 3.7 Flash. Singapore is as overseas as the United States for the Code's purposes and buys nothing. So Sydney is 2.5 Flash or it is not Sydney.

Cross-border disclosure (APP 8). Today every one of these bodies is disclosed to an overseas recipient. After the switch, the only group-data body that still is, is the dashboard insight — see §8 residual 1, which is the finding of this assessment rather than a footnote to it. The Code's cross-border clause and APP 8.1 both turn on taking reasonable steps before disclosure; moving the processing onshore is the step, and the residual names the one place it was not possible in this change.

Region routing inside one turn. A future privacy setting will let some steps run globally. This change lays the seams and flips nothing: every turn is tagged with the domain that produced it (origin), filterHistoryByOrigin is the pure function that will withhold a baby turn from a global step, and a health-vocabulary pin forces any extracted intent whose text carries medicine / dose / fever / doctor / vaccine / allergy language onto the Sydney provider whatever the level says. Today every level resolves to Sydney anyway, so this is a data-shape change; it is built now because the moment it matters is the moment it is easy to get wrong.

4. Legal basis and consents

No new consent is created and none is needed: the purposes are unchanged and each is already covered by an existing switch (voice_assistants_enabled, show_recent_summary, dashboard_insight, improve_app_traces). A stance or a preset may not consent and nothing here writes a consents row.

The privacy policy's AI section and the in-app transparency page gain one sentence naming the processor and the country. It ships as tested app copy and is released together with the provider switch — not before, because a residency claim that renders while the calls still go to OpenRouter is a false statement in a legal document.

No zero-data-retention claim appears anywhere. Our zero-data-retention (ZDR) request to Google is pending; until it is granted and evidenced, the strongest true sentence we can make is that the provider does not train on the request, which our privacy policy already says.

5. Risks and mitigations

# Risk Mitigation Kind
1 A group's words are processed overseas when the app says they are not The claim renders only with the switch; an automated check refuses any household-data node resolving to a non-Sydney endpoint; the URL is built from a constant, not from free env Structural (check + constant)
2 A baby record reaches a global step once a future privacy level allows it filterHistoryByOrigin + the health-vocabulary pin, both pure and unit-pinned; no baby tool bound outside the baby domain (asserted) Conventional today, structural once that level exists
3 Runaway spend empties the card Six layers, §6 Mixed — see §6
4 The ledger stops binding silently (a meter reading zero) Both usage shapes read; assertMetered logs loudly; an automated check asserts every call site pre-flights and bills outside its try Structural (check)
5 A model step-down makes the assistant worse at obeying instructions The defaults do not flip as part of this change. We run an evaluation suite against the deployed service and compare obedience and over-split to the 3.7 baseline in our model evaluation notes. If worse: wait for a 3.x Flash in Sydney, or accept a stated overseas surface — never a quiet compromise Conventional (a person reads a number)
6 The kill switch fires and nobody knows why It logs; the next request finds Vertex 403 and the user is told the assistant is paused for the rest of the month; an internal runbook is the re-enable procedure Conventional
7 A member's own weekly allowance stops being the first gate Layer 1 is untouched; the platform ledger is added under it, never instead of it Structural (the RPC is unchanged) — updated 2026-09-10: a database change on 10 September 2026 merges layer 1's input and output ceilings into one and doubles the total (4,800,000 per user per week). Same RPC shape, same auth.uid() identity, same check-and-charge in one statement; the gate is still first and still structural

A harm this change does not create and should be named anyway: moving to a single region and a single model means one provider outage takes the assistant down for everybody. Failing closed with a sentence is the right answer for a family organiser, and is what already happens when OpenRouter is out.

6. Structural versus conventional

Structural — Postgres, a project boundary or an IAM grant refuses it:

Conventional — a person, a test or a comment keeps it true:

7. Best interests of the child

Two things in a child's favour, one honest limit.

In favour. A child's records — their name in a task, a summary of a baby sibling's day, a spoken log — stop being disclosed to an overseas recipient in the ordinary case. And the health-vocabulary pin means a sentence that sounds medical is treated as medical for routing purposes even when the extractor did not classify it as such: the failure mode is "processed in Australia when it did not need to be", which is the direction to be wrong in.

The limit. A child account has no assistant, no daily brief and no voice, so almost none of these surfaces run for a child directly. What runs about a child is an adult's request, and the adult is the one who chose to make it. The dashboard insight residual (§8.1) therefore touches a child's information — a baby's counted cues, behind the group's own consent — and that is stated rather than minimised.

Nothing here evaluates, characterises or scores a child. The baby summary's counted-not- characterised rule — our rule that a baby's day is described in counts, never in a characterisation — is unchanged by a change of provider.

8. Residual risk we accept

  1. Closed: the dashboard insight now runs on our assistant server. The old edge function generate-dashboard-insight is replaced by POST /api/insight, which has a runtime identity a Supabase edge function never had. Its cache read/write and its roster, completions and nudge-state lookups all run on the caller's own RLS-bound client; our assistant server still holds no service-role key and never will — giving it one was considered and rejected outright, and was never built. The deprecated edge function stays deployed, unmodified beyond a banner naming the replacement, for as long as an app version from before 9 September 2026 can still call it.

    • Residual within the closure: the cache write currently fails. group_dashboard_insights grants authenticated SELECT only — it was built for the edge function's service-role writer, and nothing has granted an owner-scoped INSERT/UPDATE. The new route attempts the same upsert the edge function always did, gets refused, logs it, and serves the brief anyway — the same non-fatal handling the edge function already had for a failed cache write, so nothing a user sees breaks. What is lost is the caching itself: this route currently regenerates the insight on every request rather than serving a cached one, which is real spend that the platform ledger (layer 2) and the caller's own weekly allowance (layer 1) still bound, but bound less cheaply than intended. The fix is a follow-up database change — an owner-scoped INSERT/UPDATE grant plus a matching access policy, or a security definer function in the shape of record_inference_spend — deliberately left for later. This is tracked as a known issue.
    • The health-vocabulary pin and the Sydney pin both still apply: the dashboard insight step is pinned to Sydney in the same routing configuration as the baby summary endpoint (/api/baby/summary), so this surface comes home whatever a per-node override says once Vertex is the platform default.
    • One behavioural narrowing, disclosed rather than worked around: a signal for whether a member has used the assistant recently used to read the chat_messages table directly, inside the edge function. Our assistant server can never read that table — the transcript is sealed under the member's own key and an automated check refuses any code that tries — so this signal is now permanently true (a documented fail-closed default for a signal that cannot be read), which permanently suppresses one meal-planning nudge. Cosmetic; the shopping-list nudge is unaffected.
  2. Closed the same way. The old edge function ai-task-allocator is replaced by POST /api/allocate on our assistant server. A groupId field was added to the request body — the edge function had no notion of which group, authorising by JWT alone — because the planned device-only privacy check is group-scoped and will need one; the deprecated edge function ignores the extra field. Everything else, including the response shape and the deterministic-floor validation rule, is unchanged. Since 18 September 2026 the request may also carry rules.excludedMemberIds — member ids the household has asked to leave out this week, which are already in the members list it sends — and the assignment and the shopping-list unification are two requests rather than one; the server leaves an excluded member out of the roster the model is shown and refuses any answer naming one. No new category of information reaches the model; strictly less does. The response gains a count of refused entries and per-stage timings, numbers only.

  3. As at 9 September 2026, nothing here had been run against Vertex. The evaluation run, the first real generation call, the first real ledger row and the first budget alert are all steps we take after deployment.

  4. The kill switch is exercised only by its unit test until a threshold is actually crossed. A Pub/Sub message that does not match the schema-1.0 shape the parser expects would be ignored rather than mis-firing, which is the safe direction and also the one that fails silently.

  5. origin is persisted in chat_messages.metadata, not in a column of its own, and every turn written before this shipped has no tag. filterHistoryByOrigin ADMITS an untagged turn rather than withholding it — withholding would silently shorten the assistant's memory for everybody on a change meant to be invisible. That is today's behaviour exactly, and it becomes load-bearing only when a future privacy level is introduced that is not Sydney; at that point the honest options are to withhold untagged history or to backfill, and both are decisions rather than defaults.

  6. A single region and a single model is a single point of failure, accepted for the reason in §5.

  7. Revised by founder D164 on 10 November 2026: a member who calls record_inference_spend directly can use only their own allowance, but two accounts can fill a small slice for everyone. The RPC is still granted to authenticated, because every surface bills on the caller's own client and our assistant server holds no service-role key. So a member can still post token counts for calls that never happened. Since the migration of 31 October 2026, with limits revised by founder D164 on 10 November 2026:

    • each account's charges count toward the shared pools only up to A$4.00 a day and A$12.00 over seven days;
    • one call is bounded at 64,000 input / 16,000 output tokens (it was 2,000,000 / 200,000). Our assistant server splits a larger settle into bound-sized charges, so no real request is refused by the bound;
    • a surface the ledger doesn't know is refused.

    A hand-made loop now uses up its own account's cap and is then refused, like any other request from that account.

    What remains for the beta, with sign-up capped at 150 accounts: the earlier limits were reviewed by Security and accepted by the founder on 7 November; founder D164 subsequently raised them on 10 November. This is about availability only. A fabricated charge moves no data, reveals nothing and costs us no money: it uses up the ledger's count, not the model.

    • Any signed-in account can do this, in a group or not. It takes several calls and costs the caller that day's allowance. One person can hold several accounts, so "accounts" below need not mean several people.
    • The caller names the surface, so two accounts can fill a small slice and pause that feature for everybody until 00:00 UTC: embeddings (schedule time lookup, task search and matching a routine by name), baby_summary, mail_sifter, insight (the Home brief and routine suggestions) and allocation at A$5.00 each.
    • voice_extraction (A$1.50) can be filled too, but pauses nothing: a spoken baby log isn't checked against that slice, so that a parent's spoken log is never lost.
    • No safety or privacy check depends on a small slice, and a pause lets nothing through. A paused feature sends nothing to a model and writes nothing: an email waits unsorted, a routine is changed only by its exact title, and the Home insight falls back to a plain greeting. The attachment guard and the fast-command check are on the assistant's slice, and when it is full they refuse rather than go on unchecked.
    • Two accounts can fill the assistant's A$8 slice and three the A$10 day, fewer on a day with other use. The ledger refuses at the limit, so exact equality is enough.
    • Four accounts at their weekly limit (five in a 28-day month) can fill the A$200 month by about its last days. More accounts do it sooner, because the ledger records a hand-made charge even when the day is already full: 25 accounts in two days, 50 in one. All AI then stays paused until 00:00 UTC on the 1st, unless we raise the ceiling.

    Capping one account's share of any one surface is left for before the public App Store release.

    • The charges are attributable to accounts: the record in §14 shows which accounts reached their cap that day, though not on which surface. (The text this replaces said the ledger rows named the user. inference_spend has never held a user id. Only §14's record does.)
    • Since 31 October 2026 the ledger and the token meters refuse when they can’t answer (an error, no answer, or a refusal of their own), so no model call starts that the ceiling hasn’t just admitted. If the ledger fails during a turn, the step already under way can finish uncounted and the turn stops at its next check. Our two production switches are the way through during a long outage.
  8. /api/insight and /api/allocate read the privacy-level ladder only far enough to refuse device_only. Both are eligible for the global provider at global_all (the same eligibility rule as the organising domains and the baby summary), but neither replaces a household's real names in its prompt the way /api/baby/summary does — a step that swaps names out of the prompt and restores them afterwards. Wiring the global level into these two routes without that replacement would send a household's real names to a global endpoint the moment someone chose global_all — the exact disclosure that name replacement exists to prevent — so both routes stay Sydney-only regardless of a non-locked level, which costs slightly more and leaks nothing. An automated check pins the absence as deliberately as it pins the refusal.

9. Review trigger

29 September 2026: the third stage of the personal Home and assistant widgets changes no route — assessed against its design, and checked again the same day against it as built. Our Home, widgets and setup assessment §10 describes it, and its review triggers ask for this document to be revised if general conversation were given its own place in the routing between Australia and the global provider. It is not:

  1. General conversation has been answered since 28 September 2026 by one model call, with no lookup, on the extraction step — the step pinned to Sydney at every level — and it stays there.
  2. The combined read of tasks, calendar and, newly, meal plans runs on the insight step, as the combined read of tasks and calendar already does: Sydney at Australia and at "Global, records stay here"; at "Global, everything", Vertex global with names replaced, unless the request carries health vocabulary — the rows already in §3.
  3. The new current-meal-plan-day lookup is bound to the meal planner, which already goes to Vertex global at either Global level with names replaced; it reads no baby or health table.
  4. A request to open a tool or show a view is answered from a fixed table after extraction, with no further model call; a request to see a baby's growth chart still runs the existing growth-chart step, on its existing route.

No new node, provider, region, model or model call is added. What changes is how much of the group's records one question can carry on those existing routes, and it is capped per question at 6 lookups, 50 rows of one kind, 120 rows in all, a 31-day window and 32 KiB of text. No price row, ceiling or kill-switch setting changes.

29 September 2026: the home brief, redesigned (planned, not built). Assessed and accepted in our home brief assessment. What it changes here:

  1. A new surface that follows the Global levels. One paragraph per group, twice a day, written by a writer pass and at most one editor pass. At Australia both passes run in Sydney on 2.5 Flash. At either Global level both run at Vertex global on 3.8 Flash with names replaced, the same as the organising domains above — unless the fact sheet carries health vocabulary, a baby as a task's beneficiary or a baby's name, in which case both passes run in Sydney. Before each pass the same text goes to the same endpoint's token-counting service, at most twice. No other provider: a route that resolves to anything else writes nothing.
  2. It is paid for by the group, not by a member. The brief does not charge a member's weekly allowance (layer 1 in §6). It is bounded instead by a per-group weekly limit of A$0.50, held and checked in the database under a per-group lock before every provider attempt, with the platform ceilings (layer 2) unchanged over it; the brief's platform spend is counted under the existing insight surface. Because record_inference_spend takes its identity from a member's session, the brief's own database functions write its platform totals and check the same daily, monthly and surface ceilings.
  3. §8.8 is unchanged for today's route. /api/insight stays Sydney-only and keeps serving app versions from before the redesign until they have gone; the redesigned brief is a separate route that does replace names, which is the condition §8.8 set for going global.

28 September 2026 model and route review. Three changes, each written down before the code.

  1. Sydney goes back to Gemini 2.5 Flash for every node. 3.5 Flash in Sydney proved unreliable in practice: a side-by-side probe from our inference project on 28 September found it answering with "out of capacity" on most attempts during the Australian evening, after which our server had been quietly answering on 2.5 Flash anyway. The assistant, summaries, voice extraction and allocation all use 2.5 Flash in Sydney; the backup model and its cooldown are removed, because there is nothing left to fall back to. Processor and region are unchanged: Google, Sydney.
  2. Global-level groups are processed by Google Vertex AI at its global location, on Gemini 3.8 Flash, instead of through OpenRouter. The same steps go global as before (the organising domains at "Global, records here"; every domain but extraction at "Global, everything"), with names replaced and restored exactly as before and the health-vocabulary pin still winning. What changes is who receives the text: Google directly, rather than OpenRouter routing onward to Google. That removes a party — after this change no group's words reach OpenRouter from our assistant server at all (the deprecated edge functions in §3 keep their pre-9-September window). Vertex global may process in any Google region, which is what the Global levels' own consent screen already says ("any of our providers' regions"); no consent copy changes. 3.8 Flash is not served in Sydney, so this is only possible at a Global level.
  3. The experiment lab calls the same routes from a separate project (groupapp-evals), with synthetic data only. It shares no bill with production and cannot trip production's kill switch.

Spend: both models are priced before the switch (2.5 Flash Sydney since 9 September; 3.8 Flash global at Google's standard rate, recorded ahead of the introductory rate so the ceiling cannot under-count from 2027). Nothing in §6's structural column changes: the region for Sydney is still a constant, the global location is a constant too, and an unconfigured Vertex is still a refusal, never a fallback.

24 September 2026 model review. The routing table above records the original decision, when the Sydney endpoint exposed only Gemini 2.5 Flash. A direct call from our inference project now succeeds with Gemini 3.5 Flash on that same Sydney endpoint. The tool-using assistant can therefore use 3.5 while short summaries, voice extraction and task allocation retain 2.5. This changes neither the request bodies nor the processor or region. The new assistant model costs more per token, including reasoning output, so the existing spend ceiling needs its published price row before the model switch reaches production. The assistant tool-choice evaluation is recorded separately. This amendment does not resolve any existing global-provider residual or permit a global fallback for a Sydney call.

Revisit again when Sydney model availability or pricing changes materially; the ZDR request is answered either way; a future privacy level that is not Sydney is introduced; the insight moves onto our assistant server; a ceiling is raised; or the Code is registered and the cross-border clause moves.

10. Checklist against exposure draft s 38(2)

s 38(2) Requires Answered in
(a) nature, scope, context, flow and purposes §§1–3
(b) why collection is strictly necessary §2, per surface
(c) lawful and fair means §4 — no new collection; the change is where processing happens
(d) consistent with the best interests of the child §7
(e) how the entity complies with the Code and the Act §4, §6 (APP 8 in §3)
(f) risk of harm and potential impact §5, §7

s 38(3): written before the code, in the sequence this document sets out, and finalised first. s 39: listed on our published register.

11. Sources

12. 4 October 2026 — every group's AI runs at Vertex global

This section supersedes the Sydney routing described in §1–§9 wherever they disagree. The rest of this assessment — the spend ledger, its ceilings, the kill switch, the separate inference project, "unconfigured is a refusal, never a fallback" — is unchanged.

What changed. Every model call our assistant server makes for a group that is not Device-only now goes to Google Vertex AI at its global location, which may process in any country where Google operates, including the United States. The privacy level no longer moves a call: Standard (the stored values australia, global_records_here, global_all) all route the same way, and Device-only is still refused before any model is built. Names are no longer replaced with placeholders before Google sees a request. Health words and baby records are no longer pinned to Sydney. Models: Gemini 3.8 Flash for the assistant (extraction at low thinking), summaries, allocation and the Mail Sifter assessment; Gemini 3.5 Flash-Lite for the home brief and the voice baby-log extraction (both single-shot, neither binds tools).

Why. Sydney could no longer carry the product, measured rather than assumed (docs/ml_experimentation.md, 4 October 2026):

What stays in Australia: the database (Supabase, Sydney); the group key service (KMS, Sydney); search matching (task and meal search embeddings, gemini-embedding-001 in Sydney); consented traces are stored in LangSmith's Australian region.

Developer-time trace reads (22 October 2026, HQ PIA-003). groupapp-evals also serves Claude at global for PIA-003's developer-time trace reads; no member request reaches it (assistant tracing §3).

Second processor. TypeSafe's Jev (United States) gives a second judgment on Mail Sifter assessments. It used to apply only at "Global, everything"; it now applies to every group that uses Mail Sifter — still with roster names replaced, and never for an email carrying health words (agent-server/src/inboxAssessment.ts).

Consent and notice. The Australia/Global choice and its overseas_processing consent are no longer offered; existing grants simply lapse through the sweep, which now changes nothing. The privacy policy moves to v3.0 and every adult member sees what changed once (the policy-change sheet). The young-member AI consent changed its words, so its version moved and every 16- or 17-year-old is asked again (their switch is cleared; the server refuses until they answer). The daily-brief consent's words changed too and new answers record the new version; adults who already said yes are not asked again — they are told through the policy-change sheet every adult member sees once for v3.0 (owner decision, 4 October 2026), and can turn the brief off in Settings.

Children. A child's records, a baby's feeds and health words now reach a model outside Australia when the group is not Device-only. This is the accepted residual of this decision; the protections that remain are the ones that never depended on region: child accounts and their guardian consent, the young-member AI consent, the assistant's medical guardrail, Google's own terms saying it does not train on requests (its promise, which we cannot check), and Device-only for any group that wants nothing to reach a model. Re-check against the Children's Online Privacy Code when it is registered (due 10 December 2026) and before public beta.

Residual risks accepted. (1) Overseas processing of every group's words, including health and children's information, by Google. (2) TypeSafe now sees every Mail Sifter group's redacted emails. (3) Gemini 3.5 Flash-Lite writes the home brief without a quality evaluation — the validator still refuses a draft that breaks its rules. (4) 3.8 Flash's default thinking makes a domain step slower than 2.5 Flash in Sydney was (experiment 001: 12 s median Global turn).

Revisit when: an Australian region serves a capable Gemini model on pay-as-you-go; the Children's Code is registered; before public beta; a member asks for Australia-only processing (Device-only is the answer today); or Google designates any model GroupApp calls as a Frontier or Advanced AI model, or its abuse-monitoring page changes its retention or scope; or Google changes implicit caching's tiers, lifetime or location, or cacheConfig reverts.

Amendment, 22 October 2026 — 3.7 Flash as the capacity fallback (owner decision of 21 October). When Gemini 3.8 Flash has no capacity at global (a 429, a 5xx, a network failure or a call that hangs), our assistant server retries it with backoff and then answers with Gemini 3.7 Flash, at the same Vertex global location, in the same Google project. Same processor, same location, same terms; the request is identical except for the model's name. So nothing in this assessment's flows, notice or consents changes, and no privacy-policy change is needed. It applies only to the assistant's steps, summaries, allocation and the Mail Sifter assessment (the getModel() callers); the home brief, shopping and the Flash-Lite callers are unchanged. (Since 28 October 2026 shopping's product check uses it too, with one retry of 3.8 rather than two, before Flash-Lite; our shopping assessment sets out that change, accepted on 28 October 2026.) The location is still a constant no setting can move, a 403 from the budget kill switch still fails closed, and each call is priced at the model that answered it (supabase/migrations/20261022013738_gemini_37_flash_global_price.sql). Claude or OpenRouter as a fallback would be a different processor and would need its own assessment first.

Amendment, 24 October 2026 — pictures in Mail Sifter (PIA-008). Pictures in Mail Sifter, from email and from shares, now reach Gemini 3.8 Flash (3.7 Flash on capacity) at Vertex global, as re-encoded pixels with a fixed instruction. Unlike text, nothing in a picture is replaced: names and faces go as they are. Same processor, location and terms. Children's faces, including other families', are an accepted residual (PIA-008 R1, R2, R6). Off behind two platform switches until the next policy version is on phones. Revisit with on-device text recognition for shares (F7).

Amendment, 31 October 2026 — Mail Sifter on Gemini 4 Argon (founder decision of 30 October; Security ruling of 30 October, AG1–AG8). Mail Sifter's model calls (the assessment of a forwarded email or a share, the pictures it reads, and a "Turn into cards" turn's steps) go to Gemini 4 Argon (gemini-4-argon, the plain model), at the same Vertex global location, in the same Google project and under the same terms as 3.8 Flash. On a capacity error or a hang the call falls back to today's Flash route: 3.8 Flash, then 3.7 Flash, as in the amendment of 22 October. Nothing else moves: assistant attachments (PIA-011), the assistant's own turns, the home brief, shopping and the insight tier stay where they are. The location and the model are constants no setting can move; each call is priced at the model that answered.

What Google's terms say about logging, as read on 30 October 2026 (Google's abuse-monitoring and Argon model pages, both "Last updated 2026-10-28"):

The privacy policy's list of models gains Gemini 4 Argon in the next policy version (v3.15). Picture reading switches on only once that version is on phones. Until then Argon may answer text, as 3.7 Flash did on 22 October: same processor, location, terms and data.

Revisit when: Google designates any model GroupApp calls as a Frontier or Advanced AI model, or the abuse-monitoring page changes its retention or scope; or Google changes implicit caching's tiers, lifetime or location, or cacheConfig reverts. Re-read both pages, and Google's zero-data-retention page, on the release day that carries Argon and before each picture switch goes on. If plain Argon is listed, Mail Sifter goes back to the Flash route until Security rules.

Amendment, 28 October 2026 — photos and PDFs a member attaches to the assistant (HQ PIA-011; built, switched off). Two new calls, both at Vertex global, same processor, project and terms, each reserving the member's, the group's and the platform's allowance before it runs and charged after, under the same budget kill switch, which fails closed:

A PDF is read on our server in Sydney without a model; its words then go to the guard and to the assistant's turn, as typed words do. We make no zero-retention claim for either call; whether request-response logging is off in the production inference project is unverified as at 28 October 2026 and must be recorded before the switch goes on. Recognising text on the phone is the upgrade path that would keep pixels off our server and Google's. The full assessment is the assistant reads a photo or a PDF you attach.

7 November 2026, D151 — queue tier and time limits. Eligible retries can use Vertex Priority PayGo on the same models, processor, project and global endpoint. No new data flow, retention or disclosure is introduced. The shared setting ships off; enable retries in a later release after the conservative price rows are live. Capable phones can wait longer (48 seconds server, 58 phone); older phones retain the previous schedule, and signed assistant tool steps now get a 20-second first-attempt cap within the existing 25-second call deadline. No member-facing words or prompts change.

10 November 2026 — consent notice prominence (PIA-007 C28)

The founder's signed decision “consent notices, middle path” of 10 November 2026 amends C28 to require equal prominence: equal button size, text weight and 48 dp targets, with a filled Turn on and an outlined Not now. Neither is pre-selected.

13. 27 October 2026 — adults are asked before their requests reach a model (HQ PIA-007 C25–C31)

What changes. Until now adults were told about AI only in the privacy policy. They will be asked on a sheet, "Use AI features?", that names the two services a member's request can reach: Google (Gemini on Vertex AI, global) and TypeSafe (Jev, United States). No other provider receives a member's request: OpenRouter was taken out of our assistant server in October 2026, and the two edge functions that still called it are retired (below).

One gate on the server (agent-server/src/aiConsent.ts). Every agent-server route that can reach Vertex, TypeSafe, voice or Gemini Live asks one check, once, before anything is read for or sent to a model. A child is refused, including a child with no birth month on record who is linked to a guardian or known to be a child. A 16- or 17-year-old needs their own young_member_ai yes, as before. Everyone else needs their adult_ai switch on and, in the consent ledger, a current assistant yes at AI_DISCLOSURE_MIN_VERSION or later. The shared display and profile accounts aren't asked: their AI runs under the admins' group consents, as does Mail Sifter's server sorting (C31). An account with no birth month that isn't known to be a child is treated as an adult, as it always has been, and is asked as one (our decision, 27 October 2026: refusing them would cut off existing adults). An explicit yes to the daily brief doesn't stand in for the AI yes. Every read goes through the caller's own client, on every request, and fails closed; a missing switch row is off. agent-server/checks/aiConsent.check.ts lists every route agent-server registers and fails when one that can reach a model doesn't ask the gate first.

Inert until the founder raises the minimum (C26). The adult part refuses nobody until ai_disclosure_enforced() is true: the founder has set ai_disclosure_settings.enforce_from_app_version to the first app version with the sheet, and the iOS and Android min_supported_client_version are both at or above it. Until then an adult's AI works as it does today. The server reads the founder's value itself, so only a value it has read as unset keeps the gate off; once a value is set, a check it can't complete counts as on. When it is on, an older app's refusal reads "Update GroupApp to keep using AI features".

An adult's yes doesn't lapse (F9). The Children's Code's twelve months are for children. An adult's grant carries no expiry (a constraint refuses one with an expiry), so the sweep never touches it. Adults are asked again when a provider, region or kind of data changes: a new version and a new server minimum (C30c).

The sheet (C27–C29). From the privacy policy version that describes it (v3.14), the app asks "Use AI features?" of every account not known to be under 18, an account with no birth month included, since the server treats that account as an adult; never a child, the shared display or a profile, and not a member whose only groups are Device-only. It shows on the Assistant tab, as a card in first-run setup after an adult birth month and a first Standard group, and before the first request of any AI feature the member taps, so nothing is sent before the answer. Turn on writes the member's adult_ai switch and then an assistant consent row at ai-disclosure-1.0-2026-11, with no expiry. Not now writes nothing and sends nothing; the sheet comes back at the next AI tap, and features that run on their own fall back to their non-AI versions. You → Your choices has an "AI features" row with the date of the yes; one tap turns it off and records a withdrawal, and turning it back on shows the sheet again. If the server answers that a yes is missing, the app shows the sheet instead of an error and sends the request once more after Turn on.

16- and 17-year-olds are asked once more (C30b). Their prompt now says "What you ask may also go to TypeSafe's Jev, in the United States" (young-ai-2.1-2026-11). From policy v3.14 a member whose yes was to the earlier words is asked again, once per phone. Once enforcement is on, the server counts only a yes to the new words or later.

Retired edge functions (C25, C30a). ai-scheduler (the old meal-plan adoption, last called by apps from August 2026) and semantic-search-plans (meal search, last called before v1.82.0) both sent member requests to OpenRouter. Each now answers 410 { code: 'retired' } to every request before reading a key, a session or the body. Undeploying them is the founder's production step. No other edge function makes a model call for a member (brief-ledger meters the home brief's spend; the brief's model calls are agent-server's, behind the gate).

Residual risks accepted. (1) Until the minimum is raised, adults' requests reach Google and TypeSafe on the policy's notice alone, as today. (2) The gate reads up to six small rows per request for an adult once enforced; a read that errors refuses, so a database hiccup can refuse AI until a retry. (3) An Android minimum below the sheet's version keeps the gate inert, by design.

14. 31 October 2026 — a per-account daily spend record

What changes. To cap each account's share of the shared AI spend (§6, §8.7), the database keeps one row per account per UTC day in inference_account_spend. Each row holds:

It holds no group, surface, model, provider, token counts or content. An account that uses no AI feature that day has no row. It is the same kind of usage counter as the weekly token allowance (user_usage).

Who can see it.

The single-row inference_account_ceilings table holds only the two limits and holds no personal information.

Retention. A daily job (sweep-inference-account-spend, 02:22 UTC) deletes rows more than 14 days old. The cap reads the last seven days, so 14 gives a week of margin for a question about a refusal.

Erasure. Rows are removed with the account (on delete cascade from auth.users), at the same moment as the rest of the account. A test checks this.

Access. The record isn't in the self-serve data export, because that export reads through the member's own client and this table has none. We provide a member's rows on request, along with the rest of what we hold about them.

Children. A child, a managed account and a Device-only group are refused before any ledger call, so no row is written for them.

Review trigger. Revise this section if the record gains a surface, group, model or content column, if its retention lengthens, or if it is ever exposed to a client or included in a report with ids.