diff --git a/app/src/pages/LegalPage.test.tsx b/app/src/pages/LegalPage.test.tsx
index 5be53ca58c1..dd3141a569d 100644
--- a/app/src/pages/LegalPage.test.tsx
+++ b/app/src/pages/LegalPage.test.tsx
@@ -93,6 +93,47 @@ describe('LegalPage', () => {
expect(screen.getByText(/Cloudflare stands in front of the site/)).toBeInTheDocument();
});
+ // The data assistant's section, each claim pinned with the qualifier that
+ // makes it true: a preview only admins reach, training excluded only under
+ // Vertex AI's terms, telemetry pseudonymous (not anonymous), the data file
+ // ticked by default, and a case row that has no timer while its files do.
+ it('describes the data assistant preview without overclaiming', () => {
+ render();
+
+ expect(
+ screen.getByText(/only the operator and the\s+site.s admins can open/)
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(/served through Google Cloud Vertex AI in the\s+EU/)
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(/processed under Google Cloud.s data\s+processing terms/)
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(/don.t paste personal data you are not allowed to\s+share/)
+ ).toBeInTheDocument();
+ expect(screen.getByText(/there is no session database/)).toBeInTheDocument();
+ expect(screen.getByText(/pseudonymous, not\s+anonymous/)).toBeInTheDocument();
+ expect(screen.getByText(/up to 180 days/)).toBeInTheDocument();
+ expect(screen.getByText(/keyed hash \(HMAC\)/)).toBeInTheDocument();
+ // Unticking the box drops data.csv only; the profile sample stays.
+ expect(screen.getByText(/unticking leaves out that file only/)).toBeInTheDocument();
+ expect(screen.getByText(/first 5 rows as a sample/)).toBeInTheDocument();
+ // The renderer's answer cache has no idle timer of its own.
+ expect(screen.getByText(/keeps its last 16 results/)).toBeInTheDocument();
+ expect(screen.getByText(/nothing deletes it on a timer/)).toBeInTheDocument();
+ expect(screen.getByText(/Art\. 6\(1\)\(b\) GDPR/)).toBeInTheDocument();
+ expect(screen.getByText(/Art\. 6\(1\)\(f\) GDPR/)).toBeInTheDocument();
+ // Scope: the assistant's processing touches only the people who use it.
+ expect(screen.getByText('only if you use the data assistant (preview)')).toBeInTheDocument();
+ expect(screen.getByText(/if you\s+only browse the catalogue, none of it/)).toBeInTheDocument();
+ expect(
+ screen.getByText(/Claude models by Anthropic \(EU\) — only when you use the\s+data assistant/)
+ ).toBeInTheDocument();
+ // Browsing the catalogue still involves no model; only the assistant does.
+ expect(screen.getByText(/never sees a visitor.s request/)).toBeInTheDocument();
+ });
+
it('renders the technology stack', () => {
render();
diff --git a/app/src/pages/LegalPage.tsx b/app/src/pages/LegalPage.tsx
index 0aba7534dcf..4db795b06ba 100644
--- a/app/src/pages/LegalPage.tsx
+++ b/app/src/pages/LegalPage.tsx
@@ -189,9 +189,10 @@ export function LegalPage() {
data controller
- Markus Neusinger (see legal notice above). the ground for all of it is an
+ Markus Neusinger (see legal notice above). the ground for the site itself is an
operator's legitimate interest in protecting the site and knowing how often it is
- read; swiss data protection law applies, and the GDPR on top for visitors from the EU.
+ read; the data assistant below, which only its users meet, names its own grounds.
+ swiss data protection law applies, and the GDPR on top for visitors from the EU.
what we collect
@@ -247,6 +248,65 @@ export function LegalPage() {
front of a request — and to absorb DDoS attacks.
+
+ only if you use the data assistant (preview)
+
+
+ everything in this part applies only to people who use the data assistant. if you only
+ browse the catalogue, none of it touches you. anyplot is building an assistant that
+ adapts a catalogue plot to data you bring ("use with my data"). today it is
+ a preview that only the operator and the site's admins can open; nobody else can
+ reach it yet. this is what it does with what you give it.
+
+
+ what the model sees. what you paste or drop and the messages you
+ write go to Claude models by Anthropic, served through Google Cloud Vertex AI in the
+ EU, to make the plot you asked for. they are processed under Google Cloud's data
+ processing terms, and under Google Cloud's terms for Vertex AI your prompts are
+ not used to train the models. don't paste personal data you are not allowed to
+ share.
+
+
+ sessions. a session — your data, the conversation and the plots made
+ from it — is held in the server's memory only. it is cleared after an hour
+ without activity or when the server instance stops; there is no session database. the
+ separate render service also keeps its last 16 results (converted data, plot images)
+ in memory, until newer renders push them out or its instance stops.
+
+
+ usage telemetry. to see how the assistant is used and where it fails,
+ it writes content-free lines: event types, data formats, size buckets, the plot type
+ and library, attempts, review defect ids, statuses, latencies and token counts. a line
+ never holds your text, your data, your column names or your file names. the lines of
+ one session share a salted hash of the session, whose salt lives only as long as the
+ server instance, and they carry no user id — pseudonymous, not anonymous. they are
+ kept for up to 180 days in a Google Cloud Logging bucket in the Netherlands
+ (europe-west4) and read through BigQuery.
+
+
+ attribution and rate limits. the assistant's own log lines,
+ which debug it and enforce the per-user limits, carry a keyed hash (HMAC) of your
+ identity in place of it, and no content. they sit in the server logs above, for 30
+ days.
+
+
+ feedback cases. only when you send feedback on a result or on the
+ chat does the assistant store a case: the conversation, the profile of your data
+ (column types and the first 5 rows as a sample), the plot's code and images if
+ one was made and, while the "# include data.csv" box is ticked — it is by
+ default — the whole data file. unticking leaves out that file only: what the
+ conversation, the sample, the code and the images show of your data stays in the case.
+ the files go to a private bucket in the Netherlands (europe-west4) and are deleted
+ after 180 days. a row with your reaction, your short message, a keyed hash of your
+ identity and the technical details of the run (plot type, library, model) stays for
+ the triage, and nothing deletes it on a timer — ask and yours is gone.
+
+
+ legal basis. making the plot you asked for is the performance of your
+ request (Art. 6(1)(b) GDPR); the telemetry and the rate limits rest on legitimate
+ interest (Art. 6(1)(f) GDPR); a feedback case rests on your explicit submission.
+
+
what we do not collect
• no user accounts or personal profiles
@@ -257,8 +317,10 @@ export function LegalPage() {
• no cookies at all (localStorage holds UI preferences — theme, image size — a cached
release number, and, once you use the feedback widget, the random id above)
• no ai training: nothing you send is used to train ai models.
- the ai in this project writes the catalogue, at build time; it never sees a
- visitor's request
+ when you browse the catalogue, the ai in this project has already done its part: it
+ writes the catalogue at build time and never sees a visitor's request. only the
+ data assistant above sends what you give it to a model, under the terms described
+ there
@@ -270,8 +332,9 @@ export function LegalPage() {
hosting & third parties
the site, its database and its images run in the EU (Netherlands, europe-west4);
- Cloudflare's network is global, and Plausible is a service of its own on its own
- EU servers:
+ Cloudflare's network is global, Plausible is a service of its own on its own EU
+ servers, and the data assistant's models — used only when you use the assistant —
+ are called in Vertex AI's EU region:
@@ -295,6 +358,13 @@ export function LegalPage() {
analyticsPlausible Analytics (EU, proxied)
+
+ ai assistant
+
+ Google Cloud Vertex AI, Claude models by Anthropic (EU) — only when you use the
+ data assistant
+
+
@@ -302,12 +372,13 @@ export function LegalPage() {
you can ask what is stored about you and have it corrected, erased or its use
restricted, object to the processing on grounds relating to your particular situation,
- and complain to a supervisory authority. in practice that means a feedback entry or a
- log line; a few lines to{' '}
+ and complain to a supervisory authority. in practice that means a feedback entry, an
+ assistant feedback case or a log line; a few lines to{' '}
admin@anyplot.ai
{' '}
- are enough.
+ are enough, also to have an assistant feedback case deleted early, its files and its
+ row.
diff --git a/changelog.d/legal-ai-assistant.md b/changelog.d/legal-ai-assistant.md
new file mode 100644
index 00000000000..2cc6d5be6e1
--- /dev/null
+++ b/changelog.d/legal-ai-assistant.md
@@ -0,0 +1,10 @@
+### Changed
+
+- **The privacy policy covers the data assistant preview.** The legal page now says,
+ in a part scoped to the people who use it (browsing the catalogue is unaffected),
+ what the admin-only "use with my data" chat does with what you give it: Claude models
+ through Vertex AI in the EU, in-memory sessions, the content-free and pseudonymous usage
+ telemetry (180 days), the HMAC-keyed attribution lines (30 days), the feedback cases
+ (files 180 days, the row until you ask), and a legal basis per purpose. The "the ai never
+ sees a visitor's request" line now separates browsing the catalogue from using the
+ assistant, and the processor table gains an "ai assistant" row (#12208).
diff --git a/docs/reference/agent-telemetry.md b/docs/reference/agent-telemetry.md
index a203170d852..782ba713c0b 100644
--- a/docs/reference/agent-telemetry.md
+++ b/docs/reference/agent-telemetry.md
@@ -228,9 +228,11 @@ minutes, so reloading `/debug` runs no query at all. The answer holds aggregates
`not_configured` while the variable is unset, and `unavailable` when the query fails (no table yet, no
permission, a timeout); both come with zeros, the card shows its empty state, and a failure is never kept.
-## Before the chat opens to the public
+## On the legal page
The telemetry is content-free and keyed by a salted, per-instance session hash, but it is still a record of
-use. Before real users reach the chat, the legal page (`app/src/pages/LegalPage.tsx`) needs an entry for it:
-purpose (improving the chat), the fields in plain words, the 180-day retention, the EU location, and that
-no content is recorded. The chat is admin-only on 2026-10-16, so the page stays unchanged for now.
+use. The privacy section of the legal page (`app/src/pages/LegalPage.tsx`, "data assistant (preview)")
+describes it: the purpose, the fields in plain words, that no content is recorded, the 180-day retention,
+the EU location, and that it is pseudonymous rather than anonymous. The section describes the chat as a
+preview that only admins can open, so it stays true when the chat opens. If you change a field, the
+retention or the location here, update that section too; `app/src/pages/LegalPage.test.tsx` pins its claims.