# DNS Architecture — complyform.dev

## Record Management Split

| Subdomain | Managed By | Record Type | Why |
|---|---|---|---|
| `api.complyform.dev` | Cloudflare (manual) | A → `34.102.218.22` | GCP Global HTTPS LB static IP |
| `dashboard.complyform.dev` | Cloudflare (manual) | A → `34.102.218.22` | Same LB, URL map routes by host |
| `telemetry.complyform.dev` | Terraform + Cloudflare | CNAME → `ghs.googlehosted.com` | Cloud Run domain mapping (Terraform) + CNAME record (Cloudflare) |
| `docs.complyform.dev` | Cloudflare (manual) | CNAME → `*.pages.dev` | Cloudflare Pages custom domain |
| `status.complyform.dev` | Cloudflare (manual) | CNAME → `complyform.github.io` | GitHub Pages (Upptime) |
| `complyform.dev` (apex) | Cloudflare Pages | — | Landing page, Cloudflare-managed |
| Email (SPF/DKIM/DMARC/MX) | Cloudflare (manual) | TXT/CNAME/MX | Postmark + Google Workspace |

**Terraform manages** only the Cloud Run domain mapping for telemetry (`google_cloud_run_domain_mapping.telemetry` in `dns.tf`). All DNS records live in Cloudflare (free tier dashboard).

## Why A Records for api and dashboard (not CNAME)

`api.complyform.dev` and `dashboard.complyform.dev` point to a GCP Global HTTPS Load Balancer with a static IP (`34.102.218.22`). A records are used because:

1. The LB has a reserved static IP that won't change.
2. A records avoid CNAME-chaining latency.
3. GCP Global LB doesn't expose a stable DNS name — only the IP is guaranteed.

`telemetry.complyform.dev` uses a CNAME because Cloud Run domain mappings provide a Google-hosted CNAME target (`ghs.googlehosted.com`), not a static IP.

## Why GCP-Backed Records Must Be "DNS Only" (Gray Cloud)

Cloudflare's proxy mode (orange cloud) terminates TLS at Cloudflare's edge and re-encrypts to origin. This breaks GCP's Google-managed certificate validation:

1. Google-managed certs require the client to connect directly to the GCP LB so Google can complete the ACME challenge and serve the cert.
2. With Cloudflare proxying, the client sees Cloudflare's cert, and Cloudflare connects to GCP — but GCP's cert validation sees Cloudflare's IP, not the client's, which can cause domain verification failures.
3. Double-proxying adds unnecessary latency and complexity.

**Rule:** Any record pointing to GCP infrastructure (LB IP or Cloud Run CNAME) must be **DNS only** (gray cloud) in Cloudflare.

Cloudflare Pages records use **Proxied** (orange cloud) because Cloudflare manages TLS end-to-end for Pages.

## Google-Managed SSL Certificate Flow

1. **I-5** created `google_compute_managed_ssl_certificate.api` covering `api.complyform.dev` and `dashboard.complyform.dev`. Initial status: `PROVISIONING`.
2. **I-6** creates Cloudflare A records pointing both domains to `34.102.218.22`.
3. Google periodically checks DNS for domain ownership. Once the A records resolve to the LB IP, Google issues the certificate.
4. Status transitions: `PROVISIONING` → `ACTIVE` (typically 15–60 minutes after DNS propagation).
5. The telemetry subdomain gets its own Google-managed cert automatically via the Cloud Run domain mapping — separate from the LB cert.

## Verification

Run `scripts/setup-dns.sh` to verify all records. Key manual checks:

```bash
# DNS resolution
dig +short A api.complyform.dev @1.1.1.1          # → 34.102.218.22
dig +short A dashboard.complyform.dev @1.1.1.1     # → 34.102.218.22
dig +short CNAME telemetry.complyform.dev @1.1.1.1  # → ghs.googlehosted.com.

# SSL certificate status
gcloud compute ssl-certificates describe complyform-api-cert \
    --global --project=complyform-prod \
    --format="value(managed.status)"   # → ACTIVE

# HTTPS endpoints
curl -s -o /dev/null -w "%{http_code}" https://api.complyform.dev/health  # → 200
```

## Canonical Reference

The authoritative DNS record table is in **ops-runbook §10.3**. Update that section whenever DNS records change.
