Skip to content

QA2 검증 범위 보강 1차 — 커버리지 분모·새 의무 4개·revision 교정 (2026-09-15)

  • 기간: 2026-09-15 (세션 1개). 오너 지시 원문 "어떻게 QA2를 보강할 수 있을까 기존 우리 기록들을 쭉 봤을때 이슈 만들고 계획 세워서 진행까지 쭉 해줘 내 허락받지 말고", 같은 날 선행 지시 "QA2는 QA1이랑은 다른 철학을 만들어서 만든거니까, QA1의 레거시를 절대 QA2까지 끌고와서는 안됨".
  • 랜딩: QA2 main 3bfd296b (PR #3, 커밋 3개 1f0ed84·530b1aa·0601bf0) · 앱 qa2.lock.json 갱신은 앱 PR #1623 → 큐 병합 8a4b8b6b (release/v0.19.3, main 157bf12b에서 생성; staging·Production 미반영). 마이그레이션·Edge 변경 없음.
  • 설계서: QA2#2 본문(분석·Phase 4개·예상 효과 표)과 Phase 완료 댓글 3건.
  • 정본: QA2 승격 게이트(09-15 보강 절), QA2 저장소 docs/operating-model.md(커버리지 절)·docs/architecture.md(카탈로그 계약)·docs/database.md(새 시나리오·교정 절)·docs/browser.md.
  • 도구: QA2 src/subjects/barbelic-db.mjs(공개 경계 인벤토리), src/decision.mjs(커버리지 판정), src/report.mjs(커버리지 절), src/adapters/browser.mjs(오류 관측), scripts/product-ci.mjs(--mode focused --risk --layer).
  • 게이트: QA2 npm run check 101/101 · 앱 main 전체 실행 43/43 · 교정 4회 · 앱 precheck(static·unit-1 2107·unit-2 1778 통과) · 큐 Merge Check 통과. 원격 Full CI는 미실행(지시 범위 밖).
  • 버그리포트: 없음(제품 결함 새로 발견 없음).
  • 계약: QA2 카탈로그의 surfaces·outOfScope·retired, 판정의 coverage, 브라우저 의무의 no-unexpected-errors.

Phase 현황

Phase내용상태
Phase 1커버리지 분모 — 대상 DB에서 공개 경계 열거, 의무의 surfaces, outOfScope·retired 장부, 판정·보고서1f0ed84
Phase 2새 의무 4개 — 계정 삭제 잔여, 시스템 쓰기 보호, 단절 뒤 정확히 한 번, 예상치 못한 오류 0530b1aa
Phase 3revision 교정 — qa:ci 부분 실행 옵션, 결함 revision 실패·수정 revision 통과 증거0601bf0
Phase 4QA2 게시, 앱 qa2.lock.json 갱신(release/v0.19.3 큐), 문서✅ QA2 3bfd296b · 앱 PR #1623 → 8a4b8b6b · 문서 PR #94

1. 배경

2026-09-14에 기존 Full CI 테스트 전체가 QA1로 이관되고 같은 날 제품 약속 기반의 독립 검증 QA2가 만들어졌으며, 09-15 새벽 QA2가 승격 CI 필수 검사가 됐다(#1604). 오너는 QA1에서 QA2로 검증 체계를 옮기려 하며, "QA1의 레거시를 QA2에 가져오지 않는다"는 전제에서 QA2를 보강하도록 지시했다.

2. 문제 제기

커버리지의 분모가 없었다

QA2는 약속 6개·의무 24개·시나리오 40개를 선언했지만, 제품의 공개 경계 중 얼마를 덮는지 보고서에 나오지 않았다. 요구사항 축이 성질(storage·ownership 등)뿐이라 "계획 저장"과 "운동 저장"이 구분되지 않아 기능별 공백이 보이지 않았다.

범위 밖이 문서 문장으로만 있었다

Storage API·Edge Functions·외부 소셜 로그인·네이티브·Production은 운영 문서에만 적혀 있고 카탈로그·보고서에는 없었다.

관측된 실패의 의미에 대응하는 약속이 없었다

장애 기록 146건 중 서버·데이터 계층의 반복 유형(계정 삭제 잔여·교착, 시스템 경로의 원본 훼손, 서버 미도달 저장의 재반영, 화면에 뜨는 예상치 못한 오류)에 QA2 의무가 없었다. 기존 브라우저 의무는 성공 경로의 값만 보고 화면에 뜬 오류는 보지 않았다.

의무가 결함을 잡는다는 QA2 방식의 증거가 없었다

QA2는 대상 제품을 고치지 않으므로 QA1식 결함 주입은 쓸 수 없다.

3. 해결 방안

원칙

  • D1(오너, 2026-09-15): QA1의 케이스·helper·장부·실행기·검사 방식을 QA2로 가져오지 않는다. 참고할 수 있는 것은 제품 계약과 관측된 실패의 의미뿐이다.
  • 전제(세션): 제품 내부 단위 테스트는 QA2 범위 밖(QA2 헌장의 제품 구현 import 금지). 새 의무가 현재 제품에서 실패하면 제품 결함으로 남기고 lock 갱신 전에 보고한다.

접근

커버리지의 분모는 손으로 쓴 목록이 아니라 검증 대상 DB에서 기계로 읽는다(로그인 사용자가 부를 수 있는 SECURITY DEFINER RPC, 앱 계정에 DML 권한이 있는 표, 설치된 cron 잡). 약속의 출처는 제품 계약 문서(docs/contracts/ 50개)와 장애 기록(bug-report/ 146건)이며 정답·입력·관측 방법은 QA2에서 새로 썼다. 결함 탐지력은 제품 revision이 실행 입력이라는 QA2 구조를 써서, 결함이 있던 커밋과 고친 커밋에 같은 의무를 돌려 증명한다. 기각한 대안: QA1의 필수 목록 파일·CASE 레지스트리·결함 주입 실험·필수 신호 7종을 옮기는 것(D1 위반이자 QA2 독립성 상실).

4. 적용한 내용

Phase 1 — 커버리지 분모 (QA2 1f0ed84)

  • 대상 지문(attestation)에 공개 경계 인벤토리(surface: {rpc, table, job}) 추가. 의무는 surfaces(rpc:·table:·job:·screen:)를 선언하고, 카탈로그는 outOfScope(이유 필수, 의무가 선언한 경계와 겹치면 오류)·retired(id·이유·날짜, 재사용 금지)를 가진다. 둘은 계획(plan.scope)에 실려 재현 때 검증된다.
  • 판정 coverage: 의무 있음 / 범위 밖 / 미배정 / 선언만 된 경계(screen) / 퇴역. 제품에 없는 경계를 의무가 선언하면 근거 오류로 미검증. 미배정은 출시 판정에 들어가지 않는다. 보고서 HTML에 커버리지 절.
  • 24개 의무에 실제로 행사하는 경계를 선언. 범위 밖 선언 25개(능력 6: Storage API·Edge Functions·외부 OAuth·네이티브·Vercel /api·배포 환경; 관리자 RPC 13개·인입 RPC 4개·인입 표 6개·인입 잡 2개는 오너 결정 2026-09-10 #1478 근거).
  • 선행 수리: QA2 자체 검사 1건이 QA2 checkout(D:)과 임시 폴더(C:)가 다른 드라이브일 때 실패(path.relative가 절대 경로를 돌려줌). 같은 드라이브의 형제 폴더를 쓰도록 테스트 수정.

Phase 2 — 새 의무 4개 (QA2 530b1aa)

  • db-account-deletion-residue(ownership): 두 계정이 저장·수정한 뒤 한 계정을 auth 계정 연쇄로 지우면, auth.users를 참조하는 모든 public 표(pg_constraint에서 열거, 49개)에 잔여 0·이력 0, 다른 계정의 원본 스냅샷 불변·계속 저장 가능.
  • db-system-write-protection(storage): 티켓 없는 DB 소유자·service_role의 UPDATE/DELETE는 42501로 거부되고 원본·이력 불변, 유저 수정 저장은 이력에 행위자와 함께, 수리 티켓(lift_guild.repair_ticket) 선언 변경은 이력에 티켓과 함께.
  • browser-workout-disconnected-recovery(storage): 저장 RPC를 연결 실패로 끊은 채 운동을 끝내면 완료 화면이 오류 없이 뜨고 서버 0건, 연결을 되돌리고 문서를 재시작하면 정확히 한 번 반영(세션 1·종목 1·세트 1, 무게·횟수 보존).
  • 모든 브라우저 의무의 no-unexpected-errors: 어댑터가 페이지 오류·콘솔 오류·대화상자를 컨텍스트의 모든 페이지에서 관측하고(QA2가 막은 외부 출처의 오류는 따로), 시나리오는 일부러 일으킨 실패만 정규식으로 좁게 선언. 영수증에 관측 목록과 흡수한 선언을 남긴다.

Phase 3 — revision 교정 (QA2 0601bf0)

  • qa:ci--mode focused --risk <이름> --layer database 추가(웹 대상 생략, 출시 승인 없음). release 실행은 범위를 좁힐 수 없다.
  • db-wide-statistics를 v0.18.0 d2fba06a와 main 157bf12b에 실행: 전자는 두 시나리오 모두 목표 단언 scale-worker-finishes에서 제품 오류 53200으로 실패(BUG-144 재현, 근거 오류 0), 후자는 배치 계약(#1590)으로 통과. 증거 QA2 evidence/2026-09-15/calibration-wide-statistics/.
  • 시스템 쓰기 보호 시나리오는 거부 단언을 이력 의존 단계보다 앞에 두도록 재배치.

Phase 4 — 반영

  • QA2 PR #3 병합 → main 3bfd296b. 앱 release/v0.19.3을 main 157bf12b에서 생성, 작업 브랜치 feat/qa2-2-reinforcement-lock에서 qa2.lock.json3bfd296b로 갱신(커밋 72c7abed). precheck 통과 뒤 PR #1623을 merge:request로 큐에 넣어 병합 커밋 8a4b8b6b로 통합(큐 실행 34882818805). 문서는 Barbelic-docs PR #94.

작업 중 드러난 것

  • 시스템 쓰기 보호 시나리오의 첫 실행이 "거부 없음"으로 실패했는데 제품 결함이 아니라, 유저 수정 저장이 세트 행을 새 행으로 다시 만들어 옛 id로 보낸 UPDATE가 0행에 닿은 것이었다. 대상 행 존재 확인 단언을 추가했다. 제품은 티켓 없는 시스템 변경을 실제로 거부한다.
  • 계획했던 교정점 04af3c78(#1254 부모)은 유효하지 않았다. #1266 열 이름 변경 이전 스키마라 QA2 readback이 목표 단언 전에 중단됐고(미검증), 카탈로그가 선언한 경계 4개가 그 revision에 없어 Phase 1의 근거 오류가 설계대로 걸렸다. 이 제품에는 #1266 이후 보호 트리거가 없는 revision이 없어 그 의무의 교정은 불가하며, 시도 기록을 그대로 남겼다.
  • QA2 자체 검사에서 브라우저 어댑터 테스트 1건이 한 번 일시 실패했다(이후 3회 통과, 실패 세부 미확보). Node 테스트 러너가 파일을 병렬로 돌리며 Chromium을 함께 띄우는 경합으로 추정하며 원인은 미확정이다.
  • QA1 loader는 앱 npm 스크립트 대부분의 pre-hook이라, 앱 precheck는 QA1 고정 커밋을 GitHub에서 받아 준비한 뒤 돈다(이번 변경과 무관한 기존 구조).

5. 적용 결과

항목전 → 후
QA2 시나리오·의무40개·24개 → 43개·27개
커버리지 보고없음 → 공개 경계 208개(RPC 140·표 57·예약 작업 11) 중 의무 있음 11·범위 밖 25·미배정 172, 근거 오류 0
브라우저 의무의 오류 관측없음 → 12개 전부 필수 단언(앱 main에서 예상치 못한 오류 0, 예상 선언으로 흡수 4건)
결함 탐지 증거없음 → db-wide-statistics 결함 revision 실패·수정 revision 통과 보고서 2벌
QA2 자체 검사96/96 → 101/101
앱 main 157bf12b 전체 실행40/40 → 43/43, releaseEligible true (run 8587ab94)
qa2.lock.json046ae55b3bfd296brelease/v0.19.3에 큐 병합 완료(8a4b8b6b), staging·Production 미반영
원격 Full CI에서 QA2 lane 실행미실행(지시 범위 밖) — 다음 승격 PR에서 첫 실행

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

커버리지가 보고서에 보인다

어느 공개 경계에 약속이 있고 없는지가 QA2 보고서 한 장에 나온다. 미배정 172개가 곧 다음 약속의 후보 목록이며, 제품이 경계를 더하거나 없애면 다음 실행에서 드러난다.

장애 기록의 반복 유형이 약속이 됐다

계정 삭제 잔여, 시스템 경로의 원본 훼손, 서버 미도달 저장, 화면의 예상치 못한 오류가 승격 CI에서 매번 검사된다.

의무가 결함을 잡는다는 증거 방식이 생겼다

제품 revision을 입력으로 쓰는 교정으로, QA2가 발견했던 실제 결함(BUG-144)에서 실패하고 수정본에서 통과함을 남겼다. 구조적으로 남는 것: 카탈로그 계약(surfaces·outOfScope·retired), 판정의 coverage, qa:ci의 부분 실행 옵션.

남은 것

  • 원격 Full CI에서 새 QA2(3bfd296b)의 첫 실행 확인(다음 release→staging 승격 PR).
  • 미배정 경계 172개는 다음 보강의 후보 목록이다(그룹·피드·계획·달력·종목 카탈로그 RPC와 대부분의 표). 화면(screen:)은 기계로 열거하지 않는다.