AX notes · Incident

Privacy policy AI processing location (cross-border transfer): a notice fixed twice in one day, then tied to code

A Vertex AI global endpoint cannot be named as one country, so the notice says processing may take place outside Korea; that sentence is now tied to code constants, CI and the release gate.

By SugarMount Website and search 10 min read Last updated

On October 10, 2026, the privacy policy of AXFamily, an unreleased family planner app, changed its AI processing location twice: to "Google Cloud Seoul region" at 14:05, then to "Google global infrastructure (processing may take place outside Korea)" at 23:00 (Korea time). The second change landed 33 seconds apart in two repositories, app commit 1426e97 at 23:00:36 and site commit 89e0bb3 at 23:01:09, and it forced in-app AI consent to be asked again. A Seoul sentence that could not actually work was public for 8 h 55 min. The app had no real users yet.

Sources: commits, a decision record and CI check code in the AXFamily app repository, the git history of this site, and the agent session logs. Times are commit times in Korea time (UTC+9), as of October 22, 2026.

Contents (8)
  1. The order of events that day
  2. The AI chose Seoul because of code that had never called the endpoint
  3. The owner picked global; three options were dropped
  4. Five places tied to one commit, and the old version after the push
  5. What this setup still does not catch
  6. Steps to apply this to your own app
  7. What is left
  8. Frequently asked questions

The order of events that day

The first change came from an AI coding agent asked to fact-check the policy. Commit 13d00b8 (14:05:08) replaced the draft's Vertex AI location, "global, United States", with the Seoul region (asia-northeast3). Its evidence was the code: the backend function defaulted to Seoul, and the notice constant on the in-app AI consent screen said Seoul too. Site commit 6321f14 published that policy for the first time 49 seconds later.

At 15:13 a second agent, preparing the production deploy, called Vertex countTokens, which is not billed. The lead agent had asked for this check earlier. The two chosen models, gemini-3.5-flash-lite and gemini-3.8-flash, existed only on the global endpoint, so Seoul and Tokyo both returned 404. None of the 4 combinations in the code's allowlist (Seoul or Tokyo × two models) worked. The result went into an ops document in commit cc54a82.

At 15:27 the agent asked the owner to pick one of three options. The owner chose global at 22:46, 7 h 18 min later. The code commit followed 14 minutes after that, and the site right after the code.

Five-step timeline: 14:05 policy changed to Seoul, 15:13 countTokens 404, 22:46 owner picks global, 23:00:36 code commit, 23:01:09 site commit.
An unusable Seoul sentence was public for 8 h 55 min before the global notice replaced it that night.

The AI chose Seoul because of code that had never called the endpoint

The 14:05 reviewer saw the conflict. Its report said the instructions gave the Vertex AI location as global, but the code blocks global, so it followed the code. Matching the policy to the code was the right rule, but the code held a value that had never reached a real endpoint.

The cause has three steps.

  1. Why the policy said Seoul: the reviewer treated two constants, DEFAULT_VERTEX_LOCATION in the function and AiProcessingDisclosure.region in the app, as the source of truth.
  2. Why the code said Seoul: the October 9 release plan said to pin Vertex to a specific region and ban global, so the country could be named in the notice. The model evaluation a day earlier, on October 8, had picked the models by calling locations/global. One document chose the models, another chose the location, and no step checked them together. The product spec listed "Seoul region availability" as something to verify, and an audit note still said per-model availability in Seoul was unconfirmed.
  3. Why nobody noticed: on-device end-to-end tests ran against a demo emulator with a fake model, and the production AI switch was off. The 15:13 check was the first real call to the Seoul endpoint. The October 8 model evaluation had only called global.

Before the AI changed it to match unverified code, the pre-14:05 draft read "United States and other countries where Google has data centers (global location)". The sentence finalized at 23:00 also says processing may take place outside Korea, in the US and other countries where Google has data centers.

The owner picked global; three options were dropped

The 15:27 question offered global (the agent's recommendation), the older gemini-2.5-flash in Seoul, or turning AI off for now. The owner picked global. The decision record and the question list the rejected options.

Rejected optionWhy
Seoul + gemini-2.5-flashEasy to name the country, but the model evaluation would have to be redone, and price and accuracy differ.
Turn AI off for nowInternal testing would run without AI until the newer models reach Seoul.
Use global, tell users "United States"Global is not pinned to the US. The notice would be false.
Consent version 2026-10-10bApp, server and CI all validate the same YYYY-MM-DD pattern. The next date, 2026-10-11, was used instead.

The new notice reads "Google global infrastructure (processing may take place outside Korea)". The recipient in the cross-border transfer table is still Google LLC, and the policy says Google picks the processing region per request. No legal review went into that sentence; it restates what the code does.

Here is the Vertex AI row of the transfer table as published today in the AXFamily privacy policy (October 17, 2026 edition), copied as is. The contact, legal basis and opt-out columns are left out, and the dash is shown as a colon.

ColumnPublished text
RecipientGoogle LLC: Vertex AI Gemini (paid tier, Google Cloud global endpoint, generation and embedding models)
CountryGoogle global infrastructure (Vertex AI global location). Google chooses the processing region for each request, which may be outside Korea (the US or other countries with Google data centers). If the processing location changes, we update this policy and the in-app consent first
ItemsReduced copies (WebP, long edge up to 1280 px) of the photos and documents you ask AI to organize, text you shared or pasted, and family members' display names and internal IDs (children without guardian consent are never sent). Assistant, planner, memories and brief: your questions, plan inputs and summaries of family records the requesting adult can see (titles, dates, who is responsible, memory card values; names replaced with aliases). Records about children under 14, and health records unless you turned them on for yourself, are never sent (events and trips a child joins go with an age group instead of a name)
Timing and methodSent through our server over an encrypted connection when you use AI organizing, the assistant or the planner, and for adults who agreed, when family memories are organized (overnight batch and record changes) and for the Plus weekly brief (Sunday evening)
PurposeSuggested events and to-dos from your material, assistant answers and approval cards, trip and weekend plan drafts, a family memory index (embeddings) with person card and album suggestions, and the weekly brief summary (you decide what is saved)
RetentionGoogle does not keep it after returning the result and does not use it for training; Google may log briefly for abuse detection. Results we keep on our servers follow section 4 (assistant chats 90 days, brief summaries 30 days, suggestions and plans 7 days, the memory index until the original is deleted or you withdraw consent)

Five places tied to one commit, and the old version after the push

Commit 1426e97 changed 38 files (+216/−156). Five of the changes concern the processing location.

The old rule passed whenever the app notice was not global and matched the function default, and only logged a note if the function used global. It never read the allowlist. So with the notice saying Seoul and Tokyo (asia-northeast1) still in the allowlist, CI stayed green: one environment variable could move processing to a country the notice never named. The new rule checks three things.

app AiProcessingDisclosure.region == function DEFAULT_VERTEX_LOCATION
function ALLOWED_VERTEX_LOCATIONS  == [that value]  (exactly one)
if region == "global", consent_regionGlobal text (ko, en) must not be empty

Negative cases for this rule went from 2 to 7. Adding Seoul next to global in the allowlist, or deleting the allowlist line, must each fail for the tests to pass. We wrote up the idea of proving a guard by deleting it in 3,066 green tests hid a line that breaks opening the database.

Two-column comparison: before, the ai-region check never read the allowlist, so Tokyo stayed allowed while the notice said Seoul, with 2 negative cases; after 1426e97 the allowlist must equal the single notice location, with 7 negative cases.
The old check passed even with an undisclosed location left in the allowlist.

The same agent then updated the site. It copied six policy HTML files, passed the site build and its --check, and committed at 23:01:09. 26 seconds later both URLs returned 200 but still showed the old version, 2026-10-09; only at 23:02:01 did 2026-10-10 appear.

The policy step of the release gate checks exactly those two things: the public URL returns 200, and the data-policy-version marker in the page body matches the version file. The release gate of our other apps also checks that the policy URL returns 200 (What to check before shipping an AI-built Android app); the AXFamily gate adds the version comparison.

The site went public 6.5 minutes before the app pull request merged at 23:07:38. That order was intended: the policy says "if the processing location changes, we update this policy and the in-app consent first". It was also only possible because the AI function was not deployed yet.

What this setup still does not catch

CI compares two pairs: the generated policy HTML against processors.json, and the app notice constant against the function constant. No rule compares the country sentence in processors.json with the function constant, and the October 17 code still has none.

Steps to apply this to your own app

Do not write an AI processing location into the policy first. Confirm with an unbilled call that the model answers in that location, then write it down.

  1. Keep the processing location in one function constant. The allowlist holds that value only, and anything else is refused before the call.
  2. Give the app's notice constant the same value and have CI read both files with a regular expression and compare them. Fail if the allowlist does not have exactly one entry.
  3. Hash the consent text (Korean and English), the recipient and the location with sha256. CI fails if the hash changed but the version did not.
  4. Generate the policy's transfer table from JSON and fail CI when the generated file differs.
  5. In the pre-deploy check, call countTokens with that location and model. Stop the deploy on 404.
  6. Put a version marker in the public page body, and have the release gate check for 200 and a matching version. Right after a push the old version still shows, so poll for a few minutes.
# Step 5: does the model exist in each location? (not a generation call, not billed)
TOKEN=$(gcloud auth print-access-token)
for LOC in global asia-northeast3; do
  if [ "$LOC" = global ]; then HOST=aiplatform.googleapis.com; else HOST=$LOC-aiplatform.googleapis.com; fi
  curl -s -o /dev/null -w "$LOC %{http_code}\n" -X POST \
    -H "Authorization: Bearer $TOKEN" -H "x-goog-user-project: $PROJECT" \
    -H "Content-Type: application/json" \
    "https://$HOST/v1/projects/$PROJECT/locations/$LOC/publishers/google/models/$MODEL:countTokens" \
    -d '{"contents":[{"role":"user","parts":[{"text":"ping"}]}]}'
done

# Step 6: the version marker on the published policy
curl -s "$POLICY_URL" | grep -o 'data-policy-version="[0-9-]*"'

The global host has no region prefix such as asia-northeast3-. These commands copy the request format of the repository's pre-deploy check; they were not re-run while writing this note.

What is left

Since October 17 the embedding location (Seoul) and the generation location (global) differ, so there are now two constants to compare. Next: store a location id in the policy JSON and have CI compare it with the generation and embedding constants.

Frequently asked questions

What country do you name in a privacy policy when using the Vertex AI global endpoint?

None; we wrote "Google global infrastructure, processing may take place outside Korea", because on global Google picks the processing region for each request. Naming the United States was rejected as false.

How can I check for free whether a Gemini model is available in the Seoul region?

Call countTokens on that region's endpoint. On October 10, 2026, gemini-3.5-flash-lite and gemini-3.8-flash returned 404 in Seoul and Tokyo, and gemini-2.5-flash was the model that worked in Seoul.

This note is written from values SugarMount measured itself; an AI coding agent was used to draft it.

All notes on Website and search · All AX notes