Skip to content

feat(ci): schema drift PR gate (TS<->DB) — #373 v2#588

Merged
thxforall merged 4 commits into
devfrom
feat/373-schema-drift-pr-gate
May 28, 2026
Merged

feat(ci): schema drift PR gate (TS<->DB) — #373 v2#588
thxforall merged 4 commits into
devfrom
feat/373-schema-drift-pr-gate

Conversation

@thxforall
Copy link
Copy Markdown
Contributor

Summary

Closes #373 v2 (PR gate).

#372 trigger 사례(MAGAZINE_STATUSES 코드 ↔ post_magazines_status_check CHECK constraint drift)를 PR 단계에서 자동 차단.

  • .github/workflows/schema-drift.yml: postgres:17 service + postgresql-client-17 + supabase migrations 순차 적용 + bun script 실행 + sticky PR comment (marocchino/sticky-pull-request-comment@v2, header=schema-drift)
  • scripts/check-schema-drift.ts: ts-morph 로 TS 상수 set 추출 → psqlpg_get_constraintdef 파싱 → set-equality 비교. 세 가지 CHECK 형식 (status::text = ANY ARRAY, col = ANY ARRAY, IN (...)) 지원.
  • docs/database/schema-drift-sot.md: SOT 매핑 SSOT — 신규 enum 추가 시 본 표 + 스크립트의 SOT_MAPPINGS 동시 갱신.
  • docs/database/drift-check.md: v1 nightly + v2 PR gate 채널 분리 명시.

우회

drift-bypass label 을 PR 에 붙이면 step 이 continue-on-error 로 전환되어 job 은 green, drift report 는 comment 에 그대로 남는다. branch protection 으로 머지 차단은 별도 설정 필요.

한계 / v3 후속

  • TS AST 추출은 export const X = [...] as const 패턴만 지원
  • posts.status 는 DB CHECK constraint 가 없어 v2 SOT 미등록 — 별도 issue 권장 (CHECK 추가 마이그레이션 후 등록)
  • generated types (packages/web/lib/api/generated/) drift 는 별도 채널 필요
  • warehouse / auth schema 미검사 (기존 nightly 와 동일)

Test plan

  • 로컬 dry-run: TS AST 추출 + 4가지 constraint 정의 정규식 (status::text=ANY ARRAY, col=ANY ARRAY, IN list, apostrophe escape) 4/4 pass
  • 본 PR 자체에서 workflow 실행 결과 PR comment "OK — 1 SOT entries match" 확인
  • follow-up PR 로 의도적 drift (MAGAZINE_STATUSES'foo' 추가) → workflow fail + DRIFT 표 확인
  • drift-bypass label 붙이고 same PR 재실행 → job green + DRIFT comment 유지 확인

Related

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

thxforall added 4 commits May 28, 2026 14:47
TS 상수 ↔ DB CHECK constraint 매핑 SSOT. scripts/check-schema-drift.ts 의
SOT_MAPPINGS 와 함께 갱신한다. v2 PR gate 의 동작/허용 constraint 형식/
의도적 우회(drift-bypass label)/v3 후속 항목(posts.status, generated types)
명시.
…v2)

ts-morph 로 TS 상수 (`export const X = [...] as const`) 값 set 추출 →
psql 로 pg_get_constraintdef 파싱 → set-equality 비교. 세 가지 CHECK
constraint 형식 (status::text=ANY ARRAY, col=ANY ARRAY, IN list) 지원.
drift 1건 이상이면 exit 1, stdout 은 PR comment 용 markdown report.

devDependency: ts-morph@^24.0.0 추가.
postgres:17 service + postgresql-client-17 + bun install + supabase
migrations 순차 적용 + check-schema-drift.ts 실행 + sticky PR comment
(marocchino/sticky-pull-request-comment@v2, header=schema-drift).

drift-bypass label: continue-on-error 로 job green 유지 (comment 는 유지).
branch protection 별도 설정 필요.
v1 nightly 와 v2 PR gate 채널을 분리 명시. v2 세부는 schema-drift-sot.md
에서 별도 owning.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
decoded-app Ready Ready Preview, Comment May 28, 2026 5:50am

@thxforall thxforall moved this from Todo to In Progress in decoded-monorepo May 28, 2026
@thxforall thxforall merged commit 001d3e8 into dev May 28, 2026
10 of 11 checks passed
@thxforall thxforall deleted the feat/373-schema-drift-pr-gate branch May 28, 2026 06:20
@github-project-automation github-project-automation Bot moved this from In Progress to Done in decoded-monorepo May 28, 2026
thxforall added a commit that referenced this pull request May 28, 2026
Conflicts resolved:
- AGENTS.md: take origin/dev base (new structure/sections), preserve HEAD's
  "## Commit discipline" section, bump Last Updated to 2026-05-28
- packages/web/package.json: take both
  - scripts: notion:*/telegram:* (HEAD) + content-studio:video:local (dev)
  - deps: @notionhq/client (HEAD) + @next/third-parties (dev)
- bun.lock: take origin/dev, then `bun install` to regen with both sets of deps
  (added ts-morph@24.0.0 from #588 merge)
thxforall added a commit that referenced this pull request May 28, 2026
* fix(ci): #373 drift gate — pgvector image, pipefail, sticky comment path

#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

* fix(ci): #373 drift gate — use supabase/postgres image for all required extensions

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 위험 제거가 우선.

* fix(ci): #373 drift gate — use default postgres DB to avoid supabase init permission issue

#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 적용.

* fix(ci): #373 drift gate — bootstrap supabase namespace stubs

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) <noreply@anthropic.com>

* fix(ci): #373 drift gate v5 — drop+recreate auth/storage schemas

이전 v4 (commit 9f89ca4) 는 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) <noreply@anthropic.com>

* fix(ci): #373 drift gate v6 — connect as supabase_admin + diagnostic

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) <noreply@anthropic.com>

* ci: empty commit to re-trigger drift workflow after drift-bypass label add

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thxforall added a commit that referenced this pull request May 28, 2026
* docs(database): add schema-drift-sot.md SSOT mapping (#373 v2)

TS 상수 ↔ DB CHECK constraint 매핑 SSOT. scripts/check-schema-drift.ts 의
SOT_MAPPINGS 와 함께 갱신한다. v2 PR gate 의 동작/허용 constraint 형식/
의도적 우회(drift-bypass label)/v3 후속 항목(posts.status, generated types)
명시.

* feat(scripts): add check-schema-drift.ts for TS<->DB enum drift (#373 v2)

ts-morph 로 TS 상수 (`export const X = [...] as const`) 값 set 추출 →
psql 로 pg_get_constraintdef 파싱 → set-equality 비교. 세 가지 CHECK
constraint 형식 (status::text=ANY ARRAY, col=ANY ARRAY, IN list) 지원.
drift 1건 이상이면 exit 1, stdout 은 PR comment 용 markdown report.

devDependency: ts-morph@^24.0.0 추가.

* feat(ci): add schema-drift.yml PR gate workflow (#373 v2)

postgres:17 service + postgresql-client-17 + bun install + supabase
migrations 순차 적용 + check-schema-drift.ts 실행 + sticky PR comment
(marocchino/sticky-pull-request-comment@v2, header=schema-drift).

drift-bypass label: continue-on-error 로 job green 유지 (comment 는 유지).
branch protection 별도 설정 필요.

* docs(database): drift-check.md mark v2 PR gate landed (#373)

v1 nightly 와 v2 PR gate 채널을 분리 명시. v2 세부는 schema-drift-sot.md
에서 별도 owning.
thxforall added a commit that referenced this pull request May 28, 2026
MAGAZINE_STATUSES 에 'foo' 추가 — DB CHECK constraint 와 의도적 drift 발생.

목적:
- #588 에서 머지된 schema-drift PR gate workflow 의 red path 검증
- 기대 결과: workflow fail + sticky comment 에 TS-only: foo 표기

부수 효과:
- packages/web/lib/api/admin/__tests__/magazines.test.ts hardcoded toEqual fail
  (lint/test workflow 도 red — 무관)

검증 완료 후 PR close + branch 삭제 예정.
thxforall added a commit that referenced this pull request May 28, 2026
* feat(ci): schema drift PR gate (TS<->DB) — #373 v2 (#588)

* docs(database): add schema-drift-sot.md SSOT mapping (#373 v2)

TS 상수 ↔ DB CHECK constraint 매핑 SSOT. scripts/check-schema-drift.ts 의
SOT_MAPPINGS 와 함께 갱신한다. v2 PR gate 의 동작/허용 constraint 형식/
의도적 우회(drift-bypass label)/v3 후속 항목(posts.status, generated types)
명시.

* feat(scripts): add check-schema-drift.ts for TS<->DB enum drift (#373 v2)

ts-morph 로 TS 상수 (`export const X = [...] as const`) 값 set 추출 →
psql 로 pg_get_constraintdef 파싱 → set-equality 비교. 세 가지 CHECK
constraint 형식 (status::text=ANY ARRAY, col=ANY ARRAY, IN list) 지원.
drift 1건 이상이면 exit 1, stdout 은 PR comment 용 markdown report.

devDependency: ts-morph@^24.0.0 추가.

* feat(ci): add schema-drift.yml PR gate workflow (#373 v2)

postgres:17 service + postgresql-client-17 + bun install + supabase
migrations 순차 적용 + check-schema-drift.ts 실행 + sticky PR comment
(marocchino/sticky-pull-request-comment@v2, header=schema-drift).

drift-bypass label: continue-on-error 로 job green 유지 (comment 는 유지).
branch protection 별도 설정 필요.

* docs(database): drift-check.md mark v2 PR gate landed (#373)

v1 nightly 와 v2 PR gate 채널을 분리 명시. v2 세부는 schema-drift-sot.md
에서 별도 owning.

* docs(design-system): v2.2.0 동기화 — content fundamentals, animations, dual palette (#589)

* docs(design-system): bump README to v2.2.0 with content fundamentals + dual palette

- Version: 2.1.0 -> 2.2.0, Last Updated: 2026-05-28
- Add v2.2 변경사항: Content Fundamentals, Animations 카탈로그, Dual Palette,
  Magazine palette tokens (#573), Navigation Facts, Home sequence,
  IBM Plex Mono (inline), DecodedLogo WebGL
- Document Index: 신규 Foundation (v2.2) 섹션 — content-fundamentals.md,
  animations.md 추가
- Tech Stack table 확장: OKLCH + Hex magazine palette, typography 4종,
  three.js + @chenglou/pretext (DecodedLogo)
- Navigation Facts + Home Page Sequence 섹션 추가 (app/page.tsx:568 앵커)

Closes #572 (part 1/7).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(design-system): add content-fundamentals.md

7 sections covering Voice / Casing / Person / Language / Pricing /
Numbers / Emoji policy with Do/Don't rules and code anchors:

- Pricing: ko-KR locale + ₩ prefix, toLocaleString
  (앵커: DecodedSolutionsSection.tsx:40 — 마이그레이션 대상)
- Numbers: 사용자 표면은 toLocaleString 통과
  (앵커: lib/utils/format.ts:36)
- Voice: editorial(큐레이션) vs product UI(명료) 분리
- Language: ko-KR 기본, 고유명사·기술 용어 원문 유지
- Emoji: editorial 표면 금지, product UI 절제, Lucide 우선

Closes #572 (part 2/7).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(design-system): add animations.md catalog

15+ CSS keyframe catalog with file/line anchors from
packages/web/app/globals.css:

- dash-flow (L299), hologram-* family (L318-L351), spot-* family
  (L410-L458), slide-up (L487), shimmer (L503), ai-summary-* (L522-L535),
  card-glow (L551), marquee-c (L585)
- duration/easing/용도/reduce-motion 컬럼
- Easing 함수 정리: ease-out (단방향 reveal), ease-in-out (펄스),
  linear (무한 flow), cubic-bezier(0.22, 1, 0.36, 1) — observe pattern,
  cubic-bezier(0.16, 1, 0.3, 1) — ai-summary smooth reveal
- prefers-reduced-motion 정책: 4개 미디어 쿼리 블록 + .js-observe
  transition fallback
- 사용 예시: spot 마커, skeleton shimmer, ai-summary, .js-observe

Closes #572 (part 3/7).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(design-system): patterns.md add 5.4 dual palette usage

* docs(design-system): tokens.md add magazine palette sub-table

* docs(agent): rewrite design-system-llm.md as v2.2.0 1-pager SSOT

* docs(agent): design-system-summary.md add v2.2.0 entry

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(content-studio): Video Generation Pipeline — AI 영상 합성 통합 (#592)

* feat(content-studio): add xAI scene video pipeline

* docs(content-studio): plan video job pipeline

* docs(content-studio): harden video job plan

* feat(content-studio): add video job pipeline

* feat(content-studio): abstract video providers

* feat(content-studio): select video mode per scene

* feat(content-studio): compose final video exports

* feat(content-studio): resume persisted video jobs

* feat(content-studio): attach governance to video jobs

* fix(content-studio): strengthen short-form video prompts

* feat(content-studio): add local video download runner

* feat(content-studio): save source image for local videos

* fix(content-studio): split long local video scenes

* feat(content-studio): compose local video clips

* fix(magazines): MAGAZINE_STATUSES — 'generating' 누락 복원 (#373 drift)

DB constraint post_magazines_status_check 는 'generating' 포함 5-state 인데
TS MAGAZINE_STATUSES 는 4-state. 1주일+ 잠재한 schema drift.

#373 drift gate fix (#595) 가 처음 정상 작동하며 발견.

발생 경위:
- 20260430120000: 'generating' 추가 (api-server generate handler 초기 INSERT)
- 20260430150000: 4-state 로 축소 (TS 와 동기화)
- 20260504133842: 'generating' 복원 (api-server insert + UI 필요)
- 마지막 migration 의 TS 동기화 누락 → drift

magazines.ts 의 MAGAZINE_STATUSES 에 'generating' 추가 (DB 와 같은 순서).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant