From 11acfa6299f07c598c11491dc18c383472ac635e Mon Sep 17 00:00:00 2001 From: Muawiya Amir Date: Wed, 21 Oct 2026 22:34:07 +0500 Subject: [PATCH 1/4] ci: register protected production Edge deploy on default branch --- .../workflows/supabase-production-deploy.yml | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/supabase-production-deploy.yml diff --git a/.github/workflows/supabase-production-deploy.yml b/.github/workflows/supabase-production-deploy.yml new file mode 100644 index 00000000..fd9f3afe --- /dev/null +++ b/.github/workflows/supabase-production-deploy.yml @@ -0,0 +1,104 @@ +name: Deploy Supabase API and worker to production + +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: supabase-production-deploy + cancel-in-progress: false + +jobs: + deploy: + if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 25 + # The existing protected production environment requires an operator review. + environment: production-schema + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_PRODUCTION_ACCESS_TOKEN }} + PRODUCTION_REF: ${{ vars.SUPABASE_PRODUCTION_PROJECT_REF }} + STAGING_REF: ${{ vars.SUPABASE_STAGING_PROJECT_REF }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Require the current main commit and production target + shell: bash + run: | + set -euo pipefail + test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$GITHUB_SHA" + if [ "$PRODUCTION_REF" != 'egdszjoesxlccauahmsx' ] || \ + [ -z "$STAGING_REF" ] || [ "$STAGING_REF" = "$PRODUCTION_REF" ] || \ + [ -z "$SUPABASE_ACCESS_TOKEN" ]; then + echo '::error::A production-scoped token and distinct, reviewed project refs are required.' + exit 1 + fi + - uses: actions/setup-python@v7 + with: + python-version: '3.12' + - name: Install schema verifier + run: python -m pip install --requirement backend/requirements.txt + - name: Verify production schema before deployment + working-directory: backend + env: + PRODUCTION_PROJECT_REF: ${{ vars.SUPABASE_PRODUCTION_PROJECT_REF }} + STAGING_PROJECT_REF: ${{ vars.SUPABASE_STAGING_PROJECT_REF }} + PRODUCTION_SCHEMA_DIRECT_URL: ${{ secrets.PRODUCTION_SCHEMA_DIRECT_URL }} + DIRECT_URL: ${{ secrets.PRODUCTION_SCHEMA_DIRECT_URL }} + run: | + python -c 'from tools.apply_production_cutover import target_environment; target_environment(); print("production_connection=validated")' + python -m app.workers.schema_check + python -m tools.verify_supabase_cutover_schema + - uses: supabase/setup-cli@3c2f5e2ae34c34e428e8e206e2c4d21fa2d20fbf # v2.1.1 + with: + version: latest + - name: Deploy API from this main commit + run: supabase functions deploy api --project-ref "$PRODUCTION_REF" + - name: Deploy worker from this main commit without activating Cron + run: supabase functions deploy worker --project-ref "$PRODUCTION_REF" + - name: Check worker boot and unsigned dispatch rejection + env: + WORKER_URL: https://${{ vars.SUPABASE_PRODUCTION_PROJECT_REF }}.supabase.co/functions/v1/worker + run: | + python - <<'PY' + import os + import urllib.error + import urllib.request + + request = urllib.request.Request( + os.environ['WORKER_URL'], data=b'{}', method='POST', + headers={'Content-Type': 'application/json'}, + ) + try: + with urllib.request.urlopen(request, timeout=20) as response: + status, body = response.status, response.read(128) + except urllib.error.HTTPError as error: + status, body = error.code, error.read(128) + except Exception: + status, body = 0, b'' + ok = status == 401 and body == b'{"error":"invalid_dispatch"}' + print('production_worker_boot=' + ('ready' if ok else 'failed')) + raise SystemExit(0 if ok else 1) + PY + - name: Check production API health + env: + HEALTH_URL: https://${{ vars.SUPABASE_PRODUCTION_PROJECT_REF }}.supabase.co/functions/v1/api/health + run: | + python - <<'PY' + import os + import urllib.request + + try: + with urllib.request.urlopen(os.environ['HEALTH_URL'], timeout=20) as response: + ok = response.status == 200 + except Exception: + ok = False + print('production_api_health=' + ('ready' if ok else 'failed')) + raise SystemExit(0 if ok else 1) + PY + - name: Require main to remain at the deployed commit + shell: bash + run: test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$GITHUB_SHA" From c0f6a15bf7305cd6697caa204b0b60538962bb6b Mon Sep 17 00:00:00 2001 From: Muawiya Amir Date: Wed, 21 Oct 2026 22:34:07 +0500 Subject: [PATCH 2/4] ci: register inactive Cron installer on default branch --- .../supabase-production-cron-install.yml | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/supabase-production-cron-install.yml diff --git a/.github/workflows/supabase-production-cron-install.yml b/.github/workflows/supabase-production-cron-install.yml new file mode 100644 index 00000000..cb739835 --- /dev/null +++ b/.github/workflows/supabase-production-cron-install.yml @@ -0,0 +1,68 @@ +name: Install inactive Supabase production Cron jobs + +on: + workflow_dispatch: + inputs: + production_deploy_run_id: + description: 'Successful production API/worker deploy run ID for this main commit' + required: true + type: string + +permissions: + contents: read + actions: read + +concurrency: + group: supabase-production-cron-install + cancel-in-progress: false + +jobs: + install: + if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: production-schema + env: + PRODUCTION_PROJECT_REF: ${{ vars.SUPABASE_PRODUCTION_PROJECT_REF }} + STAGING_PROJECT_REF: ${{ vars.SUPABASE_STAGING_PROJECT_REF }} + PRODUCTION_SCHEMA_DIRECT_URL: ${{ secrets.PRODUCTION_SCHEMA_DIRECT_URL }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Require the current main commit and reviewed project references + shell: bash + run: | + set -euo pipefail + test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$GITHUB_SHA" + test "$PRODUCTION_PROJECT_REF" = 'egdszjoesxlccauahmsx' + test "$STAGING_PROJECT_REF" = 'zhrulokgngjceqgjkcmw' + test -n "$PRODUCTION_SCHEMA_DIRECT_URL" + - name: Require a successful API and worker deployment for this commit + env: + GH_TOKEN: ${{ github.token }} + DEPLOY_RUN_ID: ${{ inputs.production_deploy_run_id }} + shell: bash + run: | + set -euo pipefail + [[ "$DEPLOY_RUN_ID" =~ ^[0-9]+$ ]] + gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${DEPLOY_RUN_ID}" \ + --jq '[.path, .head_branch, .head_sha, .event, .conclusion] | @tsv' | + awk -v sha="$GITHUB_SHA" '$1 == ".github/workflows/supabase-production-deploy.yml" && $2 == "main" && $3 == sha && $4 == "workflow_dispatch" && $5 == "success" { found=1 } END { exit !found }' + - name: Install PostgreSQL client + run: sudo apt-get update && sudo apt-get install -y postgresql-client + - uses: actions/setup-python@v7 + with: + python-version: '3.12' + - name: Check production connection and worker schema without writes + working-directory: backend + run: python -m tools.install_production_cron + - name: Require main to remain at the reviewed commit before SQL + shell: bash + run: test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$GITHUB_SHA" + - name: Install and verify eight inactive jobs + working-directory: backend + run: python -m tools.install_production_cron --install + - name: Require main to remain at the installed commit + shell: bash + run: test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$GITHUB_SHA" From de78e85d8dd4f0ddc39f7001dcdb7f1747f219f0 Mon Sep 17 00:00:00 2001 From: Muawiya Amir Date: Wed, 21 Oct 2026 22:34:32 +0500 Subject: [PATCH 3/4] docs: record default-branch workflow registration gate --- docs/CODEBASE_MAP.md | 2 +- docs/WORK_LOG.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/CODEBASE_MAP.md b/docs/CODEBASE_MAP.md index e1b83ceb..e4f239fd 100644 --- a/docs/CODEBASE_MAP.md +++ b/docs/CODEBASE_MAP.md @@ -18,7 +18,7 @@ learned history, streaks, likes, saved concepts, and push reminders. | Content lifecycle | `docs/CONTENT_ARCHITECTURE.md`, `docs/CONTENT_OPERATIONS.md`; portable subject/curriculum imports, durable refill, reviewed publication, daily review, protected health report. | | Content engine | `backend/app/services/generation.py`, `pool.py`, `prefetch.py`; Gemini lessons from a curated backlog. | | Operations | `.github/workflows/`, `backend/railway.json`, `backend/Dockerfile`, `mobile/eas.json`, `mobile/app.config.js`. | -| Hosting migration | `docs/RAILWAY_MIGRATION.md`; source/destination evidence, worker handover, EAS endpoint publication, recovery redirects and retirement. New Railway services use dashboard settings because legacy Config as Code is deprecated. | +| Hosting migration | `docs/RAILWAY_MIGRATION.md`; source/destination evidence, worker handover, EAS endpoint publication, recovery redirects and retirement. New Railway services use dashboard settings because legacy Config as Code is deprecated. `.github/workflows/supabase-production-deploy.yml` and `supabase-production-cron-install.yml` are registered here on the default branch for manual, protected runs against current `main`; they are not triggered by merging `develop`. | | Supabase scheduled workers | `supabase/functions/worker/` validates signed Cron calls; migrations 0119–0120 own the disabled run ledger and HTTP reconciliation; migrations 0128–0129 feed owner reporting and bound run history; migration 0130 fences uncertain Gmail delivery; `backend/ops/supabase_workers/` installs inactive platform Cron jobs. See `docs/SUPABASE_WORKER_MIGRATION.md` for staged handover. | | Documentation | Root `README.md`, `RELEASING.md`, `CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, `docs/ROADMAP.md`, and the backend/mobile guides. | | Agent guidance | Root `AGENTS.md`; `mobile/AGENTS.md` adds Expo documentation requirements and `mobile/CLAUDE.md` references it. | diff --git a/docs/WORK_LOG.md b/docs/WORK_LOG.md index 6efbcfda..dc6288a0 100644 --- a/docs/WORK_LOG.md +++ b/docs/WORK_LOG.md @@ -7,6 +7,11 @@ claims as completed work. ## Current status +### Register protected production workflows on the default branch — 2026-10-21 + +- After release PR #434 merged into `main` at `abc2dcf`, the new public Netlify pages returned HTML with the expected security headers while their API setting still pointed to Railway. GitHub did not list the new production deploy or inactive-Cron workflow because the repository default branch is `develop` and those workflow files existed only on `main`. GitHub requires a `workflow_dispatch` file on the default branch even when the run targets another ref. +- Branch `codex/418-register-production-workflows` copies only the two exact reviewed workflow files from merged `main` into `develop` in focused commits `11acfa6` and `c0f6a15`. Both workflows still reject any dispatch except current `main` and use the protected `production-schema` environment. This default-branch registration does not deploy a function, install Cron, publish an OTA, or change Railway. A separate PR and its normal review/merge gate are required before dispatching the production deploy on `main`. + ### Supabase scheduled worker migration — issue #417, 2026-10-18 - Branch `codex/supabase-cron-workers` starts from `develop` at `69b01c5`. Scope is one feature PR for all Railway scheduled/background work, with production Supabase schedules disabled until the step-3 cutover. The owner confirmed Railway production runs `reminders` every 15 minutes and `pool-topup` at 22:00 UTC; source is `main`. On October 20, the owner showed three green recent runs of each service: reminders sent zero tokens and processed zero editorial emails, while pool-topup reported generation disabled and empty revision/enrichment work. Both deployment panels identify release PR #414, whose merge commit is `c244088aa285127a3f065cc1d7e41afdae8bf36f`; Railway's full deployed SHA and some effective non-secret flags remain to be checked. The owner later showed `reminders` with `GEMINI_MODEL=gemini-3.1-flash-lite` and `GENERATION_DAILY_CALL_CAP=200`. From ed344c649c04e29229c8669a2e962e9041f739f2 Mon Sep 17 00:00:00 2001 From: Muawiya Amir Date: Wed, 21 Oct 2026 23:34:06 +0500 Subject: [PATCH 4/4] docs: record PR 435 workflow registration review --- docs/WORK_LOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/WORK_LOG.md b/docs/WORK_LOG.md index dc6288a0..cc003623 100644 --- a/docs/WORK_LOG.md +++ b/docs/WORK_LOG.md @@ -10,7 +10,8 @@ claims as completed work. ### Register protected production workflows on the default branch — 2026-10-21 - After release PR #434 merged into `main` at `abc2dcf`, the new public Netlify pages returned HTML with the expected security headers while their API setting still pointed to Railway. GitHub did not list the new production deploy or inactive-Cron workflow because the repository default branch is `develop` and those workflow files existed only on `main`. GitHub requires a `workflow_dispatch` file on the default branch even when the run targets another ref. -- Branch `codex/418-register-production-workflows` copies only the two exact reviewed workflow files from merged `main` into `develop` in focused commits `11acfa6` and `c0f6a15`. Both workflows still reject any dispatch except current `main` and use the protected `production-schema` environment. This default-branch registration does not deploy a function, install Cron, publish an OTA, or change Railway. A separate PR and its normal review/merge gate are required before dispatching the production deploy on `main`. +- Branch `codex/418-register-production-workflows` copies only the two exact reviewed workflow files from merged `main` into `develop` in focused commits `11acfa6` and `c0f6a15`. Both workflow jobs run only for current `main` and use the protected `production-schema` environment; a dispatch against `develop` skips the job. This default-branch registration does not deploy a function, install Cron, publish an OTA, or change Railway. A separate PR and its normal review/merge gate are required before dispatching the production deploy on `main`. +- PR #435 review at `de78e85`: both workflow files have identical SHA-256 digests to merged `main`, parse as manual workflows, and retain the `main` job guard and protected environment. The production repository is public, so the current-`main` `git ls-remote` checks work with checkout credentials disabled. All three hosted quality checks passed and GitHub reported the PR mergeable. GitHub's automated Codex review could not complete because its review quota was reached; the manual review found no actionable defect. No production workflow has been dispatched. ### Supabase scheduled worker migration — issue #417, 2026-10-18