Skip to content
63 changes: 57 additions & 6 deletions .github/workflows/schema-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17
# supabase/postgres — pgvector + pg_graphql + pgsodium + pg_net 등
# Supabase 가 prod 에서 사용하는 extension 전부 포함. vanilla 또는 pgvector
# 단독 이미지는 supabase/migrations 가 요구하는 ext 일부만 충족하여 setup 에서 fail.
# 태그는 stable tag 고정 (latest 사용 시 reproducibility 깨짐).
image: supabase/postgres:17.6.1.131
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: drift
# POSTGRES_DB 를 별도로 지정하지 않고 default `postgres` DB 를 사용.
# supabase/postgres 의 init script 가 default DB 에서만 role/권한을 부여하므로
# `drift` 같은 별도 DB 를 만들면 postgres user 가 `permission denied` 를 받는다.
ports:
- 5432:5432
options: >-
Expand All @@ -34,7 +40,10 @@ jobs:
--health-timeout 5s
--health-retries 5
env:
LOCAL_DATABASE_URL: postgresql://postgres:testpass@localhost:5432/drift
# supabase/postgres image 의 postgres role 은 superuser 가 아니며 auth/storage
# schema 가 supabase_admin 소유로 잡혀있어 stub 객체 생성이 막힌다.
# supabase_admin 은 image 의 actual superuser 이고 password 는 POSTGRES_PASSWORD 와 동일.
LOCAL_DATABASE_URL: postgresql://supabase_admin:testpass@localhost:5432/postgres
steps:
- uses: actions/checkout@v6

Expand All @@ -58,16 +67,58 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile

- name: Bootstrap supabase namespaces
# SOT constraint 검증을 위한 최소 stub.
#
# supabase/postgres image 는 auth/storage/vault/graphql/realtime schema 를
# supabase_admin 소유로 미리 만들지만 그 안의 객체 (auth.users, storage.buckets) 는
# 만들지 않음 — 그건 supabase docker-compose 의 별도 service 책임.
# supabase_admin 으로 연결해서 stub 객체만 추가.
#
# 신규 migration 이 다른 supabase namespace 객체를 참조하면 stub 객체를 여기 추가.
# 본 게이트의 범위는 CHECK constraint 정합성이지 prod-parity 가 아님.
run: |
set -euo pipefail

# 진단: role 확인 (다음 fail 시 즉시 원인 파악용)
psql "$LOCAL_DATABASE_URL" -c "SELECT current_user, current_database();" || true
psql "$LOCAL_DATABASE_URL" -c "\du" || true

psql "$LOCAL_DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL'
-- auth.users: FK + trigger target. stub PK 는 INSERT 안 하므로 DEFAULT 불필요
-- (gen_random_uuid 는 supabase image 에서 extensions.gen_random_uuid 라 search_path 의존).
CREATE TABLE IF NOT EXISTS auth.users (
id uuid PRIMARY KEY
);

-- auth.uid(): RLS USING/WITH CHECK 절에서 호출됨. NULL 반환 stub 충분.
CREATE OR REPLACE FUNCTION auth.uid() RETURNS uuid
LANGUAGE sql STABLE AS $$ SELECT NULL::uuid $$;

-- storage.buckets: INSERT target (id text, name text, public boolean)
CREATE TABLE IF NOT EXISTS storage.buckets (
id text PRIMARY KEY,
name text,
public boolean DEFAULT false
);
SQL

- name: Run schema drift check
id: drift
# drift-bypass label → step failure를 무시하여 job green 유지
# 단, drift report 는 항상 PR comment 로 게시
continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'drift-bypass') }}
run: bun run scripts/check-schema-drift.ts | tee /tmp/drift.md
shell: bash
# pipefail 필수 — `bun ... | tee` 형태면 script exit 1 이 tee 의 exit 0 에 가려 step 이 green 으로 잘못 표시됨.
# 또한 `tee /tmp/...` 는 hashFiles() 가 workspace 외부를 못 보므로 sticky comment step skip. drift.md 는 workspace 에 둔다.
run: |
set -eo pipefail
bun run scripts/check-schema-drift.ts > drift.md
cat drift.md

- name: Comment PR with drift report
if: always() && hashFiles('/tmp/drift.md') != ''
if: always() && hashFiles('drift.md') != ''
uses: marocchino/sticky-pull-request-comment@v2
with:
header: schema-drift
path: /tmp/drift.md
path: drift.md
Loading