Bedrock Zero-Data-Retention (ZDR) Rollout Roadmap
Goal
Today, Amazon Bedrock is wired into exactly one path β VPAT/ACR parsing
(workers/vpat-parse, issue #1192). Every other LLM call goes directly to the
Anthropic API, the Google Gemini AI-Studio API, or the OpenAI API, none of
which give us a contractual Zero-Data-Retention guarantee by default.
The objective of this roadmap is ZDR on every LLM path, with the PDF conversion pipeline as the first and highest-value target. ZDR means: the provider does not persist prompt/response content after serving the request and does not use it for training.
What βZDR via Bedrockβ actually requires
ZDR is not a code change β it is an AWS account posture. The code change is
only βroute the request through Bedrock instead of the direct provider.β The
guarantee comes from two AWS settings (already done for the VPAT account, see
bedrock-vpat-setup.md):
bedrock:PutAccountDataRetentionβmode: none(account-wide).- Model invocation logging OFF β otherwise prompts land in CloudWatch/S3 and defeat ZDR.
Critical model constraint already discovered in #1192: models that require
provider_data_share (Fable 5 / Mythos 5 tier) return status: unavailable
under mode: none. Pre-Fable-5 Claude models (Sonnet 4.x, Opus 4.x, Haiku
4.5) and Amazon Nova support none. Any path we migrate must use a
ZDR-eligible model on Bedrock.
The hard problem: Gemini and OpenAI have no Bedrock equivalent
This is the single most important decision in the whole roadmap. Bedrock hosts Anthropic Claude, Amazon Nova, Meta Llama, Mistral, etc. β it does not host Google Gemini or OpenAI GPT. So βZDR on all pathsβ forces a choice on every Gemini/OpenAI call site:
| Option | ZDR? | Effort | Notes |
|---|---|---|---|
| A. Re-point to a Claude/Nova model on Bedrock | β (Bedrock account ZDR) | Medium β re-prompt + re-tune per task | Cleanest. Consolidates everything onto one ZDR provider. May change output quality/cost per task. |
| B. Move Gemini to Vertex AI with data-governance ZDR | β (Google enterprise ZDR) | Low code (the getGeminiClient factory already supports Vertex) | Conflicts with a standing rule: this repo is βGemini AI-Studio API key only; never re-enable USE_VERTEX_AI.β Needs an explicit reversal of that decision to use. |
| C. OpenAI ZDR addendum / OpenAI on Azure | β (contractual) | Low code | Keeps GPT-4o-mini; requires a zero-retention agreement with OpenAI. Adds a third ZDR vendor to manage. |
| D. Leave as-is | β | None | Not acceptable for the stated goal. |
Recommendation: default to Option A (consolidate to Bedrock) for the conversion pipeline, because it gives one auditable ZDR story (βeverything runs on our ZDR Bedrock accountβ) instead of three separate vendor agreements. Hold Option B/C in reserve for the handful of tasks where a Gemini/GPT model is materially better and re-prompting to Claude/Nova regresses quality.
Decision needed before Phase 2 β confirm A vs B for the Gemini-heavy course-map and image-enhancement paths. This is a business/quality call, not a purely technical one.
Current-state inventory (what we are migrating)
Already on Bedrock
workers/vpat-parse/src/worker.tsβ Converse API viaaws4fetch+ SigV4, static AWS keys as Wrangler secrets. Fails closed (HTTP 409) if ZDR required but Bedrock unavailable.
Anthropic direct β Bedrock (easy: same models exist on Bedrock)
~25 call sites in workers/api/src, each constructing new Anthropic({ apiKey })
inline. Highlights: claude-converter, agentic-vision-converter,
opus-page-converter, unpdf-claude-converter, alt-text-suggester,
opus-jury, verification-prograder, css-designer, ux-optimizer,
ai-fix, quality-gate, region-refiner, image-region-recovery,
vision-table-extractor, all org-chart/*, plus routes/support-chat.ts,
routes/campaigns.ts, the segment-cli tool, and the exemption-review
package.
Gemini direct β needs A/B decision
workers/api: gemini-converter, image-ocr, image-enhancer,
qr-alt-text-enhancer, vision-equation-audit, visual-polisher,
wcag-visual-checker, wcag-ai-quality-checker, long-description-generator,
prompt-optimizer, ai-content-suggester, plus the Gemini branch of
smart-cascade-converter and mistral-ocr-converter.
workers/course-map-api: entirely Gemini (gemini.ts, plan-extract.ts,
llm-course-extract.ts, gemini-onboarding-discovery.ts, catalog-lookup.ts,
map-quality.ts, catalog-recon.ts).
workers/remediate: Gemini via the Vercel ai SDK.
OpenAI direct β needs A/C decision
smart-cascade-converter (gpt-4o-mini vision tier), image-enhancer.
Architectural facts that make this easier
- Gemini already has a factory (
workers/api/src/utils/gemini-client.ts) β one chokepoint for allworkers/apiGemini calls. Course-map and remediate have their own. - Anthropic has NO factory β 25 inline
new Anthropic()calls. This is the main refactor cost, but itβs mechanical. workers/apiruns on AWS Lambda (prod) + EC2 Node (10.1.1.4). Both can carry an IAM role, so Bedrock from here needs no static keys β unlike the CF worker, which had to useaws4fetchwith secret keys. Use the official@anthropic-ai/bedrock-sdk, which mirrorsmessages.create()1:1.- Cost table (
workers/api/src/utils/llm-cost.ts) is centralized β add Bedrock model IDs/pricing here once.
Phased plan
Phase 0 β Foundations (1β2 days, no behavior change)
- Confirm the Bedrock ZDR account posture covers the AWS account that Lambda/EC2
use (the VPAT worker may use a different account/keys β verify, donβt assume).
Re-run the
PutAccountDataRetention+ logging-off checks frombedrock-vpat-setup.mdagainst the conversion account. - Add IAM
bedrock:InvokeModel(+ inference-profile ARNs) to the Lambda execution role and the EC2 instance role. No static keys. - Add Bedrock model IDs + pricing to
llm-cost.ts(Sonnet 4.x, Opus 4.x, Haiku 4.5, Nova-lite/micro inference-profile IDs). - Build a provider abstraction. Create
utils/anthropic-client.tsmirroring the existinggemini-client.ts:getAnthropicClient(config)returns either anew Anthropic()(direct API) ornew AnthropicBedrock()(@anthropic-ai/bedrock-sdk) β both expose the same.messages.create(), so call sites are untouched below this line.anthropicConfigFromEnv()reads a newLLM_BACKENDflag (anthropic|bedrock, defaultanthropic) plusAWS_REGION.- Add an optional
disableDataRetentionconcept that fails closed (mirror the vpat-parse #1192 pattern) β never silently fall back to a retaining provider when ZDR was requested.
Phase 1 β PDF conversion pipeline, Anthropic call sites (the priority) (3β5 days)
Migrate the Claude-based converters and quality gates to route through the new factory. These are the highest-value paths and the lowest risk (same models on Bedrock):
- Replace every inline
new Anthropic({ apiKey })inworkers/api/src/servicesandrouteswithgetAnthropicClient(...). Mechanical; one PR per cluster (converters / quality-gates / org-chart / routes). - Flip
LLM_BACKEND=bedrockin a staging/canary Lambda alias first. - Verify per converter: output parity, latency (Bedrock cold-start +
inference-profile routing), and cost (recorded via existing
llm-costtracking). Watchopus-page-converterspecifically β it uses the Anthropic Files API, which Bedrock does not support; rework it to inline base64 / a different upload path before migrating, or leave it on direct API behind the flag and document the gap. - Canary % rollout β 100%. Keep the flag so we can revert instantly.
Phase 2 β Gemini & OpenAI conversion paths (depends on A/B/C decision) (1β2 weeks)
- Apply the Phase-0 decision per task. For Option A, port the Gemini/OpenAI
prompts in
smart-cascade-converter,image-enhancer,image-ocr,visual-polisher, the WCAG checkers, etc. to Claude/Nova on Bedrock and re-baseline quality against the existing eval corpus. - For any task kept on Gemini/OpenAI, implement the chosen ZDR mechanism (Vertex data-governance, or OpenAI ZDR addendum) and document why it stayed.
- Re-run the conversion benchmark suite; gate rollout on no quality regression.
Phase 3 β Course-map, remediate, support, campaigns, CLI/library (1 week)
workers/course-map-apiis 100% Gemini β biggest Option-A re-prompt effort, but lower stakes than conversion. Migrate behind its own flag.workers/remediate(CF Worker) β same posture asvpat-parse: it canβt use an IAM role, so it needs theaws4fetch+ static-key approach, OR proxy through the Node API. Prefer proxying throughworkers/apiso all Bedrock traffic flows through the IAM-roleβd account and we donβt manage more static keys at the edge.support-chat/campaigns(Haiku, CF Worker) β same edge decision.- CLI (
segment-cli) and theexemption-reviewpackage β migrate via the same factory; low volume, do last.
Phase 4 β Enforce & verify (ongoing)
- Make ZDR the default: flip
LLM_BACKEND=bedrockas the baseline, keepanthropicdirect as the break-glass fallback only. - Add a CI/lint check that forbids new inline
new Anthropic()/new GoogleGenAI()/new OpenAI()outside the factory files β prevents regressions back to non-ZDR paths. - Add a startup assertion +
/healthfield reporting the active backend and ZDR posture, so a misconfiguration is visible. - Update the public ZDR marketing claim (currently VPAT-only in
apps/vpat) to cover the conversion product once Phases 1β2 ship β only after verification, not before.
Risks & open questions
- Model availability under
mode: noneβ re-confirm each target model is ZDR-eligible on Bedrock (the Fable-5provider_data_sharetrap). Do this in Phase 0, not at rollout. - Files API gap (
opus-page-converter) β no Bedrock equivalent; needs rework or a documented carve-out. - Quality regression on GeminiβClaude re-prompts β the Gemini paths were tuned for Gemini; budget eval time, donβt assume parity.
- Two AWS accounts? β verify whether vpat-parseβs Bedrock keys and the Lambda/EC2 account are the same; ZDR posture is per-account.
- Edge workers (
remediate,support-chat,campaigns) canβt use IAM roles β decide proxy-through-API vs static-key SigV4 early. - Cost shift β Bedrock per-token pricing differs from direct API and from Geminiβs much cheaper rates; the consolidation may raise spend on high-volume paths (alt-text runs up to 80 parallel calls/page). Track via the existing cost ledger and confirm acceptable before 100% rollout.
Suggested issue breakdown
#xxxxPhase 0: Bedrock account posture + IAM +anthropic-client.tsfactory#xxxxPhase 1: Migrate Claude converters/quality-gates to factory + canary#xxxxDecision: Gemini/OpenAI ZDR strategy (A consolidate vs B Vertex vs C addendum)#xxxxPhase 2: Conversion Gemini/OpenAI paths#xxxxPhase 3: course-map / remediate / support / campaigns / CLI#xxxxPhase 4: enforce default + lint guard + health/marketing