# [Lv.3] RP 배치1 — 담보명 fallback(a042) + 만기·상품유형 저장(골든 계약 동기)

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "extension/content.js"
    - "extension/background/ingest.js"
    - "extension/__tests__/**"
    - "server/schemas/consultation_history_v1.py"
    - "server/routes/consultation_history_v1.py"
    - "server/tests/**"
    - "tests/contract/**"
    - "docs/contracts/**"
  forbidden_paths:
    - "src/**"
    - "extension/manifest.json"
    - "server/migrations/**"
    - ".github/**"
  commands: ["npx vitest","pytest","npx tsc","node","git"]
  merge_policy: "none"
  ttl_hours: 18
```
> ★ merge_policy=none. base=현 origin/main. **골든 계약(확장 OHMY_PPD_V2 ↔ 서버 model_validate) 원자적 동기 필수.** migration 금지(query_condition=JSONB라 nested 추가는 컬럼 변경 불필요).

## 배경 (회장 확정 + Codex 계약검토)
복합설계 RP 상담을 위해 저장 레코드가 (a) 담보명 정상 (b) 납입기간(만기) (c) 상품유형(비갱신)을 담아야 웹앱이 X년 총액·비교블록을 그린다. Codex 검토 = 만기는 **이미 확장이 파싱 중**(content.js:249 `maturityRaw.match(/(\d+년).*?(\d+세)/)`)이나 **query_condition에 저장 안 됨**. 담보명은 코드 fallback 버그.

## 작업 A — 담보명 실제 텍스트 fallback (a042 해결, 계약 무관 독립)
- content.js:293 `const coverageName = el.getAttribute("coverage_name") || cd` → **fallback 체인 보강**: `coverage_name` 속성 → **화면 표시 담보명 텍스트(해당 담보 행/라벨의 visible text)** → `cd`(최후).
- a042처럼 `coverage_name` 속성이 빈 담보도 오마이매니저 화면에 보이는 실제 이름(예 "가족일상생활중배상책임")을 담게. DOM 실측으로 담보명 텍스트 위치 확인(bojang_lists 행 텍스트 등).
- analysis_matrix.coverages[].name 에 이 값이 실림(서버는 JSONB 그대로 저장·계약 영향 없음). matrix DOM 테스트(extension/__tests__/matrix-detection.test.ts 등) 갱신.
- **주의**: 계약 픽스처 골든과 무관하지만, 담보명이 바뀌면 관련 스냅샷 테스트가 있으면 동반 갱신.

## 작업 B — 만기·상품유형을 query_condition에 저장 (계약 동기 필수)
- **캡처**: 만기("20년/100세") 파싱값(납입기간年·만기歲) + 상품유형("손보 종합(무해지)")을 캡처 payload에 포함. 만기 파싱은 기존 로직 재사용(content.js:249), 상품유형은 상단 폼 select 값 신규 캡처.
- **query_condition 확장**(Codex 최소침습 권고): 기존 age/gender/insurance_type/plan_id 에 **optional** `payment_years`(int, 납입기간) · `maturity_age`(int, 만기세) · `product_type`(str, 상품유형) 추가. top-level 신규필드 금지(query_condition JSONB에 nested).
- **★ 골든 계약 원자적 동기 (Codex 경고 — 순서·전수)**:
  1. **서버 스키마 먼저**: `server/schemas/consultation_history_v1.py` `QueryCondition`(+최상위 모델) 의 `extra="forbid"` 때문에 **스키마에 optional 필드 선반영 안 하면 422**. 서버가 새 필드를 받아들이게 먼저.
  2. **확장 payload**: content.js·ingest.js 가 새 필드 전송.
  3. **골든 픽스처 재생성**: `tests/contract/generate_golden.mjs` → `tests/contract/ohmy_ppd_v2.golden.json` 재생성. 관련 exact-match 테스트 전부 갱신: extension/__tests__/contract-golden.test.ts·contract-golden-provenance.test.ts·track-a-preview.test.ts(exact-key assertion), server/tests/test_contract_golden.py·test_consultation_history_v1.py.
  4. **계약 문서**: docs/contracts/INSURO_LV4_PROGRAM_CONTRACTS_V1.md 갱신.
- **request_fingerprint 불변**: 동일조회 판정(dedup)은 여전히 4요소(age/gender/insurance_type/plan_id) 기준. **새 필드를 fingerprint에 넣지 말 것**(문서에 명시).
- **하위호환**: 새 필드 optional → 기존 저장 레코드(필드 없음) 로드 시 정상(웹앱은 없으면 20년 기본 or 미표시 graceful). optional additive.
- **파싱 위치**: 만기 파싱은 확장 캡처시(기존 패턴 유지). payment_years=만기의 "년" 부분(20/30). 서버는 받은 값 저장만(신규 서버 파생 로직 도입 금지 — 현 패턴 유지).

## 제약
- **X년 총액 계산·비교블록 UI는 이번 범위 아님(배치2 웹앱).** 배치1은 캡처·저장·계약까지.
- consent/egress/single-egress/taint-guard·저장 전송 로직 불변. 비갱신 필터는 상품유형 저장까지만(필터 로직은 웹앱 배치2).
- 확장 manifest 불변(버전만 필요시 finish 단계 아닌 별도). migration 0.

## 검증
- `npx vitest run extension/__tests__/` 전체 PASS(계약 골든 포함 재생성 후). 신규: 담보명 fallback(coverage_name 빈 담보→visible text·있으면 속성 우선·최후 cd) / 만기·상품유형 캡처·payload 포함.
- `pytest server/tests/` — QueryCondition optional 필드 수용(있음/없음 both), 계약 골든 PASS, extra=forbid 위반 없음, fingerprint 4요소 불변.
- 골든 픽스처 재생성 후 확장↔서버 exact-match 정합. 기존 저장 레코드(신규필드 없음) 하위호환.
- `npx tsc`(확장 ts 테스트) 클린.

## 완료 (★ 순서 B)
- 변경 = allowed_resources 내부만. src/(웹앱)·manifest·migration 불변.
- **dev6 금지** — dev2. 커밋 → push → finish-task **foreground 1회** → .done 즉시 종료. **background+wait 금지.** ANU 검증·Codex 재검토·머지·새 파일럿빌드 전송은 ANU.

## goal_assertions (auto-generated)
- `npx vitest run extension/__tests__/`
- `pytest server/tests/`
- `npx tsc`
