From f99f7959a3321c5cd45f45ba77874d55e943aafd Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 16:29:34 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix(ci):=20#373=20drift=20gate=20=E2=80=94?= =?UTF-8?q?=20pgvector=20image,=20pipefail,=20sticky=20comment=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #594 red-path smoke 가 노출한 3 가지 결함 동시 수정. 1. postgres service `image: postgres:17` → `pgvector/pgvector:pg17` - supabase/migrations 가 `CREATE EXTENSION vector` 를 호출하므로 vanilla postgres:17 으로는 setup 단계에서 첫 migration 부터 fail - 결과: SOT 비교 자체가 한 번도 실행된 적 없음 2. `bun run ... | tee /tmp/drift.md` → `set -eo pipefail` + `> drift.md` - GitHub Actions 의 default shell 은 `bash -e` (no pipefail) 이므로 pipe 의 last command (tee) exit 0 이 전체 exit code 가 된다. script `process.exit(1)` 이 가려져 job 이 항상 green 으로 표시됨 - pipe 자체를 제거하고 redirect → cat 으로 동일 효과 + log 가시성 유지 3. `hashFiles('/tmp/drift.md')` → `hashFiles('drift.md')` - `hashFiles()` 는 GITHUB_WORKSPACE 내부 파일만 해시화. `/tmp/*` 는 항상 빈 문자열 → `'' != ''` false → sticky comment step 영구 skip - drift.md 를 workspace 에 두고 path 도 동기화 ## 검증 본 PR self-validate: - workflow trigger (path 매치: `.github/workflows/schema-drift.yml`) - pgvector 이미지로 23 migration 정상 적용 기대 - MAGAZINE_STATUSES 는 dev 와 동일 → "OK — 1 SOT entries match" sticky comment 기대 머지 후 #594 (red-path smoke) 를 origin/dev rebase + force push 하면 의도된 DRIFT 결과 + drift-bypass label flow 까지 자동 재검증된다. ## Refs - 검증 PR: #594 (test/373-drift-smoke, DO NOT MERGE) - 원본 workflow: #588 - SOT: docs/database/schema-drift-sot.md --- .github/workflows/schema-drift.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index 5663a218..197c9872 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -22,7 +22,9 @@ jobs: runs-on: ubuntu-latest services: postgres: - image: postgres:17 + # pgvector/pgvector:pg17 — postgres:17 base + pgvector preinstalled. + # supabase/migrations 가 `CREATE EXTENSION vector` 를 요구하므로 vanilla postgres:17 으로는 setup 단계에서 fail. + image: pgvector/pgvector:pg17 env: POSTGRES_PASSWORD: testpass POSTGRES_DB: drift @@ -63,11 +65,17 @@ jobs: # 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 From aac45cfbd7a1a5ed606989f94b8f901850de9b78 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 16:38:41 +0900 Subject: [PATCH 2/7] =?UTF-8?q?fix(ci):=20#373=20drift=20gate=20=E2=80=94?= =?UTF-8?q?=20use=20supabase/postgres=20image=20for=20all=20required=20ext?= =?UTF-8?q?ensions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit self-validate 첫 시도 (PR #595 initial) 가 노출: pgvector/pgvector:pg17 도 부족. 20260409075040_remote_schema.sql 이 `CREATE EXTENSION pg_graphql` 요구. supabase/postgres 는 prod 에서 사용 중인 모든 ext (vector, pg_graphql, pgsodium, pg_net, pgaudit, pgjwt 등) 를 포함하는 공식 이미지. 17.6.1.131 (2026-05-27 stable) 태그 고정 — latest 사용 시 reproducibility 깨짐. cold start 가 vanilla 대비 다소 느릴 수 있으나 setup fail 위험 제거가 우선. --- .github/workflows/schema-drift.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index 197c9872..d82c98a0 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -22,9 +22,11 @@ jobs: runs-on: ubuntu-latest services: postgres: - # pgvector/pgvector:pg17 — postgres:17 base + pgvector preinstalled. - # supabase/migrations 가 `CREATE EXTENSION vector` 를 요구하므로 vanilla postgres:17 으로는 setup 단계에서 fail. - image: pgvector/pgvector:pg17 + # 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 From a878a456fd0ee712b43849817447962ff7a99186 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 17:00:42 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix(ci):=20#373=20drift=20gate=20=E2=80=94?= =?UTF-8?q?=20use=20default=20postgres=20DB=20to=20avoid=20supabase=20init?= =?UTF-8?q?=20permission=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #595 v2 self-validate 실패 노출: `POSTGRES_DB: drift` 로 별도 DB 를 만들면 supabase/postgres image 의 init script 가 default `postgres` DB 에서만 role/권한을 설정하므로 postgres user 가 `permission denied for database drift` 를 받는다. 별도 DB 만들지 말고 image 가 init 한 default `postgres` DB 위에서 migration 적용. --- .github/workflows/schema-drift.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index d82c98a0..da05d902 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -29,7 +29,9 @@ jobs: 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: >- @@ -38,7 +40,7 @@ jobs: --health-timeout 5s --health-retries 5 env: - LOCAL_DATABASE_URL: postgresql://postgres:testpass@localhost:5432/drift + LOCAL_DATABASE_URL: postgresql://postgres:testpass@localhost:5432/postgres steps: - uses: actions/checkout@v6 From 9f89ca4f595ad46a2b09a3fe94081430b78f8c54 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 17:12:43 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix(ci):=20#373=20drift=20gate=20=E2=80=94?= =?UTF-8?q?=20bootstrap=20supabase=20namespace=20stubs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit supabase/postgres image 의 init script 는 storage/auth/vault/graphql/realtime 객체를 만들지 않아 (그건 별도 docker-compose service 가 담당) storage.buckets INSERT 같은 migration 에서 setup 이 fail 함. 게이트 범위는 CHECK constraint 정합성이지 prod-parity 가 아니므로, migration 적용 전에 FK/INSERT target 만큼만 minimal stub 으로 만든다. stub: - auth, storage, vault, graphql, realtime schemas - auth.users(id uuid PRIMARY KEY) — FK + trigger target - auth.uid() — NULL 반환 stub (RLS USING/WITH CHECK 에서 호출) - storage.buckets(id, name, public) — INSERT target 신규 migration 이 다른 supabase namespace 객체를 참조하면 여기 추가. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/schema-drift.yml | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index da05d902..1cbabeee 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -64,6 +64,41 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Bootstrap supabase namespaces + # SOT constraint 검증을 위한 최소 stub. + # supabase/postgres image 의 init script 는 storage/auth/vault/graphql/realtime + # 객체를 만들지 않으므로 (그건 supabase docker-compose 상의 별도 service 책임), + # migration 적용 전에 FK/INSERT target 만큼만 미리 만들어준다. + # + # 신규 migration 이 다른 supabase namespace 객체를 참조하면 여기 추가. + # 본 게이트의 범위는 CHECK constraint 정합성이지 prod-parity 가 아님 — + # supabase stack 전체를 띄울 이유는 없다. + run: | + set -euo pipefail + psql "$LOCAL_DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' + CREATE SCHEMA IF NOT EXISTS auth; + CREATE SCHEMA IF NOT EXISTS storage; + CREATE SCHEMA IF NOT EXISTS vault; + CREATE SCHEMA IF NOT EXISTS graphql; + CREATE SCHEMA IF NOT EXISTS realtime; + + -- auth.users: FK + trigger target + CREATE TABLE IF NOT EXISTS auth.users ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid() + ); + + -- 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 유지 From f310c750c941c60d0a1cea321c253c0148708814 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 17:16:32 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix(ci):=20#373=20drift=20gate=20v5=20?= =?UTF-8?q?=E2=80=94=20drop+recreate=20auth/storage=20schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전 v4 (commit 9f89ca4f) 는 schema 가 supabase image 에 이미 존재하지만 ownership 이 supabase_admin 이라 postgres user 가 CREATE TABLE 시 `permission denied for schema auth` 로 fail 했음. 해결: auth / storage 만 DROP CASCADE 로 날리고 postgres owner 로 재생성. vault / graphql / realtime 은 첫 migration 의 CREATE EXTENSION 이 의존하므로 건드리지 않음. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/schema-drift.yml | 31 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index 1cbabeee..fceefd5b 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -66,24 +66,29 @@ jobs: - name: Bootstrap supabase namespaces # SOT constraint 검증을 위한 최소 stub. - # supabase/postgres image 의 init script 는 storage/auth/vault/graphql/realtime - # 객체를 만들지 않으므로 (그건 supabase docker-compose 상의 별도 service 책임), - # migration 적용 전에 FK/INSERT target 만큼만 미리 만들어준다. # - # 신규 migration 이 다른 supabase namespace 객체를 참조하면 여기 추가. - # 본 게이트의 범위는 CHECK constraint 정합성이지 prod-parity 가 아님 — - # supabase stack 전체를 띄울 이유는 없다. + # supabase/postgres image 는 auth/storage/vault/graphql/realtime schema 자체는 + # 미리 만들어 두지만 ownership 이 supabase_admin (또는 별도 role) 으로 잡혀있어 + # postgres user 가 CREATE TABLE 시 `permission denied for schema auth` 가 난다. + # 또한 그 schema 안에 우리 migration 이 참조하는 객체 (auth.users, storage.buckets) + # 는 만들어지지 않음 — 그건 supabase docker-compose 의 별도 service 책임. + # + # 해결: auth / storage 만 DROP CASCADE 로 날리고 postgres owner 로 재생성. + # vault / graphql / realtime 은 첫 migration 의 CREATE EXTENSION 이 의존하므로 + # 건드리지 않음 (스키마 자체는 비어있어도 무방). + # + # 신규 migration 이 다른 supabase namespace 객체를 참조하면 stub 객체를 여기 추가. + # 본 게이트의 범위는 CHECK constraint 정합성이지 prod-parity 가 아님. run: | set -euo pipefail psql "$LOCAL_DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' - CREATE SCHEMA IF NOT EXISTS auth; - CREATE SCHEMA IF NOT EXISTS storage; - CREATE SCHEMA IF NOT EXISTS vault; - CREATE SCHEMA IF NOT EXISTS graphql; - CREATE SCHEMA IF NOT EXISTS realtime; + DROP SCHEMA IF EXISTS auth CASCADE; + DROP SCHEMA IF EXISTS storage CASCADE; + CREATE SCHEMA auth; + CREATE SCHEMA storage; -- auth.users: FK + trigger target - CREATE TABLE IF NOT EXISTS auth.users ( + CREATE TABLE auth.users ( id uuid PRIMARY KEY DEFAULT gen_random_uuid() ); @@ -92,7 +97,7 @@ jobs: LANGUAGE sql STABLE AS $$ SELECT NULL::uuid $$; -- storage.buckets: INSERT target (id text, name text, public boolean) - CREATE TABLE IF NOT EXISTS storage.buckets ( + CREATE TABLE storage.buckets ( id text PRIMARY KEY, name text, public boolean DEFAULT false From da7bc5f3fd4600cd61f4085bf40492ea49be58af Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 17:21:25 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix(ci):=20#373=20drift=20gate=20v6=20?= =?UTF-8?q?=E2=80=94=20connect=20as=20supabase=5Fadmin=20+=20diagnostic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v5 (drop+create) 가 `must be owner of schema auth` 로 fail. postgres role 은 supabase/postgres image 에서 superuser 가 아님 — schema 가 supabase_admin 소유로 잡혀있어 DROP/CREATE TABLE 둘 다 막힘. v6 변경: - LOCAL_DATABASE_URL 의 user 를 supabase_admin 으로 변경 (POSTGRES_PASSWORD 공용) - DROP/CREATE SCHEMA 제거, IF NOT EXISTS stub 객체만 추가 - gen_random_uuid DEFAULT 제거 (stub PK 는 INSERT 안 함, extensions schema 의존 회피) - 진단 라인 (current_user / \du) 추가 — 다음 fail 시 즉시 원인 파악 v6 도 fail 하면 stub 접근 폐기 → supabase CLI 로 전환. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/schema-drift.yml | 37 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/schema-drift.yml b/.github/workflows/schema-drift.yml index fceefd5b..e866027f 100644 --- a/.github/workflows/schema-drift.yml +++ b/.github/workflows/schema-drift.yml @@ -40,7 +40,10 @@ jobs: --health-timeout 5s --health-retries 5 env: - LOCAL_DATABASE_URL: postgresql://postgres:testpass@localhost:5432/postgres + # 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 @@ -67,29 +70,25 @@ jobs: - name: Bootstrap supabase namespaces # SOT constraint 검증을 위한 최소 stub. # - # supabase/postgres image 는 auth/storage/vault/graphql/realtime schema 자체는 - # 미리 만들어 두지만 ownership 이 supabase_admin (또는 별도 role) 으로 잡혀있어 - # postgres user 가 CREATE TABLE 시 `permission denied for schema auth` 가 난다. - # 또한 그 schema 안에 우리 migration 이 참조하는 객체 (auth.users, storage.buckets) - # 는 만들어지지 않음 — 그건 supabase docker-compose 의 별도 service 책임. - # - # 해결: auth / storage 만 DROP CASCADE 로 날리고 postgres owner 로 재생성. - # vault / graphql / realtime 은 첫 migration 의 CREATE EXTENSION 이 의존하므로 - # 건드리지 않음 (스키마 자체는 비어있어도 무방). + # 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 - psql "$LOCAL_DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' - DROP SCHEMA IF EXISTS auth CASCADE; - DROP SCHEMA IF EXISTS storage CASCADE; - CREATE SCHEMA auth; - CREATE SCHEMA storage; - -- auth.users: FK + trigger target - CREATE TABLE auth.users ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid() + # 진단: 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 충분. @@ -97,7 +96,7 @@ jobs: LANGUAGE sql STABLE AS $$ SELECT NULL::uuid $$; -- storage.buckets: INSERT target (id text, name text, public boolean) - CREATE TABLE storage.buckets ( + CREATE TABLE IF NOT EXISTS storage.buckets ( id text PRIMARY KEY, name text, public boolean DEFAULT false From a307ca356e664320f45dadab9922d30783c3f680 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 28 May 2026 17:30:07 +0900 Subject: [PATCH 7/7] ci: empty commit to re-trigger drift workflow after drift-bypass label add