# InsuRo 소식지 GPT 교차검증 v2 (task-2902 백엔드 #183 + task-2903 UI #182) — 둘 다 머지 (260805)

## 배경 / 초점 교정
회장 260805 verbatim: 보험사명(파일명)·기준월/개정일(사용자 직접지정)은 이미 신뢰 → **교차검증 불필요**. 진짜 검증 대상은 1차 추출 모델(Claude opus/sonnet)이 (A) **숫자 오인식/오전사**(자릿수·단위·엉뚱한 항목 부착), (B) **본문 의미 왜곡**. 이를 **다른 엔진(GPT gpt-4o)**이 원문 대조로 더블체크 → 사람 최종확인. 보험사명·날짜 교차검증은 초점분산이라 금지.

- v1(task-2900 백엔드 PR#181 / task-2901 UI #180)은 insurer/date까지 교차검증하는 초점이라 **superseded**. #181은 close(중복), #180(UI v1)은 이미 머지된 base 위에 #182가 재초점 교정.

## 무엇을 머지했나 (main 체인)
`84b3d0b(#183 task-2902 백엔드 v2) ← 77c5a22(#182 task-2903 UI v2) ← 4cf8dd5(#180 UI v1) ← a914594(#179 REST)`
- **#183 백엔드(task-2902, 불칸/sonnet, dev1)**: `server/newsletter_cross_verify.py` 신규(252줄) + `newsletter_review.py` 배선 +13줄 + 테스트16. squash 84b3d0b.
- **#182 UI(task-2903, 오딘/dev2)**: `src/components/newsletter/` ValidationFlags/ReviewDetailPanel + reviewApi. v1의 보험사명 강제 select 게이트 제거, 숫자·의미 확인 게이트로 재초점. squash 77c5a22.

## 설계 불변식 (백엔드)
- `cross_verify(source_text, extracted_body, model_version, *, openai_call_fn=None, api_key=None, model="gpt-4o", timeout=30)` — **insurer/title/date 파라미터 아예 없음**(초점=body와 그 안 숫자만).
- httpx async로 OpenAI Chat Completions 직접 호출(codex CLI subprocess 미사용). response_format=json_object.
- 확증편향 방지: system prompt가 정리본을 "검사 대상"으로 두고 원문에서 먼저 독립 추출 후 대조하게 강제. model_version을 회의 컨텍스트로 주입.
- extractive-only(수정/생성 0, 플래그만) · graceful(키부재→네트워크 호출 0 즉시반환 / 타임아웃 / 예외 / JSON파싱실패 → `cross_model_unavailable` INFO 1건, 예외 전파 없음) · source_quote 빈 finding은 보류(할루시네이션 방지).
- 배선: run_review_pipeline step2(AI 자기검증) 직후·step3(grounding) 전 append-only 삽입 → step4 UPDATE의 validation_flags 병합. transition_review_status/ALLOWED_TRANSITIONS/예외계층 일절 미수정. pending_review 라우팅 재사용 = fail-closed 불변.
- 플래그 스키마(UI 계약): `number_misread`(HIGH), `meaning_drift`(HIGH=의미뒤바뀜/없는주장·MED=뉘앙스), `cross_model_unavailable`(INFO). UI ValidationFlags.tsx가 number_misread/meaning_drift 렌더 확인.

## 검증 (ANU 독립)
- clean /tmp worktree(CI parity, .env 오염 배제) newsletter 3파일: **276 passed / 0 failed / 0.65s / hang 없음** — 봇 주장 그대로 재현.
- 두 PR 전 CI 체크 GREEN(ci/e2e/guard/qc/merge-safety/gemini-gate 등, diagnostic=continue-on-error 비차단). CORS 1건 실패·전체 스위트 hang은 pre-existing(.env 오염·무관 네트워크 테스트)로 독립 확정(선행 2896/2897/2899 박제와 동일).
- Codex CC 반증 사전 리뷰(회장 승인 순서 step2, 머지 후) 결과:
  - **CRITICAL**: 확증편향 방지가 프롬프트 텍스트 수준일 뿐 코드 미강제. source_text+extracted_body가 같은 요청에 들어가고 "독립추출 먼저" 지시를 검증 안 함. 반환 `source_numbers`가 findings 채택 전 소비/검증 안 됨 → GPT가 정리본 수긍해 `[]`만 내도 통과.
  - **HIGH**: `source_quote` 가드가 `.strip()`만 — 위조(원문에 없는) 비어있지 않은 인용을 못 거름(source_text 포함여부 대조 없음). 테스트도 빈문자열/공백만 커버.
  - **MEDIUM**: 배선(newsletter_review.py) except가 로그만, `cross_model_unavailable` fallback 플래그 append 없음 → cross_verify가 예기치 못하게 raise하면 검토자가 "교차검증 미실행"을 못 봄.
  - **LOW**: 소식지 원문 전체가 감사 마커 없이 OpenAI로 전송(egress 인지 필요).
  - **반증 실패(OK)**: 초점(회사명/날짜 비대상), extractive-only, 회귀0(advance_to_pending_review 재사용), UI 스키마 계약 정합 — 확인됨.
  - ANU 판정: 머지 자체는 안전(additive·fail-closed·graceful·human 최종게이트 불변, before=교차검증 0 대비 순증). 단 CRITICAL/HIGH는 회장이 원한 "더블체크 효력"을 약화 → **배포 전 surgical 하드닝 태스크 권장**: (1)source_quote ∈ source_text 검증(위조 인용 drop) (2)source_numbers 소비해 실제 대조 강제/무증거 [] 플래그 (3)배선 except에 cross_model_unavailable append. deploy는 미승인(OPENAI_API_KEY+비용/egress 회장 결정 대기).

## 회장 후속 (배포 = 미승인, 지시 대기)
- **★ OpenAI 유료 엔진 신규 도입**: 서버 rollout 시 `OPENAI_API_KEY` 환경변수 설정 필요. 키 부재 시 cross_model_unavailable INFO로 안전 degrade(코드는 graceful). 비용·rate limit·데이터 egress(소식지 원문을 OpenAI로 전송) 정책은 회장 확인 권장.
- 서버 `insuro-api.service` `git pull`+재시작, 웹앱 CF Pages 배포(insuro.biz)해야 실제 반영.
- 마이그레이션 017(검토 상태/감사 테이블)은 이미 회장이 Supabase 직접 적용 완료.

관련: [[project_insuro_newsletter_review_phase3b_task2901_merged_task2900_pr_open_260805]] · [[project_insuro_newsletter_review_phase3a_task2899_merged_260805]]
