Skip to content

릴리스 승격 검사 3단계·QA1 퇴역 — 잔여 정리와 지침 정렬 (2026-09-15)

  • 기간: 2026-09-15 20:20 ~ 23:30 KST (세션 1, Claude 0272c6d3). 오너 지시 원문: "release에 병합할때는 precheck 없이 merge check만하고, release->stage 올릴때 병합된 release 기준으로 precheck만 한번, 그리고 stage->main 올릴때 QA2를 진행하는걸로 바꿔줄래" + "QA1는 이번 배포부터는 완전히 deprecated 처리 할 것 (워크플로우에서 자동실행되지 않도록 할것)".
  • 랜딩: 앱 이슈 #1661. 앱의 정책 반영은 같은 지시를 받은 #1659 세션의 PR #1663(70b96140PR #1664(4552cb45, release/v0.19.3)로 먼저 들어갔다. 이 트랙의 PR #1665는 같은 내용이라 닫았다. 잔여 앱 정리 = PR #1677(feat/1661-queue-cleanup, release/v0.19.4 병합 커밋 5d9095c5, 아래 §4). 문서 = 이 저장소 PR(본 문서 포함). 전역 지침 = 오너 CLAUDE.md §31. 마이그레이션·엣지·Vercel 배포 없음.
  • 설계서: 없음 — 이슈 본문의 분석·Phase 계획·예상 효과 표가 설계다.
  • 정본: 릴리스 프로세스(2026-09-15 개정 배너·§1~§3·실행 기준 표), QA2 승격 게이트, 공통 규칙 §5, 세션 제목. 앱 코드 = .github/workflows/policy-contract.yml(lane 스위치)·scripts/check-promotion.mjs·scripts/migrations/release-landing-service.mjs.
  • 도구: 없음(스크래치 검증 스크립트는 레포 밖).
  • 게이트: 승격 ① precheck 레인 첫 실측 run 34966023249(scope·static-checks·migration-smoke·verify 성공, qa2 skipped). 잔여 정리 브랜치는 npm run check:static 통과·워크플로 6개 YAML 파싱·node --check. QA1 퇴역으로 pgTAP·e2e 기록 없음.
  • 버그리포트: 없음(오너가 승인한 실행 정책 변경).
  • 계약: 작업→release·두 승격의 검사 실행 시점, QA1 호출 계약 종료.

Phase 현황

Phase내용상태
Phase 1앱 승격 CI 3단계화 + QA1 자동 실행 제거✅ #1659 세션 PR #1663·#1664 경유(이 트랙 PR #1665는 중복으로 닫음). 잔여 정리(큐 dry-run·tree 기록·안내문·npm 항목) = PR #1677 → release/v0.19.4 병합 5d9095c5(큐 실행 34974264364)
Phase 2문서·지침 정렬(이 저장소 + 전역 CLAUDE.md §31)✅ 이 PR

1. 배경

2026-09-10 #1491 이후 승격 검사는 "작업 세션 precheck → 큐 Merge Check → release→staging 최종 후보 full CI → staging→main은 같은 tree 성공 기록 재사용"이었다. 2026-09-15 저녁 §30(precheck 없이 Merge Check만)에 이어 오너가 밤에 나머지 두 단계를 정했다: release→staging은 병합된 release 후보에서 precheck 1회, staging→main은 QA2. 그리고 QA1(2026-09-14 #1591로 분리된 기존 테스트 전체)은 이번 배포(v0.19.3)부터 완전히 폐기해 어떤 워크플로도 자동으로 돌리지 않는다.

같은 지시가 두 세션(#1659 릴리스 준비 세션, #1661 이 세션)에 각각 내려졌다. 두 세션은 서로의 작업을 모른 채 같은 파일(승격 CI·큐·배포 워크플로·package.json·check-promotion)을 고쳤고, #1659 세션의 PR이 먼저 큐를 통과했다.

2. 문제 제기

승격 CI가 "Full 하나"로 묶여 있어 단계별로 켜고 끌 수 없었다

scope 잡이 requires_full·reused_tree 두 값만 냈고 모든 검사 잡이 requires_full == 'true' 하나에 묶여 있었다. release→staging에서 정적·DB만, staging→main에서 QA2만 고를 수 없었다.

QA1이 워크플로 7곳에 물려 있었다

승격 CI(단위 2조각·브라우저 4조각·화면 정합성·증거 대조·migration-smoke의 pgTAP·DB 테스트·e2e), 큐 runner(prepare-qa1 + dry-run의 ci:full-local), 배포 뒤 CRUD smoke(staging·Production), Production smoke (manual), 소셜 로그인 매일 점검(가정 로그인 3종), 관리자 fixture(QA1 ci-local.mjs의 상수 3개), 백업 신선도(QA1의 복원 훈련 기록). 앱 정적 검사 2곳(check:deployment의 성능 프로브 대조, check:ts-scopetests/ 요구)도 QA1 파일이 있어야 통과했다.

같은 지시가 두 세션에 내려져 작업이 겹쳤다

이 세션의 PR #1665는 큐 Merge Check에서 #1663·#1664와 10개 파일 충돌(run 34966327943)로 실패했다. 공유 인프라를 바꾸기 전에 같은 release의 열린 PR·최근 병합을 확인하는 단계가 없었다.

3. 해결 방안

원칙 (오너 결정, 2026-09-15)

  • D1 "release에 병합할 때는 precheck 없이 Merge Check만" — 채택(§30과 동일).
  • D2 "release→stage는 병합된 release 기준으로 precheck만 한 번" — 채택. precheck = static-checks + migration-smoke(빈 DB 재적용·schema.sql 대조). 단위 테스트·pgTAP는 QA1 소유라 D4로 함께 빠진다.
  • D3 "stage→main은 QA2" — 채택. 같은 tree 성공 기록 재사용은 폐기.
  • D4 "QA1은 이번 배포부터 완전히 deprecated(워크플로 자동 실행 0)" — 채택.

접근

내용판단
scope 잡이 lane을 판정하고 잡마다 lane 조건으로 켜진다 (#1663)lane = staging / production / manual, static-checks·migration-smoke는 staging·manual, qa2-product-contracts는 production·manual채택(반영됨) — 정책이 코드 한 곳에 있고 필수 검사 verify 이름·보호 규칙 불변
스위치 3개(static·db·qa2)로 잡을 켜고 DB 단계는 서버 파일 변경 시만 (#1665)승격 diff에 supabase/·`scripts/sqlmigrations/`가 있을 때만 migration-smoke
승격 ①·② 워크플로를 따로 만든다yml 2개기각 — 필수 검사 이름·concurrency·prepare 액션이 두 벌로 갈라진다
QA1을 남기고 워크플로에서만 뺀다qa1.lock.json·hook 유지기각 — 오너가 "완전히"라고 했고 남은 hook이 앱 명령에 QA1을 끌어들인다(#1663이 전부 삭제)

4. 적용한 내용

Phase 1 — 앱 (PR #1663·#1664, #1659 세션)

  • policy-contract.yml: lane 출력, QA1 잡 4종 삭제, migration-smoke를 재적용·schema.sql 대조만으로. verify는 lane별 성공/skip 대조.
  • check-promotion.mjs: tree 재사용 삭제, staging 증거 = database·functions·frontend 잡 성공.
  • 배포·큐·백업·관리자 fixture에서 prepare-qa1 제거, prod-smoke.yml·social-login-daily-check.yml·prepare-qa1 액션·scripts/qa1.mjs·qa1.lock.json 삭제, npm hook 31개·test·smoke:* 삭제, check = check:static. 관리자 fixture는 앱 소유 scripts/ci/local-sandbox-config.mjs로.
  • 첫 실측: 승격 ① run 34966023249 성공(잡 4개). 승격 ② QA2 레인은 v0.19.3 릴리스 PR에서 처음 돈다.

Phase 1 잔여 — PR #1677 (release/v0.19.4 병합 5d9095c5)

release/v0.19.3 4552cb45에 아직 남은 것: 큐 runner dry-run이 QA1 ci:full-local·legacy tag·tree 성공 기록을 부르는 경로(merge:request --dry-run은 실패한다) → Merge Check만 하고 push 안 함; merge:request 안내문·큐 워크플로 주석/입력 설명; policy-contract.yml의 tree 기록 저장 단계·full_ci 입력·checks: write; scripts/ci/validated-trees.mjs 삭제; 배포 호출자의 issues: write; 관리자 fixture 트리거 경로; 없는 QA1 파일을 가리키던 npm 항목 5개(ci:local·ci:precheck-local·ci:full-local·check:test-manifest·check:error-cases); ci-wait 안내문. v0.19.3이 이미 staging에 승격돼 정리만으로 재승격을 만들지 않기 위해 v0.19.4에 넣었다 — 오너 지시("v0.19.4 만들어서 머지해줘")로 release/v0.19.4를 main dd7d2828(v0.19.3 릴리스 PR #1671 병합 직후)에서 만들고, 최신 main을 합친 뒤 큐로 병합했다(큐 코드는 main 도달 뒤에만 효력).

Phase 2 — 문서·지침 (이 PR + 전역 CLAUDE.md)

  • 이 저장소: AGENTS.md 앱 CI·릴리스 통합 절(3단계 원문·폐기 목록), agent-shared-rules.md(구현 완료 후 절·§5 배너·검증 명령 표·hosted CI·워크플로 이름 표), session-titles.md([Precheck] 폐기), release-landing-queue.md, release-process.md(배너·§1·표·§3), ci-local.md(기록으로 격하), deployment-pipeline.md 순서 1·2, ci-flow.md·branch-workflow.md·migration-landing.md 배너, qa2-promotion-gate.md(QA2 = 승격 ② 유일 레인), docs/updates/README.md 게이트 줄.
  • 전역 CLAUDE.md §31: 지시 원문, 세 단계, QA1 폐기, 세션 규칙 변화(npm run check = 정적만, 단위 테스트 서술 폐기, QA2 부분 실행), 첫 실측, 교훈.

주요 결정과 그 근거

  • "precheck"의 정의를 static-checks + migration-smoke로 둔 것: 단위·pgTAP는 QA1 소유라 D4와 양립할 수 없고, 마이그레이션 재적용·schema.sql 대조는 앱 소유라 남길 수 있다.
  • 중복 PR을 닫고 잔여만 맡은 것: §27(다른 에이전트 PR 불간섭)과 "사용자 목표 보존" — 목표는 이미 반영됐고, 충돌 해결로 같은 변경을 두 번 넣을 이유가 없다.

작업 중 드러난 것

  • 같은 오너 지시가 두 세션에 내려져 같은 파일을 동시에 고쳤다. 공유 인프라(워크플로·큐·package.json)를 바꾸기 전에 gh pr list --base release/vX.Y.Zgit log --first-parent origin/release/vX.Y.Z로 같은 범위의 PR·병합을 확인해야 한다(CLAUDE.md §31 교훈).
  • Git Bash에서 git show ref:.github/...는 MSYS 경로 변환에 걸려 빈 출력이 된다 — MSYS_NO_PATHCONV=1을 붙여야 한다. 이 때문에 release 쪽 워크플로 상태를 처음에 잘못 읽었다.
  • 앱 정적 검사 2곳이 QA1 파일 존재에 기대고 있었다(check:deployment·check:ts-scope). #1664가 같은 것을 고쳤다.
  • #1663이 Social login daily check를 통째로 지웠는데 그 안의 provider-config 잡(앱 스크립트)은 QA1이 아니다 → 복원 여부 오너 결정 대기.

5. 적용 결과

항목전 → 후
승격 ① 잡 실행 수13(정적·단위 2·DB·브라우저 4·화면·증거·QA2·verify·scope) → 4(scope·static-checks·migration-smoke·verify), 실측 run 34966023249
승격 ② 검사같은 tree 기록 재사용(없으면 Full 13) → QA2 레인 1회(scope·qa2·verify) — 미실측(v0.19.3 릴리스 PR에서 처음)
작업 PR 전 세션 검증ci:precheck-local 11분 × 재합치기 → 0(Phase마다 npm run check 정적만)
워크플로에서 QA1 자동 실행7곳 → 0
남은 QA1 잔재큐 dry-run 경로·tree 기록 단계·안내문·npm 항목 5개 → PR #1677로 release/v0.19.4 병합(큐 runner 코드는 main 도달 뒤 효력)
문서의 "precheck 필수·full CI·재사용" 서술9개 문서 → 배너·본문 개정(이 PR)

미검증: 정리된 큐 runner 코드의 실제 첫 실행(v0.19.4가 main에 도달한 뒤 새 요청부터). 승격 ② QA2 레인의 GitHub 첫 실행 결과는 v0.19.3 릴리스 PR #1671의 #1659 기록을 따른다.

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

  • 승격 판정이 lane 하나로 읽힌다(release→staging = 정적·마이그레이션, staging→main = 제품 계약).
  • QA1 유지 비용(앱 변경마다 QA1 테스트 갱신·핀 올림)이 사라졌다.
  • 지침(전역·저장소)이 실제 워크플로와 같은 말을 한다.

남은 것

  • release/v0.19.4의 승격 ①·②(릴리스 담당 몫). 정리된 큐 runner는 v0.19.4가 main에 도달한 뒤 효력.
  • 소셜 로그인 제공자 설정 매일 점검(provider-config) 복원 여부 — 오너 결정.
  • scripts/review-account/verify.mjs·scripts/migrations/release-v0171-projection-rollout.mjs가 QA1 ci-local.mjs를 import한다(수동 도구·과거 롤아웃 스크립트) — 언급만, 이번 범위 밖.