From 5d43ea574762663ab979c13958d0c07750fa74dc Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Tue, 10 Mar 2026 21:35:29 +0100 Subject: [PATCH] refactor: Rename SUPABASE_SERVICE_KEY to SUPABASE_SECRET_KEY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align with Supabase's new key naming (service_role → secret key). Also adds the secret key to Terraform, deploy.yml, and Modal secrets sync — it was previously missing from the production deploy pipeline entirely. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/project-setup/SKILL.md | 2 +- .../skills/project-setup/references/runbook.md | 4 ++-- .env.example | 6 +++--- .github/scripts/modal-sync-secrets.sh | 3 ++- .github/workflows/db-reset.yml | 6 +++--- .github/workflows/deploy.yml | 3 +++ README.md | 2 +- changelog.d/rename-secret-key.changed | 1 + docker-compose.yml | 4 ++-- docs/SETUP_RUNBOOK.md | 4 ++-- docs/src/app/setup/page.tsx | 2 +- scripts/init.py | 6 +++--- src/policyengine_api/api/analysis.py | 2 +- src/policyengine_api/config/settings.py | 2 +- src/policyengine_api/modal_app.py | 16 ++++++++-------- src/policyengine_api/services/storage.py | 12 ++++++------ terraform/main.tf | 4 ++++ terraform/variables.tf | 6 ++++++ 18 files changed, 50 insertions(+), 35 deletions(-) create mode 100644 changelog.d/rename-secret-key.changed diff --git a/.claude/skills/project-setup/SKILL.md b/.claude/skills/project-setup/SKILL.md index 355be05..c992cac 100644 --- a/.claude/skills/project-setup/SKILL.md +++ b/.claude/skills/project-setup/SKILL.md @@ -55,7 +55,7 @@ The `.env.example` file documents all variables. For local dev, only three need | Variable | Source | |----------|--------| | `SUPABASE_KEY` | Output of `supabase start` (anon key) | -| `SUPABASE_SERVICE_KEY` | Output of `supabase start` (service_role key) | +| `SUPABASE_SECRET_KEY` | Output of `supabase start` (secret key) | | `HUGGING_FACE_TOKEN` | huggingface.co/settings/tokens (for seeding datasets) | ### Common Local Commands diff --git a/.claude/skills/project-setup/references/runbook.md b/.claude/skills/project-setup/references/runbook.md index fed2ddc..396c73a 100644 --- a/.claude/skills/project-setup/references/runbook.md +++ b/.claude/skills/project-setup/references/runbook.md @@ -34,7 +34,7 @@ The defaults in `.env.example` work for local development with Supabase. You onl | Variable | Where to get it | Required? | |----------|----------------|-----------| | `SUPABASE_KEY` | Output of `supabase start` (anon key) | Yes | -| `SUPABASE_SERVICE_KEY` | Output of `supabase start` (service_role key) | Yes | +| `SUPABASE_SECRET_KEY` | Output of `supabase start` (secret key) | Yes | | `HUGGING_FACE_TOKEN` | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) | For seeding datasets | | `ANTHROPIC_API_KEY` | [console.anthropic.com](https://console.anthropic.com) | For `/agent` endpoint only | | `LOGFIRE_TOKEN` | [logfire.pydantic.dev](https://logfire.pydantic.dev) | Optional (observability) | @@ -45,7 +45,7 @@ The defaults in `.env.example` work for local development with Supabase. You onl supabase start ``` -This starts local Postgres (port 54322), PostgREST (port 54321), and storage. Copy the `anon key` and `service_role key` from the output into your `.env`. +This starts local Postgres (port 54322), PostgREST (port 54321), and storage. Copy the `anon key` and `secret key` (formerly service_role key) from the output into your `.env`. ### Step 4: Initialize database diff --git a/.env.example b/.env.example index 282ba16..0476b3a 100644 --- a/.env.example +++ b/.env.example @@ -13,8 +13,8 @@ SUPABASE_URL=http://127.0.0.1:54321 # Supabase anon/public key (safe to expose in client) SUPABASE_KEY=your-anon-key -# Supabase service role key (server-side only, never expose) -SUPABASE_SERVICE_KEY=your-service-role-key +# Supabase secret key (server-side only, never expose) +SUPABASE_SECRET_KEY=your-secret-key # PostgreSQL connection string for direct database access # For production Supabase: use the "connection string" from Dashboard > Settings > Database @@ -80,7 +80,7 @@ MODAL_ENVIRONMENT=main # DATABASE_URL='postgresql://...' \ # SUPABASE_URL='https://...' \ # SUPABASE_KEY='...' \ -# SUPABASE_SERVICE_KEY='...' \ +# SUPABASE_SECRET_KEY='...' \ # STORAGE_BUCKET='datasets' # # 2. modal secret create anthropic-api-key \ diff --git a/.github/scripts/modal-sync-secrets.sh b/.github/scripts/modal-sync-secrets.sh index ab8627d..3dc6921 100755 --- a/.github/scripts/modal-sync-secrets.sh +++ b/.github/scripts/modal-sync-secrets.sh @@ -1,7 +1,7 @@ #!/bin/bash # Sync secrets from GitHub Actions to a Modal environment # Usage: ./modal-sync-secrets.sh -# Required env vars: SUPABASE_DB_URL, SUPABASE_URL, SUPABASE_KEY, LOGFIRE_TOKEN +# Required env vars: SUPABASE_DB_URL, SUPABASE_URL, SUPABASE_KEY, SUPABASE_SECRET_KEY, LOGFIRE_TOKEN set -euo pipefail MODAL_ENV="${1:?Modal environment required (staging or main)}" @@ -13,6 +13,7 @@ uv run modal secret create policyengine-db \ "DATABASE_URL=${SUPABASE_DB_URL}" \ "SUPABASE_URL=${SUPABASE_URL}" \ "SUPABASE_KEY=${SUPABASE_KEY}" \ + "SUPABASE_SECRET_KEY=${SUPABASE_SECRET_KEY}" \ "STORAGE_BUCKET=${STORAGE_BUCKET:-datasets}" \ --env="$MODAL_ENV" \ --force diff --git a/.github/workflows/db-reset.yml b/.github/workflows/db-reset.yml index 82f7ff5..960d6e1 100644 --- a/.github/workflows/db-reset.yml +++ b/.github/workflows/db-reset.yml @@ -84,7 +84,7 @@ jobs: SUPABASE_DB_URL: ${{ secrets.SUPABASE_POOLER_URL }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} - SUPABASE_SERVICE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }} + SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }} LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }} LOGFIRE_ENVIRONMENT: prod run: | @@ -97,7 +97,7 @@ jobs: SUPABASE_DB_URL: ${{ secrets.SUPABASE_POOLER_URL }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} - SUPABASE_SERVICE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }} + SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }} STORAGE_BUCKET: ${{ vars.STORAGE_BUCKET }} LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }} LOGFIRE_ENVIRONMENT: prod @@ -112,7 +112,7 @@ jobs: SUPABASE_DB_URL: ${{ secrets.SUPABASE_POOLER_URL }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} - SUPABASE_SERVICE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }} + SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }} HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} STORAGE_BUCKET: ${{ vars.STORAGE_BUCKET }} LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5b820b0..13d928f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -141,6 +141,7 @@ jobs: env: TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }} TF_VAR_supabase_key: ${{ secrets.SUPABASE_KEY }} + TF_VAR_supabase_secret_key: ${{ secrets.SUPABASE_SECRET_KEY }} TF_VAR_supabase_db_url: ${{ secrets.SUPABASE_DB_URL }} TF_VAR_logfire_token: ${{ secrets.LOGFIRE_TOKEN }} TF_VAR_logfire_environment: prod @@ -208,6 +209,7 @@ jobs: SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }} LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }} run: | chmod +x .github/scripts/*.sh @@ -325,6 +327,7 @@ jobs: SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }} SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }} LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }} run: | chmod +x .github/scripts/*.sh diff --git a/README.md b/README.md index c9f311f..e4531d0 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ Copy `.env.example` to `.env` and configure. All variables are documented in `.e |----------|-------------|----------| | `SUPABASE_URL` | Supabase API URL | Yes | | `SUPABASE_KEY` | Supabase anon/public key | Yes | -| `SUPABASE_SERVICE_KEY` | Supabase service role key | Yes | +| `SUPABASE_SECRET_KEY` | Supabase secret key | Yes | | `SUPABASE_DB_URL` | PostgreSQL connection string | Yes | | `STORAGE_BUCKET` | Supabase storage bucket name | Yes (default: `datasets`) | | `HUGGING_FACE_TOKEN` | HuggingFace token for dataset downloads | For seeding | diff --git a/changelog.d/rename-secret-key.changed b/changelog.d/rename-secret-key.changed new file mode 100644 index 0000000..61c08e4 --- /dev/null +++ b/changelog.d/rename-secret-key.changed @@ -0,0 +1 @@ +Rename SUPABASE_SERVICE_KEY to SUPABASE_SECRET_KEY across codebase, aligning with Supabase's new key naming. Add secret key to Terraform, deploy.yml, and Modal secrets sync. diff --git a/docker-compose.yml b/docker-compose.yml index 60aa598..9f1d377 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: SUPABASE_URL: http://supabase_kong_policyengine-api-v2-alpha:8000 SUPABASE_KEY: ${SUPABASE_KEY} - SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY} + SUPABASE_SECRET_KEY: ${SUPABASE_SECRET_KEY} SUPABASE_DB_URL: postgresql://postgres:postgres@supabase_db_policyengine-api-v2-alpha:5432/postgres LOGFIRE_TOKEN: ${LOGFIRE_TOKEN} DEBUG: "false" @@ -33,7 +33,7 @@ services: environment: SUPABASE_URL: http://supabase_kong_policyengine-api-v2-alpha:8000 SUPABASE_KEY: ${SUPABASE_KEY} - SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY} + SUPABASE_SECRET_KEY: ${SUPABASE_SECRET_KEY} SUPABASE_DB_URL: postgresql://postgres:postgres@supabase_db_policyengine-api-v2-alpha:5432/postgres LOGFIRE_TOKEN: ${LOGFIRE_TOKEN} volumes: diff --git a/docs/SETUP_RUNBOOK.md b/docs/SETUP_RUNBOOK.md index fed2ddc..396c73a 100644 --- a/docs/SETUP_RUNBOOK.md +++ b/docs/SETUP_RUNBOOK.md @@ -34,7 +34,7 @@ The defaults in `.env.example` work for local development with Supabase. You onl | Variable | Where to get it | Required? | |----------|----------------|-----------| | `SUPABASE_KEY` | Output of `supabase start` (anon key) | Yes | -| `SUPABASE_SERVICE_KEY` | Output of `supabase start` (service_role key) | Yes | +| `SUPABASE_SECRET_KEY` | Output of `supabase start` (secret key) | Yes | | `HUGGING_FACE_TOKEN` | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) | For seeding datasets | | `ANTHROPIC_API_KEY` | [console.anthropic.com](https://console.anthropic.com) | For `/agent` endpoint only | | `LOGFIRE_TOKEN` | [logfire.pydantic.dev](https://logfire.pydantic.dev) | Optional (observability) | @@ -45,7 +45,7 @@ The defaults in `.env.example` work for local development with Supabase. You onl supabase start ``` -This starts local Postgres (port 54322), PostgREST (port 54321), and storage. Copy the `anon key` and `service_role key` from the output into your `.env`. +This starts local Postgres (port 54322), PostgREST (port 54321), and storage. Copy the `anon key` and `secret key` (formerly service_role key) from the output into your `.env`. ### Step 4: Initialize database diff --git a/docs/src/app/setup/page.tsx b/docs/src/app/setup/page.tsx index cad7242..ba7bc2a 100644 --- a/docs/src/app/setup/page.tsx +++ b/docs/src/app/setup/page.tsx @@ -57,7 +57,7 @@ export default function SetupPage() {
{`# Supabase (from \`supabase start\` output)
 SUPABASE_URL=http://127.0.0.1:54321
 SUPABASE_KEY=eyJ...
-SUPABASE_SERVICE_KEY=eyJ...
+SUPABASE_SECRET_KEY=eyJ...
 SUPABASE_DB_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
 
 # Storage
diff --git a/scripts/init.py b/scripts/init.py
index f7a64eb..84e150e 100644
--- a/scripts/init.py
+++ b/scripts/init.py
@@ -24,7 +24,7 @@
 from sqlmodel import create_engine
 
 from policyengine_api.config.settings import settings
-from policyengine_api.services.storage import get_service_role_client
+from policyengine_api.services.storage import get_secret_client
 
 console = Console()
 
@@ -36,7 +36,7 @@ def reset_storage_bucket():
     console.print("[bold blue]Resetting storage bucket...")
 
     try:
-        supabase = get_service_role_client()
+        supabase = get_secret_client()
         bucket_name = settings.storage_bucket
 
         # Try to delete the bucket (will fail if it doesn't exist)
@@ -67,7 +67,7 @@ def ensure_storage_bucket():
     console.print("[bold blue]Ensuring storage bucket exists...")
 
     try:
-        supabase = get_service_role_client()
+        supabase = get_secret_client()
         bucket_name = settings.storage_bucket
 
         # Try to get bucket info
diff --git a/src/policyengine_api/api/analysis.py b/src/policyengine_api/api/analysis.py
index a46eaf2..92ad590 100644
--- a/src/policyengine_api/api/analysis.py
+++ b/src/policyengine_api/api/analysis.py
@@ -724,7 +724,7 @@ def _download_dataset_local(filepath: str) -> str:
     if cache_path.exists():
         return str(cache_path)
 
-    client = create_client(settings.supabase_url, settings.supabase_service_key)
+    client = create_client(settings.supabase_url, settings.supabase_secret_key)
     data = client.storage.from_("datasets").download(filepath)
 
     cache_path.parent.mkdir(parents=True, exist_ok=True)
diff --git a/src/policyengine_api/config/settings.py b/src/policyengine_api/config/settings.py
index 63205ce..192b0bf 100644
--- a/src/policyengine_api/config/settings.py
+++ b/src/policyengine_api/config/settings.py
@@ -18,7 +18,7 @@ class Settings(BaseSettings):
     # Supabase
     supabase_url: str = "http://localhost:54321"
     supabase_key: str = ""
-    supabase_service_key: str = ""
+    supabase_secret_key: str = ""
     supabase_db_url: str = ""
 
     # Worker
diff --git a/src/policyengine_api/modal_app.py b/src/policyengine_api/modal_app.py
index 86638b1..d0f23f1 100644
--- a/src/policyengine_api/modal_app.py
+++ b/src/policyengine_api/modal_app.py
@@ -773,7 +773,7 @@ def simulate_economy_uk(simulation_id: str, traceparent: str | None = None) -> N
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -946,7 +946,7 @@ def simulate_economy_us(simulation_id: str, traceparent: str | None = None) -> N
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -1122,7 +1122,7 @@ def economy_comparison_uk(job_id: str, traceparent: str | None = None) -> None:
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -1805,7 +1805,7 @@ def economy_comparison_us(job_id: str, traceparent: str | None = None) -> None:
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -2757,7 +2757,7 @@ def compute_aggregate_uk(aggregate_id: str, traceparent: str | None = None) -> N
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -2913,7 +2913,7 @@ def compute_aggregate_us(aggregate_id: str, traceparent: str | None = None) -> N
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -3064,7 +3064,7 @@ def compute_change_aggregate_uk(
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
@@ -3269,7 +3269,7 @@ def compute_change_aggregate_us(
             database_url = get_database_url()
             supabase_url = os.environ["SUPABASE_URL"]
             supabase_key = os.environ.get(
-                "SUPABASE_SERVICE_KEY", os.environ["SUPABASE_KEY"]
+                "SUPABASE_SECRET_KEY", os.environ["SUPABASE_KEY"]
             )
             storage_bucket = os.environ.get("STORAGE_BUCKET", "datasets")
 
diff --git a/src/policyengine_api/services/storage.py b/src/policyengine_api/services/storage.py
index 4a690d0..784c494 100644
--- a/src/policyengine_api/services/storage.py
+++ b/src/policyengine_api/services/storage.py
@@ -19,9 +19,9 @@ def get_supabase_client() -> Client:
     return create_client(settings.supabase_url, settings.supabase_key)
 
 
-def get_service_role_client() -> Client:
-    """Get Supabase client with service role key for admin operations."""
-    return create_client(settings.supabase_url, settings.supabase_service_key)
+def get_secret_client() -> Client:
+    """Get Supabase client with secret key for admin operations."""
+    return create_client(settings.supabase_url, settings.supabase_secret_key)
 
 
 def upload_dataset(file_path: str, object_name: str | None = None) -> str:
@@ -51,7 +51,7 @@ def upload_dataset(file_path: str, object_name: str | None = None) -> str:
 
 
 def upload_dataset_for_seeding(file_path: str, object_name: str | None = None) -> str:
-    """Upload dataset using service role key (for seeding operations).
+    """Upload dataset using secret key (for seeding operations).
 
     Args:
         file_path: Local path to dataset file
@@ -60,12 +60,12 @@ def upload_dataset_for_seeding(file_path: str, object_name: str | None = None) -
     Returns:
         Object name (key) in storage
     """
-    supabase = get_service_role_client()
+    supabase = get_secret_client()
 
     if object_name is None:
         object_name = Path(file_path).name
 
-    # Upload file using service role client
+    # Upload file using secret client
     with open(file_path, "rb") as f:
         supabase.storage.from_(settings.storage_bucket).upload(
             object_name,
diff --git a/terraform/main.tf b/terraform/main.tf
index d7adbd4..8e4874a 100644
--- a/terraform/main.tf
+++ b/terraform/main.tf
@@ -82,6 +82,10 @@ resource "google_cloud_run_v2_service" "api" {
         name  = "SUPABASE_KEY"
         value = var.supabase_key
       }
+      env {
+        name  = "SUPABASE_SECRET_KEY"
+        value = var.supabase_secret_key
+      }
       env {
         name  = "SUPABASE_DB_URL"
         value = var.supabase_db_url
diff --git a/terraform/variables.tf b/terraform/variables.tf
index e630120..359c577 100644
--- a/terraform/variables.tf
+++ b/terraform/variables.tf
@@ -28,6 +28,12 @@ variable "supabase_key" {
   sensitive   = true
 }
 
+variable "supabase_secret_key" {
+  description = "Supabase secret key (admin operations, bypasses RLS)"
+  type        = string
+  sensitive   = true
+}
+
 variable "supabase_db_url" {
   description = "Supabase PostgreSQL connection URL"
   type        = string