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

---

## 참가자

| 이름 | 역할 | 전문 영역 | 미팅 1 이후 사전 과제 완료 여부 |
|------|------|-----------|--------------------------------|
| 토르 | 백엔드 개발자 | Python Worker, Pydantic 모델 방안 | Worker Firestore 저장 필드 목록 초안 완료 |
| 프레이야 | 프론트엔드 개발자 | Zod 스키마 런타임 검증, TypeScript 타입 안전성 | interface vs Firestore 실제 필드 대조표 완료 |
| 헤임달 | 테스터 | E2E 통합 테스트, 데이터 흐름 검증 전략 | Firestore 에뮬레이터 POC 조사 완료 |
| 미미르 | 아키텍트 | 전체 아키텍처 관점, 도구/패턴 선택 | 신규 참가자, 전체 관점 제공 |
| 마아트 | QC 매니저 | QC-RULES.md 개정안, 새 verifier 설계 | schema_contract.py 요구사항 초안 완료 |

---

## 개회 — 퍼실리테이터

**퍼실리테이터**: 미팅 1에서 우리는 근본 원인을 명확히 했습니다. "각 레이어는 자기 테스트를 통과했지만, 레이어 사이 경계를 테스트하는 사람이 없었다." 오늘은 그 경계를 구체적으로 어떻게 검증할 것인지 설계합니다. 미미르(아키텍트)가 오늘 처음 참여하는데, 전체 관점을 제시해 주시길 부탁드립니다.

**미미르**: 감사합니다. 사전에 미팅 1 자료를 검토했습니다. 제가 먼저 전제 하나를 말씀드리겠습니다. 오늘 설계의 핵심 제약은 **"Agent(AI)가 코드를 작성하는 환경"**이라는 점입니다. 즉, 도구가 복잡할수록 Agent가 잘못 사용하거나 유지보수 부담이 높아집니다. 단순하고 기계적으로 작동하는 방안이 화려한 방안보다 우선입니다. 이 원칙을 계속 상기하면서 진행하겠습니다.

---

## 개선안 브레인스토밍

---

### 안건 1: E2E 통합 테스트 자동화 방안

**퍼실리테이터**: 헤임달, 사전 과제로 Firestore 에뮬레이터 기반 통합 테스트 POC를 조사하셨죠. 결과를 먼저 발표해 주세요.

---

**헤임달 (테스터)**:

"Firestore 에뮬레이터 + pytest + 실제 Worker 출력 샘플 기반 E2E 테스트"

사전 조사 결과를 공유합니다. 접근법은 3가지입니다.

**접근법 A: Firebase Emulator Suite + pytest-firebase**
- Firebase CLI로 로컬 Firestore 에뮬레이터 기동
- Worker를 에뮬레이터 대상으로 실행 → 실제로 저장
- Python 테스트에서 에뮬레이터 Firestore 읽어서 스키마 검증
- 장점: 실제 Firestore와 완벽히 동일한 환경
- 단점: Firebase CLI 설치 필요, 에뮬레이터 기동 시간(~5초), CI 환경 셋업 복잡도 높음

**접근법 B: Worker 샘플 출력 JSON 파일 + 오프라인 스키마 검증 (권장)**
- Worker의 `process()` 함수를 실행하여 출력 dict를 JSON 파일로 저장 (`sample_output.json`)
- 이 JSON 파일을 공유 스키마 정의에 대해 검증하는 pytest 작성
- 프론트엔드 측에서도 이 JSON을 입력으로 Zod 파싱 테스트 실행
- 장점: Firestore 불필요, 빠름, Agent가 이해하기 쉬움, 유지보수 단순
- 단점: 실제 Firestore write/read 경로를 커버하지 않음

**접근법 C: Firestore 에뮬레이터 + 스키마 덤프 비교**
- 에뮬레이터에 Worker 실행 → 저장된 도큐먼트 구조를 JSON으로 덤프
- 덤프 JSON을 버전 관리에 커밋하여 "실제 Firestore 계약서"로 사용
- 장점: Firestore 저장 경로까지 커버
- 단점: 에뮬레이터 의존성

**나의 권장**: 접근법 B를 즉시 시작, 접근법 A를 중기 목표로 설정.

---

**토르 (백엔드)**:

헤임달 발표에 동의합니다. 접근법 B에서 중요한 것은 "Worker 샘플 출력이 실제 코드 실행 결과"여야 한다는 점입니다. mock이 아니라 실제 `process()` 함수를 테스트 데이터와 함께 실행해서 나온 dict를 직렬화한 것이어야 합니다.

제가 추가로 제안할 것은 **Worker 자체에 스키마 검증 레이어를 내장**하는 것입니다. Pydantic 모델을 정의하면, Worker가 Firestore에 저장하기 전에 자동으로 구조 검증을 합니다. 이 Pydantic 모델을 JSON Schema로 export하면, 그것이 자동으로 "계약서"가 됩니다.

```python
# workers/info_keyword/models.py (신규)
from pydantic import BaseModel
from typing import Optional
import json

class KeywordDataSchema(BaseModel):
    keyword: str
    keyword_count: int
    search_volume: int
    difficulty_score: float
    created_at: str
    updated_at: str
    source: str
    task_id: str

# JSON Schema export (단 한 번 실행으로 계약서 생성)
# python -c "from models import KeywordDataSchema; print(KeywordDataSchema.schema_json(indent=2))"
```

Pydantic이 있으면 `process()` 함수 반환 직전에 `KeywordDataSchema(**result)` 한 줄로 검증이 완료됩니다. 검증 실패 시 Worker가 예외를 던지므로, 잘못된 데이터가 Firestore에 절대 저장되지 않습니다.

---

**프레이야 (프론트엔드)**:

토르가 Pydantic에서 JSON Schema를 export하는 아이디어가 핵심입니다. 그 JSON Schema를 프론트엔드에서 `zod-from-json-schema` 또는 `json-schema-to-zod`로 Zod 스키마로 변환하면, **하나의 공유 소스에서 양쪽 검증 스키마가 파생**됩니다.

하지만 저는 현실적으로 더 단순한 방법을 선호합니다. JSON Schema 변환 도구를 거치지 않고, 사람(또는 Agent)이 직접 작성한 Zod 스키마를 `shared/schemas/` 에 두는 방식입니다. 양쪽이 같은 파일을 참조하는 "단일 진실 공급원(Single Source of Truth)" 개념입니다.

E2E 통합 테스트 관점에서, 프론트엔드가 Worker 샘플 출력 JSON을 Zod로 파싱하는 테스트를 `vitest`로 작성하면:
```typescript
// tests/integration/worker-output.test.ts
import { KeywordDataZodSchema } from '../../shared/schemas/keyword-data.schema'
import sampleOutput from '../../shared/schemas/keyword-data.sample.json'

test('Worker 샘플 출력이 프론트엔드 스키마와 일치해야 한다', () => {
  const result = KeywordDataZodSchema.safeParse(sampleOutput)
  expect(result.success).toBe(true)
})
```

이 테스트가 실패하면 Worker와 Frontend의 계약이 깨진 것입니다.

---

**미미르 (아키텍트)**:

양쪽 발표를 종합하면 핵심 흐름이 보입니다. 제가 정리하겠습니다:

```
Worker process() 실행
    → Pydantic 모델 검증 (Python)
    → Firestore 저장
    ↓
[공유 스키마 파일: shared/schemas/keyword-data.schema.json]
    ↑                           ↓
Pydantic으로 생성 (Python)   Zod 스키마로 참조 (TypeScript)
    ↑                           ↓
Worker 단위 테스트        Frontend Zod 런타임 파싱
                               ↓
                         E2E 통합 테스트 (Worker 샘플 JSON → Zod 파싱)
```

중요한 아키텍처 결정이 하나 있습니다. **camelCase 변환 책임 소재를 지금 결정해야 합니다.** (미팅 1에서 P2 Critical 문제). 변환 책임이 어디에 있느냐에 따라 공유 스키마의 키 컨벤션이 달라집니다.

세 가지 옵션:
1. Worker가 camelCase로 저장 → 프론트엔드는 그대로 사용
2. Worker는 snake_case 유지, 프론트엔드 fetch 레이어에서 변환
3. Firestore에는 snake_case, API 게이트웨이가 변환 (현재 없음)

저의 권장은 **옵션 2 (Worker snake_case 유지, 프론트엔드 어댑터 변환)**입니다. Python 관례를 깨지 않고, 프론트엔드에서 `humps` 라이브러리 한 줄로 변환 가능합니다. 그리고 이 변환은 Zod 파싱 이후에 수행하여 검증 대상은 항상 snake_case 원본으로 통일합니다.

---

**안건 1 토론 요약**

| 방안 | 접근 | 장점 | 단점 |
|------|------|------|------|
| A: Firestore 에뮬레이터 풀 E2E | Firebase CLI + pytest | 완벽한 실환경 | 셋업 복잡, CI 부담 |
| B: 샘플 JSON 기반 오프라인 E2E | Worker 실행 → JSON → Zod 파싱 | 단순, 빠름, Agent 친화적 | Firestore write 경로 제외 |
| C: Pydantic → JSON Schema → Zod | 단일 진실 공급원 자동 파생 | 완전한 동기화 | 변환 도구 의존성 |
| D: 수동 작성 Zod + 샘플 JSON | 가장 단순 | 즉시 실행 가능 | 수동 동기화 필요 |

**잠정 합의**: B + D 조합. 즉, Worker 샘플 JSON 기반 오프라인 E2E 테스트 + 수동 작성 Zod 스키마(초기), 이후 Pydantic JSON Schema export 자동화 고려.

---

### 안건 2: 스키마 일관성 검증 도구

**퍼실리테이터**: 기술 선택이 핵심입니다. JSON Schema, Zod, Pydantic 중 어떻게 조합할지 토론해 주세요.

---

**미미르 (아키텍트)**:

기술 선택 전에 원칙을 먼저 정해야 합니다. 우리 환경에서 도구 선택의 기준은:

1. **Agent 친화성**: Agent가 코드를 읽고 이해하고 수정할 수 있는가?
2. **설치 부담**: 새로운 패키지/도구 의존성 최소화
3. **에러 메시지 명확성**: 검증 실패 시 "무엇이 잘못되었는지" 즉시 파악 가능한가?
4. **언어 생태계 적합성**: Python에서는 Python 관례, TypeScript에서는 TypeScript 관례

이 기준으로 평가하면:

| 도구 | Agent 친화성 | 설치 부담 | 에러 명확성 | 생태계 적합성 |
|------|-------------|-----------|------------|--------------|
| JSON Schema | 중간 (장황함) | 낮음 (표준) | 중간 | 언어 독립적 |
| Pydantic (Python) | 높음 | 낮음 (이미 일반적) | 높음 | Python 최적 |
| Zod (TypeScript) | 높음 | 낮음 (npm) | 매우 높음 | TypeScript 최적 |
| jsonschema (Python lib) | 중간 | 낮음 | 낮음 | Python |

**권장 조합**: Python 쪽은 Pydantic, TypeScript 쪽은 Zod, 공유 계약 파일은 JSON Schema.
이유: Pydantic과 Zod 모두 JSON Schema와 상호 변환 가능. 공유 파일을 JSON Schema로 두면 언어 독립적으로 유지되며, 각 언어에서 자신의 생태계 도구로 파싱.

---

**토르 (백엔드)**:

Pydantic 선택에 적극 동의합니다. 현재 우리 Worker들이 dict를 반환하는데, Pydantic 모델로 감싸면 세 가지가 동시에 해결됩니다:

1. **타입 강제**: `keyword_count`에 문자열이 들어오면 즉시 ValidationError
2. **스키마 자동 문서화**: `model.schema_json()` 한 줄로 JSON Schema 생성
3. **Firestore 저장 키 공식화**: `model.dict()` 결과가 곧 Firestore 필드 목록

구체적 설계:
```python
# workers/info_keyword/models.py
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime

class KeywordFirestoreDoc(BaseModel):
    """InfoKeyword Worker가 Firestore에 저장하는 도큐먼트 스키마.
    이 모델이 Worker-Frontend 계약의 단일 진실 공급원(SSoT)입니다.
    """
    keyword: str = Field(..., description="검색 키워드")
    keyword_count: int = Field(..., description="검색 결과 수", ge=0)
    search_volume: int = Field(..., description="월간 검색량", ge=0)
    difficulty_score: float = Field(..., description="키워드 난이도 0.0~1.0", ge=0.0, le=1.0)
    source: str = Field(..., description="데이터 출처 (예: google, naver)")
    task_id: str = Field(..., description="생성 Worker 태스크 ID")
    created_at: str = Field(..., description="ISO 8601 생성 시각")
    updated_at: str = Field(..., description="ISO 8601 갱신 시각")

    class Config:
        # JSON Schema에 설명 포함
        schema_extra = {
            "title": "KeywordFirestoreDocument",
            "description": "InfoKeyword Worker → Firestore 저장 스키마. 변경 시 Frontend Zod 스키마와 반드시 동기화."
        }
```

이 모델에서 JSON Schema를 export하는 명령을 Makefile 또는 스크립트로 만들어두면, 계약 갱신이 `make export-schema` 한 번으로 됩니다.

---

**프레이야 (프론트엔드)**:

Zod 선택에 동의합니다. Zod의 가장 큰 장점은 **파싱과 타입 추론이 하나로 통합**된다는 점입니다:

```typescript
// shared/schemas/keyword-data.schema.ts
import { z } from 'zod'

// Worker가 Firestore에 저장하는 원본 스키마 (snake_case)
export const KeywordFirestoreDocSchema = z.object({
  keyword: z.string(),
  keyword_count: z.number().int().nonnegative(),
  search_volume: z.number().int().nonnegative(),
  difficulty_score: z.number().min(0).max(1),
  source: z.string(),
  task_id: z.string(),
  created_at: z.string(),
  updated_at: z.string(),
})

// TypeScript 타입은 Zod 스키마에서 자동 추론
export type KeywordFirestoreDoc = z.infer<typeof KeywordFirestoreDocSchema>

// 프론트엔드 표현용 (camelCase 변환 후)
export const KeywordDisplaySchema = KeywordFirestoreDocSchema.transform((data) => ({
  keyword: data.keyword,
  keywordCount: data.keyword_count,
  searchVolume: data.search_volume,
  difficultyScore: data.difficulty_score,
  source: data.source,
  taskId: data.task_id,
  createdAt: data.created_at,
  updatedAt: data.updated_at,
}))

export type KeywordDisplay = z.infer<typeof KeywordDisplaySchema>
```

이 파일 하나로:
- `KeywordFirestoreDocSchema`: Firestore 데이터 파싱 (snake_case 원본 검증)
- `KeywordDisplaySchema`: 변환 + 타입 추론 (camelCase 프론트엔드 타입)
- camelCase 변환이 Zod transform에 포함 → 책임 공백(P2) 해결

파싱 실패 시 Zod는 "keyword_count는 number여야 하는데 undefined입니다" 같은 구체적 에러를 던집니다. undefined 묵음 실패(P4) 완전 해결.

---

**헤임달 (테스터)**:

도구 선택에 동의합니다. 테스터 관점에서 한 가지 추가 요구사항을 드립니다.

스키마 파일과 샘플 출력 파일을 함께 버전 관리해야 합니다. 구체적으로:

```
shared/schemas/
    keyword-data.schema.json     # Pydantic에서 export한 JSON Schema (정규형)
    keyword-data.sample.json     # Worker 실행 결과 샘플 (실제 데이터)
    keyword-data.schema.ts       # Zod 스키마 (프론트엔드 사용)
```

`keyword-data.sample.json`은 테스트 픽스처이자 "계약 예시"입니다. Worker 로직이 변경되면 이 파일도 갱신해야 하고, 갱신 후 Zod 파싱 테스트가 통과해야 PR merge가 가능하도록 CI 규칙을 추가합니다.

Worker가 필드를 추가하면 `sample.json`이 바뀌고 → Zod 테스트가 실패하고 → 개발자가 Zod 스키마를 업데이트해야 합니다. 이것이 자동화된 계약 관리입니다.

---

**마아트 (QC)**:

기술 선택 토론을 정리하면, 이 접근법은 qc_verify.py에서 검증하기도 좋습니다. `schema_contract.py` verifier가 다음을 체크할 수 있습니다:

1. `keyword-data.schema.json` 파일이 존재하는가?
2. `keyword-data.sample.json`이 JSON Schema에 대해 유효한가? (`jsonschema` Python 라이브러리)
3. Pydantic 모델 파일 (`models.py`)과 JSON Schema 파일의 마지막 수정 시각이 일치하는가? (드리프트 감지)

`jsonschema` 라이브러리는 표준 Python 라이브러리이며, 추가 설치 없이 pip 패키지 하나로 JSON Schema 검증이 가능합니다. Agent가 이해하기 쉽고 유지보수도 단순합니다.

---

**안건 2 토론 요약**

**기술 스택 결정**:
- Python (Worker): Pydantic v2 (스키마 정의 + 검증 + JSON Schema export)
- TypeScript (Frontend): Zod (런타임 파싱 + 타입 추론 + camelCase 변환)
- 공유 계약 파일: JSON Schema (`.schema.json`)
- QC 검증 (Python): `jsonschema` 라이브러리

**camelCase 변환 책임 소재 결정**: 프론트엔드 Zod transform 레이어 (P2 해결)

---

### 안건 3: Worker-Frontend 계약(Contract) 테스트 방안

**퍼실리테이터**: 계약 테스트는 미팅 1에서 가장 큰 구조적 공백으로 지목되었습니다. 구체적 구현 방안을 설계해 주세요.

---

**미미르 (아키텍트)**:

Consumer-Driven Contract Testing(Pact) 같은 완전한 도구 도입은 과도합니다. 우리 시스템 특성(Agent 코드 생성 환경, Firestore 중심 데이터 흐름)에 맞는 경량 계약 테스트를 설계해야 합니다.

제가 제안하는 **경량 계약 테스트 패턴**:

```
[계약 파일]
shared/schemas/keyword-data.schema.json  ← 단일 진실 공급원

[Producer 테스트 - Python/pytest]
workers/info_keyword/tests/test_contract.py
  - Worker process() 실행 → 출력 dict가 JSON Schema를 만족하는가?

[Consumer 테스트 - TypeScript/vitest]
frontend/tests/integration/keyword-contract.test.ts
  - sample.json을 Zod로 파싱 → 성공해야 한다
  - 파싱 결과로 컴포넌트 렌더링 → 모든 필드가 undefined 아니어야 한다
```

Pact와 비교:
- Pact: 별도 서버, pact 파일 관리, 복잡한 CI 연동
- 우리 방안: JSON 파일 하나, pytest + vitest, 기존 CI에 추가만 하면 됨

이 수준으로도 "Producer가 Consumer의 기대를 충족하는가"를 자동으로 검증할 수 있습니다.

---

**토르 (백엔드)**:

Worker 측 계약 테스트를 구체적으로 설계했습니다:

```python
# workers/info_keyword/tests/test_contract.py
import json
import jsonschema
import pytest
from pathlib import Path
from ..models import KeywordFirestoreDoc
from ..worker import process  # 실제 Worker 함수

SCHEMA_PATH = Path(__file__).parent.parent.parent.parent / "shared" / "schemas" / "keyword-data.schema.json"
SAMPLE_PATH = Path(__file__).parent.parent.parent.parent / "shared" / "schemas" / "keyword-data.sample.json"

@pytest.fixture
def schema():
    with open(SCHEMA_PATH) as f:
        return json.load(f)

def test_worker_output_matches_contract(schema):
    """Worker 실제 출력이 계약 스키마와 일치해야 한다."""
    # 실제 Worker 실행 (테스트 데이터 사용)
    result = process(keyword="테스트", task_id="test-contract-001")

    # Pydantic 검증 (내부 검증)
    doc = KeywordFirestoreDoc(**result)

    # JSON Schema 검증 (공유 계약 검증)
    jsonschema.validate(doc.dict(), schema)  # 예외 없으면 PASS

def test_sample_matches_schema(schema):
    """샘플 출력 파일이 계약 스키마와 일치해야 한다."""
    with open(SAMPLE_PATH) as f:
        sample = json.load(f)
    jsonschema.validate(sample, schema)  # 예외 없으면 PASS

def test_pydantic_schema_matches_json_schema(schema):
    """Pydantic 모델에서 생성한 JSON Schema가 공유 파일과 일치해야 한다."""
    pydantic_schema = KeywordFirestoreDoc.schema()
    # 핵심 필드 목록이 일치하는지 확인
    pydantic_fields = set(pydantic_schema["properties"].keys())
    contract_fields = set(schema["properties"].keys())
    assert pydantic_fields == contract_fields, (
        f"Pydantic 모델 필드와 계약 스키마 필드 불일치:\n"
        f"  Pydantic에만 있음: {pydantic_fields - contract_fields}\n"
        f"  계약에만 있음: {contract_fields - pydantic_fields}"
    )
```

---

**프레이야 (프론트엔드)**:

Frontend 측 계약 테스트:

```typescript
// frontend/tests/integration/keyword-contract.test.ts
import { describe, test, expect } from 'vitest'
import { KeywordFirestoreDocSchema, KeywordDisplaySchema } from '../../shared/schemas/keyword-data.schema'
import sampleOutput from '../../../shared/schemas/keyword-data.sample.json'

describe('Worker-Frontend 계약 테스트', () => {
  test('Worker 샘플 출력이 Firestore 스키마와 일치해야 한다', () => {
    const result = KeywordFirestoreDocSchema.safeParse(sampleOutput)
    if (!result.success) {
      throw new Error(
        `스키마 불일치:\n${result.error.issues.map(i => `  - ${i.path.join('.')}: ${i.message}`).join('\n')}`
      )
    }
    expect(result.success).toBe(true)
  })

  test('Firestore 데이터가 Display 스키마로 변환되어야 한다', () => {
    const result = KeywordDisplaySchema.safeParse(sampleOutput)
    expect(result.success).toBe(true)
    if (result.success) {
      // camelCase 변환 검증
      expect(result.data.keywordCount).toBeDefined()
      expect(result.data.searchVolume).toBeDefined()
      expect(result.data.difficultyScore).toBeDefined()
      // snake_case 키가 남아있지 않아야 함
      expect((result.data as any).keyword_count).toBeUndefined()
    }
  })

  test('모든 필드가 undefined가 아니어야 한다 (silent failure 방지)', () => {
    const result = KeywordDisplaySchema.safeParse(sampleOutput)
    expect(result.success).toBe(true)
    if (result.success) {
      const data = result.data
      Object.entries(data).forEach(([key, value]) => {
        expect(value, `${key} 필드가 undefined입니다`).not.toBeUndefined()
      })
    }
  })
})
```

---

**헤임달 (테스터)**:

두 분의 테스트 설계가 훌륭합니다. 제가 여기에 **테스트 실행 전략**을 추가합니다.

계약 테스트는 일반 단위 테스트와 분리하여 별도 테스트 스위트로 관리해야 합니다:

```
tests/
  unit/          # 기존 단위 테스트 (빠름, 자주 실행)
  contract/      # 계약 테스트 (중간 속도, PR 시 필수 실행)
  e2e/           # 완전한 E2E (느림, 스테이징 배포 후 실행)
```

CI 파이프라인 규칙:
- `git push`: unit 테스트만
- PR open/update: unit + contract 테스트
- staging 배포: 전체 (unit + contract + e2e)

계약 테스트가 FAIL이면 PR merge 블락. 이것이 "계약 위반 방지 게이트"입니다.

---

**마아트 (QC)**:

헤임달의 테스트 전략을 qc_verify.py에 통합하는 방안을 제안합니다.

`--contract-schema` 플래그를 추가하면, `schema_contract.py` verifier가 다음을 수행합니다:

```bash
# 계약 테스트 포함 QC 실행
python3 qc_verify.py \
  --task-id task-xxx \
  --contract-schema /path/to/shared/schemas/keyword-data.schema.json \
  --contract-sample /path/to/shared/schemas/keyword-data.sample.json
```

Worker가 Firestore 저장 로직을 변경한 경우, QC에서 계약 검증이 자동으로 수행됩니다.

---

**안건 3 토론 요약**

**계약 테스트 구조**:
```
shared/schemas/keyword-data.schema.json  (단일 진실 공급원)
    ├── Producer 테스트 (pytest): Worker 출력 → JSON Schema 검증
    └── Consumer 테스트 (vitest): sample.json → Zod 파싱 검증
```

**계약 위반 감지 시나리오**:
- Worker 필드 추가 → sample.json 미갱신 → Python 계약 테스트 FAIL
- Frontend Zod 스키마와 JSON Schema 불일치 → TypeScript 계약 테스트 FAIL
- camelCase 변환 누락 → `keyword_count` 남아있음 → 변환 테스트 FAIL

---

### 안건 4: QC-RULES.md 개정안

**퍼실리테이터**: 마아트, 사전 과제로 개정안 초안을 준비했죠. 발표해 주세요.

---

**마아트 (QC)**:

현행 QC-RULES.md의 핵심 문제는 미팅 1에서 명확히 했습니다. 셀프 QC 5항목이 인터페이스 계약 검증을 요구하지 않고, qc_verify.py 4개 verifier 중 스키마를 검사하는 것이 하나도 없습니다.

**개정안 1: 셀프 QC 항목 확장 (5항목 → 6항목)**

현행 5항목:
1. 이 변경이 다른 파일에 영향을 미치는가?
2. 이 로직의 엣지 케이스는 무엇인가?
3. 이 구현이 작업 지시와 정확히 일치하는가?
4. 에러 처리와 보안은 확인했는가?
5. 테스트가 모든 경로를 커버하는가?

추가 항목 (6번):
> **6. 이 변경이 다른 컴포넌트와의 인터페이스 계약에 영향을 미치는가?**
> - Worker 출력 필드가 변경된 경우: `shared/schemas/` 계약 파일 갱신 및 계약 테스트 재실행
> - Frontend 타입/스키마가 변경된 경우: Zod 스키마와 JSON Schema 동기화 확인
> - API 응답 구조가 변경된 경우: 소비자(Consumer) 측 영향 범위 명시

**개정안 2: 자동 검증 강화 - schema_contract verifier 추가**

현행 qc_verify.py ALL_CHECKS:
```python
ALL_CHECKS = ["api_health", "file_check", "data_integrity", "test_runner"]
```

개정안:
```python
ALL_CHECKS = ["api_health", "file_check", "data_integrity", "test_runner", "schema_contract"]
```

`schema_contract.py` verifier 요구사항:

```python
# verifiers/schema_contract.py
"""
schema_contract.py - Worker-Frontend 스키마 계약 검증 verifier

검증 항목:
1. 계약 파일(schema.json) 존재 여부 확인
2. 샘플 파일(sample.json)이 schema.json을 만족하는지 검증
3. (선택) Pydantic 모델 파일과 schema.json 필드 목록 일치 여부

플래그:
  --contract-schema: JSON Schema 파일 경로
  --contract-sample: 샘플 출력 JSON 파일 경로
  --contract-model: Pydantic 모델 파일 경로 (선택)
"""

def verify(schema_path: str = "", sample_path: str = "", model_path: str = "") -> dict:
    """
    계약 파일 존재, 샘플 유효성, 필드 드리프트를 검증합니다.
    schema_path가 지정되지 않으면 SKIP (하위 호환성 유지).
    """
    ...
```

**개정안 3: 검증 레벨별 계약 검증 의무화**

| 레벨 | 계약 검증 의무 |
|------|--------------|
| normal | schema_contract verifier 실행 (SKIP 가능 - schema 지정 없으면) |
| critical | schema_contract verifier 필수 실행 (SKIP 불가), 계약 테스트 결과 포함 |
| security | critical 전체 + 계약 파일 무결성 해시 확인 |

**개정안 4: QC-RULES.md에 "인터페이스 계약" 섹션 추가**

기존 QC-RULES.md는 4개 섹션(셀프 QC, 자동 검증, 마아트 독립 검증, 로키 보안 감사)으로 구성됩니다. 여기에:

> **섹션 1.5: 인터페이스 계약 점검 체크리스트 (셀프 QC 직후)**
>
> Worker 또는 Frontend 데이터 모델 변경 시 반드시 수행:
> - [ ] `shared/schemas/*.schema.json` 갱신 완료
> - [ ] `shared/schemas/*.sample.json` 갱신 완료 (Worker 실제 실행 결과 기반)
> - [ ] Python 계약 테스트 (`test_contract.py`) PASS 확인
> - [ ] TypeScript 계약 테스트 (`keyword-contract.test.ts`) PASS 확인
> - [ ] `camelCase ↔ snake_case` 변환 레이어 Zod transform에 반영 확인

---

**프레이야 (프론트엔드)**: 마아트, 셀프 QC 6번 항목이 좋습니다. 그런데 "이 변경이 인터페이스에 영향을 미치는가"를 Agent가 어떻게 판단하는지가 관건입니다. Worker 코드 변경 작업이면 계약 점검이 필수, 순수 UI 변경이면 선택 — 이런 조건을 명시해야 합니다.

**마아트**: 좋은 지적입니다. 다음과 같이 구체화하겠습니다:

> **6항목 적용 조건**:
> - Workers 디렉토리 하위 파일 변경 → 무조건 적용
> - `src/types/`, `src/services/`, `shared/schemas/` 변경 → 무조건 적용
> - UI 컴포넌트 변경만 → 6번 "해당 없음"으로 체크 허용

**토르**: 조건이 명확해서 Agent가 판단하기 쉽겠네요. Pydantic 모델 파일 (`models.py`)이 변경되면 반드시 JSON Schema 재export + 계약 테스트 재실행이 트리거되어야 합니다.

**헤임달**: 마아트의 개정안에 하나 추가하겠습니다. 마아트 독립 검증(critical 레벨)에서 "계약 테스트 결과 확인" 항목을 추가해야 합니다. 현재 마아트 검증 지시문에 계약 테스트 재실행이 포함되어 있지 않습니다.

**마아트**: 동의합니다. 마아트 역할 지시에 다음을 추가:
> "7. 계약 테스트가 있으면 직접 재실행하세요: `pytest workers/.../test_contract.py` + `npx vitest run tests/integration/`"

---

**안건 4 토론 요약**

QC-RULES.md 개정 포인트 4가지:
1. 셀프 QC 6번 항목 추가 (인터페이스 계약 영향 점검)
2. qc_verify.py에 `schema_contract` verifier 추가
3. critical 레벨 마아트 검증 지시문에 계약 테스트 재실행 추가
4. QC-RULES.md에 "인터페이스 계약 점검 체크리스트" 섹션 추가

---

## 우선순위 평가 (Impact vs Effort 매트릭스)

**퍼실리테이터**: 지금까지 나온 개선안들을 Impact(사고 재발 방지 효과)와 Effort(구현/유지보수 비용) 기준으로 평가합니다. 각 참가자가 점수를 매긴 후 평균을 냅니다.

**평가 기준**:
- Impact: 1(낮음) ~ 5(높음) — "이것이 없었다면 이번 사고가 발생했는가?"
- Effort: 1(낮음) ~ 5(높음) — "구현 + 유지보수 비용이 얼마나 큰가?"
- 우선순위: Impact 높고 Effort 낮을수록 우선

| # | 개선안 | 토르 Impact | 프레이야 Impact | 헤임달 Impact | 미미르 Impact | 마아트 Impact | **평균 Impact** | 토르 Effort | 프레이야 Effort | 헤임달 Effort | 미미르 Effort | 마아트 Effort | **평균 Effort** | **우선순위** |
|---|--------|------------|----------------|--------------|--------------|--------------|----------------|------------|----------------|--------------|--------------|--------------|----------------|------------|
| I1 | Pydantic 모델로 Worker 출력 스키마 공식화 | 5 | 4 | 5 | 5 | 5 | **4.8** | 2 | 1 | 1 | 2 | 1 | **1.4** | **P1** |
| I2 | Zod 스키마 + camelCase transform (Frontend) | 4 | 5 | 5 | 5 | 4 | **4.6** | 2 | 2 | 1 | 2 | 1 | **1.6** | **P2** |
| I3 | 공유 JSON Schema 파일 생성 (SSoT) | 5 | 5 | 5 | 5 | 5 | **5.0** | 2 | 2 | 1 | 2 | 2 | **1.8** | **P3** |
| I4 | 샘플 JSON + Python 계약 테스트 (pytest) | 4 | 4 | 5 | 4 | 5 | **4.4** | 2 | 1 | 2 | 2 | 2 | **1.8** | **P4** |
| I5 | 샘플 JSON + TypeScript 계약 테스트 (vitest) | 4 | 5 | 5 | 4 | 5 | **4.6** | 2 | 2 | 2 | 2 | 2 | **2.0** | **P5** |
| I6 | QC-RULES.md 셀프 QC 6항목 추가 | 3 | 3 | 4 | 3 | 5 | **3.6** | 1 | 1 | 1 | 1 | 1 | **1.0** | **P6** |
| I7 | schema_contract.py verifier (qc_verify.py) | 4 | 4 | 4 | 4 | 5 | **4.2** | 3 | 2 | 2 | 3 | 3 | **2.6** | **P7** |
| I8 | QC-RULES.md 인터페이스 계약 섹션 추가 | 3 | 3 | 4 | 3 | 5 | **3.6** | 1 | 1 | 1 | 1 | 1 | **1.0** | **P8** |
| I9 | Firestore 에뮬레이터 E2E (접근법 A) | 4 | 4 | 5 | 3 | 3 | **3.8** | 4 | 4 | 5 | 5 | 3 | **4.2** | **P9** |
| I10 | CI 파이프라인 계약 테스트 게이트 | 4 | 5 | 5 | 4 | 4 | **4.4** | 3 | 3 | 3 | 3 | 3 | **3.0** | **P10** |

**매트릭스 시각화**:
```
Impact
  5 |  I3
    |  I1  I2  I5
  4 |      I4  I10     I9
    |  I7
  3 |          I6  I8
    |
  2 |
    |                        I-에뮬레이터(I9)
  1 +--+--+--+--+--+--→ Effort
     1  2  3  4  5

[우선 실행 영역: Impact≥4, Effort≤2] → I1, I2, I3, I4, I5
[빠른 승리: 낮은 Effort] → I6, I8 (QC-RULES.md 텍스트 수정)
[중기 과제] → I7 (schema_contract.py), I10 (CI 게이트)
[장기 과제] → I9 (Firestore 에뮬레이터)
```

---

## 최종 개선안 목록 (우선순위순)

### Phase 1 — 즉시 실행 (이번 주, Impact 높음 + Effort 낮음)

**[A1] 공유 스키마 디렉토리 + JSON Schema 파일 생성** (I3, P3)
- 담당: 토르 + 마아트
- 작업: `shared/schemas/` 디렉토리 생성, `keyword-data.schema.json` 작성 (Pydantic export 기반)
- 작업: `keyword-data.sample.json` 생성 (Worker 실제 실행 결과)
- 완료 기준: JSON Schema 파일이 버전 관리에 커밋됨

**[A2] Worker Pydantic 모델 추가** (I1, P1)
- 담당: 토르
- 작업: `workers/info_keyword/models.py` 생성 (`KeywordFirestoreDoc`)
- 작업: Worker `process()` 함수 반환 직전 Pydantic 검증 추가
- 완료 기준: Worker가 잘못된 필드명으로 저장 시도 시 즉시 예외 발생

**[A3] Frontend Zod 스키마 + camelCase Transform 추가** (I2, P2)
- 담당: 프레이야
- 작업: `shared/schemas/keyword-data.schema.ts` 생성 (`KeywordFirestoreDocSchema` + `KeywordDisplaySchema`)
- 작업: Firestore fetch 서비스 함수에 Zod 파싱 적용 (기존 `as KeywordData` 타입 단언 제거)
- 완료 기준: Firestore에서 잘못된 키가 오면 즉시 파싱 에러 발생 (silent failure 제거)

**[A4] QC-RULES.md 텍스트 개정** (I6 + I8, P6 + P8)
- 담당: 마아트
- 작업: 셀프 QC 5항목 → 6항목 (인터페이스 계약 영향 점검 추가)
- 작업: 인터페이스 계약 점검 체크리스트 섹션 추가
- 작업: 마아트 독립 검증 지시문에 계약 테스트 재실행 항목 추가
- 완료 기준: QC-RULES.md 업데이트 완료, 팀 공유

---

### Phase 2 — 단기 실행 (다음 주, 계약 테스트 구현)

**[B1] Python 계약 테스트 작성** (I4, P4)
- 담당: 헤임달 + 토르
- 작업: `workers/info_keyword/tests/test_contract.py` 작성
- 테스트 항목: Worker 출력 → JSON Schema 검증, sample.json → JSON Schema 검증, Pydantic ↔ JSON Schema 필드 일치
- 완료 기준: `pytest test_contract.py` 통과

**[B2] TypeScript 계약 테스트 작성** (I5, P5)
- 담당: 헤임달 + 프레이야
- 작업: `frontend/tests/integration/keyword-contract.test.ts` 작성
- 테스트 항목: sample.json → Zod 파싱, camelCase 변환 검증, undefined 필드 없음 검증
- 완료 기준: `vitest run tests/integration/` 통과

---

### Phase 3 — 중기 실행 (2~3주 내, 도구 통합)

**[C1] schema_contract.py verifier 구현** (I7, P7)
- 담당: 마아트 + 미미르
- 작업: `verifiers/schema_contract.py` 신규 작성
- 작업: `qc_verify.py`에 schema_contract 통합 + `--contract-schema`, `--contract-sample` 플래그 추가
- 완료 기준: `qc_verify.py --contract-schema ... --contract-sample ...` 실행 시 계약 검증 수행

**[C2] CI 파이프라인 계약 테스트 게이트** (I10, P10)
- 담당: 헤임달 + 미미르
- 작업: PR CI에 `pytest test_contract.py` + `vitest run tests/integration/` 추가
- 작업: 계약 테스트 FAIL 시 PR merge 블락 규칙 설정
- 완료 기준: 계약 위반 PR이 자동으로 merge 블락됨

---

### Phase 4 — 장기 검토 (1개월 내, 완전한 E2E)

**[D1] Firestore 에뮬레이터 기반 E2E 테스트** (I9, P9)
- 담당: 헤임달
- 작업: Firebase CLI 기반 에뮬레이터 CI 환경 구축
- 작업: Worker 실행 → 에뮬레이터 저장 → 프론트엔드 fetch → 렌더링 검증
- 완료 기준: 스테이징 배포 전 E2E 테스트 자동 실행
- 주의: 복잡도 높음, Phase 3 완료 후 착수

---

## 기술 스택 결정

| 영역 | 기술 | 용도 | 선택 이유 |
|------|------|------|-----------|
| Python Worker 스키마 | Pydantic v2 | 출력 스키마 정의 + 런타임 검증 + JSON Schema export | Python 생태계 표준, Agent 친화적, 자동 문서화 |
| TypeScript Frontend 스키마 | Zod | Firestore 데이터 런타임 파싱 + camelCase transform + 타입 추론 | TypeScript 생태계 최적, 에러 메시지 명확 |
| 공유 계약 파일 | JSON Schema (`.schema.json`) | 언어 독립적 단일 진실 공급원 | Pydantic/Zod 모두 JSON Schema 지원 |
| Python 계약 검증 | `jsonschema` 라이브러리 | JSON Schema 파일 대상 Python 데이터 검증 | 표준 패키지, 추가 의존성 최소 |
| Python 계약 테스트 | pytest | 계약 테스트 실행 | 기존 테스트 프레임워크 재사용 |
| TypeScript 계약 테스트 | Vitest | 계약 테스트 실행 | 기존 테스트 프레임워크 재사용 |
| E2E (장기) | Firebase Emulator Suite | 완전한 Firestore 통합 테스트 | 실환경 동일, Phase 4 |

**채택하지 않은 기술 및 이유**:

| 기술 | 검토 이유 | 채택하지 않은 이유 |
|------|-----------|------------------|
| Pact (Consumer-Driven Contract) | 계약 테스트 전문 도구 | 별도 서버, pact 파일 관리 복잡도, Agent 환경에 과도함 |
| humps (Python/JS camelCase 변환) | 자동 변환 유틸 | Zod transform으로 충분히 대체 가능, 의존성 최소화 |
| json-schema-to-zod (자동 변환) | Pydantic → Zod 자동화 | 변환 도구 신뢰성 불확실, 수동 동기화가 더 명확 |
| io-ts / valibot | TypeScript 런타임 검증 | Zod가 팀 내 가장 익숙하고 에러 메시지 우수 |
| GraphQL 스키마 | 타입 안전 API | 현재 Firestore 직접 접근 구조 대규모 변경 필요 |

---

## 결정된 아키텍처 설계

### camelCase 변환 책임 소재 (P2 해결)

**결정**: 프론트엔드 Zod transform 레이어에서 처리

```
Worker process()
    → Pydantic 검증 (snake_case, Python 관례 유지)
    → Firestore 저장 (snake_case 원본 저장)
    → Frontend Zod 파싱 (snake_case → camelCase transform)
    → React 컴포넌트 (camelCase, TypeScript 관례)
```

**근거**:
- Worker는 Python 관례(snake_case) 유지 → 코드 일관성
- Firestore 저장 키는 snake_case 원본 → 쿼리/검색 일관성
- 변환 책임이 Zod 파일 한 곳에 명시 → 계약 문서이자 구현체
- camelCase transform 변경 시 TypeScript 타입이 자동으로 변경됨 (타입 안전성)

### 파일 구조

```
shared/
    schemas/
        keyword-data.schema.json        # Pydantic export JSON Schema (SSoT)
        keyword-data.sample.json        # Worker 실제 실행 결과 샘플
        keyword-data.schema.ts          # Zod 스키마 (Frontend 사용)

workers/
    info_keyword/
        models.py                       # Pydantic KeywordFirestoreDoc (신규)
        worker.py                       # Pydantic 검증 통합 (수정)
        tests/
            test_contract.py            # Python 계약 테스트 (신규)

frontend/
    src/
        services/
            keyword-service.ts          # Zod 파싱 통합 (수정)
        types/
            keyword.ts                  # Zod infer로 교체 (수정)
    tests/
        integration/
            keyword-contract.test.ts    # TypeScript 계약 테스트 (신규)

teams/dev1/qc/
    qc_verify.py                        # schema_contract 통합 (수정)
    verifiers/
        schema_contract.py              # 신규 verifier (신규)

teams/shared/
    QC-RULES.md                         # 셀프 QC 6항목 + 계약 섹션 추가 (수정)
```

---

## 다음 미팅(미팅 3: 최종 확정)을 위한 준비 사항

### 미팅 3 안건

1. **Phase 1 구현 결과 검토** — 각 담당자가 A1~A4 완료 상태 보고
2. **schema_contract.py 상세 설계 확정** — verifier 함수 시그니처, 에러 메시지 포맷
3. **QC-RULES.md 최종 개정안 승인** — 팀 전체 합의 필요
4. **CI 계약 게이트 설계 확정** — PR 블락 조건, 예외 처리 방안
5. **Firestore 에뮬레이터 E2E 타임라인 확정**

### 사전 과제 (미팅 3 전까지)

| 담당 | 과제 | 완료 기준 |
|------|------|----------|
| 토르 | `workers/info_keyword/models.py` 초안 구현 | Pydantic 모델 + JSON Schema export 스크립트 |
| 프레이야 | `shared/schemas/keyword-data.schema.ts` 초안 구현 | Zod 스키마 + camelCase transform |
| 헤임달 | `test_contract.py` + `keyword-contract.test.ts` 초안 | 기본 케이스 3개 테스트 통과 |
| 마아트 | `schema_contract.py` verifier 요구사항 상세 명세 | 함수 시그니처 + 검증 항목 목록 |
| 미미르 | CI 계약 게이트 설계 초안 | GitHub Actions 또는 기존 CI에 통합 방안 |
| 전체 | `keyword-data.schema.json` + `keyword-data.sample.json` 합의 | 필드 목록 + 타입 최종 확정 |

### 미결 사항 (미팅 3에서 결정)

1. **JSON Schema 생성 자동화**: Pydantic export 명령을 언제, 누가 실행하는가? (Makefile? CI step? 수동?)
2. **schema.json 드리프트 감지**: Pydantic 모델과 schema.json이 불일치할 때 어떻게 감지하는가?
3. **샘플 JSON 갱신 주기**: Worker 로직 변경 시마다? 주기적으로?
4. **다른 Worker로의 확장**: InfoKeyword 외 다른 Worker들에도 동일 패턴 적용 타임라인
5. **레거시 `as KeywordData` 타입 단언 제거**: 기존 코드 migration 범위와 순서

---

## 미팅 결론

**핵심 합의사항**:

1. **단일 진실 공급원 채택**: `shared/schemas/keyword-data.schema.json`이 Worker-Frontend 계약의 유일한 정의 파일
2. **기술 스택 확정**: Python = Pydantic, TypeScript = Zod, 공유 = JSON Schema, QC = jsonschema
3. **camelCase 변환 책임 확정**: 프론트엔드 Zod transform (P2 해결)
4. **계약 테스트 구조 확정**: pytest (Producer) + vitest (Consumer) + JSON Schema (계약 파일)
5. **4단계 실행 계획 확정**: Phase 1(즉시) → Phase 2(단기) → Phase 3(중기) → Phase 4(장기)
6. **QC-RULES.md 개정 방향 확정**: 셀프 QC 6항목 + 계약 섹션 + schema_contract verifier

**미미르의 최종 평가**:
> "오늘 설계한 방안은 모두 Agent가 이해하고 유지보수할 수 있는 수준입니다. JSON Schema 파일 하나를 중심으로 Python과 TypeScript가 각자의 생태계 도구로 검증하는 구조는 단순하면서도 강력합니다. Pact 같은 전문 도구를 도입하지 않고도 계약 테스트의 본질적 목표를 달성할 수 있습니다."

**마아트의 최종 평가**:
> "qc_verify.py에 schema_contract verifier가 추가되면, 이번 사고와 동일한 유형의 문제는 QC 단계에서 자동으로 검출됩니다. 이것이 오늘 미팅의 가장 중요한 성과입니다."
