ADR 0030 — 테스트 신뢰성 위기 회복 (P0 클러스터 회고)
- Status: Accepted — 2026-04-29 5인 역할 회의 (P1·P2·P3·P4·P5) 3회차 (R1 발산 → R2 충돌 → R3 수렴) 합의
- Date: 2026-04-29
- Tier: A (회수 불가능 production 사고 군집)
- 회고 원문:
retrospectives/2026-04-29-p0-cluster-test-trust-crisis— R1·R2·R3 전 과정 보존 SSoT - Decision Drivers: 2026-04-29 P0 사고 3건 동시 노출 (재동의 모달 영구 stuck · web build fail · ops KPI=0). 테스트 4,698 통과 + production fail = 테스트·CI 신뢰성 정의 자체의 위기. 사용자 발언 "우리가 왜 테스트코드를 만들죠... 노력이 물거품".
- Anti-Goal Compliance: 4종 무관 (인프라 안전선 정책).
결정 (요약)
코드/테스트 layer 통과 ↔ production 노출 사이의 gap 을 다중 안전망 으로 닫는다. 단일 layer 강화로는 같은 origin 사고 재발. 1인 + 자율 모드 + LLM 보조 환경의 ROI 제약 안에서 cost-effective frontier 우선순위로 8 task 즉시 시행 + 2 ADR 후속 분리.
즉시 시행 (1주 내)
| ID | Task | 산출물 | SLA |
|---|---|---|---|
| T1 | CI 에 pnpm --filter=@neohollo/web build 추가 (PR check) | .github/workflows/*.yml | 즉시 |
| T2 | fake-firestore.ts .set dot-path 키 throw + 다른 도메인 grep audit | 1 PR + audit retrospectives commit | 즉시 |
| T3 | useServerAction() 강화판 — finally 강제 reset + 자동 toast + timeout SLA (10초 격상·30초 강제 에러) + AsyncBoundary wrapper. 신규 호출부 lint 강제, 기존 sweep PR 일괄 전환 | wrapper + ESLint rule + 마이그레이션 PR | 1주 |
| T4 | C-G phase 1 — production 5 화면 (login → dashboard → cases → case detail → portal entry) HTTP 200 smoke + Cloud Logging error spike → 카톡/Slack 알림 채널 | smoke script + alert webhook | 1주 |
| T5 | 외부 사용자 동의 corruption 영향 audit — production users 컬렉션에 literal key 존재 케이스 식별. 발견 시 §부록 A SOP 즉시 적용 | audit 결과 + 영향 사용자 통보 commit | 1주 |
| T6 | PR template 강제 항목 — P0 hotfix 시 동형 패턴 audit 결과 1줄 (예: "동형 패턴 audit: 0건/N건"). 24시간 SLA | .github/pull_request_template.md | 즉시 |
| T7 | CLAUDE.md "ADR 0028 패키지 구조 — pure/mutation 분리" 가이드 1 섹션 — Client Component import 할 순수 함수는 deep export entry 격리 | CLAUDE.md edit | 즉시 |
| T8 | LLM hedging 패턴 ESLint no-restricted-syntax 차단 — typeof X.collection !== "function" · ({} as Firestore) · 빈 catch 블록 정규식 | eslint.config.mjs | 1주 |
별도 ADR 후속
- ADR 0031 (2주 내, P4 주도) — 디자인 시스템 4-state (loading/success/empty/error) 시맨틱 토큰 + SystemStatusBadge 컴포넌트 + Next.js error.tsx · not-found.tsx 표준화 spec
- 6개월 후 별도 ADR — fakeDb 단계 폐기 → emulator 단일화 결정 (P5 주도, 1주 안정성 데이터 input 후)
폐지
- ❌
/meeting 빈 상태 vs 실패 상태 UI 패턴 통일 tier=B5-13 데드라인 회의 — ADR 0029 후속으로 분리됐던 회의. 본 ADR 0030 에 흡수. 분리 결정 자체가 4-29 사고 origin 의 일부 였음을 기록. - ❌ C-H 사고 후 broader audit 전면 의무화 — P0 한정 차등으로 축소
- ❌ C-G phase 2 (preview channel 전체 자동화) — phase 1 ROI 검증 후 결정
- ❌ fakeDb 즉시 전면 폐기 — 점진안 (T2 즉시 throw + 신규 emulator 의무 + 6개월 ADR 단일화) 으로 대체
배경
사고 클러스터 (3건, 2026-04-29)
- 재동의 모달 영구 stuck (PR #1168) —
consent-records.ts:113의.set({merge:true})+ dot-path 키 ("consents.terms") literal 저장. fakeDb 가 production semantic 모방 못 해 테스트 통과.setSubmitting(false)누락으로 spinner 영구. - web build 실패 (PR #1169) — Client Component 가
@neohollo/business-logic/casesbarrel 에서 순수 함수 import → firebase-admin 까지 client bundle 로 끌려옴 →node:fs에러. PR #1058 머지 시점에 type-check + test 통과, CI 에next build부재로 production 배포까지 가서야 노출. - ops 콘솔 KPI=0 (ADR 0029 origin) — 12 곳 v8 호환 가드
typeof !== "function"가 v9 modular SDK 인스턴스에서 항상 silent skip.
공통 origin 패턴 — "성공한 척 + 다른 layer 에서 깨짐"
- write 성공 반환 + retrieval 위치 불일치 (사고 1)
- 빌드/타입체크 통과 + production bundle 깨짐 (사고 2)
- 가드 통과 + 실제 호출 silent skip (사고 3)
세 사고 모두 코드/테스트 layer 통과 → production·사용자 화면에서 노출. 사용자 시각 "silent 0" (영구 spinner / 흰 화면 / KPI 0). "실패" 단어 떠올리지 않음.
사용자 코칭 (학습 자산화)
사용자(개발팀 대표) 가 본 ADR 골격을 직접 코칭:
- "전체 코드를 분석해봐야 하지 않나요?" → broader audit 의무화 origin
- "빌드 깨지는건 정말 크리티컬... 우리가 왜 테스트코드를 만들죠" → T1 (CI build) origin
- "회고하는 회의를 하는게 좋지않나요? 반드시 정확히 정밀하게 짚고 넘어가야해요" → 본 5인 회의 origin
- "회고회의는 특별한 탭 또는 공간에 기록되어야... 문서로 반드시 남겨야" →
retrospectives/디렉토리 origin
분노 자체가 정당한 코칭이었음을 기록. 다음 자율 세션에서 같은 위기 신호 (테스트 통과 + production fail) 발생 시 즉시 회고 trigger.
대안 검토
| 대안 | 평가 |
|---|---|
| 단일 layer 강화 (CI build 만 또는 fakeDb 폐기 만) | 거부 — 사고 3종이 다른 layer 에 분포. 단일 안전망으로 모두 못 잡음 |
| 8 task 모두 즉시 시행 | 거부 — 1인 환경 over-spec. ROI 음수 task 후순위 분리 (ADR 0031 / 6개월 ADR) |
| 사고 후 broader audit 전면 의무화 (C-H) | 거부 — P3 PM 분석: 절차 over-spec, ship cadence 손상. P0 한정 차등 으로 축소 |
| ADR 0029 후속 5-13 회의 분리 유지 | 거부 — P4 분석: 분리 결정 자체가 origin 의 일부, 13일 못 가서 같은 origin 사고 재현. ADR 0030 에 흡수 |
| 사용자 분노 회복 공식 응답·사과문 | 거부 — P3·P1 합의: over-spec. ADR ship + 1주 후 status update 로 충분 |
| fakeDb 즉시 전면 폐기 → emulator 단일화 | 거부 — P3 분석: test suite 시간 폭발 + emulator brittle + sweep cadence 손상. 점진안 (T2 throw + 6개월 ADR) 으로 대체 |
측정·성공 지표
본 ADR ship 후 1주 시점 status update 시 검증:
- T1~T8 즉시 시행 8 task 머지 완료 + commit 링크
- CI build green rate (T1 ship 후 1주, 새 PR 의 build 검증 통과율)
- T5 외부 사용자 영향 audit 결과 — 영향 사용자 N명 / 통보 + 재동의 적용 수
- T4 production smoke + alerting 채널 작동 (테스트용 의도적 실패 1회 trigger 후 알림 도달 확인)
- LLM hedging 패턴 grep 결과 0건 (T8 ship 후 회귀 차단)
- 6개월 시점 — 동일 origin (silent 0) 사고 재발 0건
Minority Report
P3 PM Minority — fakeDb 단일화 시점
P5 의 fakeDb 폐기 → emulator 단일화 방향은 옳다. 그러나 6개월 timeline 도 1인+LLM 환경에서 over-spec 일 수 있음. emulator brittle 함이 또 다른 사고 origin 가능. 별도 ADR 결정 시점에 emulator 안정성 데이터 (1주간 CI 실패율) 가 input 되어야. 데이터 없이 단일화 결정 시 다른 형태의 신뢰 위기 발생 가능.
→ 수용: 6개월 후 ADR 본문에 "1주 안정성 데이터 input 필수" 명시.
P4 디자이너 Minority — UX 토큰화 강도
ADR 0031 디자인 토큰 spec 이 2주 timeline 으로 분리됐지만 P0 사고 회복은 1주 내 즉시 시행. UX 안전망이 또 후순위로 밀린 것 — 같은 origin 패턴이 ADR 0030 안에서도 재현. AsyncBoundary wrapper 만이라도 즉시 시행 묶음 T3 에 포함 — 이게 빠지면 또 다음 사고에서 영구 spinner 발생 가능.
→ 수용: T3 의 useServerAction 강화판에 AsyncBoundary timeout wrapper 명시적으로 포함. ADR 0031 은 시각 토큰 (4-state 색상·SystemStatusBadge 컴포넌트 디자인) 만 다룸.
P1 변호사 Minority — 외부 사용자 통보 SOP
T5 외부 사용자 영향 audit 결과 잘못 저장된 케이스 발견 시 통보·재동의·로그 보존 SOP 가 본 ADR 에 명시되지 않았음. audit 만 하고 SOP 없으면 발견 시점에 또 회의해야 함. 선제 SOP 명시 필요.
→ 수용: 본 ADR 부록 A 로 추가 (아래).
부록 A — 동의 corruption 발견 시 SOP
T5 audit 결과 잘못 저장된 동의 케이스 발견 시:
- 영향 사용자 식별 —
users/{uid}에 literal key ("consents.terms"·"consents.privacy"·"consents.beta"·"consents.marketingEmail"·"consents.marketingEmail.withdrawnAt") 존재하는 모든 uid 추출 - 재동의 모달 강제 trigger —
consents.terms.version을 "force-reconsent" sentinel 값으로 set →(workspace)/layout.tsx검사가needsRecconsent=true판정 - 로그 양쪽 보존:
- 잘못 저장된 literal key 는 audit 보존 후
FieldValue.delete()삭제 - 신규 동의는 정상 nested 위치에 저장
- 양쪽 timestamp · user-agent 는
audit/consents/{uid}별도 컬렉션 보존 (정통망법 §50 ⑥ 정합)
- 잘못 저장된 literal key 는 audit 보존 후
- commit — audit 결과 + 영향 사용자 N명 + 통보 시점
retrospectives/2026-04-29-p0-cluster-test-trust-crisis.md에 follow-up commit
후속 회의 (별도)
/meeting 디자인 시스템 4-state 토큰 spec tier=B(ADR 0031 발산, P4 주도, 2주 내)/meeting fakeDb 폐기 → emulator 단일화 결정 tier=B(6개월 후, 안정성 데이터 input 후)/meeting C-G phase 2 preview channel 자동화 tier=C(phase 1 ROI 검증 후)
관련 PR · ADR
- 회고 원문: retrospectives/2026-04-29-p0-cluster-test-trust-crisis
- 관련 ADR: 0028 비즈니스 로직 추출 · 0029 Firebase fail-loud
- 사고 hotfix PR: #1167 docs comprehensive · #1168 dot-path silent corruption · #1169 web build barrel pollution