# Cloudflare Workers Deployment Design

## Goal

Convert the GEO Agent Article Optimizer into a Cloudflare Workers deployable app
using the official Cloudflare bindings path for persistence and private exports.
The deployed app must be a real production-shaped target, not only a static or
partially working preview.

## Current State

The app is a Next.js application with API routes. It currently stores data in a
local SQLite file through `better-sqlite3` and writes exports under
`data/exports/<jobId>/`. That is suitable for local MVP use, but it does not map
to Cloudflare Workers because production Workers should not depend on a mutable
local filesystem or native SQLite addons.

## Chosen Approach

Use Next.js on Cloudflare Workers through the OpenNext Cloudflare adapter and
Wrangler. Cloudflare production persistence will use:

- D1 binding `DB` for relational application data.
- Private R2 binding `EXPORT_BUCKET` for generated export objects.

The app will keep local Node development separate from Cloudflare deployment:

- `npm run dev` remains the local developer path.
- Local Node tests can continue using local SQLite and filesystem exports.
- Cloudflare preview/deploy scripts use OpenNext and Wrangler.
- Production code paths access D1 and R2 only through bindings, not REST APIs.

## Environment Separation

Local and online environments must be explicit.

- Local development uses `.env.local`, local SQLite, and local export files.
- Cloudflare preview uses Wrangler local bindings and Cloudflare-compatible
  runtime behavior.
- Staging and production each get their own D1 database, R2 bucket, Worker name,
  and secrets.
- Deployments are manual. No Git auto-deploy or automatic hot update workflow is
  configured.

Recommended script split:

- `dev`: local Next.js development.
- `build`: regular Next.js build for local validation.
- `build:worker`: OpenNext Cloudflare build.
- `preview:worker`: Wrangler local preview.
- `deploy:worker:staging`: explicit staging deploy.
- `deploy:worker:production`: explicit production deploy.

## API Access Control

All `/api/*` routes are protected by default with an API access key.

- The expected secret is `API_ACCESS_KEY`.
- Clients send the key in `x-api-key`.
- Local development and tests may use an explicit test/dev bypass or a local
  `.env.local` value.
- Production secrets are set through Wrangler secrets, not committed files.
- Missing or incorrect keys return `401`.

The first implementation can use a single shared key because the current app has
no account model. The design should leave room for a future account/session
layer without mixing that concern into the Cloudflare migration.

## Data Storage

### Repository Boundary

API routes should not call `better-sqlite3` directly. Introduce a repository
boundary for article jobs, brand templates, fact cards, optimized articles, and
QA reports.

Implementations:

- Local Node implementation: uses existing SQLite behavior for tests and local
  development.
- Cloudflare implementation: uses D1 binding `DB` and async queries.

The route layer chooses the implementation based on runtime context. Online
Workers must fail clearly if required bindings are missing instead of silently
falling back to local storage.

### D1 Schema Management

D1 schema is managed only through migrations.

- Runtime code must not automatically create, rebuild, drop, or clear tables.
- Initial schema lives in a D1 migration file.
- Future schema changes use compatibility-first migrations, such as
  `ALTER TABLE ... ADD COLUMN` when possible.
- If a change requires reshaping existing data, add a dedicated data migration
  script or migration step with clear staging validation instructions.
- Before production deployment, migrations are applied and verified against
  local/staging D1 first, then applied to production.

This replaces the current local `initializeSchema` runtime behavior for the
Cloudflare path. Local test setup may still initialize local SQLite test
databases because those are disposable test fixtures, not deployed production
state.

## Export Storage

Split export handling into rendering and storage.

Rendering remains runtime-agnostic:

- Markdown renderer.
- QA JSON renderer.
- DOCX renderer.

Storage becomes environment-specific:

- Local storage writes to `data/exports/<jobId>/`.
- Cloudflare storage writes objects to private R2 through `EXPORT_BUCKET`.

R2 object keys:

- `exports/<jobId>/optimized.md`
- `exports/<jobId>/optimized.docx`
- `exports/<jobId>/qa_report.json`

The R2 bucket is private. Do not configure a public bucket URL for app exports.
Downloads go through the authenticated Worker API, which reads the object from
R2 and returns it as an attachment.

## Request Flow

1. `POST /api/jobs` checks `x-api-key`, normalizes input, writes the job to the
   active repository, and returns the candidate fact card.
2. `POST /api/jobs/:jobId/confirm-fact-card` checks the key, loads the job,
   writes the brand template and confirmed fact card, and updates job status.
3. `POST /api/jobs/:jobId/optimize` checks the key, loads job and fact card,
   runs the optimization workflow, saves article and QA report, and writes
   export objects if QA does not fail.
4. `GET /api/jobs/:jobId/exports/:fileName` checks the key and returns the
   object from local storage or private R2.

## Wrangler And Bindings

Use `wrangler.jsonc`.

Required production bindings:

- D1 database binding `DB`.
- R2 bucket binding `EXPORT_BUCKET`.

Required secrets:

- `API_ACCESS_KEY`.
- LLM provider secrets such as `DEEPSEEK_API_KEY` or `OPENAI_API_KEY`.

Recommended non-secret vars:

- `LLM_PROVIDER`.
- `DEEPSEEK_BASE_URL`.
- `DEEPSEEK_MODEL`.
- `DEEPSEEK_THINKING`.
- `OPENAI_MODEL`.
- `APP_RUNTIME=cloudflare` for clarity if needed by runtime selection.

Enable `nodejs_compat` because the app and dependencies use Node-compatible
packages, including document generation and the OpenAI client.

## Error Handling

- Missing API key: `401`.
- Missing Cloudflare binding in Worker runtime: `500` with a concise operational
  error, without exposing secret values.
- Missing job or export object: `404`.
- Fact card not confirmed before optimization: `409`.
- D1/R2 operation failures: structured server errors and enough logging context
  to identify the operation and job id.

## Testing And Verification

Tests should cover both local behavior and Cloudflare-specific behavior.

Local tests:

- Existing repository tests continue to cover SQLite behavior.
- Export renderer tests continue to cover Markdown, JSON, and DOCX output.
- API tests cover access-key enforcement.

Cloudflare-focused tests:

- D1 repository tests using local D1/Miniflare-compatible bindings or Wrangler.
- R2 export store tests for write, read, missing object, and content type.
- Runtime selection tests proving Cloudflare path uses bindings and does not
  fall back to local files.

Verification commands:

- `npm test`
- `npm run build`
- `npm run build:worker`
- `npx wrangler deploy --dry-run`

Migration verification before production:

1. Apply D1 migrations locally.
2. Apply and verify against staging D1.
3. Deploy staging Worker and smoke test protected API calls and exports.
4. Apply migrations to production D1.
5. Deploy production Worker explicitly.

## Out Of Scope

- User accounts and per-user permissions.
- Public R2 asset hosting.
- Git-triggered auto-deploy.
- Replacing the existing LLM provider abstraction.
- Batch queues or background workflow orchestration.
