# 미팅 1: 현황 진단
**일시**: 2026-03-04
**주제**: InfoKeyword 프로젝트 Worker-Frontend 스키마 불일치 사고 — 현황 진단
**퍼실리테이터**: 개발2팀 Agent
**형식**: 역할 시뮬레이션 미팅 (토르 / 프레이야 / 헤임달 / 마아트)

---

## 참가자

| 이름 | 역할 | 책임 영역 |
|------|------|-----------|
| 토르 | 백엔드 개발자 | Python Worker, Firestore 저장 로직 |
| 프레이야 | 프론트엔드 개발자 | TypeScript 클라이언트, 데이터 렌더링 |
| 헤임달 | 테스터 | 단위/통합/E2E 테스트 설계 및 실행 |
| 마아트 | QC 매니저 | qc_verify.py 운영, QC-RULES.md 관리 |

---

## 각 참가자 발표

### 토르 (백엔드)

**"Worker는 자기 일을 했습니다. 문제는 계약서가 없었다는 겁니다."**

1. **출력 스키마 명세 부재**
   Worker는 Python 관례에 따라 `snake_case` 키(예: `keyword_count`, `search_volume`, `created_at`)로 Firestore에 데이터를 씁니다. 이 키명이 Firestore 도큐먼트의 "최종 계약"인지, 아니면 어딘가에서 변환되어야 하는지 명시된 문서가 한 건도 없습니다. Worker 코드의 주석, README, 설계서 어디에도 "Firestore 저장 키 목록"이 공식 명세로 존재하지 않습니다.

2. **단위 테스트가 내부 로직만 검증**
   Worker의 pytest 단위 테스트는 `process()` 함수의 반환값이 올바른 값인지 확인하지만, 반환 dict의 **키 이름**이 정해진 스키마와 일치하는지는 검사하지 않습니다. 즉, `keyword_count` → `keywordCount`로 키명이 바뀌어도 단위 테스트는 통과합니다.

3. **camelCase 변환 책임자 불명확**
   "snake_case → camelCase 변환은 누가 합니까?"라는 질문에 팀 내 합의가 없었습니다. Worker가 해야 하는지, API 게이트웨이에서 해야 하는지, 프론트엔드 fetch 레이어에서 해야 하는지 — 아무도 공식적으로 담당하지 않는 **책임 공백 구간**이 존재합니다.

4. **Firestore 컬렉션/필드 문서화 없음**
   현재 Firestore에 어떤 컬렉션이 있고, 각 도큐먼트에 어떤 필드가 몇 가지 타입으로 저장되는지 정의한 명세(spec)가 없습니다. 새로운 팀원이 투입되거나 Worker 로직이 변경될 때마다 Firestore 실제 데이터를 직접 들여다봐야만 파악 가능합니다.

5. **Worker 변경 시 하위 호환성 검사 없음**
   Worker가 새 필드를 추가하거나 기존 필드명을 변경해도, 이를 소비하는 프론트엔드에 알림이 가지 않습니다. 현재 CI/CD 파이프라인에 "스키마 변경 감지 및 영향 범위 알림" 단계가 없습니다.

---

### 프레이야 (프론트엔드)

**"TypeScript가 컴파일 타임에 모든 것을 잡아준다고 믿었습니다. 그건 착각이었습니다."**

1. **런타임 데이터 검증 없음 (Zod 미도입)**
   TypeScript `interface`와 `type`은 컴파일 타임 추상화입니다. Firestore SDK가 반환하는 객체는 `as KeywordData` 같은 타입 단언(type assertion)으로 받아오는데, 이는 실제 데이터 구조를 전혀 검사하지 않습니다. Zod, io-ts, valibot 같은 런타임 파서가 없기 때문에 `keyword_count`가 들어와도 TypeScript는 이를 `keywordCount`로 받은 것처럼 행동하지 않고, 그냥 `undefined`를 반환합니다.

2. **undefined 렌더링이 에러가 아닌 묵음(silent failure)**
   `{data.keywordCount}` 가 `undefined`일 때 React는 아무것도 렌더링하지 않습니다. 콘솔 에러도 없고, 빌드 에러도 없습니다. 사용자 화면에는 빈칸이 나타나고, 이것이 "데이터가 없는 것"인지 "키 불일치"인지 개발자가 직접 Firestore를 까봐야 알 수 있습니다.

3. **데이터 모델이 Firestore 실제 구조와 동기화되지 않음**
   `src/types/keyword.ts`의 `interface KeywordData`는 개발자가 임의로 작성한 것입니다. Worker가 실제로 Firestore에 저장하는 필드 목록과 이 인터페이스가 일치한다는 보장이 없으며, 두 파일이 언제 마지막으로 동기화되었는지 추적하는 메커니즘이 없습니다.

4. **Fetch/Adapter 레이어에 변환 로직 없음**
   Firestore 데이터를 가져오는 서비스 함수(`getKeywordData()` 등)에 snake_case → camelCase 변환 유틸리티가 없습니다. 표준 JS 라이브러리(`camelCase()` 유틸이나 `humps` 패키지 등)를 사용하는 어댑터 패턴이 적용되지 않았습니다.

5. **스냅샷 테스트가 실제 데이터 기반이 아님**
   프론트엔드 Jest 스냅샷 테스트는 개발자가 하드코딩한 mock 데이터로 돌아갑니다. mock 데이터는 camelCase로 작성되어 있어 테스트가 통과되지만, 실제 Firestore에서 오는 snake_case 데이터를 기반으로 한 테스트가 없습니다.

---

### 헤임달 (테스터)

**"빌드가 초록불이었습니다. 그래서 저는 OK라고 판단했습니다. 그것이 제 실수입니다."**

1. **End-to-End 데이터 흐름 테스트 부재**
   현재 테스트 스위트는 Worker 단위 테스트와 React 컴포넌트 단위 테스트로 구성됩니다. Worker → Firestore 저장 → 프론트엔드 fetch → 렌더링까지 이어지는 **전체 데이터 파이프라인을 하나의 테스트로 검증하는 통합 테스트**가 없습니다. 이 구간이 바로 이번 사고가 발생한 지점입니다.

2. **"빌드 성공 = QC 완료" 관행**
   `npm run build`와 `pytest`가 모두 통과하면 QC 체크리스트에 체크를 했습니다. 빌드 성공은 타입 일관성과 코드 문법을 보장할 뿐, **실제 데이터가 올바른 키로 전달되는지**는 전혀 보장하지 않습니다. 이 판단 기준 자체가 잘못된 것이었습니다.

3. **실제 Firestore 데이터 샘플 기반 테스트 없음**
   테스터로서 Firestore 스테이징 환경에서 실제 도큐먼트를 샘플링하여 프론트엔드에서 올바르게 렌더링되는지 확인하는 절차가 없었습니다. 스테이징 데이터와 mock 데이터가 동일한지 비교하는 자동화 도구가 없습니다.

4. **계약 테스트(Contract Test) 개념 미도입**
   Consumer-Driven Contract Testing(예: Pact) 같은 방법론을 통해 "프론트엔드가 기대하는 데이터 구조"와 "백엔드가 실제로 제공하는 데이터 구조"를 자동으로 대조하는 테스트가 없습니다.

5. **QA 단계에서 Firestore 직접 조회 없음**
   QA 프로세스에서 "Firestore 도큐먼트를 직접 조회하여 키 목록을 눈으로 확인"하는 수동 체크리스트 항목도 없었습니다. 자동화되지 않았더라도 최소한 수동 확인 단계가 있었다면 사전에 발견할 수 있었습니다.

---

### 마아트 (QC)

**"qc_verify.py는 '작업이 완료되었는가'를 확인하지, '작업 결과물이 올바른가'를 확인하지 않습니다."**

1. **qc_verify.py 4개 verifier 모두 스키마 검증 불능**
   현재 verifier 구성:
   - `api_health.py`: HTTP 200 응답 여부만 확인. 응답 body의 필드 구조 검증 없음.
   - `file_check.py`: `.done` 파일과 보고서 파일의 **존재 여부와 크기**만 확인. 파일 내용의 정합성 검증 없음.
   - `data_integrity.py`: `task-timers.json`의 status 필드와 `.done` 파일 존재 여부 **교차 대조**만 수행. Firestore 실제 저장 데이터 구조 검증 없음.
   - `test_runner.py`: pytest exit code만 확인. 테스트가 스키마 일관성을 검사하는지 여부는 관여하지 않음.

   결론: **4개 verifier 중 데이터 스키마를 검사하는 것이 하나도 없습니다.**

2. **셀프 QC 5항목이 너무 추상적**
   QC-RULES.md의 셀프 QC 5항목 중 스키마 불일치를 명시적으로 요구하는 항목이 없습니다:
   - "이 변경이 다른 파일에 영향을 미치는가?" → Worker 키명 변경이 프론트엔드에 영향을 미친다는 것을 인지하려면 개발자가 이미 그 관계를 알고 있어야 합니다.
   - "테스트가 모든 경로를 커버하는가?" → "모든 경로"의 정의에 end-to-end 데이터 흐름이 포함되는지 불명확합니다.

   즉, 체크리스트가 있어도 **체크리스트 항목 자체가 이 유형의 버그를 지목하지 않습니다.**

3. **Worker-Frontend 간 계약(Contract) 검증 단계 없음**
   QC 파이프라인에 "Worker 출력 스키마 ↔ Frontend 입력 스키마 일치 여부 확인" 단계가 존재하지 않습니다. 이 계약 검증은 qc_verify.py에도, QC-RULES.md 어디에도 언급되어 있지 않습니다.

4. **마아트 독립 검증(critical 레벨)도 스키마 사각지대**
   critical 레벨에서 마아트가 직접 qc_verify.py를 재실행하고 결과물을 검토하지만, qc_verify.py 자체에 스키마 검증이 없으므로 마아트 검증을 해도 스키마 불일치는 발견되지 않습니다. 도구의 한계가 그대로 검증 결과의 한계가 됩니다.

5. **QC 규칙이 "프로세스 완료 검증"에 편향**
   전체 QC 파이프라인(셀프 QC → qc_verify.py → 마아트 → 로키)이 "작업을 완료했는가"와 "코드에 보안 취약점이 있는가"에 집중되어 있습니다. **"시스템 컴포넌트 간 인터페이스가 올바른가"**라는 관점의 검증이 빠져 있습니다.

---

## 교차 토론 핵심

### 라운드 1: 책임 소재

**프레이야 → 토르**: "토르, Worker가 snake_case로 저장할 때 적어도 팀에 공지하거나 문서화해야 했던 것 아닌가요? 우리가 camelCase를 기대한다는 걸 알고 있었잖아요."

**토르 → 프레이야**: "맞습니다. 하지만 프레이야도 `interface KeywordData`를 정의할 때 Firestore에서 실제로 어떤 키가 오는지 확인했어야 합니다. `as KeywordData` 타입 단언은 TypeScript가 거짓말을 해도 모른다는 의미입니다."

**합의**: 양측 모두 책임이 있으나, 근본 원인은 개인의 주의 부족이 아닌 **공유 계약(shared contract) 부재**입니다.

---

### 라운드 2: 테스트의 역할

**헤임달 → 토르+프레이야**: "두 분 다 단위 테스트는 작성했는데, 두 테스트가 만나는 지점의 테스트가 없었습니다. 이건 테스트 전략 자체의 문제입니다."

**토르 → 헤임달**: "통합 테스트를 작성하려면 Firestore 에뮬레이터 환경이 필요합니다. 그 환경 셋업 비용을 누가 부담합니까? 테스터 역할에 포함됩니까, 개발자 역할입니까?"

**헤임달**: "그 경계가 불명확했다는 것 자체가 문제입니다. 통합 테스트 환경 구축 책임이 아무에게도 할당되지 않았습니다."

**합의**: 통합 테스트 환경 책임자 미지정이 구조적 공백입니다.

---

### 라운드 3: QC 도구의 한계

**마아트 → 헤임달**: "헤임달, '빌드 성공 = OK' 판단을 했다고 했는데, QC-RULES.md를 보면 qc_verify.py PASS가 OK의 기준입니다. qc_verify.py가 PASS를 줬다면 저도 같은 판단을 했을 겁니다."

**헤임달 → 마아트**: "그렇다면 qc_verify.py가 PASS를 주는 기준 자체를 다시 설계해야 하는 것 아닌가요?"

**마아트**: "동의합니다. 현재 qc_verify.py는 '프로세스가 완료되었는가'를 검증하지 '결과물이 올바른가'를 검증하지 않습니다. 도구 자체에 스키마 검증 verifier를 추가해야 합니다."

**프레이야 → 마아트**: "셀프 QC 5항목에 '다른 컴포넌트와의 인터페이스를 검토했는가?' 같은 항목이 있었다면, 개발자 스스로 이 부분을 점검했을 겁니다."

**합의**: QC 도구와 체크리스트 모두 인터페이스/계약 검증 항목이 누락되어 있습니다.

---

### 라운드 4: 즉시 가능한 개선 vs. 구조적 개선

**토르**: "단기적으로는 Worker 출력 부분에 JSON Schema 또는 Pydantic 모델을 명시적으로 정의하고, 그것을 Firestore 저장 시 반드시 통과하도록 할 수 있습니다."

**프레이야**: "프론트엔드에서는 Firestore 데이터를 받는 시점에 Zod로 파싱하면 됩니다. 파싱 실패 시 에러를 던지면 undefined 묵음 현상이 사라집니다."

**헤임달**: "Firestore 에뮬레이터를 이용한 통합 테스트 환경을 CI에 추가하는 것이 중기 과제입니다."

**마아트**: "qc_verify.py에 5번째 verifier `schema_contract.py`를 추가해야 합니다. Worker 출력 스키마와 Frontend 기대 스키마를 하나의 공유 파일로 관리하고, 양쪽 모두 이 파일을 기준으로 검증합니다."

---

## 합의된 문제점 목록

| # | 문제점 | 영향 범위 | 심각도 |
|---|--------|-----------|--------|
| P1 | Worker 출력 Firestore 키 명세(spec) 부재 | 백엔드-프론트엔드 전체 | Critical |
| P2 | camelCase 변환 책임자 미지정 (계약 공백) | 시스템 아키텍처 | Critical |
| P3 | 프론트엔드 런타임 스키마 검증 없음 (Zod 미도입) | 프론트엔드 | High |
| P4 | undefined 렌더링이 묵음 실패 (silent failure) | 프론트엔드 | High |
| P5 | Worker → Firestore → Frontend 통합 테스트 없음 | 테스트 전략 | Critical |
| P6 | 통합 테스트 환경(Firestore 에뮬레이터) 책임자 미지정 | 프로세스 | High |
| P7 | qc_verify.py에 스키마/계약 verifier 없음 | QC 파이프라인 | High |
| P8 | QC-RULES.md 셀프 QC 5항목이 인터페이스 검증 미요구 | QC 프로세스 | Medium |
| P9 | 마아트 독립 검증도 qc_verify.py 결과에 의존 → 도구 한계 전파 | QC 프로세스 | Medium |
| P10 | Worker 변경 시 하위 호환성 알림 체계 없음 | CI/CD | Medium |

---

## 근본 원인 분석 (5 Whys + 피시본)

### 5 Whys

**현상**: InfoKeyword Worker가 저장한 `snake_case` 키가 프론트엔드에서 `undefined`로 렌더링되어 프로덕션 사고 발생

| Why | 질문 | 답 |
|-----|------|----|
| Why 1 | 왜 프론트엔드가 undefined를 렌더링했는가? | Firestore 도큐먼트의 실제 키(snake_case)와 TypeScript interface의 키(camelCase)가 불일치했기 때문 |
| Why 2 | 왜 이 불일치가 감지되지 않았는가? | 런타임 스키마 검증 도구(Zod 등)가 없고, 통합 테스트도 없어서 빌드 성공만으로 OK 판단했기 때문 |
| Why 3 | 왜 런타임 검증과 통합 테스트가 없었는가? | "Worker 출력 스키마"와 "Frontend 입력 스키마"를 연결하는 공유 계약(contract)이 정의되지 않았기 때문 |
| Why 4 | 왜 공유 계약이 정의되지 않았는가? | camelCase 변환 책임이 어느 레이어에 있는지 팀 내 합의가 없었고, Firestore 필드 명세 문서화 관행이 없었기 때문 |
| Why 5 | 왜 이 합의와 문서화가 이루어지지 않았는가? | **QC 파이프라인(qc_verify.py + QC-RULES.md)이 "컴포넌트 간 인터페이스 계약"을 검증하는 단계를 요구하지 않았기 때문** |

**근본 원인**: QC 프로세스와 도구가 "각 컴포넌트가 내부적으로 올바른가"만 검증하도록 설계되어 있고, "컴포넌트 간 데이터 계약이 일관성을 유지하는가"를 검증하는 메커니즘이 전혀 없었음.

---

### 피시본 다이어그램 (Ishikawa)

```
                              [프로덕션 사고: snake_case vs camelCase 불일치]
                                              |
           ┌──────────────────────────────────┼──────────────────────────────────┐
           |                                  |                                  |
    [프로세스]                          [도구/기술]                          [조직/구조]
           |                                  |                                  |
  ┌────────┴──────────┐            ┌──────────┴──────────┐            ┌──────────┴──────────┐
  계약 정의 절차 없음   셀프QC       qc_verify.py        Zod/런타임     camelCase 변환        Firestore
  변경 시 소통         5항목 추상   스키마 verifier 없음  검증 미도입    책임자 불명확         스키마 명세
  체계 없음            적                                              (계약 공백)           문서 없음
                                   통합 테스트 환경      TypeScript
                                   책임 미지정           타입단언      통합 테스트            Worker 변경
                                                        남용          환경 구축 책임         알림 체계
                                                                      미지정                없음
```

---

### 핵심 구조적 결론

> **"각 레이어는 자기 테스트를 통과했다. 하지만 레이어 사이의 경계를 테스트하는 사람이 없었다."**

현재 QC 시스템의 설계 철학은 **"컴포넌트 완성도 검증"**에 머물러 있습니다. 다음 미팅(미팅 2: 개선 방향 설계)에서는 **"인터페이스 계약 검증"** 개념을 QC 파이프라인 전체에 내재화하는 방안을 도출해야 합니다.

---

## 다음 미팅 사전 과제

| 담당 | 과제 |
|------|------|
| 토르 | Worker Firestore 저장 필드 목록 초안 작성 (Pydantic 모델 형태) |
| 프레이야 | 현재 `src/types/` 하위 interface 목록과 Firestore 실제 필드 대조 표 작성 |
| 헤임달 | Firestore 에뮬레이터 기반 통합 테스트 POC 가능성 조사 |
| 마아트 | `schema_contract.py` verifier 요구사항 초안 + QC-RULES.md 셀프 QC 개정안 |

**다음 미팅**: 미팅 2 - 개선 방향 설계 및 우선순위 결정
