Skip to content

릴리스 큐 명령·워크플로 이름 정리, 배포 워크플로 분리, 소셜 로그인 매일 점검 3종 (2026-09-09)

  • 기간: 2026-09-09 (세션 1개 2abb2dcf-ad12-4750-94e5-46cac9598f22, 오너 지시 "지금 지정한 프로세스 업데이트도 17.6에 같이 넣어서 진행해줘" → "그냥 다 묶어서 PR 1개로 해")
  • 랜딩: PR 1개(base release/v0.17.6, 커밋 5개 — Phase 1~3 46cfce06 · Phase 4 3e002643 · Phase 5 ca4a8d25 · Phase 6 문서) — 마이그레이션·엣지 없음, 앱 화면 변경 없음. 목표 릴리스 v0.17.6 (release/v0.18.0 은 리팩터링 전용, 오너 결정 09-09)
  • 설계서: 이슈 #1456·#1457·#1458 본문("예상 효과·개선사항" 절 포함)
  • 정본: 릴리스 프로세스 · 릴리스 랜딩 큐 · PR 전 로컬 검증 · 배포 파이프라인 — 각 문서 머리의 2026-09-09 개정 안내
  • 도구: scripts/ci-local.mjs(precheck·full 모드·워크트리별 포트), scripts/migrations/landing-request.mjs(merge:request), scripts/ci-wait.mjs(큐 추적), scripts/check-social-provider-config.mjs(3종)
  • 게이트: tests/react/ciLocal.test.mjs·ciWait.test.mjs·ciToolchain.test.mjs·releaseLanding.test.mjs·externalAuthE2eConfig.test.mjs·deploymentTargets.test.mjs·check-error-case-registry.mjs(60 케이스) — 전부 로컬 통과. 실배포 검증(Staging Deploy 1회·Production Deploy 1회·매일 점검 첫 실행)은 이 PR 이 main 에 도달한 뒤에만 가능(아래 §5 미검증)
  • 버그리포트: 없음(프로세스 정비)
  • 계약: 없음(DB·API 무변경)

Phase 현황

Phase내용상태
1큐·워크플로 개명, 명령 등록, 실행 제목 [#이슈 → 대상] 제목 · PR #N
2ci:precheck-local(release 합치기 → verify → 서버 변경 시 DB), full 은 큐 안에서만, --only 재현은 워크트리별 포트, landing:lock·db:preflight 폐기
3ci:wait 가 PR base 에 따라 큐 실행(제목) 또는 GitHub Full CI(head) 추적
4배포 워크플로를 1-Production Deploy·2-Staging Deploy(단계 정의 deploy-steps.yml)로 분리, Production smoke 통합·수동 전용
5소셜 로그인 매일 점검: 스위치 제거·3종 설정 검사·제공자별 로그인 경계 검사(CASE-003·059·060)
6문서·지침 정합, 이 기록
7큐 CI 실패 수리 — e2e 통계 드레인을 Production 과 같은 cron 3단계 경로로 통일 + 저장 직후 통계 게시를 앱이 끝까지 기다리도록🔄

1. 배경

2026-09-08 오너 결정으로 릴리스 경로가 작업 브랜치 → release/vX.Y.Zstagingmain 이 됐고, 작업 PR 은 GitHub 큐(self-hosted runner 가 최신 release 와 합친 후보에서 전체 검증 뒤 그 커밋을 그대로 반영)로만 들어간다. 그런데 명령·워크플로 이름은 옛 main 랜딩 시절 그대로였고(landing:request, ci:local, Landing queue, Repository checks, Deploy), 세션마다 PR 전에 전체 검증을 한 번 더 돌리며, 브라우저 단계의 고정 포트 4173 에서 세션끼리 충돌했다. 같은 날 오너가 Actions 목록·워크플로 목록을 하나씩 확인하며 이름과 역할을 정했다.

2. 문제 제기

  • 이름이 역할을 말하지 않는다. "landing:request 가 뭐야", "Repository checks 는 언제 도는 거야", "Branch 열이 왜 전부 main 이야" — 오너가 목록만 보고는 무엇이 무엇을 하는지 알 수 없었다.
  • 같은 전체 검증을 두 번 돌린다. 세션의 PR 전 ci:local(전체, 약 11분) + 큐의 전체. 검증한 것 = 머지되는 것을 보장하는 큐 쪽은 필수, 세션 쪽은 낭비이고 PC 부하와 포트 충돌의 원인.
  • Production smoke 가 배포마다 빈 실행 3~4건. deployment_status 트리거가 Vercel 의 모든 배포 상태 이벤트(PR 미리보기 포함)에 반응해 즉시 건너뛰는 실행만 남겼고, 배포의 smoke 와 같은 계정으로 CRUD 를 동시에 돌려 헛빨간불(v0.15.0)을 낸 적이 있다.
  • 소셜 로그인 매일 점검이 8일 연속 건너뜀. 켜는 변수(ENABLE_KAKAO_E2E)가 설정된 적이 없고, 카카오만 대상이라 구글·애플은 범위 밖이었다.

3. 해결 방안

원칙(오너 결정 2026-09-09): ① 검증은 ci:, 통합 요청은 merge: 접두어 ② 세션은 사전 검증만, 전체 검증은 큐 안에서만(포트 4173 하나가 "이 PC 에서 full 은 한 번에 하나"를 지킨다) ③ 워크플로 표시 이름에 번호를 붙여 목록 맨 위에 순서대로(1 Production Deploy · 2 Staging Deploy · 3 Release Merge Request) ④ 점검은 돌 수 없으면 실패지 건너뜀이 아니다.

내용판단
A. 이름·범위·포트 정책을 한 PR 로 정리 (채택)명령 4개(ci:precheck-local·ci:full-local·merge:request·ci:wait), 워크플로 7개 이름, precheck 정의, full 큐 전용, 재현 포트 분리, 배포 분리, smoke 통합, 점검 3종오너가 "다 묶어서 PR 1개로"
B. 이름만 바꾸고 검증 범위는 그대로개명만두 번 검증·포트 충돌이 남는다. 기각
C. 포트를 워크트리별로 풀어 full 병렬 허용4173 하드코딩 제거Docker 과부하로 문제가 옮겨간다(#1405 실측). 오너가 "full 은 포트 하나로 고정"을 택함. 기각

4. 적용한 내용

Phase 1 — 개명과 실행 제목. landing-queue.ymlrelease-merge-request.yml(3-Release Merge Request). 실행 제목은 [#이슈 → 병합 대상] 이슈 제목 앞부분 · PR #번호(dry-run 표기), 요청 UUID 는 잡 이름(merge · 요청 <id>)과 잡 요약 표에만. merge:request 가 head 브랜치(feat/1405-…)·PR 본문의 #N·PR 번호 순으로 이슈 번호를 찾고 이슈 제목에서 상태 토큰을 떼어 40자로 자른다. 새 워크플로가 main 에 없으면 옛 landing-queue.yml 로 내려가는 전환기 호환(실행 찾기는 요청 시각 이후 이 PR 번호가 든 가장 새 실행). Repository checksGitHub Full CI, User data daily copyDaily data backup. 옛 land(main 직접 랜딩)·landing-queue(호환 검사, 전환 뒤 항상 건너뜀) 잡은 제거.

Phase 2 — 사전 검증과 큐 전용 full. ci:precheck-local = 작업 트리가 깨끗한지 확인 → HEAD 와 가장 가까운 origin/release/*(또는 --release)를 합친다(충돌이면 파일 목록과 함께 종료 코드 1 — 큐 슬롯을 낭비하지 않기 위해 여기서 해결) → verify → supabase/·scripts/sql|migrations/·tests/db/ 변경이 있으면 DB 단계. ci:full-localBARBELIC_MERGE_QUEUE=1(큐 runner 가 켬)이 없으면 안내만 하고 종료, --only 재현은 허용하되 미리보기 포트를 워크트리 경로 해시(4300~4399)로 잡아 Playwright 에 E2E_PREVIEW_PORT 로 넘긴다. landing:lock·db:preflight 스크립트와 통과 기록 쓰기 제거(db-preflight.mjs 는 위험 게이트 라이브러리로 남김).

Phase 3 — ci:wait. PR base 가 release/* 면 큐 모드: 브랜치가 아니라 실행 제목으로 찾고, 실행이 없어도 발화하지 않으며(요청은 merge:request 가 낸다), 상태에 따라 [CI대기중]/[CI진행중] 안내. 그 외는 GitHub Full CI 를 head 로 찾는 종전 동작. 충돌 안내는 base 브랜치 이름으로. heartbeat 옵션 제거.

Phase 4 — 배포 분리(#1457). deploy.yml 의 잡(database → functions → frontend·docs-frontend → smoke → release-tag)을 deploy-steps.yml(workflow_call, 입력 = environment·base_sha)로 옮기고, production-deploy.yml(main push, 1-Production Deploystaging-deploy.yml(staging push, 2-Staging Deploy)이 superseded 검사 뒤 부른다. Production 에는 smoke 뒤 browser-journeys(번호 붙은 실서비스 여정) 잡을 붙이고 release-tag 는 그 뒤. prod-smoke.ymldeployment_status 트리거를 떼고 Production smoke (manual). 승격 검사는 staging-deploy.yml 실행을 보고, staging 잡 판정은 deploy / database (staging) 이름 끝으로 본다.

Phase 5 — 소셜 로그인 매일 점검(#1458). social-login-daily-check.yml(Social login daily check): 변수 스위치 없음, 비밀 없으면 실패, 제공자 설정 검사 3종(check-social-provider-config.mjs kakao google apple), 제공자별 로그인 경계 검사를 E2E_SOCIAL_PROVIDER 매트릭스(순차, production-smoke 그룹)로. 경계 검사 코드는 제공자 호스트 맵·openid 기대값(애플은 email·name 만)으로 매개변수화. CASE-059(구글)·CASE-060(애플)을 CASE-003 에서 복제(status active, 경계 검증 pending) — 각 케이스는 자기 제공자 실행에서만 돈다.

주요 결정과 근거: 큐 워크플로는 항상 main 의 파일로 실행되므로 새 이름은 이 PR 이 main 에 도달한 뒤 유효하다 — 그때까지 merge:requestci:wait 가 옛 파일로 내려간다. 옛 npm 이름(landing:request, ci:local)은 별칭으로 남겨 다른 체크아웃이 깨지지 않게 했다.

Phase 7 — 큐 CI 실패 수리(통계 드레인 경합). 큐 2차 실행(34324908737)이 브라우저 e2e 8건(CASE-001·002·004·005·006·011·015·025)에서 실패했다. 7건은 정리 단계 "cleanup did not drain its statistics generation", 1건은 임시 저장 격리 차단. 로컬 샌드박스 DB 실측: 작업 processing 1 · 계산 결과 computed 1 · 상태 requested=applied — v0.17.5(20260913230000_isolated_stats_convergence)부터 통계 작업은 5초 cron 세 단계(claim → compute → publish)로 처리되는데, e2e 하네스의 드레인(e2e/support/harness.mjs createStatsDrainer)은 옛 직접 worker process_user_exercise_stats_refresh_jobs 를 불러 같은 큐를 다른 상태 기계로 처리했다. cron 이 먼저 잡은 작업이 있을 때 직접 worker 가 다른 작업으로 게시 세대를 올리면 cron 의 게시가 "초과됨"으로 실패하고 작업이 processing 에 남는다. #1455 가 정리 단계에 게시 대기(waitForStatsGeneration)를 넣었지만 드레인이 그대로라 경합은 남았다. 수리 = 드레인을 claim_stats_projection_job_v1 → compute_stats_projection_job_v1 → publish_stats_projection_job_v1 반복 호출로 바꿔 열린 작업이 0이 되고 이번 드레인 동안 갱신된 상태 행에 requested>applied 가 없을 때 끝나게 했다(앱 코드·마이그레이션 무변경, DB 검증 D01 은 cron 을 멈추고 직접 worker 를 쓰므로 그대로). 첫 판(서비스 키 RPC 호출)은 로컬·큐 3차 실행(34329012667) 모두에서 새 실패를 냈다: PostgREST 의 authenticator 역할에 safeupdate 가 실려 있어 계산 함수 안의 WHERE 없는 UPDATE 가 "UPDATE requires a WHERE clause" 로 막히고, 그 계정의 상태 행이 실패로 남아 뒤 테스트의 수렴 검사까지 연쇄 실패(로컬 17건). cron 은 postgres 역할이라 걸리지 않으므로 드레인도 같은 역할로 샌드박스 DB 에 직접 붙어(tests/db/support/pgSessions.mjs) 돌게 바꿨다. 그래도 11건이 남았다 — 저장 직후 하루 요약 0kg·기록 없음·PR 성장 0건(CASE-004·005·006·025·049·052·054·057 등). 이건 테스트가 아니라 유저가 겪는 회귀다: v0.17.5 핫픽스 뒤 대화형 갱신 RPC 는 관측만 하고 돌아오고(deferred) 게시는 cron 3단계(각 5초 주기, 최악 약 15초)가 하는데, 앱의 재확인 사다리(statsRecoveryCoordinator.ts)는 0·0.75·2초 세 번(누적 2.75초)만 돌고 멈추며, 저장 영수증 화해는 관측 결과가 deferred 면 다시 읽지 않고 끝난다. 3차 수리(앱): ① 사다리 예산을 누적 약 30초로 늘리고 ② 저장 영수증 화해가 deferred 면 사다리를 직접 시작해 게시 확인 뒤 홈·달력·활성 개요를 다시 읽는다. 단위 테스트 1개 추가. cron 주기 단축(DB)은 별도 이슈. 마지막 1건 CASE-025(계획 → 라이브 운동 → 완료 저장 → 메모 후속 저장)는 별개 결함이었다: 계획에서 시작한 운동의 완료 저장은 초안의 기록 식별자(source_ref)로 보내지만 서버는 계획의 식별자를 유지하는데(save_session_v5 정체 불변), 앱이 확정 사본을 요청 값으로 만들어 다음 후속 저장이 "영수증 source_ref 불일치"로 격리되고 메모가 안 붙었다(로컬 단독 재현 3/3). 수리: 확정 사본의 식별자를 서버 영수증에서 가져온다(pendingSavesFeatureBinding.ts confirmedSessionOf, 단위 테스트 2개). 앱 회귀이므로 유저도 같은 경로에서 "동기화하지 못한 운동 기록" 안내를 봤을 것이다.

작업 중 드러난 것: ① 이 PC 의 Bash 도구가 히어독 안의 역슬래시를 망가뜨려(\\s\s, 문자열 안 \\n→ 실제 줄바꿈) 스크립트 두 개가 깨졌다 — 파일 생성은 Write 도구로 한다(메모리에 기록). ② 공유 node_modules junction 이 최신 lockfile 과 어긋나(@babel/core 없음) 로컬 게이트가 첫 단계에서 죽었다 — 워크트리에 npm ci. ③ prod-smoke.yml 의 브라우저 여정 잡은 원래 수동 실행에서만 돌고 있었다(자동은 CRUD 만). ④ 큐 1차 실행(34324557501)은 main 의 옛 컨트롤러가 BARBELIC_MERGE_QUEUE 없이 ci:local --full 을 불러 새 스크립트가 자기를 거부한 전환기 조합 — 로컬 재현 불가, runningInsideQueue()CI=true·RUNNER_TRACKING_ID 도 큐로 인식하게 수리. ⑤ 큐 2차 실행 실패(Phase 7)는 로컬 ci:full-local --only browser 로 재현됐다(경합이라 실패 케이스 집합은 실행마다 다름). 사전 검증(ci:precheck-local)은 설계상 브라우저를 돌리지 않으므로 "사전 검증 누락" 이 아니라 큐가 잡아야 할 종류다. ⑥ hosted GitHub Full CI 는 v0.17.4 핫픽스(09-08) 이후 초록인 적이 없다 — v0.17.4 staging 실행은 승격 검사 실패, v0.17.5 staging 실행은 취소, main 기준 문서 PR #1462 의 실행(09-09)은 migration-smoke(schema.sql 스냅샷 ≠ 재생 DB)·viewport·browser 실패. release/v0.17.6 은 스냅샷 수리(1dd16c92)와 이 Phase 를 실어 main 보다 앞선다. ⑦ 큐 러너는 프로세스 전체에 BARBELIC_DB_SANDBOX 를 실어 주므로, 그 변수를 보고 샌드박스 설정을 읽는 단위 테스트(#1455 의 prOverviewFixtureClock.test)가 샌드박스가 생기기 전인 verify 단계에서 ENOENT 로 죽었다(로컬·hosted 는 변수가 없어 건너뜀) → verify 단계에는 그 변수를 비운다. ⑧ 같은 실행에서 통계 수렴 probe 의 statement timeout 과 CASE-038·045 의 미리보기 서버 연결 리셋(재시도 뒤 통과)은 이 PC 에 샌드박스 DB 스택 8개가 동시에 떠 있던 과부하 증상 — 큐 러너와 세션 샌드박스가 같은 PC 를 쓰는 구조적 비용(#1464).

5. 적용 결과

항목
작업 PR 통합 명령npm run landing:requestnpm run merge:request(별칭 유지)
PR 전 세션 검증ci:local 전체 약 11분, 브라우저 포함ci:precheck-local verify(+DB) 약 2~4분, 브라우저 없음 — 미실측(첫 실사용에서 잰다)
전체 검증 실행 주체세션·큐 각 1회큐 runner 1회(ci:full-local, 4173 고정)
포트 4173 충돌세션끼리·runner 와세션 재현은 워크트리별 포트 — 로컬 단위 테스트로 확인, 실사용 미실측
Actions 큐 실행 제목Landing #1449 @ <sha> [<uuid>][#1405 → release/v0.18.0] 제목 · PR #1448main 도달 뒤 실측
Production smoke 자동 실행배포당 3~4건 skipped0건(수동 전용) — 다음 Production 배포에서 확인
소셜 로그인 매일 점검8일 연속 skipped, 카카오만매일 3종 실행 — 첫 실행 결과 미확인(구글·애플 경계 검사는 pending)
워크플로 목록6개(Deploy·Landing queue·Repository checks·…)8개 파일(1-Production Deploy·2-Staging Deploy·3-Release Merge Request·GitHub Full CI·Production smoke (manual)·Social login daily check·Daily data backup·deploy-steps)
e2e 통계 드레인옛 직접 worker, 5초 cron 과 경합(큐 CI 8건 실패)Production 과 같은 claim→compute→publish 반복 — 로컬 전체 e2e 재검증 뒤 확정

로컬 검증: npm run check(Phase 1~3 시점) — deploymentTargets·releaseLanding 두 테스트를 이름 변경에 맞춰 고친 뒤 재실행 예정(§검증 줄은 PR 본문). 단위·계약 테스트 묶음(ciLocal 21·ciWait 21·ciToolchain·releaseLanding·externalAuthE2eConfig 31·promotion·coverageInventory·deploymentTargets·testAdminSessionApi) 통과, 레지스트리 60 케이스 통과, npm run check:deployment 통과.

6. 이번 개선으로 향상된 것

  • 명령·워크플로 이름만 보고 역할이 읽힌다(검증 ci:, 통합 merge:, 배포는 환경 이름, 목록은 1·2·3 순).
  • 세션은 PR 전에 몇 분짜리 사전 검증만 하고, 전체 검증은 큐가 한 번만 — PC 부하와 포트 충돌의 구조적 원인이 사라진다.
  • 큐 실행 목록에서 어느 이슈가 어느 release 로 가는지 바로 보인다.
  • 실서비스 점검이 실제로 돈다(매일 3종, 못 돌면 빨간불).

남은 것

  • 이 PR 이 release/v0.17.6 → staging → main 으로 나간 뒤: 첫 2-Staging Deploy·1-Production Deploy 실행이 초록인지, 큐 실행 제목이 새 형식인지, Social login daily check 첫 실행에서 구글·애플 경계 검사가 통과하는지 확인하고 CASE-059·060 을 production_verified 로 올린다. 실패하면 그 시점의 실행 링크와 함께 수리 이슈.
  • 옛 이름 별칭(landing:request, ci:local, landing-queue.yml 폴백) 제거 — 모든 체크아웃이 옮겨간 뒤 별도 이슈.
  • 전환 뒤 첫 staging 배포의 docs-frontend 실패(vitepress: not found)는 v0.17.4 핫픽스 #1438 에서 이미 수리됨(이 트랙과 무관).
  • 엣지 함수 supabase/functions/stats-process-refresh-jobs 도 아직 옛 직접 worker process_user_exercise_stats_refresh_jobs 를 부른다 — Production 에서 호출되고 있다면 cron 3단계와 같은 경합이 가능하므로 호출 여부 확인 뒤 폐기 또는 전환(별도 이슈).
  • main 의 hosted GitHub Full CI 빨간불(schema.sql 스냅샷 dblink·viewport bottom-clearance)은 이 release 가 main 에 도달하면 해소되는지 승격 PR 의 CI 로 확인.