AGENTS.md@packages/integration-cloudflare-worker · git:20260810.f1a6879 · 2026-08-10 · sha256 aa96a88378ab6d6a
AGENTS.md@packages/integration-cloudflare-worker git:20260810.f1a6879A
Immutable. This exact content is served forever at /api/v1/blob/aa96a88378ab6d6a.
# 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`.
The current adapter uses **direct push** — the customer's Worker sends each
filtered request to a canonry ingest endpoint. The event/batch contract and
generated capture path are transport-neutral: `deliveryMode = direct-push`
selects the final delivery adapter, and the reserved `queue-pull` mode will
reuse the same edge event through a Cloudflare Queue.
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 an ES-module Worker with env/secret bindings and a transport-neutral capture path. `generateWranglerToml` emits non-secret vars plus an exact zone route when `zoneId` is known. |
| `src/canonical-json.ts` | Deterministic JSON encoding embedded into the generated Worker and reused by receiver signature verification. |
| `src/normalize.ts` | `normalizeCloudflareEdgeEvent` — one edge event → `NormalizedTrafficRequest`; the old Worker-named export is a compatibility alias. |
| `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 + "." + canonicalJson(batch)` 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` and Worker secret bindings.** The DB
stores only the sha256 of the bearer (`traffic_sources.ingestTokenHash`).
The HMAC secret never goes to the DB in any form. Neither secret is emitted
in generated source, Wrangler TOML, API output, or MCP output.
- **ES-module Worker + `waitUntil`.** The generated Worker exports
`{ fetch(request, env, ctx) }` and uses `ctx.waitUntil(...)` so delivery 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.
- **Delivery is the seam.** Filtering builds a `CloudflareEdgeEventBatch`,
then `deliverEdgeEventBatch` selects `deliverViaDirectPush`. Queue support
adds a delivery branch and Wrangler queue binding; it must not fork the
filter, event schema, canonical encoding, or normalizer.
- **`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 either secret in generated source/TOML or `traffic_sources.configJson`.** Both
shared secrets belong in `~/.canonry/config.yaml`; only the bearer hash
goes to the DB, and deployment installs them through Worker secret bindings.
- **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` — `cloudflareEdgeEventSchema`,
`cloudflareEdgeEventBatchSchema`, `cloudflareTrafficSourceConfigSchema`,
plus the compatibility Worker-named aliases,
`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