PR 전 사전검증과 진단용 로컬 전체 검증
2026-09-15 개정(오너 지시, #1661·#1659) — 이 문서가 설명하는
ci:precheck-local·ci:full-local·ci:local은 QA1 소유 실행기(scripts/ci-local.mjs)였고 QA1 퇴역으로 더는 실행할 수 없다(npm 명령은 #1661 잔여 PR에서 삭제). PR 전 precheck는 하지 않는다. 승격 검사 3단계 — ① 작업 PR→release는 큐 Merge Check만(PR 전 precheck 폐기) ② release→staging은 GitHub Full CI precheck 레인(static-checks+migration-smoke) ③ staging→main은 QA2 레인(qa2-product-contracts)만. QA1은 완전히 퇴역(어느 워크플로도 실행하지 않음). 아래 본문은 그 개정 전 기록이며, Full CI 실패 수리 절차(승격 레인에 그대로 적용)와 QA2 승격 게이트 절만 현행이다. 정본 = 릴리스 프로세스.
QA1 테스트 저장소 분리 (2026-09-14)
2026-09-15 개정(오너 지시, #1659) — v0.19.3 출시부터 QA1은 완전히 퇴역한다(워크플로 어디서도 자동 실행되지 않음). 승격 CI는 두 레인뿐이다: release→staging = precheck 레인(
static-checks+migration-smoke: 마이그레이션 전체 재적용·schema.sql 대조), staging→main = QA2 레인(qa2-product-contracts)뿐. 동일 tree 성공 기록 재사용, QA1 단위·브라우저·viewport·e2e-evidence 잡, 배포 뒤 QA1 CRUD smoke 잡(smoke (staging/production)),prod-smoke.yml·social-login-daily-check.yml, 앱의qa1.lock.json·scripts/qa1.mjs·npm run test·smoke:*·QA1 npm 자동 훅은 모두 제거됐다.ci:precheck-local·ci:full-local·merge:request --dry-run의 QA1 단계(단위·pgTAP·tests/db·e2e)는 더 이상 준비되지 않아 실행되지 않는다. 아래 본문은 그 이전 기록이다.
앱 #1591·QA1 #1의 사용자 결정으로 기존 Full CI 테스트·fixture·helper·검사 전용 실행기를 비공개 dekerd/Barbelic-QA1로 이관한다. 앱은 qa1.lock.json의 고정 커밋과 suite tree를 검사한 뒤 기존 경로에 실행용 파일을 준비하며, npm run check·ci:precheck-local·ci:full-local -- --only <단계>의 호출과 검사 범위는 유지한다. 아래의 tests/·검사 전용 스크립트 경로는 이관 후 QA1의 suite/ 아래 원본에 대응한다. 제품 코드·DB 마이그레이션·빌드·배포의 소유자는 계속 앱이다.
검증 기록은 앱 후보 SHA/tree뿐 아니라 QA1 커밋·suite tree와 실행 환경을 식별해야 한다. QA1 버전이 바뀌면 이전 테스트 조합의 성공을 새 조합의 성공으로 재사용하지 않는다. 저장소 분리 자체는 Full 실행 승인이나 비용 절감 증거가 아니다. 명시적으로 승인된 범위의 원격 실행만 수행하며, 로컬 부분 실행·미실행 범위와 실제 Full 실행을 구분한다. 설정·실행·이관 상태의 상세 안내는 QA1 테스트 저장소를 따른다.
QA2 승격 게이트 (2026-09-15)
앱 #1604의 사용자 지시로 GitHub Full CI에 qa2-product-contracts job이 추가되어 verify의 필수 선행 job이 되었다. release→staging 승격 PR과 staging→main 릴리스 PR은 QA1 lane과 함께 QA2(비공개 dekerd/Barbelic-QA2, 앱 qa2.lock.json에 고정)를 통과해야 한다. staging→main의 같은 tree 재사용 규칙은 그대로이며 그 기록은 QA2가 포함된 실행에서만 만들어진다.
로컬 재현은 ci:full-local이 아니라 QA2 checkout에서 npm run qa:ci -- --app <앱 checkout> --revision <커밋>으로 한다(소유 DB·인증/REST 컨테이너·제품 빌드·전체 목록·정리를 한 명령이 수행). 빨간불 처리와 버전 고정 방법은 QA2 승격 게이트를 따르며, 아래 Full CI 실패 수리 절차가 그대로 적용된다.
Full CI 실패 수리와 재실행 (2026-09-11)
사용자 결정: 최종 후보에 Full CI를 모으는 정책은 이미 적용했지만, 실패한 테스트 하나를 고칠 때마다 전체를 다시 실행하며 Actions 비용을 쓰는 방식을 금지한다. Codex·Claude와 그 밖의 모든 작업자에게 적용한다. Full CI는 최종 통합 검증이며 반복적인 오류 탐색 도구가 아니다.
- 실패 전체를 수집한다. 이전 실행의 모든 실패 잡·단계·로그·산출물을 확인하고, 제품 결함·테스트 결함·환경/인프라 문제·원인 미확인으로 구분한다. 먼저 보인 오류 하나만 읽고 재실행하지 않는다. 선행 실패로 실행되지 않은 검사는 미검증으로 남긴다. 이 절차를 무관한 테스트 감사나 추가 수리로 확대하지 않는다.
- 해당 범위를 로컬에서 재현한다. 실패 당시 후보와 수정 후보의 차이를 확인하고 CI의 Node·DB·브라우저 버전 및 관련 설정을 맞춘다. 개별 실패 테스트부터 해당 검사 묶음까지 필요한 범위만 실행한다. 앱은 기존
npm run ci:full-local -- --only <단계>및 관련 테스트 명령을 사용한다. 로컬 전체 CI를 반복하는 방식으로 대체하지 않는다. - 알려진 실패를 모아서 해결한다. 이전 실행에서 확인된 모든 실패의 원인·조치·관련 검증 결과를 정리한 뒤 재실행한다. 선행 실패 때문에 가려졌던 같은 묶음의 후속 검증도 로컬에서 확인한다. 실패했다는 이유만으로 테스트가 틀렸다고 단정하거나, 제품 요구사항 확인 없이 단언 완화·skip·재시도 증가로 통과시키지 않는다.
- 재실행을 일으키는 행동 전에 근거를 남긴다. 수동 Full CI, 전체 재실행, 진단용
merge:request --dry-run뿐 아니라 열린 승격 PR의 head 갱신처럼 자동 Full을 유발하는 반영도 포함한다. 담당자는 아래 최소 기록을 기존 이슈/PR 작업 기록에 남기고, 자기 권한 범위에서 수정들을 준비한 뒤 반영한다. 개별 수리 작업자는 관련 로컬 검증을 마치고 기존 precheck/Merge Check 경로로 통합하며, 릴리스 담당자는 승인된 담당 경계 안에서 승격 후보 갱신을 조율한다. 타인 브랜치·큐 조작이나 검증 우회는 하지 않는다. - 실패한 범위를 우선 재실행한다. 코드·head/base와 필요한 검증 입력이 같고 기존 성공 증거가 유효한 재시도는 실패한 잡과 필요한 후속 집계만 재실행한다. 수정으로 후보가 바뀌면 옛 실행의 재시도를 새 코드 검증으로 취급하지 않으며, 필수 precheck와 현재 후보의 최종 Full 검증/유효한 결과 재사용 기준을 유지한다.
- 반복되면 진단을 바꾼다. 같은 유형의 실패가 다시 나오면 자동적인 Full 반복을 멈추고 재현 누락·환경 차이·관련 원인을 확인한다. 로컬 재현이 불가능하면 이유, 이미 확인한 범위, 원격에서만 확인할 가설과 최소 실행 범위를 명시한다. 결제·사용 한도·권한 장애는 해소 증거 없이 반복 실행하지 않는다. 기존 승인 범위 안의 수리·필수 검증은 계속하며, 새로운 비용 설정이나 실제 오너 전용 조치만 별도로 요청한다.
재실행 전 최소 기록
별도 승인 장부를 만들지 않고 기존 이슈/PR 기록에 다음을 적는다.
- 이전 실행 링크·attempt·검증 후보 SHA 및 이번 후보와의 차이.
- 실패 목록별 원인(미확인이면 명시)·수정·로컬 검증 명령/결과·검증한 커밋.
- 남은 미검증 범위, 로컬 재현 불가 사유와 원격에서 확인할 가설(해당 시).
- 이번 실행 범위와 필요한 이유. 전체 실행이면 부분 실행/기존 성공 결과 재사용으로 충족되지 않는 이유.
로컬에서 재현 가능했는데 빠뜨린 실패는 기존 기준대로 사전 검증 누락으로 기록한다. 부분 검증은 Full 성공으로 보고하지 않는다.
적용 수준: 이 변경은 공통 지침과 작업 절차다. GitHub Actions 트리거·재실행 API를 기계적으로 차단하는 기능을 추가한 것은 아니다. 그러한 강제가 필요하면 기존 실행 진입점에서 증거·후보·중복 실행을 확인하는 별도 구현이 필요하며, 이 문서 변경을 그 구현의 승인이나 완료로 해석하지 않는다. 기존 세션이 수정 파일을 자동으로 다시 읽었다고 가정하지 않는다.
2026-09-10 승인 정책 (#1491, v0.17.8 대상): 작업 세션은
ci:precheck-local, 작업→release 큐는 Merge Check만 수행한다. 최종 release→staging 후보에서 full CI를 실행하고, staging→main은 동일 tree의 유효한 full 성공 기록과 현재 staging 배포·smoke 증거를 확인해 재사용한다. 명시적merge:request --dry-run은 진단용 full 실행을 유지한다. 앱 main6afe58ec에 설치됐으며 정상 큐의 실제 실행 시간과 운영 배포 성공은 아직 확인 전이다. 상세 상태는 적용 기록에서 구분한다. 아래 과거 전환 기록보다 이 절차가 우선한다.
v0.17.7 표시 이름 — #1470: 유저 실사용 시뮬레이션(
browser-journeys, 로컬e2e-browser)과 화면 정합성 검사(viewport-matrix, 로컬e2e-viewport)를 실행 계획·진행 로그·요약·증거 집계에 동일하게 쓴다.--only browser·--only viewport와 실행 명령은 유지한다. 문서 게시와 앱 실행 설정의 적용 시점은 구분한다.
2026-09-09 개정 (이슈 #1456·#1457·#1458, 오너 결정) — 명령·워크플로 이름이 바뀌었다. 작업 PR 통합 요청
npm run merge:request(구 landing:request), PR 전 사전 검증npm run ci:precheck-local(최신 release 합치기 → verify → 서버 변경 시 DB 단계, 브라우저 없음), 전체 검증npm run ci:full-local(릴리스 큐 runner 안에서만; 세션은--only <단계>재현만). 워크플로 표시 이름:3-Release Merge Request(release-merge-request.yml) ·GitHub Full CI(policy-contract.yml) ·1-Production Deploy/2-Staging Deploy(production-deploy.yml·staging-deploy.yml, 단계 정의는 deploy-steps.yml) ·Production smoke (manual)·Social login daily check·Daily data backup.landing:lock·db:preflight스크립트는 폐기됐다(GitHub concurrency 가 잠금, DB 검사는 사전 검증과 큐가 한다). 아래 본문의 옛 이름은 그 개정 전 기록이다.
v0.17.7 release 병합 완료: #1464 병렬 로컬 CI 운영에 기본 4개·사용 후 종료·유휴 회수와 최종 full CI 7분 32초 통과를 기록했다. Production 반영과 trusted main의 큐 전 prune 활성화는 별도로 확인한다.
#1463 저장소 분리 이후 적용: 아래 앱 release·DB·데이터 보호 규칙은 앱 저장소에 적용한다. 과거 docs/admin 결합 빌드·같은 SHA 배포·앱 main 문서 직행·문서 문자열 검사 설명은 이관 전 기록이다. 모든 문서와 작업 기록은 dekerd/Barbelic-docs에서 관리하며, 현행 저장소 경계와 문서·관리자 독립 운영이 그 부분을 대체한다. 세션 제목은 현재 Phase/전체 Phase 규칙을 따른다. 코드 준비·원격 활성화·실제 배포 성공은 독립 운영 문서의 기록으로 구분한다.
최신 main 기준의 순수 문서 PR은 main에 직접 병합할 수 있으며, 오너가 CI 생략을 요청하면 이 절차도 생략한다. 실행 코드·DB·workflow·배포/빌드 설정이 포함되면 예외가 아니다. 상세 기준은 릴리스 프로세스의 순수 문서 예외를 따른다.
이 문서는 개발 세션과 self-hosted runner가 사용하는 로컬 검증 명령의 절차다. 작업 → release의 최종 검증·병합은 릴리스 랜딩 큐의 같은 GitHub 슬롯에서 실행한다. 실행 시점과 두 원격 승격 CI의 정본은 릴리스 프로세스다. 명령은 이슈 #1225에서 도입했다.
full 실행 시점과 결과 보고
2026-09-15: full CI와 동일 tree 성공 기록 재사용은 폐기됐다. 승격 ①은 precheck 레인, 승격 ②는 QA2 레인이며 아래는 기록이다.
#1491의 2026-09-10 승인 정책에 따라 작업 PR 전에는 ci:precheck-local을 수행하고 일반 작업→release 큐는 최신 후보의 Merge Check만 수행한다. 일반 큐의 성공은 full 성공이 아니며, full 기록을 조회·재사용·발급하지 않는다. 최종 release→staging 후보는 hosted full CI를 실행한다.
staging→main은 동일 파일 내용·경로·모드의 Git tree에 유효한 full 성공 기록이 있고 현재 staging 배포·smoke 증거가 있을 때 재사용한다. 기록 없음·무효·조회 오류는 full을 실행한다. 기록 형식·원 Actions 실행의 성공과 차수 검증은 릴리스 프로세스를 따른다.
명시적 수동 full과 큐 --dry-run은 항상 full을 실행하며 dry-run은 병합하지 않는다. npm run check, --only, --verify-only, --plan, 과거 preflight 파일은 full 성공을 대신하지 않는다. 재사용한 승격은 새 full 성공 기록을 만들지 않는다.
로그·PR에는 개별 사전검증, release Merge Check, 실제로 실행한 full, 재사용한 tree·원 full 링크·현재 staging 배포/smoke 확인을 구분한다. 일반 release 병합이나 일부 검사·재사용을 이번 실행의 full 통과로 적지 않는다. 앱 main 반영과 실제 실행 확인은 적용 기록을 따른다.
왜 이 절차인가
CI(.github/workflows/policy-contract.yml)는 한 번 돌면 11분이고, 빨간불이면 고치고 또 11분이다. 2026-09-04 직전 두 PR(#1219·#1223)에서 CI가 합쳐 9번 빨간불이 났는데 앱 결함은 0건이었고 전부 pgTAP·e2e 쪽이었다. 그 실패들은 이 PC의 로컬 Supabase 스택에서 PR 전에 똑같이 재현할 수 있었지만, "한 번에 도는 명령"이 없어 세션마다 8~10개 명령을 손으로 조립하다가 건너뛰고 CI를 첫 실행으로 쓰고 있었다.
개발 중에는 변경 관련 테스트로 확인하고 PR 전에 사전검증을 수행한다. merge:request 큐는 최신 release와 후보를 합쳐 Merge Check를 마친 뒤 순차 병합한다. 최종 release→staging 후보에서 hosted full CI를 실행하므로 여러 작업의 통합에서 생긴 문제는 이 시점에 발견될 수 있다. staging→main 재사용은 같은 tree의 유효한 성공 기록과 현재 staging 배포·smoke 증거를 요구한다.
명령 (2026-09-10 개정 — 2026-09-15 QA1 퇴역으로 실행 불가, 기록)
# PR 을 열기 전, 마지막 Phase 가 끝난 워크트리에서(커밋된 상태로):
npm run ci:precheck-localci:precheck-local 은 ① HEAD 와 가장 가까운 origin/release/vX.Y.Z 를 새로 받아 이 브랜치에 합치고(충돌이면 파일 목록을 보여 주고 멈춘다 — 여기서 해결해야 큐 슬롯을 낭비하지 않는다) ② verify 묶음(정적 검사·단위 테스트·빌드)을 돌리며 ③ supabase/·scripts/sql|migrations/·tests/db/ 를 건드렸으면 DB 단계(샌드박스 재생·스냅샷 검사·pgTAP)를 붙인다. 브라우저·viewport 단계는 돌리지 않으므로 미리보기 포트를 쓰지 않고, 세션 여럿이 동시에 돌려도 된다. 마지막 줄 검증: ci:precheck-local … 을 PR 본문에 붙인다. 대상 release 를 직접 고르려면 -- --release origin/release/vX.Y.Z.
일반 release 병합 요청은 ci:full-local을 실행하지 않는다. 병합 없이 전체를 진단하려면 npm run merge:request -- --pr <N> --dry-run을 명시한다. 이때 큐 runner가 최신 base와 PR head의 두 부모 후보에서 ci:full-local --base <고정 SHA>를 실행한다. 세션에서 전체 명령을 직접 실행하면 안내 후 종료하는 제한과 포트 4173을 유지한다. 실패 단계만 재현하려면 npm run ci:full-local -- --only browser를 사용한다. 부분 실행은 워크트리별 포트를 사용하며 full 성공을 대신하지 않는다.
| 옵션 | 하는 일 |
|---|---|
--precheck (= ci:precheck-local) | 최신 release 합치기 → verify → 서버 변경 시 db. 브라우저 없음 |
--release <origin/release/vX.Y.Z> | 사전 검증이 합칠 release (기본: HEAD 와 공통 조상이 가장 최근인 release/*) |
(없음, 구 ci:local) | 바뀐 파일로 CI 범위(verify-only / full)를 판정 — full 이면 큐 안이 아닌 한 안내만 하고 끝 |
--plan | 판정과 실행 계획만 출력하고 끝. 어떤 파일이 full을 켰는지 확인할 때 |
--full | full 강제 (CI의 full-ci 라벨과 같음). 문서만 바뀐 경우에도 전체를 확인할 때 |
--verify-only | verify 묶음만 |
--only <단계,…> | 일부 단계만. 단계 이름: verify, db, local, empty, cardio, browser, viewport |
--base <ref> | 기준 커밋 지정 (기본: origin/main과의 공통 조상) |
--sandbox <폴더> | 샌드박스 위치 지정 (기본: BARBELIC_DB_SANDBOX 환경변수 → 없으면 임시 폴더 barbelic-ci-local/<체크아웃 해시>) |
--stop | 샌드박스 스택 종료 |
종료 코드: 0 전부 통과 · 1 검증 실패 · 2 사용법 오류 · 3 환경 준비 실패(Docker·CLI·포트·줄바꿈).
테스트 시간 제한 — v0.17.7 통합 검증 통과
2026-09-09 오너 승인, 앱 이슈 #1468. 아래 기준은 해당 앱 변경이 포함된 실행부터 적용한다. 문서 게시만으로 기존 release·운영 실행 설정이 바뀌지는 않는다.
개별 CASE의 승인에는 테스트케이스 품질관리 기준 15개를 함께 적용한다. 아래 시간·재시도 제한은 그중 번호 11~13이며, 격리·단언·결함 탐지·정리 증거를 대신하지 않는다.
2026-09-10 KST, 통합 PR #1484의 실제 후보 e56604cb / tree 12d339cd에서 전체 CI가 통과했다. 브라우저 여정 56건·화면 검사 22건 모두 실패·skip·flaky 0이며 release/v0.17.7과 staging에 병합됐다. 이 시점의 Production 배포 완료를 뜻하지 않는다. 작업 기록에 최초 실패와 보완·통합 결과를 구분했다.
| 대상 | 제한 |
|---|---|
| Node·Playwright 테스트 1건 | 개별 준비와 본문 실행 합계 60초 |
| 클릭·입력 대상, 화면 전환·내용 확인 | 대기 1회당 최대 10초 |
| 페이지 접속·새로고침·이동 | 대기 1회당 최대 10초 |
| 폰트·유한 애니메이션·배치 안정화 | 하나의 제한 안에서 최대 10초 |
| 테스트 자동 재시도 | 0회 |
- 모든 동작 대기는 케이스 전체 예산 안에서 실행한다. 제한을 늘리거나 실패를 skip·성공으로 바꾸지 않는다. 60초를 넘는 검사는 기존 단언과 시나리오 연결을 보존하면서 필요한 경우에만 독립 케이스로 나눈다.
- Node 개별 준비는
test본문에 둔다. nativebeforeEach는 케이스 타이머 밖이므로 개별 준비를 거기로 옮겨 예산을 우회하지 않는다.package.json의 테스트 명령과 직접 Node DB 실행은tests/support/registerNodeTestBudget.mjs를 먼저 읽어 케이스 등록에 상한을 적용한다. Node 22의--test-timeout은 파일 전체를 끊으므로 이 용도로 쓰지 않는다. - 파일·suite 전체의 누적 시간, 공통 빌드·DB 기동·공유 준비, 사후 정리는 개별 케이스와 별도다. pgTAP SQL과 CI job의 기존 단계 제한은 이번 Node·Playwright 변경으로 대체되지 않는다. Node 타이머는 동기 CPU 작업을 강제로 종료하는 장치가 아니다.
- 브라우저 기본값은 세 Playwright 설정과 Node 기반 브라우저 검사의 context/page 기본값에 적용한다. 없는 클릭 대상의 실제 10초 실패·재실행 0회, Node22/24의 케이스 취소·정리·다음 케이스 실행을 회귀 검사로 확인한다.
- 정상 CI 전체의 시간 절감률은 같은 조건의 성공 실행으로 측정한다. 실패 포함 실행에서 짧아진 대기를 전체 정상 성능으로 일반화하지 않는다.
CI와 어떻게 같은가
| CI 잡 | ci:local | 같음을 보장하는 것 |
|---|---|---|
| scope — 바뀐 파일로 verify-only/full 판정 | 같은 scripts/ci-scope-policy.mjs로 판정. 실행 코드·설정·의존성·테스트·DB·미분류 경로는 전체 실행, 명시된 비실행 문서 경로만 예외. 커밋 전 작업물과 미추적 파일까지 본다 | 같은 코드 |
static-checks·unit-tests — check:policy-snapshots → check:legal-documents → check → check:unused → build | 같은 순서로 npm run. POLICY_BASE_REF·LEGAL_BASE_REF·MIGRATION_BASE_REF에 기준 커밋을 넣는다 | tests/react/ciLocal.test.mjs가 워크플로 원문의 npm run 순서와 대조 |
로컬 스택 준비(composite action local-supabase-stack) — 도구 버전 읽기 → CLI → 이미지 핀 → supabase start -x … → 이미지 검사 | 레포 밖 샌드박스에서 같은 옵션·같은 핀으로 | 같은 테스트가 -x 목록·핀 참조를 action 파일과 대조 |
migration-smoke — 준비 → db reset --local --no-seed → build-schema-snapshot.mjs --check(supabase/schema.sql이 방금 재생한 DB의 덤프와 바이트까지 같은지, 이슈 #1252) → supabase test db → test:e2e-local → test:e2e-empty → test:e2e-cardio | 같은 순서로(--check는 샌드박스의 컨테이너를 본다) | 같은 테스트가 reset 옵션·--check 위치·순서를 대조 |
유저 실사용 시뮬레이션(browser-journeys) — 준비 → test:e2e-browser (CI는 4개 독립 스택 샤드로 병렬, 자동 번호 CASE 57개) | 스택이 하나라 직렬로 전부. 같은 로컬 Auth/DB/Edge·앱/관리자 번들과 필수 local 프로필·JWT signer, CI=true로 플레이키 실패·기존 서버 재사용 금지까지 CI와 같다. #1468 적용 시 자동 재시도는 1회에서 0회로 바뀐다 | 같은 테스트가 순서를 대조 |
화면 정합성 검사(viewport-matrix) — 준비 → test:e2e-viewport | 같은 환경변수·CI=true | 같은 테스트가 순서를 대조 |
실제 full을 실행하는 승격 PR의 verify는 scope·정적·unit·DB·browser·viewport·e2e-evidence를 모두 기다린다. staging→main 재사용 경로는 유효한 기록과 full 잡의 의도된 skip을 확인한다. 로컬 full도 browser와 viewport가 끝나면 같은 증거 집계기를 실행한다. 사전 목록 57+14, 동일 SHA/run, 첫 시도 통과, 실제 여정 완료와 정리 증거를 요구하며 누락·skip·retry-only 통과는 실패다. 실제 provider QA CASE003은 별도 조건부 실행이다. 집중 실행(--only)은 full 통과를 대신하지 않는다.
#1463 이후 문서·관리자 검사는 문서 저장소의 독립 CI에서 수행한다. 앱 로컬 full·release 큐·승격 full에 docs/admin 빌드를 추가하지 않는다.
워크플로 파일을 고치면 이 스크립트도 같이 고친다. 단계 목록이 어긋나면 tests/react/ciLocal.test.mjs(npm run check에 포함)가 실패한다.
샌드박스 스택
- 위치: 레포 밖 임시 폴더(
os.tmpdir()/barbelic-ci-local/<체크아웃 경로 해시>). 체크아웃(워크트리)마다 다른 폴더·다른project_id(cil+ 해시)·다른 포트를 쓰므로 여러 세션이 동시에 돌려도 서로의 DB를 지우지 않는다. - 그 안의
supabase/config.toml은 스크립트가 만들고,supabase/migrations·supabase/tests는 이 체크아웃으로 연결(junction)한다. 레포 안의supabase/config.toml은 건드리지 않는다 —project_id원문을 단언하는 테스트가 있어 그걸 바꾸면npm run check가 빨개진다. - 한 번 뜬 스택은 다음 실행에서 재사용한다(기동 시간 절약).
db reset이 매번 빈 DB에서 마이그레이션을 다시 적용하므로 재사용해도 결과는 같다. 끝내려면--stop. schema.sql 스냅샷 --check가 실패하면 마이그레이션 내용이 바뀌었는데supabase/schema.sql을 다시 만들지 않은 것이다. 요약에 적힌 대로npm run schema:snapshot -- --sandbox <샌드박스>(reset 직후라--reset불필요)로 다시 만들어 커밋한다. 손으로 고치지 않는다.
Postgres 이미지와 CLI 버전은 Production에 맞춘다 (이슈 #1233)
세 환경이 같은 Supabase Postgres 이미지 위에서 테스트해야 결과가 어긋났을 때 원인을 찾을 수 있다. 정본은 package.json의 supabaseToolchain 상수 하나다.
| 키 | 값의 뜻 | 누가 읽나 |
|---|---|---|
cli | supabase CLI 버전 | CI(setup-cli)와 ci:local(설치본이 다르면 종료 코드 3) |
postgresVersion | Postgres 이미지 태그 = Production 프로젝트의 버전(Management API database.version) | CI와 ci:local이 supabase start 전에 supabase/.temp/postgres-version에 써서 그 이미지로 띄우고, 뜬 컨테이너의 태그가 다르면 실패 |
supabase start는supabase/.temp/postgres-version이 있으면 그 태그의 이미지를 쓴다(supabase link가 링크된 프로젝트에 남기는 것과 같은 파일). 레지스트리(ghcr.io / public.ecr.aws)는 CLI 버전에 따라 다르므로 태그만 비교한다.- Production이 올라가면(Supabase 대시보드에서 오너가 올린 뒤)
postgresVersion만 새 값으로 바꾼다. CI 1회로 새 빌드에서의 통과를 확인한다. - CLI를 올리고 싶으면
cli를 바꾸고 이 PC의 설치본도 맞춘다(scoop install supabase@<버전>). - 샌드박스가 이미 다른 이미지로 떠 있으면
ci:local이 멈추고 안내한다 —npm run ci:local -- --stop(볼륨까지 삭제) 뒤 다시 실행하면 핀 이미지로 새로 뜬다.
(기록) 랜딩 잠금 preflight 기록 (이슈 #1224)
2026-09-09 이전에는 마이그레이션 묶음의 파일 잠금(landing:lock acquire)이 이 워크트리의 .git/landing-preflight.json(로컬 pgTAP 통과 기록, db:preflight 가 씀)을 요구했다. 잠금과 db:preflight 는 폐기됐다 — 현행 DB 검사는 사전검증(ci:precheck-local의 서버 변경 시 DB 단계)과 최종 승격 full에서 수행한다. 일반 release 큐는 DB 검사를 다시 실행하거나 사전검증 기록을 새 게이트로 요구하지 않는다. 이 스크립트는 더 이상 통과 기록을 쓰지 않는다. 샌드박스 배치(<샌드박스>/supabase/config.toml + 이 워크트리로 향한 migrations·tests junction, BARBELIC_DB_SANDBOX)는 그대로다.
실패했을 때 읽는 법
마지막 "요약" 블록만 보면 된다.
ci:local 요약 — 브랜치 feat/x @ 0123abcd · 기준 fedcba98
- CI 범위: full — 근거 supabase/migrations/20260905000000_x.sql
- verify: 통과 (5단계) (4분 10초)
- db reset(마이그레이션 전체 적용): 통과 (2분 8초)
- pgTAP: 통과 21파일/405 assert (18초)
- 유저 실사용 시뮬레이션(e2e-browser): 실패 29/30 (5분 36초)
✖ regression.spec.mjs › CASE-011 › 이어하기
- 총 소요: 12분 30초
PR 본문용 한 줄:
검증: ci:local full · verify 통과 (5단계) · pgTAP 통과 21파일/405 assert · 유저 실사용 시뮬레이션(e2e-browser) 실패 29/30 · 12분 30초- 실패한 단계의 자세한 출력은 그 위에 그대로 있다(
━━ 유저 실사용 시뮬레이션(e2e-browser)제목 아래). - 브라우저 저니 실패의 화면·추적은
test-results/playwright/·playwright-report/에 남는다 (CI가 올리는 아티팩트와 같은 위치). - verify는 첫 실패 단계에서 멈추고, DB 묶음은 별도로 계속 돈다(CI에서 두 잡이 병렬인 것과 같음). e2e는
db reset이나 pgTAP이 실패하면 건너뛴다.
환경 준비 실패(종료 코드 3)와 대처
| 메시지 | 원인 | 대처 |
|---|---|---|
| Docker 엔진에 연결할 수 없다 | Docker Desktop이 꺼져 있거나, 이 PC의 알려진 문제(지워지지 않는 소켓 파일 잔존)로 엔진이 멈춤 | Docker Desktop을 완전히 종료 → %LOCALAPPDATA%\Docker\run과 %LOCALAPPDATA%\docker-secrets-engine 두 폴더의 이름을 동시에 바꾸고(예: .broken1) 다시 실행. 하나만 바꾸면 다른 쪽에서 다시 멈춘다 |
| SQL n개가 CRLF 줄바꿈이다 | Windows 체크아웃(core.autocrlf=true)이라 SQL이 CRLF. 함수 원문을 찾는 마이그레이션이 로컬에서만 죽는다 | .git/info/attributes에 supabase/**/*.sql text eol=lf 추가 → supabase/migrations/*.sql 삭제 → git checkout -- supabase/. 워크트리는 본체 .git/info를 공유하므로 한 번이면 된다 |
| 포트 n을 다른 프로그램이 쓰고 있다 | 샌드박스 포트를 다른 스택이 점유 | 그 스택을 끄거나 --sandbox로 다른 폴더(새 폴더는 빈 포트를 새로 고른다) |
| 포트 4173을 다른 프로그램이 쓰고 있다 | 다른 세션의 미리보기 서버. CI처럼 이 스크립트가 직접 빌드한 앱을 띄워야 한다 | 그 서버를 끄고 다시 실행 |
| supabase CLI를 찾을 수 없다 | CLI 미설치 | supabase --version이 되게 설치 |
| 설치된 supabase CLI n이 레포 핀 m과 다르다 | 이 PC의 CLI가 package.json supabaseToolchain.cli(= CI)와 다름 | scoop install supabase@m. 핀을 바꾸는 게 맞으면 상수를 고친다. 이번 한 번만 넘기려면 CI_LOCAL_ALLOW_CLI_MISMATCH=1 |
| 샌드박스 스택이 핀과 다른 이미지로 떠 있다 / 뜬 이미지가 핀과 다르다 | 상수를 바꾸기 전에 띄운 스택이 남아 있음 | npm run ci:local -- --stop 뒤 다시 실행 |
PR 본문에 적는 것
"검증"에는 실제 사전검증 명령·결과·실패/미실행 범위를 적는다. 일반 release 병합은 큐 실행 링크와 base/head·candidate/tree·Merge Check 결과로 남기며 full 통과라고 쓰지 않는다. 진단용 full을 실행했다면 그 실행 링크와 요약을 별도로 적는다. release→staging full 결과와 staging→main의 실제 full 또는 재사용 근거·현재 배포/smoke 결과는 승격 PR에 기록한다.
완료 범위 — 2026-09-10 오너 정정
구현이 끝나면 최신 목적 release 반영·필수 precheck·자기 PR의 Merge Check·실제 release 병합까지 이어서 처리한다. Draft PR이나 선택 검증에서 멈추고 오너에게 다음 실행을 재요청하지 않는다. 이미 승인된 정책의 미반영은 자기 작업에 필요한 최소 변경으로 해결하며 다른 담당자의 브랜치·큐는 조작하지 않는다. 상세 승인 경계와 완료 기준은 공통 지침을 따른다.