← All privacy impact assessments

PIA: Semantic task search — finding a task by meaning

Status: Built — written before the code, and revised against the shipped code the same day. The feature was built as designed: every mechanism below is live, the caps §3.2 asked for are numbers now (stated there), and the independent review of the build found no deviation from this assessment. A further change, opening an encrypted task for a search (§3.1), was built and merged on 30 September 2026 and has not yet been released. Written: 18 September 2026 · Last revised: 5 November 2026 (routine-edit lookup and its generative step, §9); previously 30 September 2026 (opening an encrypted task for a search, §3.1, built but not yet released) Nothing here is legal advice. Children's Code citations are to the exposure draft; section numbers may move on registration (due 10 December 2026).

As built, 30 September 2026. §3.1's change — reading a sealed task's title and notes for a search, inside the member's own request, once a task's words are sealed — has been built and merged, matching this assessment with no deviation we found. It has not reached anyone yet: task sealing itself is a separate change (see the content encryption assessment) that has not been released, so today every task's words are still stored in plain text and this change has nothing sealed to open.


1. Purpose and scope

A member looking for "that dentist thing" should be able to find the task "Ada — orthodontist referral" without remembering its exact words. The design is two search modes with a hard line between them:

This app already runs exactly one embedding surface — the schedule time lookup, which compares a new event's title against the group's own recent titles — and this design deliberately copies its shape rather than the meal catalogue's, because a household's task text and a public recipe catalogue have opposite privacy postures.

In scope: what text leaves the device and when, where it is processed, what (if anything) is stored, how the group's privacy level routes or refuses the call, deletion, children, and cost. Out of scope, covered elsewhere: the general residency position and spend ceilings (the cloud-inference assessment); the search interface (paging, filters, history lists), which moves no new data.

2. Data involved

Kind Whose Children's? Sensitive? Free text?
The query the member types the member's own words possibly — a query can name a child ("Ada dentist") possibly — health-adjacent errands are ordinary task language yes
Candidate task titles and notes, sent for comparison on a submit the group's — written by any member often — tasks in a family group are frequently about children possibly — "pick up Ada's antibiotics" is a task title yes
The result a ranked list of task identifiers the member can already read — — no

Not collected or created, and these absences carry the design:

3. Flows and locations

Data Device Supabase (Sydney, ap-southeast-2) GCP Vertex AI (australia-southeast1) Region
Typing-time matching entirely on the device, over rows already loaded — — the phone
A submitted semantic query sent from the device with the member's own session our assistant server reads the group's tasks through the member's own access, checks the group's privacy level, and meters the spend the query text and the candidate titles/notes are embedded; similarity is computed on our server; nothing is retained by us Sydney, Australia

3.1 What the candidate text's protection actually is today

A task's title and notes are still stored in plain text in our database today, for every group except a Device-only one. We have built, but not yet released, sealing a task's words on the phones at every level (see the content encryption assessment in this register): new and edited tasks as they are saved, older ones once a member runs "Encrypt older tasks", once released. This assessment said that when task notes were encrypted it would be re-read, and that sealed content would be excluded from search rather than opened. It was re-read, and that second sentence was changed on purpose. Excluding encrypted tasks would make search find nothing at all in a group whose history had been encrypted — a search box that answers "no match" for every question is a wrong answer, not a private one. What the change will do and will not do, once released:

3.2 What bounds a request, beyond the money

The spend ledger stops a runaway costing money; it does not, on its own, stop a single request being enormous, and a request's size is a privacy question as well as a cost one — the more of a household's task text travels per submit, the more a single accidental or hostile call discloses. The design bounds each request in its own right:

The caps were deliberately specified as requirements with reasons rather than as numbers before the build: the numbers belong with the build, and this assessment is what says a build without them is not the design that was assessed.

As built (18 September 2026): the server fetches at most 150 candidate rows — plus one, so it can tell the screen the set was truncated rather than let an incomplete search read as "no match"; a query longer than 200 characters is refused; each candidate's text is cut to 300 characters before it is compared; at most 20 results come back, and only those whose similarity to the query reaches 0.55; a member holds one semantic request at a time — a second submit while one is open is refused with a "try again in a moment" answer rather than queued. Sealed titles are excluded before ranking whenever they could not be opened; since 30 September 2026 the rest are opened in the request, after the two spend pre-flights and before ranking, and nothing opened is kept. Every cap is applied on the server, whatever the app declared. The order of the server's checks — sign-in, rate limit, query shape, the child-account refusal, the group's privacy level, the candidate read through the member's own access, the two spend pre-flights, the ranking, and the charge recorded only after the work is done — is pinned by an automated check that fails the build if any step is removed or reordered, and a second check refuses any log line in that handler that could carry the query or a title.

4. Legal basis and consents

No new consent purpose. The member's own explicit submit is the act — the same basis as typing a question to the assistant, and narrower: nothing is generated and the processing stays in Australia. The privacy policy's existing description of model processing covers task text reaching a model on a member's request, and its wording was found to enumerate surfaces — so this precondition is now satisfied: CURRENT_PRIVACY_POLICY_VERSION v1.7-2026-09-18 (§5) names this feature by name, dated the day this assessment was revised. No switch is added: the group-level control is the privacy level itself, which already expresses "keep our words off servers" (Device-only) and is enforced on this path server-side.

5. Risks and mitigations

Risk Who is harmed Mitigation Structural or conventional? Residual
A member assumes typing is already "AI search" and that every keystroke is leaving the phone — or the reverse: assumes Return is still local and sends a sensitive query without meaning to the member, and whoever the query names The two modes are visibly different acts (typing filters instantly; Return is labelled as search-by-meaning), and the semantic call happens only on submit — there is no code path from a keystroke to the network Structural for submit-only; conventional for the labelling A member can still submit words they regret. The query is not stored, which bounds it
A Device-only group's task text reaches a server because a client was old, modified, or lied about the level the whole group — the level's promise is broken silently The level is read server-side per request through the member's own access, and refusal fails closed on an unreadable level — the same posture every other model surface already takes Structural —
A stored index is added later "for performance" and becomes household text at rest that deletion no longer reaches every member; children named in tasks, whose records outlive the task This design has no index, and §9 makes adding one a re-assessment with named obligations (deletion cascade, sealed-row exclusion, at-rest posture) rather than an optimisation Conventional — a documented gate Accepted: nothing stops a future change technically; the gate is this register
The candidate set quietly widens — completed history, baby records, chat — because "search should find everything" whoever wrote or is named in the newly included text The candidate columns are an explicit allowlist (titles and notes), asserted by test, and §9 names widening as a trigger Conventional, test-pinned —
The query itself is free text about a person, processed by a provider whoever is named Processing is in Australia, per-request, not retained by us, and the provider does not train on it (per our standing terms); the query is never logged Structural for the region pin (the endpoint is a constant, not configuration); conventional for the no-logging discipline Accepted — the same acceptance as every Sydney surface, stated rather than hidden
A single submit carries far more of the group's task text than the member imagines — a scripted or accidental loop turns a search box into a bulk export of a household's tasks to a provider every member, and whoever their tasks name Per-request caps on the candidate set, the query length and the text taken from any one row, all checked on the server; one in-flight request per member, a new submit cancelling the last; the weekly allowance and spend ledger underneath all of it (§3.2) Structural for the server-side caps and the metering; conventional for the screen saying when the candidate window truncated Accepted: a member can still submit repeatedly within their allowance. Nothing is retained at either end, which is what bounds the consequence
Results imply tasks that do not exist, or leak rows the member cannot read the member misled; the group if access widened Results are task identifiers resolved against rows fetched through the member's own access — a similarity score cannot mint a row, and a row the member cannot read is never in the candidate set to begin with Structural — the member's own access is the fetch path —

6. Structural versus conventional

Structural: the semantic call exists only behind an explicit submit; the privacy level is read server-side per request and fails closed; candidates are fetched through the member's own access (row security decides visibility, so search cannot see more than the member); an encrypted row's words are opened only by the one part of our server that may open group content, only inside the member's request, with a record of each opening, and a row it cannot open is left out rather than compared as a placeholder; the Sydney endpoint is a constant in the code rather than a setting; nothing is stored, so retention and deletion questions have nothing to attach to; the per-request caps on the candidate set, the query's length and the text taken from any one row are applied on the server rather than trusted from the client; the spend is metered by the same ledger as every other call.

Conventional, each to be test-pinned in the build: the candidate-column allowlist; the scope sentence on the search screen, including saying when the candidate window truncated; the no-query-in-logs discipline; the child-account gate (§7); one in-flight request per member; the absence of any generative step over the results.

7. Best interests of the child

Two different children are involved: a child named in task text, and a child using the search.

8. Residual risk we accept

9. Review trigger

Re-read and revise this assessment before building any of:

Revised against the shipped code, 18 September 2026. Implemented as designed; no deviation was found. What the build added beyond the draft is only the numbers in §3.2 and the two automated checks that keep them and the server's order of refusals in place. None of the triggers above has fired: there is still no stored index, no generative step, no widening of the candidate set, and child accounts and Device-only groups still get local search only.

Decided, 30 September 2026. Search by meaning stays on the server, as described here, for groups at the Australia and Global levels. Device-only groups get a spelling search on the phone when they press Search (§3), which sends nothing and is described in search and suggested times on the phone; that is not a semantic mode, so the trigger above about Device-only groups has not fired. No model is added to the app. Child accounts in other groups still get local search only. The paragraph below is the plan as it stood before the decision.

Planned move to the phone, 30 September 2026. Search by meaning is planned to run on the phone, over the tasks the phone already holds, so that pressing Search sends nothing — see search and suggested times on the phone. When that ships, this assessment changes in four places: §3's server flow is gone for app versions that have the phone-side search; the child-account and Device-only refusals in §3 and §7 no longer have a reason, because nothing leaves the phone, and the phone-side search is offered to both; §3.2's caps stop mattering for those versions; and the server route stays only for older app versions, then is switched off. Until then nothing here has changed. The first evaluation found that without a model the phone-side search finds noticeably less than this server version does for questions about meaning, and whether to add a model to the app for it is still open — so the server version stays in place for now.

4 November 2026 — routine-edit lookup. Routine-edit lookup is a second consumer of the same lookup. It sends titles only, without notes, under the same caps, to the same region, with the same refusals. Ranked titles go back to the assistant's model. That is a generative step over the results, so they are fenced as untrusted data (ROUTINE_RESULT). Both review triggers — a second consumer and a generative step — apply. Assessed in routines §10.

10. Checklist against exposure draft s 38(2)

s 38(2) Requires Answered in
(a) nature, scope, context, flow and purposes of handling children's personal information §§1–3
(b) why the collection is strictly necessary §2 — nothing new is collected; existing rows are compared on the member's own ask and nothing is retained
(c) lawful and fair collection §4
(d) consistency with the best interests of the child §7
(e) how we comply with the Code and the Act §4 and §6
(f) risk of harm to children §5, §7

s 38(3): this assessment precedes the build. s 39: listed on the published register.

11. Sources

Primary: the Children's Online Privacy Code exposure draft, ss 10, 26, 38, 39.

In repo: agent-server/src/routineMatch.ts (routine-edit lookup) and agent-server/src/domains/choreAgent.ts (untrusted routine-result fencing); agent-server/src/server.ts and the schedule time-lookup route (the per-request embedding shape this copies, and the child-account refusal); agent-server/src/vertex.ts (the Sydney region constant and the reasoning that it must not become configuration); agent-server/src/privacyLevel.ts (readPrivacyLevel, isLockedLevel — the fail-closed level read); docs/pia/cloud-inference.md §§2–3 (embeddings carry group information; the residency test is the request body); docs/pia/privacy-levels.md (what Device-only promises); components/Meals/ExplorePlans.tsx (the local-then-submit interaction precedent — and the catalogue/household distinction that stops its server design being copied); our internal engineering rules on identifiers in logs being data an erasure cannot reach, and on household data being processed in Australia.