AI data egress — the disclosure source of truth¶
Plain-language version: What We Send To AI, And Where It Goes
Status: WING-418. This document is the factual basis that ToS and privacy-policy copy is derived from. It is not itself legal copy and must not be published as such. Legal review is required before any of this reaches terms.
Why a doc and not a paragraph in the ToS. The honest answer differs by edition, and a single "we use AI providers" sentence misrepresents both of them. Writing the facts down once, in the repo, next to the registry that enforces them, is what keeps the published version true after the code changes.
1. The two editions have genuinely different answers¶
| Desktop | Cloud / pilot (WorkWingman__Edition=server) |
|
|---|---|---|
| Whose provider account | The user's own — their BYOK API key, or their own signed-in CLI (claude -p --tools "") |
Ours — Secret Manager secret workwingman-anthropic-api-key for text, and server-owned Google credentials for GoogleCloudMediaProvider and GeminiStudyCastClient |
| Who holds the vendor relationship | The user. They accepted that vendor's terms directly | WorkWingman |
| What we can promise about retention | Nothing beyond what their chosen vendor and plan tier already say | Whatever our own contract says — see §4, currently unresolved |
The vendor list differs by edition, and the difference is credentials.
Cloud depends on how it is deployed and on what tenants configure — it is not a fixed list.
The current pilot deployment provisions three secrets (CloudDeploymentPlan.cs:196-198):
workwingman-anthropic-api-key, workwingman-google-media-api-key,
workwingman-adzuna-credentials-demo. But:
CloudDeploymentPlan.cs:109acceptsProviderIdof"anthropic"OR"openai", and maps the provider secret accordingly — a cloud deployment can be stood up against OpenAI.IntegrationKeysControllerlets an authenticated tenant supply their own API keys at runtime, edition-neutral, while the study-visual and audio endpoints stay registered. A tenant can therefore light up keyed media vendors on a cloud instance.
So a cloud subprocessor list must be derived per deployment and per tenant configuration, not asserted once. Stating flatly that cloud "cannot reach" OpenAI or the media vendors would be a false custody disclosure.
Desktop is where the long list lives, because the user supplies the keys. Depending on which features they enable and which keys they configure, their content can additionally reach OpenAI (text, images, speech), ElevenLabs (speech, music), AWS/Polly (speech), Beatoven and Stability (music), Magnific (image sharpening and image-to-video — which receives the image file itself), and a NotebookLM MCP endpoint if they configure one. These are the user's own vendor relationships under their own terms, per §1 — but they still belong in any honest account of where data can go.
Derive the enumeration from EgressSurfaceRegistry, and read §6 on why even that is a floor
rather than a census.
This split is the single most important fact in this document. On desktop the request is made with the user's own credentials to their own vendor account, and does not pass through any WorkWingman server; in cloud it is made with ours. Copy that blurs the two is wrong in both directions — it overclaims our control on desktop and underclaims our responsibility in cloud.
Stated as engineering fact, not as a legal conclusion. Whether that makes us a controller, a joint controller or neither on the desktop path is a determination for counsel, not for this document — and note the app still assembles the prompt in-process on the user's machine, so "their key, their vendor" is not the same as "our code never touches it."
BYOK is not the same as private. A user's own consumer-tier plan carries that vendor's own
retention and, on some tiers, training. "Your own key" reads to a user as "nobody else sees this",
and it does not mean that. Since PlanSettingsService already collects the declared tier per key,
we are in a position to say something specific rather than something reassuring.
2. What content actually leaves¶
Not "your data". The concrete list, because vagueness here is what a regulator and a pilot customer both read as evasion.
This list is NON-EXHAUSTIVE and must not be used as one. It covers the paths a user thinks of
as "the AI writing something". It does not enumerate every egress — notably the server edition's
FirestoreTenantDocumentStore (whole serialized tenant documents, continuously, with no
user-visible moment of sending), McpNotebookLmPublisher (inert today — the only registered
transport throws and the publisher checks IsConfigured first — but it publishes an entire study
pack once a user configures an MCP server, which is a configuration change, not a code change),
and OllamaTextEmbedder (every embedded chunk, and host-configurable off-box). Derive a
subprocessor list or an Art. 30 record from EgressSurfaceRegistry, not from this section, and
then read §6 on why even that is a floor.
- Identity, on drafting tasks:
Contact.PreferredNameif set, otherwise the combinedLegalFirstName+LegalLastName— one or the other, not all three. Email, phone and address are NOT sent to the model; the app inserts those into the finished document afterwards. The student fact-pack path sends no contact identity fields at all. Getting this wrong in either direction matters: overstating it invents an egress we do not perform. - Structured profile: work history, education, certifications, skills, stories, voice settings, and writing samples (capped ~1500 chars). For federal templates, additionally announcement number, target series/grade, hours, salary and supervisor contact details.
- Full resume text on the extraction path (
ClaudeResumeExtractor) — the whole uploaded file. - Serialized work history, education, stories and retrospectives on
InterviewQuestionCoachService. Note this path does not currently applyPromptSafetyfencing — see §3. - Job descriptions and scraped company research — third-party content (§5).
- Academic fact packs for student document paths.
- For media features: whatever the user types as an image, speech or music prompt — plus, on the Magnific paths, the base64 image itself, not merely a prompt describing it.
3. There is no PII redaction before a prompt, and that is deliberate¶
ClaudeDrafter.BuildProfileBlock appends the user's real identity verbatim. This is correct: a
tailored resume and cover letter cannot be written without knowing who the person is. Minimization
on this path is field selection and task scoping, not redaction.
Do not write, and do not let marketing write, that content is anonymized or redacted before reaching a model. It is not, the feature requires that it is not, and the claim is trivially falsifiable by anyone who reads the code. See WING-417 for the internal doc that made exactly this error.
What genuinely bounds the prompt, and what may be said accurately:
- Explicit per-call field selection. No code path found serializes a domain aggregate into a prompt; this is a reviewed property of the current prompt builders, not a compiler-enforced one.
PromptSafetyfencing of untrusted third-party text, with forged-marker neutralization and a no-URL / no-image-tag output rule that closes the markdown-exfil vector. Not universal:InterviewQuestionCoachServicedoes not apply it today. Do not state fencing as a product-wide property until it is.- Fact-pack grounding with mandatory citation — student path only.
StudentFactPackInstruction+FactPackCiteGuardenforce it. The primary professional drafting path (ClaudeDrafter.StandardInstruction) requires JSON output but does not require citations. Any copy promising "the AI must cite its facts" is false for the main path. DraftHonestyGuard, which rejects a draft asserting an employer, degree or date the profile does not contain — this one does run on the professional path and is the real backstop there.- Tools disabled at the CLI boundary — on the Claude and grok presets only.
--tools ""for claude (note--allowedTools ""was empirically proven insufficient and must not be substituted); explicit disallow list plus deny-all permission rules for grok. The codex preset currently launchescodex exec --skip-git-repo-checkwith tools ENABLED — see WING-431. Until that is fixed, no product-wide "the AI gets no tools" statement is true, and none may be published. The Anthropic and OpenAI API paths send notoolsfield, so they are unaffected. - The
ai-usagetelemetry record has no field intended for content — it carries counters, enums, ids and timings. Be precise about the strength of this:AiUsageEvent.Providerand.Modelare unconstrainedstring, so the type could physically hold arbitrary text and a regression assigning prompt content to either would compile and serialize. What actually keeps the ledger content-free is the metering flow never populating them from prompts. That is a strong convention with no content-shaped field to tempt it, not a type-level impossibility. Constrained value types would make it one; until then, do not claim it is structural.
4. Cloud retention, training and region — UNRESOLVED, do not draft around it¶
Blocked on WING-415 item 1. Until the current commercial terms are confirmed in writing — training use, input and output retention windows, abuse-scanning retention, whether we hold a zero-retention addendum, data region, subprocessor list — no statement about any of it may enter user-facing copy.
An unverified claim in published terms is worse than saying nothing: it converts an open question
into a written representation. The llm.cli entry in EgressSurfaceRegistry previously asserted
"no training use"; WING-417 replaced that with an explicit note that vendor training and retention
terms are unverified. If you find a no-training assertion anywhere else in the codebase or docs,
it is unsourced — treat it as a defect, not as evidence.
5. Content we transmit whose owner never consented¶
Job descriptions and scraped company research belong to someone else. If the inbox surface ever ships, message bodies contain a sender's personal data, and that sender has no relationship with us at all — which is why the council cut that surface pending per-message consent and zero model egress.
This is not covered by the user's acceptance of our terms, because the user is not the data subject. It needs its own answer, not a sentence in a ToS the third party never reads.
6. Where the enumeration lives¶
src/WorkWingman.Core/Privacy/EgressSurfaceRegistry.cs is the checkable inventory — every path
data takes off the machine, with the producing type resolved by reflection. Disclosure copy should
be derived from it rather than written independently, so the two cannot drift.
Caveat, and it is a real one: the coverage test proves that a listed type exists. Nothing yet proves that an existing producer was listed, so the omission direction is structurally uncovered — and it has failed twice, most recently when eighteen live egress paths were found missing from an inventory whose entire purpose is completeness (WING-430).
Treat this registry as a floor, not a census. Until the test enumerates senders from the assembly and diffs them against the list, "derived from the registry" means "derived from the paths somebody remembered". Any subprocessor list or Art. 30 record built on it inherits that uncertainty and should say so.
Note also that this is a developer inventory of producing types, not an Art. 30 record of processing activities: it carries no purpose, data-subject category, recipient category, international-transfer safeguard, or retention period. Those have to be authored separately.
7. Open items before publication¶
- WING-415 item 1 — cloud contractual posture. Blocks §4 entirely.
- WING-430 — registry omission-direction coverage. Bounds how much §6 can be trusted.
- In-product disclosure at the moment of use, not only in terms. The first time a feature sends content to a model, the user should be able to see where it goes — consistent with the standing on-the-rails principle that the user is present and authorizing.
- Subprocessor list for the cloud edition.
- DPA posture for pilot customers who ask. They will.
- Legal review of the derived copy.