> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel ↔ Supabase Environment Alignment

> Version: 1.0.0 Last Updated: 2026-04-24 Owner: Platform / Infra

**Version:** 1.0.0
**Last Updated:** 2026-04-24
**Owner:** Platform / Infra

This runbook locks in the canonical mapping of GitHub branches → Vercel projects → Supabase projects so that:

* Day-to-day PRs only touch the Dev environment.
* Promotions to live production are an explicit `prod → production` release PR.
* Supabase Branching only spins up preview DBs in **one** Supabase project per PR (no more duplicated `Supabase Preview` failures from two competing projects, as seen on PR #123/#124).

When this document conflicts with `docs/development/supabase/SUPABASE_MULTI_ENV_SETUP.md` or any in-dashboard setting screenshot, **this document wins**. Update both together.

***

## 1. Canonical mapping

| Tier           | GitHub branch    | Vercel project  | Vercel project ID                  | Supabase project | Supabase project ref   |
| -------------- | ---------------- | --------------- | ---------------------------------- | ---------------- | ---------------------- |
| **Dev**        | `prod` (default) | `encoreos_dev`  | `prj_iMHAljBMLDQXONzL9g7XUHH6CzrP` | `Encore OS`      | `zkgxozahyczcnzpwhbbf` |
| **Production** | `production`     | `encoreos_prod` | `prj_NZTWEPkUXJ3jM51uh91Ty8Np9K7c` | `encore_os_prod` | `srcaoozjkrughebmbvfb` |

> **Naming gotcha.** The GitHub default branch is named `prod`, but it is the **dev tier**. The branch literally named `production` is the live production tier. This mismatch is intentional (preserves history) but easy to misread. Do not rename.

Frontend pairing is enforced at build time in [`vite.config.ts`](../../vite.config.ts) via the platform-managed `VERCEL_PROJECT_ID` system variable. Do **not** add user-defined Vercel env vars for `VITE_SUPABASE_URL` / `VITE_SUPABASE_PUBLISHABLE_KEY` (CI guards this via `npm run check:no-vercel-env-config`).

CLI/migrations pairing is captured in [`supabase/config.toml`](../../supabase/config.toml):

```toml theme={null}
[remotes.dev]        # paired with GitHub `prod`        and Vercel encoreos_dev
project_id = "zkgxozahyczcnzpwhbbf"

[remotes.production] # paired with GitHub `production`  and Vercel encoreos_prod
project_id = "srcaoozjkrughebmbvfb"
```

***

## 2. PR / deployment lifecycle

### 2.1 Feature work (default)

1. Branch off `prod`: `git checkout -b cursor/<task>-####`.
2. Open a PR with **base = `prod`**.
3. On open, the following side effects occur:
   * **Vercel `encoreos_dev`** builds a Preview Deployment, statically pinned to Supabase `zkgxozahyczcnzpwhbbf`.
   * **Supabase `Encore OS` (`zkgxozahyczcnzpwhbbf`)** Branching creates a per-PR preview DB and replays migrations. The `Supabase Preview` GitHub check reports its status.
   * **Vercel `encoreos_prod`** and **Supabase `encore_os_prod`** are **not** touched (see §3 for how this is enforced).
4. Merge to `prod` ⇒ Vercel `encoreos_dev` deploys to its production URL (the dev environment), and Supabase `Encore OS` is updated by a successful Branching deploy run.

### 2.2 Promotion to live production

1. Open a release PR with **head = `prod`** and **base = `production`**.
2. On open, only the production tier is touched:
   * Vercel `encoreos_prod` builds a Preview Deployment.
   * Supabase `encore_os_prod` (`srcaoozjkrughebmbvfb`) Branching creates a per-PR preview DB.
3. Merge ⇒ Vercel `encoreos_prod` deploys to the live production URL; Supabase `encore_os_prod` Branching applies migrations to the live production DB.

> Production is a one-way door. Do not direct-commit to `production`.

### 2.3 Promotion order: Branching preview, GitHub merge, and `db push`

**Canonical intent:** The **Supabase Preview** check on a `prod → production` PR replays `supabase/migrations` against the **production-tier preview** database (`srcaoozjkrughebmbvfb`). Treat that preview as the primary validation signal for “what will run in prod.”

**Recommended sequence:**

1. Open the release PR (`prod` → `production`) and wait until **Supabase Preview** (and your usual Vercel preview checks) are **green**.
2. **GitHub branch protection:** mark the Supabase GitHub check as a **required status** on `production`, so merges are blocked when preview fails.
3. Merge the PR on GitHub. With **Deploy to production** enabled in the Supabase GitHub integration (see §3.2), Branching applies the same migration chain to the live production database.
4. **Deploy Supabase (production)** workflow ([`.github/workflows/supabase-deploy-prod.yml`](../../.github/workflows/supabase-deploy-prod.yml)) still runs `supabase db push --yes` on push to `production` as a **reconcile** with the repo. Keep migration files identical to what Branching replays so both paths stay aligned.
5. **Break-glass:** if migrations were applied **only** via the Supabase Dashboard (or another one-off path) and `db push` would be redundant or risky, run the same workflow manually with **`skip_db_push`** enabled so Edge Functions and other steps can still run without a second push.

**Single writer rule:** Do not routinely apply different SQL manually and then `db push` different content. One owner per promotion: repo migrations + Branching replay + optional CI `db push`, or documented break-glass only.

***

## 3. Supabase dashboard configuration (operator)

Both Supabase projects use the same GitHub repository (`Encore-OS/encoreos`) but must each declare a **different** Production Branch and have appropriate Branching scope so previews don't collide.

For each project below, open Supabase Dashboard → Project Settings → Integrations → GitHub Integration → **Manage**.

### 3.1 `Encore OS` (`zkgxozahyczcnzpwhbbf`) — Dev tier

| Field                 | Value                                                                 |
| --------------------- | --------------------------------------------------------------------- |
| Repository            | `Encore-OS/encoreos`                                                  |
| Supabase directory    | `supabase`                                                            |
| **Production branch** | **`prod`**                                                            |
| Automatic branching   | **ON**                                                                |
| Supabase changes only | ON (recommended — only create preview DBs when `supabase/**` changes) |
| Deploy to production  | ON (so merges to `prod` deploy migrations to the dev DB)              |

Effect: every PR opened against `prod` gets exactly **one** preview DB inside this project. Today the Production Branch on this project is incorrectly `prod` already (verified via MCP `list_branches`: default branch row has `git_branch: "prod"`), so the only required change here is to confirm settings match the table.

### 3.2 `encore_os_prod` (`srcaoozjkrughebmbvfb`) — Production tier

| Field                 | Value                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------- |
| Repository            | `Encore-OS/encoreos`                                                                      |
| Supabase directory    | `supabase`                                                                                |
| **Production branch** | **`production`**                                                                          |
| Automatic branching   | **OFF** *(recommended)* — only create a preview DB when a release PR targets `production` |
| Supabase changes only | ON                                                                                        |
| Deploy to production  | ON                                                                                        |

Effect: regular feature PRs targeting `prod` will **not** create preview DBs in this project. Only the explicit `prod → production` release PR creates a preview DB here. This eliminates the duplicate `Supabase Preview` failure pattern observed on PR #123/#124, where a single feature PR triggered identical migration replay in both projects.

> If "Automatic branching = OFF" turns out to be too restrictive (e.g. you want a smoke deploy on every release-candidate PR), set it to ON + "Supabase changes only = ON" so only PRs that touch `supabase/**` and target `production` consume a production-tier preview DB.

### 3.3 Verification

After applying §3.1 and §3.2:

```bash theme={null}
# Should list ONE non-default branch row per active PR base (excluding the
# default branch row itself), keyed to the GitHub branch the PR targets.
gh api /repos/Encore-OS/encoreos/pulls -q '.[] | {n: .number, base: .base.ref, head: .head.ref}'
```

Then in MCP / CLI:

* `Encore OS` (`zkgxozahyczcnzpwhbbf`) `list_branches` should show `default_branch_row.git_branch = "prod"` and one row per PR targeting `prod`.
* `encore_os_prod` (`srcaoozjkrughebmbvfb`) `list_branches` should show `default_branch_row.git_branch = "production"` and one row only when there is an open `prod → production` release PR.

***

## 4. Vercel dashboard configuration (operator)

For each Vercel project, open Vercel → Project → Settings → Git.

### 4.1 `encoreos_dev` (`prj_iMHAljBMLDQXONzL9g7XUHH6CzrP`)

| Field                 | Value                                                                                                                    |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Connected repo        | `Encore-OS/encoreos`                                                                                                     |
| **Production Branch** | **`prod`**                                                                                                               |
| Ignored Build Step    | inherits `vercel.json` (`ignoreCommand` lives in repo)                                                                   |
| Preview deployments   | All branches **except** `production` (set Settings → Git → "Branch Tracking" or use `vercel.json`-side filter; see §4.3) |

### 4.2 `encoreos_prod` (`prj_NZTWEPkUXJ3jM51uh91Ty8Np9K7c`)

| Field                 | Value                                    |
| --------------------- | ---------------------------------------- |
| Connected repo        | `Encore-OS/encoreos`                     |
| **Production Branch** | **`production`**                         |
| Preview deployments   | Only `prod` (release-candidate previews) |

### 4.3 Optional: prevent both Vercel projects from previewing the same PR

By default Vercel will deploy a Preview for every PR on a connected repo, regardless of which branch the PR targets. To restrict each project so a single PR only produces a preview in the matching project:

* In **`encoreos_dev`** → Settings → Git → "Ignored Build Step", keep the in-repo `vercel.json` `ignoreCommand` (already set). It will short-circuit identical builds.
* In **`encoreos_prod`** → Settings → Git → "Branch Tracking", set "Only deploy preview branches matching pattern" to `prod`. That stops it from building previews for arbitrary `cursor/*` branches.

Both Vercel projects share the same repo, but only the project whose Production Branch matches the PR's base branch should build it. Branch Tracking patterns are the cleanest way to enforce that.

***

## 5. Repo-side guardrails

These already exist; the alignment relies on them staying in place.

| Guardrail                                                                                                  | Where                                                                                       | What it enforces                                                                                                                              |
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Static `VITE_SUPABASE_*` defines keyed by `VERCEL_PROJECT_ID`                                              | [`vite.config.ts`](../../vite.config.ts) `SUPABASE_PROJECTS` + `VERCEL_PROJECT_TO_SUPABASE` | Vercel project → Supabase pairing cannot drift from this file                                                                                 |
| `[remotes.dev]` / `[remotes.production]`                                                                   | [`supabase/config.toml`](../../supabase/config.toml)                                        | CLI `supabase link --project-ref` targets are spelled out                                                                                     |
| `npm run check:no-vercel-env-config`                                                                       | `scripts/ci/*` (run by `validate:governance`)                                               | Re-introducing user-defined Vercel env vars for Supabase URL/key fails CI                                                                     |
| `audit:integration-contracts`, `audit:routes-navigation`, `audit:permissions`, `audit:breadcrumb-coverage` | `npm run validate:governance`                                                               | Unrelated, but these all key off `prod` as the canonical baseline branch                                                                      |
| Migration baseline                                                                                         | `supabase/migrations/20260211182655_*.sql` (2026-04 reconcile baseline)                     | Single source of truth for both Supabase projects' schema; subsequent migrations must remain idempotent (see `SUPABASE_RECONCILE_2026-04.md`) |

***

## 6. One-time cleanup actions

After §3 and §4 are applied, perform these once:

1. **Close stale duplicate-tier PRs.** PR #123 (`Gr-ux → production`) and PR #124 (`Gr-ux → prod`) both exist for historical reasons. Once branching is realigned, decide which target is correct:
   * If the change is dev-bound, keep PR #124 (`→ prod`) and close PR #123.
   * If it is also intended for live production, do **not** target `production` directly from a feature branch; merge to `prod` first, then open a release PR `prod → production`.
2. **Re-run the Supabase Preview check** on the surviving PR after §3 is applied. It should now run against only **one** Supabase project.
3. **Optional but recommended:** delete or repurpose the legacy GitHub branch named `production` if the team decides the dev/prod separation should be `prod → main` or `develop → prod` instead. Keep this document in sync if you do.

***

## 7. Failure-mode reference

| Symptom                                                                | Likely cause                                                                                             | Fix                                                                                                                     |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Same `Supabase Preview` check failure on two PRs sharing a head SHA    | Both Supabase projects have Branching ON for the same Production Branch (or for branches that overlap)   | Apply §3.2 — set `encore_os_prod` Production Branch to `production` and Automatic Branching = OFF                       |
| Vercel preview deploys to the wrong Supabase                           | `vercel.json` introduced `env` block, or someone re-added `VITE_SUPABASE_URL` to Vercel project env vars | Remove; CI's `check:no-vercel-env-config` catches this. Pairing is enforced via `VERCEL_PROJECT_ID` in `vite.config.ts` |
| Migration replay fails in preview with `42P07 relation already exists` | Non-idempotent `CREATE TABLE` after the 2026-04 baseline                                                 | Add `IF NOT EXISTS`; precedent: PR #125 fix to `pf_oauth_state`                                                         |
| Two Vercel previews per PR                                             | Both Vercel projects accept all branches                                                                 | Apply §4.3 Branch Tracking pattern to `encoreos_prod`                                                                   |

***

## 8. References

* [`supabase/config.toml`](../../supabase/config.toml)
* [`vite.config.ts`](../../vite.config.ts) (`SUPABASE_PROJECTS`, `VERCEL_PROJECT_TO_SUPABASE`)
* [`docs/development/supabase/SUPABASE_MULTI_ENV_SETUP.md`](./supabase/SUPABASE_MULTI_ENV_SETUP.md)
* `docs/development/supabase/SUPABASE_RECONCILE_2026-04.md`
* `docs/development/VERCEL_ENV_REMOVAL_EXECUTION_2026-04-20.md`
* [Supabase Branching — GitHub Integration](https://supabase.com/docs/guides/deployment/branching/github-integration)
* [Supabase Branching — Working with branches](https://supabase.com/docs/guides/deployment/branching/working-with-branches)
* [Vercel for GitHub](https://vercel.com/docs/deployments/git/vercel-for-github)
