# Environment Variables

> Audience: vendor implementation team and operator pre-handover reviewers.
> Goal: enumerate every environment variable read by the application, classify visibility, and document the Azure replacement.
> Source of truth for examples: [`.env.example`](../../.env.example) (placeholder values only — do not commit real secrets).

---

## Visibility classes

| Class                  | Where it is readable                                                              |
| ---------------------- | --------------------------------------------------------------------------------- |
| **Client + Server**    | Variable starts with `NEXT_PUBLIC_`. Bundled into the browser. Treat as public.   |
| **Server only**        | Read in route handlers, server components, middleware, scripts. Never exposed.    |
| **Build only**         | Read at `next build` time only. Not present at runtime.                            |
| **Owner / Tooling**    | Used only by the owner portal or platform tooling, not the tenant runtime.        |

---

## Reference table

| Variable                                | Class            | Purpose                                                                                                          | Local | Dev | Staging | Prod | Azure replacement                                                                                                                              |
| --------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | :---: | :-: | :-----: | :--: | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_APP_ENV`                   | Client + Server  | Distinguishes `development` / `staging` / `production` for runtime branching.                                    |   ✅   |  ✅  |    ✅    |   ✅  | Same name; sourced from Container Apps env settings or per-environment Key Vault.                                                              |
| `APP_PROFILE`                           | Server only      | Selects the active composition profile (`config/profiles/`, ADR 013) — controls branding + feature flags (e.g. `workModeCapture`, `consultantLoadReport`) via `loadProfile()`. Unknown values fail boot; **unset silently falls back to `default`** (all flags on). The BMW production deployment must set this to `bmw` explicitly — a missing value does not error, it just re-enables flags BMW opted out of. |   ◯   |  ◯  |    ◯    |   ◯  | Same name; sourced from Container Apps env settings. Profile selection itself is infra-agnostic. |
| `NEXT_PUBLIC_SUPABASE_URL`              | Client + Server  | Supabase project URL used by client + server SDKs.                                                                |   ✅   |  ✅  |    ✅    |   ✅  | Replaced by Azure-side public API origin (or removed once Supabase is decommissioned).                                                          |
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`  | Client + Server  | Supabase anon / publishable key. Safe for the browser.                                                            |   ✅   |  ✅  |    ✅    |   ✅  | Replaced by Entra ID client config (no anon key — auth is via OIDC).                                                                            |
| `SUPABASE_SERVICE_ROLE_KEY`             | Server only      | Bypasses RLS. Read **only** through `lib/supabase/privileged-env.ts` (`npm run check:secrets` enforces).         |   ✅   |  ✅  |    ✅    |   ✅  | Replaced by a privileged DB role + `SET LOCAL app.user_id`. The plaintext key disappears from the platform. See `service-role-intent-register.md`. |
| `NEXT_TELEMETRY_DISABLED`               | Build only       | Set to `1` to silence Next.js telemetry.                                                                          |   ◯   |  ◯  |    ◯    |   ◯  | Same name; set in the build pipeline.                                                                                                          |
| `CONTROL_PLANE_SUPABASE_URL`            | Server only      | URL of the control-plane Supabase project. Falls back to `NEXT_PUBLIC_SUPABASE_URL` in single-instance setups.    |   ◯   |  ◯  |    ✅    |   ✅  | Azure-side control-plane DB connection string (or service abstraction) sourced from Key Vault.                                                  |
| `CONTROL_PLANE_READONLY_KEY`            | Server only      | SELECT-only key for the control plane. Production must **not** fall back to the service-role key.                 |   ◯   |  ◯  |    ✅    |   ✅  | Replaced by an Azure-side read-only DB role with managed identity.                                                                              |
| `SUPABASE_MANAGEMENT_API_KEY`           | Owner / Tooling  | Used by the owner portal to provision new tenants. Never required on the tenant runtime.                          |   ◯   |  ◯  |    ✅    |   ✅  | Replaced by Azure Resource Manager / Bicep / Terraform credentials held by the operator's tooling.                                              |
| `SUPABASE_ORGANIZATION_ID`              | Owner / Tooling  | Identifies the Supabase organisation when calling the management API.                                              |   ◯   |  ◯  |    ✅    |   ✅  | Removed in Azure (no Supabase organisation concept).                                                                                            |
| `VERCEL_API_TOKEN`                      | Owner / Tooling  | Used by provisioning to manage tenant deployments on Vercel.                                                      |   ◯   |  ◯  |    ✅    |   ✅  | Removed in Azure (replaced by Container Apps deployment automation).                                                                            |
| `VERCEL_TEAM_ID`                        | Owner / Tooling  | Vercel team scope for the management calls above.                                                                  |   ◯   |  ◯  |    ✅    |   ✅  | Removed in Azure.                                                                                                                              |
| `CHECK_FORBIDDEN_LEVEL`                 | Tooling          | `warn` (default) or `error`. Drives `scripts/check-forbidden-strings.mjs`. ADR 010 + 015.                          |   ◯   |  ◯  |    ✅    |   ✅  | Same name; CI sets `error` in the vendor's pipeline.                                                                                            |

✅ = required, ◯ = optional.

The `Local` column reflects what is needed for the dev server to start. The `Prod` column reflects what is needed for the live runtime.

---

## Loader rules

- The privileged-env loader [`lib/supabase/privileged-env.ts`](../../lib/supabase/privileged-env.ts) is the **only** path that reads `SUPABASE_SERVICE_ROLE_KEY`. `npm run check:secrets` fails the build if any other code path reads it.
- The control-plane client [`lib/control-plane/client.ts`](../../lib/control-plane/client.ts) prefers `CONTROL_PLANE_READONLY_KEY` and only falls back to `SUPABASE_SERVICE_ROLE_KEY` in development.
- Variables consumed at module scope must come from `process.env` directly. Wrappers that read from a config singleton are deliberately avoided.

---

## Secret hygiene

- `.env.local` is the only file that should hold real values for local dev. It is gitignored.
- `.env.example` contains placeholder strings only and is checked in.
- Real secrets must never appear in git history, logs, or screenshots. If one leaks, follow [`docs/security/service-role-key-rotation.md`](../security/service-role-key-rotation.md).
- The placeholder format used in this repository is `${VARIABLE_NAME}` or `your-...` strings. Examples in onboarding docs use these placeholders, never real keys.

---

## Azure target replacements (summary)

| Today                                | Azure target                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------------- |
| Supabase URL + publishable key       | Microsoft Entra ID OIDC + Azure-side API origin.                                       |
| Supabase service-role key            | Privileged DB role + `SET LOCAL app.user_id` + audit (per call site).                  |
| Vercel env vars (production)         | Container Apps env vars + Azure Key Vault references.                                  |
| Supabase Storage bucket              | Azure Blob Storage container.                                                          |
| Vercel logs / `console.log`          | Azure Application Insights via OTel exporter.                                          |
| `cp_tenant_environments.supabase_service_role_key` plaintext column | Key Vault secret reference (operator-controlled rotation).        |

This table is informational. The vendor owns the migration plan; the operator does not pre-empt it inside this repository.
