HQ as Cloudflare Replacement — Logic Audit (locked 2026-08-31)

**Operator directive**: "this HQ is meant to be a replacement for that [Cloudflare], so however Cloudflare works would be good. think about that"

**The framing**: HQ is single-tenant, operator-focused, Mavis-powered. Not multi-tenant Cloudflare. The goal: every Cloudflare-shaped power, none of the multi-tenant complexity. G + Mavis threads are the only "users."

Inventory of Cloudflare-shaped things HQ already has

| Cloudflare thing | HQ equivalent | Status |

|---|---|---|

| Pages (git push → URL) | `/api/mavis/deploy` (POST returns 202 + job_id) | ✅ works, but thread must call it |

| Wildcard certs for `*.freshvibeapps.com` | Caddy on-demand (a1806) | ✅ done |

| Custom domain support | Allowlist of 35 subdomains (hardcoded in `panel/src/mavis-hq.js`) | 🟡 partial — needs DB-driven allowlist |

| R2 (object storage) | Artifacts (`/api/artifacts`, descriptions enforced) | ✅ done, but artefact-shaped not general |

| Tunnels (`cloudflared`) | None | ❌ gap — Mavis sandboxes can't expose themselves |

| Access (auth on URLs) | PocketBase auth shim on each subdomain | 🟡 partial — per-app, not platform-level |

| DNS UI | None | ❌ gap — adding a subdomain needs SSH/bulletin |

| Single dashboard | Operator panel at `panel.freshvibeapps.com` | ✅ done, but operator-only not Mavis-facing |

| Pages preview URLs per PR | Preview subdomains (`<name>.freshvibeapps.com`) | 🟡 partial — no per-PR automation |

| Workers (serverless) | `/api/mavis/*` endpoints | ✅ done |

| Workers KV / D1 | None | ❌ gap |

| Queue | None | ❌ gap (deploy jobs run in-process, no retry queue) |

| Stream (video) | App.html storage | 🟡 partial — files only, no streaming |

| Images (optimization) | None | ❌ gap |

| Email routing | None | ❌ gap |

| WAF / rate limiting | Vault rate limits, general apiLimiter | 🟡 partial |

| Analytics | Mavis HQ timeline | ✅ done, but not visitor analytics |

| Logs | `/var/log/*.log` on VPS | 🟡 partial — not queryable from HQ |

The 3-second deploy flow (the Cloudflare Pages killer feature)

**Current state**: a Mavis thread calls `POST /api/mavis/deploy` with `repo_url + ref + dest + operator_command`. Panel clones, builds, rsyncs. ~30s-3min depending on build. Returns job_id, bulletin posted when done.

**The gap**: thread must explicitly call the endpoint. Cloudflare auto-deploys on push.

**Phase 1 plan** (the killer feature):

1. **Mavis-pushable deploy key** — every Mavis thread gets a deploy key at registration time. `https://<key>@api.panel.freshvibeapps.com/deploy/auto?dest=<name>&ref=<branch>`

2. **GitHub webhook receiver** — `POST /api/mavis/auto-deploy` accepts GitHub push webhooks. If the repo's deploy key is configured, panel clones, builds, deploys automatically.

3. **Per-repo config** — `.hq-deploy.yml` in the repo declares: `dest`, `build_cmd`, `dist_subdir`, `branch`. No API call needed.

4. **Result**: `git push` → URL live in 30s. Zero Mavis involvement.

The new-Mavis flow (design → preview → deploy)

**Current state**: a new Mavis thread has to discover HQ via `/api/mavis/me` and figure out which endpoints to use. No recipe.

**Phase 2 plan** (the template):

1. **`HQ_DESIGN_TEMPLATE.md`** in HQ — first thing a Mavis reads. Tells it:

- "If asked to design X, here's the 4-step recipe"

- Step 1: post 1 bulletin (status: started)

- Step 2: upload 1-N artifacts (mockups, with descriptions)

- Step 3: ask G for review

- Step 4: on deploy request, call `/api/mavis/deploy` with repo + dest

2. **Design-mode flag** — `/api/mavis/me` returns `capabilities.design_template` pointing to the URL

3. **The "design X" affordance** — a Mavis that's been told "design X" can pull this template and follow it

Dynamic subdomains + random preview names

**Current state**: 35 hardcoded subdomains in the allowlist. New subdomains need DB edit.

**Phase 3 plan** (Cloudflare Pages preview URLs per PR):

1. **DB-driven allowlist** — replace hardcoded array with `SELECT name FROM preview_clients` where the directory exists

2. **Random subdomain generator** — `POST /api/mavis/preview/spawn {prefix}` returns `https://<prefix>-<6-char-random>.freshvibeapps.com`. Caddy serves from `/var/www/freshvibeapps/preview-clients/<name>/`.

3. **Auto-cleanup** — previews expire after 7 days (longer than the 15d artifact TTL, shorter than the 30d default for permanent sites)

4. **Use case**: "show me a variant real quick" → spawn `test-vibe-2k3f9x.freshvibeapps.com` → drop files → URL live in 5 seconds

Object storage as first-class

**Current state**: artifacts are tied to "Mavis thread uploaded this." No general storage.

**Phase 4 plan** (R2-style):

1. **`/api/storage`** — POST a file, get back an ID + URL. No thread binding. No description required (this is storage, not artifact).

2. **TTL + pin** — like app.html, 15d default, `pinned=1` for permanent

3. **Path structure** — `storage.freshvibeapps.com/<id>` or `*.freshvibeapps.com/storage/<id>` via the wildcard cert

4. **Use case**: Mavis wants to drop a binary (PDF, image, video) anywhere without it being "an artifact of a Mavis session"

Mavis-facing dashboard (the "single UI for my stuff")

**Current state**: operator panel at `panel.freshvibeapps.com/mavis`. Operator-only. Mavis sessions use API only.

**Phase 5 plan** (the dashboard):

1. **`/mavis/my`** — when a Mavis thread loads the URL, it sees its own: threads, deployments, artifacts, previews, storage items

2. **Token in URL** — `https://panel.freshvibeapps.com/mavis/my?token=<thread-token>` (no separate login for Mavis)

3. **What it shows**: every URL I've ever deployed, every preview I spawned, every artifact I uploaded, with "delete" / "redeploy" / "open" buttons

4. **The Cloudflare Pages dashboard feel** — single page, all my stuff, all action buttons

The whole new-thread experience (end-to-end)

Imagine a fresh Mavis session. First turn:

```

G: "design a website for my cousin's bakery, then deploy it to the VPS"

Mavis (registers with HQ → gets token)

→ reads HQ_DESIGN_TEMPLATE.md

→ posts b1 "started: bakery website design"

→ uploads artifact a1 "homepage mockup" (with description)

→ uploads artifact a2 "menu page mockup"

→ posts b2 "design done, ready for review"

→ waits

G: "looks good, deploy it"

Mavis

→ POST /api/mavis/deploy {dest: "bakery", repo_url: "https://github.com/.../bakery", operator_command: "deploy bakery site"}

→ gets job_id, polls

→ posts b3 "deploying bakery..."

→ 60s later, deploy bulletin auto-posts

→ posts b4 "bakery.freshvibeapps.com is LIVE"

```

**Compare to current state**: a Mavis today has to discover the deploy endpoint, figure out the allowlist, post bulletins correctly with `level: "info"`, write artifact descriptions, etc. The template + auto-deploy-on-push + DB-driven allowlist collapse this from 30+ minutes of "how does HQ work" to ~5 minutes of "I read the template and did the steps."

What ships in each phase (rough effort)

| Phase | What | Effort | Operator-visible win |

|---|---|---|---|

| 1 | Auto-deploy on push (GitHub webhook + per-repo config) | 2-3 days | `git push` → URL live. The Cloudflare Pages moment. |

| 2 | New-Mavis template (design → preview → deploy) | 1 day | Every new Mavis thread knows what to do without asking |

| 3 | Dynamic random subdomains + DB-driven allowlist | 1-2 days | `abc1dxyz.freshvibeapps.com` works, no chown, no DB edit |

| 4 | Object storage (R2-style) | 2-3 days | Drop a file anywhere, get a URL |

| 5 | Mavis-facing dashboard | 2-3 days | Mavis sees all its stuff in one UI |

**Total**: ~10-12 days of work. Not all needed at once.

**Phase 1 is the most important** — auto-deploy on push is the single feature that makes HQ feel like Cloudflare. Without it, every deploy is a manual API call. With it, Mavis can just `git push` and walk away.

Cross-cutting concerns

Open questions for G (operator)

1. **Phase ordering** — go straight to Phase 1, or do Phase 2 (template) first because the template tells Mavis sessions to use the new auto-deploy?

2. **GitHub integration** — G's GitHub is `avidtech6`. Webhook receiver needs G to set the webhook URL in each repo, or do it org-wide? Or HQ auto-registers when a Mavis calls `POST /api/mavis/repos {repo_url, auto_deploy: true}` and the panel figures it out?

3. **Per-PR previews** — do you want `git push origin feature-thing` to create `feature-thing.<repo>.freshvibeapps.com` automatically? Or is that overkill?

4. **Random preview expiry** — 7 days right? Or longer?

5. **The 35-name allowlist** — can I rip it out and replace with DB-driven, or do you want the explicit list to stay as a "permanent sites" filter?