From 3a81883dcde758903479d43f4085a2c4bda00906 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:09:14 +0000 Subject: [PATCH 1/3] Docs: Vercel Deployment Protection is the pre-launch gate; maintenance mode stays operational Co-Authored-By: Chad --- AI_INSTRUCTIONS.md | 5 ++++- README.md | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/AI_INSTRUCTIONS.md b/AI_INSTRUCTIONS.md index 395ecaa..f59eb41 100644 --- a/AI_INSTRUCTIONS.md +++ b/AI_INSTRUCTIONS.md @@ -33,7 +33,10 @@ when the token is unset. workflow, #168 — owner contact is staff-only via authorized server actions), `reports` (#179 — aggregate metrics workspace + audited CSV export; public counterpart is `/statistics`, a separate suppressed DTO) -- **Production gate**: `SITE_MAINTENANCE_MODE=true` (Vercel Production +- **Pre-launch access**: Vercel Deployment Protection (Vercel + Authentication, All Deployments) — deployment configuration, not code. +- **Maintenance mode** (operational, not the launch gate): + `SITE_MAINTENANCE_MODE=true` (Vercel Production only) redirects all public paths to `/under-construction`; `/login`, `/admin`, `/api/auth`, and static assets stay reachable diff --git a/README.md b/README.md index 2857b59..98c20b2 100644 --- a/README.md +++ b/README.md @@ -231,9 +231,32 @@ boundary and §18 for the Storage procedure. 3. Add all environment variables from `.env.example` in the Vercel dashboard 4. Deploy +### Pre-launch access (Vercel Deployment Protection) + +Pre-launch access to the whole site is controlled by Vercel, not by the +application: Vercel → Settings → Deployment Protection → Vercel +Authentication → **All Deployments** restricts production domains, generated +`*.vercel.app` URLs and previews to Vercel team members. At launch, switch to +**Standard Protection** (previews stay protected). Standard Protection alone +does not protect the production domain. Check for Protection Exceptions, +Shareable Links and unused Protection Bypass for Automation secrets. + +While All Deployments protection is on, unauthenticated machine callers are +blocked before reaching the app — notably the Resend webhook +(`/api/webhooks/resend`) and owner `/portal` links in reminder emails. Confirm +Vercel Cron reaches `/api/cron/*` in the cron logs. If a machine caller must +get through, use a Protection Bypass for Automation secret scoped to that URL, +never a Protection Exception; the routes keep their own authentication +(`RESEND_WEBHOOK_SECRET`, `CRON_SECRET`). + +Application authorization (`ADMIN`/`EDITOR`/`MODERATOR`) is independent of +Vercel settings. + ### Maintenance mode -The production public site can be gated behind `/under-construction` while +Maintenance mode is an operational control (risky work, freezing public +writes), not the pre-launch gate. The production public site can be gated +behind `/under-construction` while `/login`, `/admin`, the auth session API, and framework/static assets stay reachable. From 5878d0a2507693d15df5f7f06cf59bce4896ee1a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:31:56 +0000 Subject: [PATCH 2/3] Docs: Protection Bypass for Automation secrets are project-wide Co-Authored-By: Chad --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 98c20b2..035aa04 100644 --- a/README.md +++ b/README.md @@ -245,8 +245,10 @@ While All Deployments protection is on, unauthenticated machine callers are blocked before reaching the app — notably the Resend webhook (`/api/webhooks/resend`) and owner `/portal` links in reminder emails. Confirm Vercel Cron reaches `/api/cron/*` in the cron logs. If a machine caller must -get through, use a Protection Bypass for Automation secret scoped to that URL, -never a Protection Exception; the routes keep their own authentication +get through, give only that caller's URL a Protection Bypass for Automation +secret. The secret is project-wide (it bypasses protection on every +deployment), so treat it as a credential and rotate it if exposed. Never use a +Protection Exception; the routes keep their own authentication (`RESEND_WEBHOOK_SECRET`, `CRON_SECRET`). Application authorization (`ADMIN`/`EDITOR`/`MODERATOR`) is independent of From 653b0d704af301fbf4a003d29edad261af761a0f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:32:32 +0000 Subject: [PATCH 3/3] =?UTF-8?q?Docs:=20address=20review=20=E2=80=94=20depl?= =?UTF-8?q?oy=20order,=20admin/editor=20roles,=20maintenance=20exemptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Chad --- AI_INSTRUCTIONS.md | 6 ++++-- README.md | 24 ++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/AI_INSTRUCTIONS.md b/AI_INSTRUCTIONS.md index f59eb41..3d14b85 100644 --- a/AI_INSTRUCTIONS.md +++ b/AI_INSTRUCTIONS.md @@ -37,8 +37,10 @@ when the token is unset. Authentication, All Deployments) — deployment configuration, not code. - **Maintenance mode** (operational, not the launch gate): `SITE_MAINTENANCE_MODE=true` (Vercel Production - only) redirects all public paths to `/under-construction`; `/login`, - `/admin`, `/api/auth`, and static assets stay reachable + only) redirects public paths to `/under-construction` unless the request + has a verified admin session; `/login`, `/admin`, `/portal`, `/api/auth`, + `/api/cron`, `/api/webhooks`, and static assets stay reachable, so portal, + cron, and webhook writes continue ## Architectural invariants — do not casually violate diff --git a/README.md b/README.md index 035aa04..6cf4492 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,9 @@ boundary and §18 for the Storage procedure. 1. Push code to GitHub 2. Import the project in [Vercel](https://vercel.com) 3. Add all environment variables from `.env.example` in the Vercel dashboard -4. Deploy +4. Before the first deploy, enable Vercel Authentication → **All Deployments** + (see Pre-launch access below) so production is never briefly public +5. Deploy ### Pre-launch access (Vercel Deployment Protection) @@ -251,16 +253,17 @@ deployment), so treat it as a credential and rotate it if exposed. Never use a Protection Exception; the routes keep their own authentication (`RESEND_WEBHOOK_SECRET`, `CRON_SECRET`). -Application authorization (`ADMIN`/`EDITOR`/`MODERATOR`) is independent of -Vercel settings. +Application authorization (`admin`/`editor` roles in `admin_users`, owner +`/portal` sessions) is independent of Vercel settings. ### Maintenance mode -Maintenance mode is an operational control (risky work, freezing public -writes), not the pre-launch gate. The production public site can be gated -behind `/under-construction` while -`/login`, `/admin`, the auth session API, and framework/static assets stay -reachable. +Maintenance mode is an operational control for risky work, not the pre-launch +gate. The production public site can be gated behind `/under-construction` +while `/login`, `/admin`, `/portal`, `/api/auth/*`, `/api/cron/*`, +`/api/webhooks/*`, and framework/static assets stay reachable; verified admins +also bypass the redirect. It does **not** stop owner `/portal` actions, cron +jobs, or webhook writebacks — pausing those needs a separate action. - **Enable (production only):** set `SITE_MAINTENANCE_MODE=true` in the Vercel **Production** environment and redeploy. @@ -491,8 +494,9 @@ placeholder-only variable lists. Categories: `NEXT_PUBLIC_FIREBASE_*`, `NEXT_PUBLIC_GTM_ID`, `NEXT_PUBLIC_SITE_URL` - **Server-only secrets** (never commit): `FIREBASE_ADMIN_*`, `ADMIN_EMAILS`, `VERCEL_TOKEN`, `VERCEL_PROJECT_ID` -- **Behavior flags** (server-only): `SITE_MAINTENANCE_MODE` (production - gate, see Deployment); `NEXT_PUBLIC_USE_FIREBASE_EMULATOR` is set only +- **Behavior flags** (server-only): `SITE_MAINTENANCE_MODE` (operational + maintenance mode, see Deployment — pre-launch access is Vercel Deployment + Protection); `NEXT_PUBLIC_USE_FIREBASE_EMULATOR` is set only by the E2E harness — never set it for real deployments Analytics runs through Google Tag Manager (`NEXT_PUBLIC_GTM_ID`), which