# PACA_SYSTEMS.md

How the Paca AI plugin and its two backing Cloudflare Workers fit together. Covers the operational system only — the marketing/docs site (`dushakov92.github.io`) is reference material, not part of this flow.

## Components

- **paca-ai** (this repo) — WordPress plugin. Post-editor content assistant (excerpt, help-write, proofread, image gen) and the AI Search block/widget. Runs entirely on the customer's own site.
- **paca-ai-streaming-subscription** — Cloudflare Worker + D1 database. The control plane: turns a Gumroad purchase into a license record, provisions a per-customer streaming worker, and answers entitlement checks.
- **paca-ai-worker-js** — Cloudflare Worker (TypeScript). The streaming data plane. Its build is uploaded to R2 as a shared bundle; the control plane deploys one Worker instance per customer from that bundle, so each licensed site gets its own `worker_url`.

## Without a license (free tier)

Excerpt, help-write, and proofread requests call `paca_generate_excerpt()` (`includes/content-assistant/ajax.php`), which goes straight to OpenAI/Gemini via the WordPress AI Client using the site's own API key. Synchronous request/response, no Worker involved. AI Search likewise falls back to `paca_ai_search_answer` — a non-streaming AJAX call using the same direct client.

## Activation (Gumroad → control plane → plugin)

1. Customer buys a license on Gumroad. Gumroad calls **paca-ai-streaming-subscription**'s `POST /gumroad/webhook` (authenticated by a shared secret query param), which writes/updates a row in the `licenses` D1 table and immediately provisions a per-customer Worker via the Cloudflare API.
2. Customer pastes the license key into the plugin's settings (`PACA_LICENSE_KEY_FIELD`). On save, `paca_on_license_key_updated()` (`includes/helpers.php`) calls **paca-ai-streaming-subscription**'s `POST /activate` with `license_key` + the site's domain.
3. The control plane validates the license and returns `worker_url` + `site_token`, which the plugin stores as WordPress options (`paca_worker_url`, `paca_site_token`) and caches in a transient for 6 hours via `paca_get_streaming_credentials()`.
4. Separately, `paca_is_active_license()` periodically re-verifies the license directly against Gumroad's own `licenses/verify` API (1-hour cache) — this is independent of the control plane's D1 status and gates whether activation is attempted at all.

## Streaming a request (licensed)

1. Browser asks a plugin AJAX endpoint to prepare a prompt — e.g. `paca_create_excerpt_stream_handler` or the AI Search stream handler (`includes/search/ajax.php`). For AI Search, this is also where the server-side embedding similarity search runs (`wp_paca_embeddings` table) to build context for the prompt.
2. The endpoint requires cached worker credentials (403 "Streaming not activated" if absent), resolves provider/model via `paca_resolve_streaming_provider_model()`, and returns `{ prompt, provider, model }` plus the cached `worker_url`/`site_token` to the browser — no AI call happens server-side here.
3. The browser streams directly to the customer's per-customer **paca-ai-worker-js** instance over SSE (`stream-from-worker.js`), authenticating with header `X-PACA-Token: <site_token>`.
4. The Worker checks entitlement against **paca-ai-streaming-subscription**'s `GET /entitlement` (5-minute in-memory cache), enforces a per-customer RPM cap via a Durable Object rate limiter and a monthly quota from the entitlement response, then proxies the prompt to OpenAI/Gemini and streams the SSE response back.
5. On a successful stream, the Worker calls the control plane's usage-increment endpoint to bump `month_requests` for that license.

## Notes

- The plugin never talks to OpenAI/Gemini through the Worker for non-streaming features (image generation, proofreading without streaming) — those always use the site's own API key directly.
- `BASE_WORKER_DOMAIN` + the Cloudflare API token/account ID (held by the control plane) are what let it spin up a new Worker per customer on activation, rather than sharing one Worker across all customers.
