# [Lv.4] 생보 3대질병 — 기존 레코드 업데이트(멱등 append) + 계단식 2시나리오 표시

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "server/routes/consultation_history_v1.py"
    - "server/schemas/consultation_history_v1.py"
    - "server/tests/**"
    - "src/components/composite/**"
    - "src/lib/**"
    - "src/**/__tests__/**"
    - "tests/e2e/**"
    - "extension/content.js"
    - "extension/__tests__/**"
  forbidden_paths:
    - ".github/**"
    - "extension/manifest.json"
    - "server/migrations/**"
    - "tests/contract/**"
  commands: ["npx vitest","pytest","npx tsc","git"]
  merge_policy: "none"
  ttl_hours: 20
```
> ★ merge_policy=none. base=현 origin/main(#159, life_matrix 계약·컬럼 존재). 골든 계약 픽스처 변경 금지(life_matrix optional 이미 반영됨).

## 배경 (회장 확정)
- **B안 채택**: 손보 저장(1회) 후, 생보 3대질병 캡처·저장 시 **기존 손보 레코드에 life_matrix를 업데이트(append)** — 중복 레코드·충돌 없이. InsuRo 페이지도 갱신되어 고객에게 live 반영. (상담 파워: 3사 손보 설명 후 "3대질병은 생보가 더 저렴한 경우 많다" → 생보 조회·업데이트 → 페이지 갱신)
- 현재 버그: 2번째 저장(life_matrix 포함)이 **IDEMPOTENCY_CONFLICT로 거부** → 생보 저장 안 됨.

## 작업

### A. 서버 — 멱등 append (충돌 대신 업데이트)
- `consultation_history_v1.py` 멱등 처리(현재 line ~415): 같은 idempotency_key + **body_sha256 다름** → 지금은 `IDEMPOTENCY_CONFLICT` 거부.
- **변경**: body 차이가 **오직 life_matrix 추가**인 경우 → **기존 레코드 UPDATE**(life_matrix 채움), 아니면 기존대로 CONFLICT.
  - 판정: 신규 body에 life_matrix 존재 AND 기존 레코드 life_matrix가 NULL(아직 없음) AND **신규 body에서 life_matrix 제거한 것의 sha256 == 기존 record body_sha256**(즉 손보 부분·query_condition 완전 동일, life_matrix만 추가).
  - 성립 시: 기존 레코드 `life_matrix` 컬럼 업데이트 + body_sha256 갱신 + updated_at. 응답 `{record_id, status, life_appended:true}`. **새 레코드 생성 0.**
  - 불성립(손보 analysis_matrix/query_condition도 달라짐): 기존대로 IDEMPOTENCY_CONFLICT.
- **가드**: 이 경로로 analysis_matrix/기타 필드 덮어쓰기 금지 — 오직 NULL→life_matrix 채움만. 이미 life_matrix 있는 레코드 재-append 시도는 body 동일이면 idempotent replay, 다르면 conflict.
- consent/tenant/RLS/fingerprint 로직 불변.

### B. 웹앱 — 생보 계단식 2시나리오 (손보 스타일)
- `LifeThreeCriticalDiff`/`lifeThreeCritical.ts` 를 **계단식 화살표** 형태로(손보 계단식과 동일 스타일: 직각·exact 원·회사명·큰 총액차이 폰트).
- **baseline = 손보 3사 복합설계 총액**. 그 아래 2 시나리오:
  - **(1) 암진단비만 생보 최저사로 대체 시**: 손보 3사 복합에서 암진단비의 손보 배정 보험료를 빼고 **생보 최저 암진단비 보험료**로 대체 → 새 총액. **baseline 대비 총액 차이**(월 및 N년, exact). 생보사명 표기.
  - **(2) 3대질병 모두 1개 생보사로 대체 시**: 3담보(암·뇌혈관·허혈성심장) 합계가 **가장 저렴한 단일 생보사** 선정 → 손보 3사 복합에서 3담보 손보배정을 그 생보사 3담보 합으로 대체 → 새 총액. **baseline 대비 총액 차이**. 생보사명 표기.
  - 계단식: `손보 3사 복합(총액) → (1) 암진단비만 생보 → (2) 3대질병 1생보사`, 각 단계 화살표에 **총액 차이(baseline 대비, exact 원)** + 큰 폰트. 손보 계단식과 시각적으로 통일.
- 손보 배정가 = 기존 3사 조합 coverages[].premium(담보명 매칭 3담보). 생보 = life_matrix 담보별. exact 원(반올림 금지), "절감" 단어 지양(차이/차액), 곡선 금지(직각).
- life_matrix 없는 상담: 이 섹션 미표시 graceful(기존 유지).

### C. 확장 — 재저장(업데이트) 흐름 매끄럽게 (필요시)
- 손보 저장 후 "생보 이어서 캡처" → **저장 버튼 재활성/재클릭 가능**하게(2번째 저장 트리거). 손보 previewSnapshot 유지(생보 캡처가 덮어쓰지 않음 — captureLifeMatrixToSession은 별도 슬롯). 재저장 body=손보 snapshot+life_matrix.
- 사용자 문구: 업데이트 성공 시 "생보 3대질병이 추가 저장됐어요"류 안내(선택). 최소 변경.

## 검증
- `pytest server/tests/` — 멱등 append: 손보저장→생보 추가저장=기존 레코드 life_matrix 업데이트(신규 0)·life만 다를 때만 / 손보도 바뀌면 여전히 CONFLICT / 이미 life 있으면 동일body=replay·다르면 conflict. 기존 멱등/tenant 회귀 0.
- `npx vitest run`·`npx tsc` — 계단식 2시나리오(baseline 손보3사·(1)암진단비만·(2)3담보 1생보사·총액차이 exact·생보사명·직각·"절감"부재) / life 없으면 미표시. 회귀 0.
- e2e clean worktree PASS.

## 완료 (★ 순서 B)
- 변경 = allowed_resources 내부. manifest·migrations·골든 픽스처 불변.
- **dev6 금지** — dev1. 커밋 → push → finish-task **foreground 1회** → .done 즉시 종료. **background wait 금지.** ANU 검증·머지·서버재시작·CF배포·확장전송은 ANU.

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