diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index 5663a218..e866027f 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -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: >- @@ -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 @@ -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