Cloudflare Architecture — CAC (LOCKED) Intent
Operator input → resolve config → generate PDFs → send email → write DB
Zero code change for new course/event/TP (data-only via KV/R2)
One worker serves infinite configs.
Workflow Resolve domain (Host → KV) Resolve event (URL → KV) Load all config (KV) Load templates/assets (R2) Decide docs Render PDFs (Puppeteer) Send email (Resend) Write D1
Execution Context domain:{host} → {tp_key, payment_paths[], grant_tp_key, owner_key} event:{course_id}:{slug} → full event course:{course_id} → course
Null → stop (404). No fallback.
Config Loads TP → from event or domain (grant path enforced) Owner → from domain Trainer → R2 PDF (required) Templates → R2 (required) Content → content:{course_id}.html (required)
Missing → throw
| form | hrdc | output |
|---|---|---|
| corporate | direct/grant | proforma + schedule + trainer |
| corporate | provider | + HRDC guide |
| public | grant | same as corporate |
| public | direct | Stripe only |
inject(html, vars) no undefined/null → use '' all vars resolved BEFORE inject
POST uses hidden fields Worker does NOT re-fetch KV on POST
PDF @cloudflare/puppeteer single browser sequential pages close before email save → proformas/{invoice}-{type}.pdf Email Resend API from: courses@claritysystems.work ONLY copy from KV (course.email_copy.*) never hardcode D1
Tables:
enquiries id, form_type, training_type, hrdc_option, status, name, email, phone, company, company_address, course_id, event_id, tp_key, pax, tentative_date, venue, message, created_at
email_events id, enquiry_id, resend_email_id, event_type, metadata_json, created_at
KV (LOCKED)
Patterns NEVER change:
domain:{host} event:{course_id}:{slug} course:{course_id} tp:{id} owner:{id} trainer:{id} invoice:last_number
Single namespace: CAC_KV
No new patterns without explicit decision
R2 (LOCKED) templates (html) content:{course_id} trainer PDFs HRDC guide output PDFs
Binding: ASSETS
Site Main (Eleventy) static reads KV at build all payment paths Subdomains (Worker) runtime render grant only minimal UI no Stripe Additions (NO CODE)
TP
add KV domain + tp add DNS
Course
add KV course upload content add Eleventy page
Event
add KV event Worker
Single worker
Routes:
GET /{slug} GET /{slug}/enquire POST /{slug}/enquire POST uses hidden fields only Constraints (ABSOLUTE) zero business data in worker KV patterns immutable one KV, one D1 never edit _site/ only counter: invoice:last_number always attach docs (grant path) email sender fixed no KV fetch on POST Open Decisions (BLOCK BUILD) KV field schema (inside objects) Eleventy KV fetch mechanism build trigger (CI/CD) worker merge Quality Gate
Reject output if:
requires code for data change adds hardcoded value breaks KV pattern needs multiple file edits for config asks user something already known
AI Behavior (MANDATORY) no explanations of options no unnecessary questions decide + execute minimal words, maximum action build at correct abstraction (data > code) prioritize compounding system design Deploy Constraints (LOCKED) Local machine: Node v18 — wrangler deploy blocked. Dashboard editor: blocks save on npm imports — never use for worker code. curl API: cannot bundle npm deps — does not work.
Worker deploy (ONLY valid path): git push to main → GitHub Actions → auto-deploys via Node 20 Workflow: .github/workflows/trainingbiz-admin-clerk.yml
Template/HTML deploy: Upload to R2 directly via Cloudflare dashboard. No wrangler needed.
Never suggest wrangler deploy locally, curl deploy, or dashboard editor for index.js.