AGENTS.md@packages/integration-cloudflare-worker · git:20260810.2bd7f96 · 2026-08-10 · sha256 d8a5385f13557daa
AGENTS.md@packages/integration-cloudflare-worker git:20260810.2bd7f96A
Immutable. This exact content is served forever at /api/v1/blob/d8a5385f13557daa.
# integration-cloudflare-worker ## Purpose Cloudflare traffic integration. Generates the JavaScript Worker that the operator deploys onto their Cloudflare zone; verifies the HMAC-signed inbound ingest requests it produces; normalizes Worker events into the provider-neutral `NormalizedTrafficRequest` shape consumed by `packages/integration-traffic`. Unlike the pull adapters (`integration-cloud-run`, `integration-vercel`, `integration-wordpress-traffic`), this package targets a **push-receive** delivery model — the customer's Worker `fetch()`-es each filtered request to a canonry ingest endpoint. The choice is justified by Cloudflare's data surface: GraphQL Analytics is aggregate-only, Logpush requires Business+ plans, so Worker push is the only universal access path. The push direction is safe because canonry is single-tenant per deployment — the Worker only ever talks to the operator's own canonry instance, never to a canonry-hosted SaaS relay. ## Key Files | File | Role | |------|------| | `src/script.ts` | `generateWorkerScript` — produces the JS string with embedded source-id, bearer, HMAC secret, version, and broad edge-signal constants. `generateWranglerToml` emits an exact zone route when `zoneId` is known and otherwise disables `workers.dev` so routing stays an explicit dashboard step. `DEFAULT_BOT_LIST` is the canonical edge-side signal set. | | `src/normalize.ts` | `normalizeCloudflareWorkerEvent` — one ingest event → `NormalizedTrafficRequest`. Returns `null` when path/observedAt/eventId are missing. | | `src/verify.ts` | `verifyRequestSignature` — timestamp window + HMAC-SHA256 check. Constant-time once inputs are well-formed. | | `src/types.ts` | `CloudflareWorkerBotList`, `GenerateWorkerScriptOptions` | | `src/index.ts` | Re-exports public API | ## Patterns - **Edge filter is generic; canonry classifier is strict.** The Worker forwards on a broad UA keyword match, an exact canonical AI-engine domain (including subdomains), AI `utm_source` evidence on the request or referrer, or Cloudflare bot signals (`cf.botManagement.verifiedBot` / `cf.botManagement.score`). The authoritative bot-id / operator decisions happen in `packages/integration-traffic` once the event lands server-side. Updating the strict list does not require a Worker redeploy; updating the generic list does. - **Versioned bot list.** `CloudflareWorkerBotList.version` is baked into the generated script and stored on the source row as `configJson.expectedBotListVersion`. The Worker reports its `workerVersion` on every ingest call; the receiver records it on `traffic_sources.lastWorkerVersion` so the `cloudflare.worker.version-stale` doctor check can flag drift. - **HMAC-SHA256 with timestamp binding.** The Worker signs `timestamp + "." + body` with the per-source HMAC secret and sends `X-Canonry-Timestamp` + `X-Canonry-Signature`. The receiver verifies a ±300s window then runs constant-time equality. Failure reasons are intentionally specific (`timestamp_invalid` / `timestamp_expired` / `signature_invalid` / `signature_mismatch`) for receiver-side logging, but **never echoed back to the Worker** — an attacker who knows which leg failed can enumerate the rest. - **Bearer + HMAC secrets live in `~/.canonry/config.yaml`.** The DB stores only the sha256 of the bearer (`traffic_sources.ingestTokenHash`). The HMAC secret never goes to the DB in any form. Both are inlined into the Worker script at generation time. - **`waitUntil` for forwards.** The generated Worker uses `event.waitUntil(fetch(...))` so the forward never blocks the customer response. Errors are swallowed — AI traffic is statistical, not transactional; dropped events are acceptable, and surfacing the failure would mask the customer response. - **`cf-ray` as event id.** Cloudflare assigns a unique `cf-ray` per request. The normalizer namespaces it as `cloudflare-worker:<ray>` so it cannot collide with another adapter's event id. - **`cf-connecting-ip` enables IP verification.** Cloudflare exposes the real client IP on every plan via this header, so unlike the Vercel adapter, Cloudflare-Worker sources can promote `claimed_unverified` → `verified` via `packages/integration-traffic/src/ip-verify.ts`. - **No classification, no DB, no I/O.** This package only generates, normalizes, and verifies. The HTTP route + DB writes live in `packages/api-routes/src/traffic.ts`. The classifier + rollup live in `packages/integration-traffic`. ## Common Mistakes - **Echoing the verifier's failure reason in the HTTP response.** Use a single 401 envelope; do not let the Worker (or anything else) learn which leg of the auth failed. - **Putting the HMAC secret in `traffic_sources.configJson`.** Both shared secrets belong in `~/.canonry/config.yaml`; only the bearer hash goes to the DB. - **Adding bot-id or operator classification in this package.** The classifier lives in `packages/integration-traffic` for one-place rule evolution across every adapter. - **Storing or reading the Worker bot list anywhere but `DEFAULT_BOT_LIST`.** The Worker is regenerated from this constant; updates must rev the `version` field so the staleness check picks up the drift. ## See Also - `packages/contracts/src/traffic.ts` — `cloudflareWorkerEventSchema`, `cloudflareWorkerIngestRequestSchema`, `cloudflareWorkerSourceConfigSchema`, `trafficConnectCloudflareRequestSchema`, `trafficConnectCloudflareResponseSchema` - `packages/integration-traffic/AGENTS.md` — classifier + rollup that the ingest route hands off to - `packages/integration-vercel/AGENTS.md` — sibling adapter (pull, not push) — mirror file layout, different delivery shape