Custom Domain Ingress, SSL Lifecycle & Ephemeral Preview Routing True Specification
Custom Domain Ingress, SSL Lifecycle & Ephemeral Preview Routing True Specification
Section titled “Custom Domain Ingress, SSL Lifecycle & Ephemeral Preview Routing True Specification”Document Status: 🟢 Active True Specification
Target Audience: Core Engineering, Platform SRE, AI Agents, Client Success
Primary Maintainer: SiteSwarm Architecture Council
Related Documents: HIGH_LEVEL_DESIGN.md, docs/PRD.md, docs/CAPABILITY_MANAGEMENT.md, docs/CLIENT_CMS.md, docs/DATA_ISOLATION_AND_STORAGE.md, ADR-0003
Implementation Tracking: Issue #57 (Supersedes Issue #30; Part of Epic #52 alongside #53, #54, #55, and #56)
1. Executive Summary & Core Architectural Invariants
Section titled “1. Executive Summary & Core Architectural Invariants”SiteSwarm delivers bespoke, high-performance web presences for local small businesses while enabling software engineers to operate the platform with zero daytime distractions. For this business model to scale sustainably across dozens of independent client applications in a monorepo, the edge ingress and routing infrastructure must solve two conflicting operational pressures:
- Production Custom Ingress Without DNS Migration Friction: Non-technical local business owners (bakeries, boutique agencies, medical clinics) own custom domains registered at fragmented third-party registrars (GoDaddy, Namecheap, Google Domains / Squarespace, Network Solutions). They have existing business email (Google Workspace, Microsoft 365) configured on their domain. Forcing a full authoritative nameserver transfer to Cloudflare introduces high customer friction, risk of broken email delivery, and customer support emergencies. Production ingress must route custom domains to edge workers purely via CNAME records with fully automated SSL provisioning.
- Ephemeral PR Preview Routing Without Resource Bloat: In a monorepo with multiple independent applications (
apps/bakery,apps/software-agency, etc.), developers and AI agents routinely push feature branches. Validating changes requires live mobile verification on real smartphone devices by stakeholders before merging. However, deploying every app on every commit exhausts CI minutes, inflates edge worker counts, and causes cloud sprawl. Ephemeral PR previews must deploy only for impacted applications, expose instant mobile inspection links (with QR codes), and automatically decommission upon PR closure.
flowchart TD subgraph ClientDNS["Third-Party Client DNS (GoDaddy, Namecheap, Google Domains)"] ClientApex["client.com (Apex)\n(URL Forwarder or ALIAS)"] ClientSub["www.client.com (Subdomain)\n(CNAME Record)"] end
subgraph PreviewDNS["SiteSwarm Staging & Preview DNS (*.siteswarm.dev)"] PRPreviewDNS["pr-55-bakery.preview.siteswarm.dev\n(Ephemeral Route)"] StagingDNS["staging-bakery.siteswarm.dev\n(Staging Route)"] end
subgraph EdgeIngress["Cloudflare Edge Fleet (SSL & Routing Layer)"] CF_SaaS["Cloudflare for SaaS (SSL for SaaS)\nCustom Hostname Engine + DCV"] CF_WAF["Cloudflare Edge WAF & Security\n(Turnstile, HSTS, Rate Limiting, CSP)"] CF_Router["Dynamic Edge Ingress Router\n(Host Header Evaluation)"] CF_Access["Cloudflare Access / Zero Trust\n(Gates /admin* and /_emdash/*)"] end
subgraph EdgeWorkers["Application Compute Layer (Cloudflare Workers)"] subgraph AppBakery["Client App: Green Leaf Bakery"] WorkerProdBakery["Worker A (Public Frontend)\napps/bakery\n(Zero Session KV, Sub-20ms TTFB)"] WorkerProdBakeryCMS["Worker B (Isolated CMS & Admin)\nservices/cms-bakery\n(EmDash Studio, D1 Database)"] WorkerProdBakery -->|Service Binding: /admin*| WorkerProdBakeryCMS end WorkerProdAgency["Production Worker\napps/software-agency (apex-labs)"] WorkerPRBakery["Ephemeral Worker A\ngreen-leaf-bakery-preview-pr-55"] end
ClientSub -->|CNAME fallback.siteswarm.dev| CF_SaaS ClientApex -.->|301 Redirect to www| ClientSub PRPreviewDNS --> CF_WAF StagingDNS --> CF_WAF CF_SaaS --> CF_WAF CF_WAF --> CF_Router
CF_Router -->|Host: www.greenleafbakery.com| WorkerProdBakery CF_Router -->|Host: www.apexlabs.io| WorkerProdAgency CF_Router -->|Host: pr-55-bakery.preview.siteswarm.dev| WorkerPRBakery CF_WAF -.->|Protected Admin Paths| CF_AccessThe Four Foundational Ingress Invariants:
Section titled “The Four Foundational Ingress Invariants:”- The Zero-Nameserver-Migration Invariant: Clients are never required to change authoritative nameservers. All production routing is orchestrated via Cloudflare for SaaS custom hostnames pointing to a designated SiteSwarm fallback origin CNAME target. Client MX, TXT (SPF/DKIM/DMARC), and other operational DNS records remain untouched at their existing registrar.
- The Zero-Touch Renewal & Fully Automated SSL Invariant: Domain Control Validation (DCV) and TLS certificate generation (via Let’s Encrypt or Google Trust Services) must execute automatically through Cloudflare’s edge pipeline without manual certificate issuance, renewal tickets, or server reboots.
- The Ephemeral Isolation & Zero-Orphan Invariant: Ephemeral PR preview environments are deployed strictly for client apps impacted by git changes (via monorepo change detection). When a PR is closed or merged, preview workers and routing routes are automatically deleted with zero residual compute or DNS records left behind.
- The 30-Second Mobile Inspection Benchmark: Pinned PR comments must provide live, edge-probed preview URLs with mobile-ready QR codes so stakeholders can inspect live changes on physical mobile devices in under 30 seconds from deployment.
2. Ingress Topology & Architecture Evaluation Matrix
Section titled “2. Ingress Topology & Architecture Evaluation Matrix”Before selecting Cloudflare for SaaS custom hostnames and dynamic worker-based ephemeral previews, we evaluated four candidate ingress models against SiteSwarm’s operating constraints: near-$0 baseline costs, zero daytime maintenance distractions, client autonomy, and preview velocity.
| Ingress & Routing Pattern | Contender 1: Full Nameserver Transfer to Cloudflare | Contender 2: Traditional Reverse Proxy VPS (Nginx / Caddy) | Contender 3: Cloudflare Pages Preview Environments | Contender 4: Cloudflare for SaaS + Dynamic Worker Previews (SiteSwarm Canonical) |
|---|---|---|---|---|
| Client DNS Friction | 🔴 High friction: Client must transfer authoritative nameservers; high risk of breaking email | 🟢 Low friction: Standard CNAME record | 🟢 Low friction: Standard CNAME for custom domains | 🟢 Zero friction: Standard CNAME to fallback origin; email untouched |
| SSL / TLS Provisioning | 🟢 Automatic (Universal SSL) | 🟡 Let’s Encrypt certbot with manual renewal cron | 🟢 Automatic Cloudflare SSL | 🟢 Zero-touch automated SSL for SaaS (Let’s Encrypt / GTS DCV) |
| Multi-App Monorepo Previews | 🔴 Single preview per repo; monorepo collisions | 🔴 Complex manual Docker container reverse proxy orchestration | 🟡 Single Pages project per repo; requires $N$ Pages projects | 🟢 Dynamic per-app preview workers (<app>-preview-pr-${PR_NUM}) scoped by change detection |
| Ephemeral Teardown | N/A | 🔴 Fragile container pruning scripts | 🟡 Manual branch deployment retention policies | 🟢 Automated PR-closed workflow runs wrangler delete --force |
| Mobile Stakeholder Review | 🔴 Must manually find preview URL in commit log | 🔴 Custom bot scripts | 🟡 Cloudflare bot posts generic commit link | 🟢 Idempotent pinned PR comment bot with inline QR codes & edge health probe |
| Baseline Infrastructure Cost | 🟢 Free tier | 🔴 $10–$40/mo per VPS | 🟢 Free tier (500 builds/mo limit) | 🟢 $0 Free / $5/mo Workers Paid (includes 100 custom SaaS hostnames) |
| Architectural Verdict | ❌ REJECTED (High client risk) | ❌ REJECTED (High server upkeep) | ❌ REJECTED (Monorepo friction) | 🟢 ADOPTED AS CANONICAL STRATEGY |
3. Production Ingress: Cloudflare for SaaS Architecture
Section titled “3. Production Ingress: Cloudflare for SaaS Architecture”3.1 Macro Architecture & Fallback Origin
Section titled “3.1 Macro Architecture & Fallback Origin”SiteSwarm utilizes Cloudflare for SaaS (also known as Custom Hostnames / SSL for SaaS). The architecture separates the platform infrastructure zone from the client’s custom domain:
- SiteSwarm Platform Zone:
siteswarm.devis an authoritative Cloudflare zone owned by the platform. - Fallback Origin Hostname: A designated CNAME target configured on the platform zone:
fallback.siteswarm.dev -> Cloudflare Worker Ingress Router (or specific client worker)
- Client Custom Domain:
www.greenleafbakery.comconfigured at GoDaddy/Namecheap with a CNAME record pointing tofallback.siteswarm.dev.
When an end user visits https://www.greenleafbakery.com:
- Client DNS resolves
www.greenleafbakery.comtofallback.siteswarm.dev. - Cloudflare Edge receives the request, identifies
www.greenleafbakery.comin its Custom Hostname registry forsiteswarm.dev, and terminates TLS using the auto-provisioned certificate. - Cloudflare executes edge WAF, Turnstile, and caching policies.
- Cloudflare forwards the request to the Fallback Origin Worker with the original
Host: www.greenleafbakery.comheader preserved. - The Worker inspects the
Hostheader to dispatch to the appropriate client Astro SSR application.
sequenceDiagram autonumber actor Customer as Local Customer Browser participant Registrar as Client Registrar DNS (e.g. GoDaddy) participant CF_Edge as Cloudflare Anycast Edge (SaaS) participant Worker as SiteSwarm Ingress Router / Client Worker participant Storage as Cloudflare D1 / R2 / KV
Customer->>Registrar: Resolve www.greenleafbakery.com Registrar-->>Customer: CNAME fallback.siteswarm.dev (Cloudflare Anycast IPs) Customer->>CF_Edge: TLS 1.3 ClientHello (SNI: www.greenleafbakery.com) CF_Edge->>CF_Edge: Match Custom Hostname & Terminate SSL (Let's Encrypt / GTS) CF_Edge->>CF_Edge: Apply Ingress Security (WAF, HSTS, Rate Limits, Cache) CF_Edge->>Worker: Route request with Host: www.greenleafbakery.com Worker->>Storage: Query tenant data (D1 / KV Cache) Storage-->>Worker: Tenant configuration & content Worker-->>CF_Edge: HTML response + Security Headers + Cache-Control CF_Edge-->>Customer: HTTP/3 response (Brotli/Zstd compressed)3.2 Automated Custom Hostname Provisioning via Cloudflare API
Section titled “3.2 Automated Custom Hostname Provisioning via Cloudflare API”Custom hostnames are provisioned automatically via the Cloudflare REST API. This allows onboarding scripts, CLI utilities, or admin interfaces to configure domains without manual dashboard navigation.
Step 1: Provision Custom Hostname
Section titled “Step 1: Provision Custom Hostname”curl -X POST "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/custom_hostnames" \ -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "hostname": "www.greenleafbakery.com", "ssl": { "method": "cname", "type": "dv", "settings": { "http2": "on", "min_tls_version": "1.2", "ciphers": [] } } }'API Response Payload & Validation Fields:
Section titled “API Response Payload & Validation Fields:”{ "success": true, "errors": [], "messages": [], "result": { "id": "e4b3c2a1-5d6e-4f8a-9b1c-7e8f9a0b1c2d", "hostname": "www.greenleafbakery.com", "status": "pending", "verification_errors": [ "custom hostname does not CNAME to this zone." ], "ownership_verification": { "type": "cname", "name": "www.greenleafbakery.com", "value": "fallback.siteswarm.dev" }, "ssl": { "id": "f5c4b3a2-6e7f-4a9b-0c2d-8f9a0b1c2d3e", "type": "dv", "method": "cname", "status": "pending_validation", "dcv_delegation_records": [ { "cname": "_cf-custom-hostname.www.greenleafbakery.com", "cname_target": "www.greenleafbakery.com.e4b3c2a1-5d6e.dcv.cloudflare.com" } ] }, "created_at": "2026-09-29T20:00:00.000Z" }}3.3 Domain Control Validation (DCV) & Zero-Touch Renewal
Section titled “3.3 Domain Control Validation (DCV) & Zero-Touch Renewal”Cloudflare for SaaS supports three Domain Control Validation methods:
- CNAME Validation (Recommended Default):
- When the client creates
CNAME www.greenleafbakery.com -> fallback.siteswarm.dev, Cloudflare uses the routing CNAME to automatically perform both hostname ownership verification and SSL certificate validation. - For pre-validation before traffic switchover, an optional DCV delegation record (
_cf-custom-hostname.www...) can be created to validate the certificate with zero downtime before changing the primary CNAME.
- When the client creates
- HTTP Validation (
/.well-known/cf-custom-hostname-challenge/):- Useful when the domain is already pointing to SiteSwarm and automated token requests are served by the edge worker.
- TXT Record Validation:
- Explicit TXT ownership record:
_cf-custom-hostname.www.greenleafbakery.comwith value provided by the API.
- Explicit TXT ownership record:
Zero-Touch Certificate Renewal:
Section titled “Zero-Touch Certificate Renewal:”- Certificates are issued by Let’s Encrypt or Google Trust Services (DV - Domain Validated).
- Cloudflare automatically revalidates and renews certificates 30 days prior to expiration via the existing CNAME target.
- No developer intervention or certbot cron job is ever required.
3.4 Client Registrar DNS Onboarding Runbooks
Section titled “3.4 Client Registrar DNS Onboarding Runbooks”Local small business owners need clear, step-by-step instructions. Below are copy-paste onboarding templates for the four most common registrar configurations.
Architecture Note: Apex (@) vs. Subdomain (www)
Section titled “Architecture Note: Apex (@) vs. Subdomain (www)”- DNS Specification RFC 1034/1035: An apex/root domain (
example.com) cannot have a CNAME record if other records (like MX or SOA) exist on@. - SiteSwarm Best Practice: The canonical site lives at
www.client.com. The apexclient.comis configured with a 301 Permanent Redirect tohttps://www.client.comat the registrar level, or uses registrar CNAME flattening / ALIAS records when available.
1. GoDaddy DNS Configuration Runbook
Section titled “1. GoDaddy DNS Configuration Runbook”Scenario: Client purchased
greenleafbakery.comon GoDaddy. They use Google Workspace or Microsoft 365 for email.
Step-by-Step Instructions for Client:
- Log into your GoDaddy Domain Portfolio.
- Select your domain, click DNS, and navigate to DNS Records.
- Locate the existing
CNAMErecord with Namewww. Click Edit and set:- Type:
CNAME - Name:
www - Value:
fallback.siteswarm.dev - TTL:
1/2 Hour(orDefault)
- Type:
- In the DNS settings page, scroll down to Forwarding > Domain Forwarding:
- Click Add Forwarding on the root domain (
@). - Destination URL:
https://www.greenleafbakery.com - Redirect Type:
Permanent (301) - Forward Settings:
Forward Only
- Click Add Forwarding on the root domain (
- Click Save. Do not alter any MX, TXT, or SPF records.
2. Namecheap DNS Configuration Runbook
Section titled “2. Namecheap DNS Configuration Runbook”Scenario: Client domain on Namecheap. Namecheap supports ALIAS (ANAME) flattening at the root apex.
Step-by-Step Instructions for Client:
- Log into your Namecheap Dashboard and click Manage next to your domain.
- Select the Advanced DNS tab.
- In the Host Records section, add or update the following records:
- Record 1 (Subdomain):
- Type:
CNAME Record - Host:
www - Value:
fallback.siteswarm.dev - TTL:
Automatic
- Type:
- Record 2 (Apex ALIAS):
- Type:
ALIAS Record - Host:
@ - Value:
fallback.siteswarm.dev - TTL:
Automatic
- Type:
- Record 1 (Subdomain):
- If Namecheap ALIAS is not desired, configure Redirect Domain from
@tohttps://www.greenleafbakery.com. - Click Save all changes.
3. Google Domains / Squarespace Domains Runbook
Section titled “3. Google Domains / Squarespace Domains Runbook”Scenario: Domain registered with Google Domains (migrated to Squarespace Domains).
Step-by-Step Instructions for Client:
- Log into your domain management dashboard at Squarespace Domains.
- Click on your domain and navigate to DNS Settings.
- Under Custom Records, create:
- Record Type:
CNAME - Host:
www - Data:
fallback.siteswarm.dev - TTL:
3600(or300)
- Record Type:
- Under Domain Forwarding / Website Redirect Rules:
- Forward from
greenleafbakery.comtohttps://www.greenleafbakery.com. - Select 301 Permanent Redirect and enable SSL for Forwarding.
- Forward from
- Save changes.
4. Cloudflare DNS (Client Already on Cloudflare) Runbook
Section titled “4. Cloudflare DNS (Client Already on Cloudflare) Runbook”Scenario: Client’s domain is already managed on Cloudflare DNS.
Step-by-Step Instructions for Client:
- Log into the Cloudflare Dashboard and select the domain zone.
- Navigate to DNS > Records.
- Add a CNAME for
wwwand@(Cloudflare automatically flattens CNAMEs at the apex root):- Record 1:
- Type:
CNAME - Name:
www - Target:
fallback.siteswarm.dev - Proxy status:
DNS only(Grey Cloud) — Crucial: Must be Grey Cloud to prevent Cloudflare-on-Cloudflare proxy conflict (“Orange to Orange”) unless using Cloudflare SaaS CNAME delegation.
- Type:
- Record 2:
- Type:
CNAME - Name:
@ - Target:
fallback.siteswarm.dev - Proxy status:
DNS only(Grey Cloud)
- Type:
- Record 1:
- Save records.
3.5 Worker Splitting Ingress Topology & Routing Rules (/admin & /_emdash/*)
Section titled “3.5 Worker Splitting Ingress Topology & Routing Rules (/admin & /_emdash/*)”As established in ADR-0003, SiteSwarm enforces physical isolation between public static delivery and dynamic CMS administration. This resolves the Astro session KV auto-provisioning problem and isolates the database blast radius.
3.5.1 Ingress Topology Architecture
Section titled “3.5.1 Ingress Topology Architecture”flowchart TD subgraph IngressGateway["Edge Ingress Gateway (Cloudflare Anycast)"] Request["Incoming HTTPS Request\nHost: www.greenleafbakery.com"] WAF["Edge WAF & DDoS Shield"] HostMatch{"Route Matcher\nPath Inspection"} AccessCheck{"Path in /admin* or /_emdash/*?"} CFAccess["Cloudflare Access / Zero Trust\n(JWT Assertion Verification)"] end
subgraph WorkerFleet["Decoupled Compute Layer"] subgraph WorkerA["Worker A: Public Static Frontend (@siteswarm/app-bakery)"] EdgeAssets["Cloudflare Assets (SSG Prerender)\nSub-20ms TTFB | 0 KB Client JS | Zero Session KV"] ProxyMiddleware["Service Binding Proxy Handler\n(env.CMS_SERVICE)"] end
subgraph WorkerB["Worker B: Isolated CMS & Admin (@siteswarm/service-cms-bakery)"] EmDashStudio["EmDash CMS Engine\n(Astro + React Admin Studio)"] AdminAuth["Passkey & Session Cookie Engine"] MCPEndpoint["MCP Agent Endpoint (/_emdash/api/mcp)"] D1Storage[("Cloudflare D1 Database\n(Atomic SQLite)")] R2Storage[("Cloudflare R2 Bucket\n(Zero-Egress Media)")] end end
Request --> WAF --> HostMatch HostMatch --> AccessCheck AccessCheck -- "No (Public Paths: /, /menu, /about, /assets/*)" --> WorkerA WorkerA --> EdgeAssets
AccessCheck -- "Yes (/admin*, /_emdash/*)" --> CFAccess CFAccess -- "Authenticated" --> WorkerA WorkerA --> ProxyMiddleware ProxyMiddleware ==>|"Service Binding\n0ms In-Memory V8 Hop"| WorkerB
WorkerB --> EmDashStudio WorkerB --> AdminAuth WorkerB --> MCPEndpoint EmDashStudio --> D1Storage EmDashStudio --> R2Storage3.5.2 Path-Based Routing Matrix
Section titled “3.5.2 Path-Based Routing Matrix”| Requested Path | Target Runtime | Delivery Mechanism | Session KV Required? | D1 Binding Exposed? | Security Tier |
|---|---|---|---|---|---|
/ (Homepage) |
Worker A (Frontend) | Edge Cached / Static Asset | ❌ No | ❌ No | Public Anonymous |
/menu, /about, /contact |
Worker A (Frontend) | Edge Cached / Static Asset | ❌ No | ❌ No | Public Anonymous |
/assets/*, /favicon.svg |
Worker A (Frontend) | Cloudflare Assets (CDN) | ❌ No | ❌ No | Public Anonymous |
/api/contact (Lead Form) |
Worker A (Frontend) | Turnstile Edge Validator | ❌ No | ❌ No | Public Rate-Limited |
/admin, /admin/* |
Worker B (CMS) | Proxied via env.CMS_SERVICE |
❌ (Cookie) | ✅ Yes | Cloudflare Access + Admin Auth |
/_emdash/* (CMS Assets & API) |
Worker B (CMS) | Proxied via env.CMS_SERVICE |
❌ (Cookie) | ✅ Yes | Cloudflare Access + Admin Auth |
/_emdash/api/mcp (AI Agent) |
Worker B (CMS) | Proxied via env.CMS_SERVICE |
❌ (Token) | ✅ Yes | Bearer Token / Service Auth |
3.5.3 Worker A Service Binding Implementation
Section titled “3.5.3 Worker A Service Binding Implementation”In Worker A’s wrangler.jsonc, the CMS worker is declared as a native Service Binding:
{ "name": "green-leaf-bakery", "compatibility_date": "2026-09-29", "compatibility_flags": ["nodejs_compat"], "services": [ { "binding": "CMS_SERVICE", "service": "green-leaf-bakery-cms" } ]}Worker A dispatches administrative paths to Worker B with zero network latency:
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const url = new URL(request.url);
// Route admin and CMS requests directly to Worker B if (url.pathname.startsWith('/admin') || url.pathname.startsWith('/_emdash')) { return env.CMS_SERVICE.fetch(request.clone()); }
// Serve public pages from static assets with edge cache headers return env.ASSETS.fetch(request); }};4. Multi-App Ephemeral PR Preview Routing Lifecycle
Section titled “4. Multi-App Ephemeral PR Preview Routing Lifecycle”4.1 Subdomain & Cloudflare Worker Naming Conventions
Section titled “4.1 Subdomain & Cloudflare Worker Naming Conventions”SiteSwarm enforces deterministic naming conventions across the monorepo to guarantee collision-free preview routing and predictable cleanup:
| Concept | Format / Pattern | Example |
|---|---|---|
| Monorepo App Path | apps/<app-folder> |
apps/bakery, apps/software-agency |
| Application ID / Slug | Defined in package.json / swarm.config.ts |
green-leaf-bakery, apex-labs |
| Worker Preview Name | <app-name>-preview-pr-${PR_NUM} |
green-leaf-bakery-preview-pr-55 |
| Preview Edge Hostname | https://pr-${PR_NUM}-${app-slug}.preview.siteswarm.dev |
https://pr-55-bakery.preview.siteswarm.dev |
| Fallback Workers URL | https://${app-name}-preview-pr-${PR_NUM}.siteswarm.workers.dev |
https://green-leaf-bakery-preview-pr-55.siteswarm.workers.dev |
flowchart LR GitBranch["Feature Branch\nPR #55 touches apps/bakery"] --> Detector["Change Detector (#53)\nimpacted_apps: ['bakery']"] Detector --> CIBuild["Selective Build (#54)\npnpm --filter=@siteswarm/app-bakery build"] CIBuild --> Deploy["Wrangler Deploy (#55)\n--name green-leaf-bakery-preview-pr-55"] Deploy --> Probe["Edge Health Probe\nGET /_emdash/api/health (200 OK)"] Probe --> Comment["PR Comment Bot\nPost Pinned Comment + QR Code"]
PRClose["PR #55 Merged or Closed"] --> Teardown["Teardown Workflow (#56)\nwrangler delete --name green-leaf-bakery-preview-pr-55"] Teardown --> DecomComment["Update PR Comment\n🧹 Preview Decommissioned"]4.2 Integration with Monorepo Change Detection (#53, #54)
Section titled “4.2 Integration with Monorepo Change Detection (#53, #54)”Ephemeral preview deployments consume outputs generated by @siteswarm/change-detector (Issue #53):
{ "has_runtime_changes": true, "is_platform_wide": false, "impacted_apps": ["bakery"], "matrix": ["apps/bakery"]}- Documentation-Only PRs (
has_runtime_changes: false):- Fast-pass exit within 20 seconds.
- Zero preview deployments triggered.
- No worker or DNS changes made.
- Single-App Impact (
impacted_apps: ['bakery']):- Deploy only
green-leaf-bakery-preview-pr-${PR_NUM}. apps/software-agencyand other client apps are untouched.
- Deploy only
- Platform-Wide Impact (
is_platform_wide: true):- Triggered when shared platform libraries (
packages/*, root configurations, shared styles) are modified. - Generates a preview deployment matrix across all registered client applications in the fleet so regression testing can be conducted across all sites.
- Triggered when shared platform libraries (
4.3 Preview Deployment Workflow Architecture (preview-deploy.yml #55)
Section titled “4.3 Preview Deployment Workflow Architecture (preview-deploy.yml #55)”The preview deployment workflow executes on pull_request events (opened, synchronize, reopened):
# .github/workflows/preview-deploy.yml (Specification)name: Ephemeral PR Preview Deployment
on: pull_request: types: [opened, synchronize, reopened]
concurrency: group: preview-${{ github.event.pull_request.number }} cancel-in-progress: true
jobs: detect: name: Evaluate Monorepo Changes runs-on: ubuntu-latest outputs: has_runtime_changes: ${{ steps.detector.outputs.has_runtime_changes }} impacted_apps: ${{ steps.detector.outputs.impacted_apps }} matrix: ${{ steps.detector.outputs.matrix }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - id: detector run: pnpm --filter=@siteswarm/change-detector run detect
deploy-previews: name: Deploy Ephemeral Worker Preview needs: detect if: needs.detect.outputs.has_runtime_changes == 'true' strategy: fail-fast: false matrix: app: ${{ fromJson(needs.detect.outputs.matrix) }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 12.4.1 - uses: actions/setup-node@v4 with: node-version: 22 cache: "pnpm" - run: pnpm install --frozen-lockfile
- name: Build Application run: pnpm --filter=${{ matrix.app }} run build
- name: Graceful Zero-Secrets Check & Edge Deploy id: deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} run: | if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then echo "::warning::Cloudflare secrets not configured. Skipping remote edge deployment (zero-secrets fallback active)." echo "skipped=true" >> $GITHUB_OUTPUT exit 0 fi
APP_NAME=$(node -p "require('./${{ matrix.app }}/package.json').name.replace('@siteswarm/app-', '')") WORKER_NAME="${APP_NAME}-preview-pr-${{ github.event.pull_request.number }}"
# Deploy ephemeral worker pnpm --filter=${{ matrix.app }} exec wrangler deploy \ --name "$WORKER_NAME" \ --compatibility-date 2026-09-29
PREVIEW_URL="https://${WORKER_NAME}.${{ secrets.CF_WORKERS_DOMAIN || 'workers.dev' }}" echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT echo "worker_name=$WORKER_NAME" >> $GITHUB_OUTPUT4.4 Cloudflare Credential Enforcement & Strict Failure Protocol
Section titled “4.4 Cloudflare Credential Enforcement & Strict Failure Protocol”SiteSwarm enforces strict build failures when Cloudflare deployment tokens (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID) are absent for runtime preview deployments:
- Hard CI Failure: If credentials are missing when deployable client applications are modified,
.github/workflows/preview-deploy.ymlexits with code1and emits an error annotation. - Actionable Failure Diagnostics: The failure message in CI logs and the pinned PR comment explicitly detail the missing credentials and provide exact setup steps and required API token permissions.
- Required Cloudflare API Token Setup:
CLOUDFLARE_ACCOUNT_ID: Found in Cloudflare Dashboard (Workers & Pages Overview).CLOUDFLARE_API_TOKEN: Created at Cloudflare API Tokens with the Edit Cloudflare Workers template, or custom permissions:- Account:
Workers Scripts: Edit - Account:
Workers KV Storage: Edit - Account:
Workers Routes: Edit - Account:
D1: Edit - Account:
Account Settings: Read - User:
Memberships: Read
- Account:
4.5 Edge Health Probing & Synthetic Smoke Verification
Section titled “4.5 Edge Health Probing & Synthetic Smoke Verification”Before broadcasting a preview URL to stakeholders, the deployment pipeline executes an automated HTTP probe against the deployed edge worker:
# Edge Health Verification LoopPROBE_TARGET="${PREVIEW_URL}/_emdash/api/health"MAX_ATTEMPTS=12INTERVAL_SEC=5
for i in $(seq 1 $MAX_ATTEMPTS); do HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PROBE_TARGET" || echo "000") if [ "$HTTP_STATUS" -eq 200 ]; then echo "✅ Edge health probe passed (HTTP 200) after ${i} attempts." exit 0 fi echo "Attempt $i/$MAX_ATTEMPTS: HTTP $HTTP_STATUS — waiting ${INTERVAL_SEC}s..." sleep $INTERVAL_SECdone
echo "❌ Error: Edge preview failed to reach healthy state within 60s."exit 1- Health Probe Endpoints:
/_emdash/api/healthfor Astro applications with EmDash CMS integrated./api/healthfor lightweight client apps.- Validates edge worker initialization, D1 database binding connectivity, and asset routing.
4.6 Pinned PR Comment Bot & Mobile QR Code Inspection
Section titled “4.6 Pinned PR Comment Bot & Mobile QR Code Inspection”Reviewing a local business website on a desktop monitor does not reflect how >75% of local bakery or restaurant customers interact with the site. The PR comment bot generates a responsive preview table with an embedded QR code for mobile inspection:
<!-- siteswarm-preview-bot -->## 🐝 SiteSwarm Ephemeral PR Previews
| Client Application | Ephemeral Preview URL | Edge Health | Mobile Inspection || :--- | :--- | :--- | :--- || **Green Leaf Bakery** (`apps/bakery`) | [View Live Preview](https://green-leaf-bakery-preview-pr-55.siteswarm.workers.dev) | 🟢 `200 OK` (Edge Healthy) |  |
*Commit: `a1b2c3d` • Deployed: 2026-09-29T21:45:00Z • Managed by SiteSwarm Fleet Automation*- Idempotency Guarantee: The bot searches existing comments for the
<!-- siteswarm-preview-bot -->HTML comment marker. If found, it updates the existing comment in-place viaoctokit.rest.issues.updateComment, avoiding comment spam across commits.
4.7 Ephemeral Teardown Lifecycle (preview-teardown.yml #56)
Section titled “4.7 Ephemeral Teardown Lifecycle (preview-teardown.yml #56)”To prevent resource sprawl and avoid hitting Cloudflare account limits, preview environments must be destroyed as soon as their pull request lifecycle concludes.
- Trigger Event:
pull_request: [closed](handles both merges and manual closes). - Cleanup Actions:
- Computes the worker names associated with the PR number:
Terminal window wrangler delete --name green-leaf-bakery-preview-pr-${PR_NUM} --force - The
--forceflag ensures the command succeeds even if the worker was never provisioned or already removed. - Updates the pinned PR comment to reflect decommissioned status:
<!-- siteswarm-preview-bot -->## 🐝 SiteSwarm Ephemeral PR Previews> 🧹 **Ephemeral Preview Decommissioned**> PR #${PR_NUM} has been merged/closed. All associated edge workers and preview routes were cleaned up at `2026-09-29T22:15:00Z`.
- Computes the worker names associated with the PR number:
5. Edge Ingress Policies, Performance & Security Layer
Section titled “5. Edge Ingress Policies, Performance & Security Layer”Every request traversing SiteSwarm’s edge ingress layer is governed by strict caching, transport, and security policies enforced at Cloudflare edge nodes before compute execution.
5.1 Cache Control & Dynamic Invalidation Matrix
Section titled “5.1 Cache Control & Dynamic Invalidation Matrix”SiteSwarm employs a multi-tiered edge cache strategy designed for sub-10ms TTFB on static assets while preserving sub-second propagation for CMS updates:
| Asset / Route Category | Cache-Control Header | Edge Cache TTL | Invalidation Mechanism |
|---|---|---|---|
Vite / Astro Bundled Assets (/_astro/*, /assets/*) |
public, max-age=31536000, immutable |
1 Year | Content-hash fingerprinting (instant on build) |
Static Images & Fonts (/fonts/*, /favicon.ico) |
public, max-age=604800, stale-while-revalidate=86400 |
7 Days | Stale-while-revalidate background refresh |
Public HTML & SSR Pages (/, /menu, /about) |
public, max-age=0, must-revalidate, s-maxage=60, stale-while-revalidate=300 |
60 Seconds | Edge Cache Purge via Cache-Tag on CMS publish |
CMS Studio & Admin Routes (/_emdash/*, /admin/*) |
private, no-cache, no-store, must-revalidate |
0 Seconds (Bypass) | Always bypassed; dynamic D1 session validation |
Dynamic Form APIs (/api/submit-inquiry) |
no-store, no-cache |
0 Seconds (Bypass) | Edge WAF & Turnstile evaluation |
Edge Cache Tagging (Cache-Tag):
Section titled “Edge Cache Tagging (Cache-Tag):”Astro SSR endpoints emit cache tags representing the tenant and application:
Cache-Tag: tenant-bakery, app-bakery-prodWhen an owner clicks “Publish Now” in EmDash CMS, an internal event triggers a Cloudflare API call to purge the associated cache tag instantly:
curl -X POST "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache" \ -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"tags": ["tenant-bakery"]}'5.2 Edge Compression Negotiation
Section titled “5.2 Edge Compression Negotiation”Cloudflare automatically inspects client Accept-Encoding request headers and negotiates next-generation compression algorithms:
- Zstandard (
zstd): Preferred for modern browsers supporting Zstandard compression (offering higher compression ratios and faster decompression times than gzip). - Brotli (
br): Enabled globally for all text, HTML, CSS, JavaScript, and JSON payloads. - Gzip (
gzip): Automatic fallback for legacy clients.
5.3 Modern Transport Protocols & TLS Configuration
Section titled “5.3 Modern Transport Protocols & TLS Configuration”All Custom Hostnames and preview environments enforce modern, zero-latency transport standards:
- HTTP/3 (QUIC): Enabled globally. Eliminates head-of-line blocking on flaky mobile cellular networks, ensuring instant page loads for restaurant customers on mobile data.
- 0-RTT Connection Resumption: Supported with TLS 1.3 for repeat visitors.
- Minimum TLS Version:
TLS 1.2(TLS 1.0 and 1.1 are permanently blocked at the edge). - Automatic HTTPS Rewrites: Automatically converts insecure
http://asset references to securehttps://.
5.4 Defense-in-Depth Security Headers
Section titled “5.4 Defense-in-Depth Security Headers”The SiteSwarm Ingress Router injects the following security headers on all responses:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=()Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https: blob:; connect-src 'self' https://challenges.cloudflare.com; frame-src https://challenges.cloudflare.com;Content Security Policy (CSP) Design Rationale:
Section titled “Content Security Policy (CSP) Design Rationale:”- Cloudflare Turnstile Support: Allows script, connect, and frame resources from
https://challenges.cloudflare.comfor spam-free form validation. - Google Fonts Support: Whitelists Google Fonts styles and fonts.
- Strict Frame Sandboxing: Blocks clickjacking by restricting frame embedding to
SAMEORIGIN.
5.5 Bot Mitigation & API Rate Limiting
Section titled “5.5 Bot Mitigation & API Rate Limiting”Local business websites are prime targets for automated spam bots submitting spam inquiries via contact and quote forms. SiteSwarm deploys three edge defense layers:
- Cloudflare Turnstile Integration:
- Frictionless, privacy-preserving CAPTCHA replacement.
- Embedded seamlessly on contact and quote submission forms (
/api/submit-inquiry). - Server-side token verification executed in Astro API endpoints against
https://challenges.cloudflare.com/turnstile/v0/siteverify.
- Edge Rate Limiting Rules (Cloudflare WAF):
- Configured on API endpoints:
/api/*limited to 10 requests per 10 seconds per IP. - Violating IPs receive
HTTP 429 Too Many Requestsat the edge before hitting Worker compute.
- Configured on API endpoints:
- Cloudflare Managed Ruleset & Bot Fight Mode:
- Automatically drops known bad bot signatures, malicious user agents, and credential-stuffing crawlers.
6. Operational Diagnostics, Edge Troubleshooting & Failure Modes
Section titled “6. Operational Diagnostics, Edge Troubleshooting & Failure Modes”When domain routing or preview deployments fail, engineers and AI agents consult the following diagnostics matrix:
| Failure Symptom | Probable Cause | Diagnostic Command / Action | Remediation Runbook |
|---|---|---|---|
ERR_SSL_VERSION_OR_CIPHER_MISMATCH on custom domain |
DCV validation pending or DNS CNAME not yet propagated. | curl -Iv https://www.client.comdig CNAME www.client.com |
Check Cloudflare Custom Hostname status via API. If pending DCV, verify CNAME target matches fallback.siteswarm.dev. Wait 15-30 mins for DNS propagation. |
Error 1000: DNS points to prohibited IP |
Client pointed domain to Cloudflare IP instead of CNAME, or Cloudflare-on-Cloudflare proxy loop. | Inspect client DNS records via dig. |
Ensure client registrar has CNAME pointing to fallback.siteswarm.dev. If client uses Cloudflare DNS, ensure record is set to DNS Only (Grey Cloud). |
Apex domain client.com not loading while www works |
Client registrar lacks CNAME flattening at apex root (@). |
Test root URL: curl -I https://client.com |
Configure registrar 301 URL redirect from @ to https://www.client.com, or switch to Namecheap/Cloudflare ALIAS record. |
Ephemeral preview returns HTTP 404 Not Found |
Preview deployment failed or worker name mismatch in PR bot. | Run wrangler tail <worker-name> or check GitHub Actions step logs. |
Verify change detection output: ensure app is listed in impacted_apps. Re-run preview-deploy.yml. |
| Edge Health Probe Timeout (HTTP 500) | D1 database binding missing or migration not applied in preview environment. | wrangler d1 info <db-name> |
Ensure preview environment configuration in wrangler.jsonc includes required D1/KV bindings or runs preview schema migrations. |
7. Architectural Governance & Verification Gates
Section titled “7. Architectural Governance & Verification Gates”Any pull request or architectural modification altering domain ingress, edge routing, or preview CI must pass the following verification gates:
- RFC-Compliant DNS Verification Gate: DNS onboarding runbooks must never instruct users to create an invalid apex CNAME without specifying ALIAS/ANAME flattening or 301 forwarding.
- Zero-Lockout & Non-Interference Gate: Changes to fallback origins or custom hostname API configurations must never impact existing active client domains.
- Resource Leak Gate: All ephemeral preview workflows (
preview-deploy.yml) must have a corresponding, verified teardown step inpreview-teardown.ymltriggered on PR close. - Zero-Secrets Safety Gate: CI workflows must cleanly handle unconfigured Cloudflare tokens without failing pull request builds for open-source contributors or local branches.