# task-2913 — 소식지 교차검증 다페이지 codex 타임아웃 해소 + 이미지 부하 경감

## 모델 사용 기록
- 루(Lugh, 백엔드): **sonnet** — 구현·테스트 작성 (일반 코딩 로직, haiku 미사용)
- 다그다(팀장, opus): 설계·직접 검증·L1 스모크·통합 (코딩 위임, 직접 코딩 없음)

## S (Situation)
codex 교차검증 2차 엔진(task-2904/2905)이 라이브에서 다페이지 PDF 처리 시 타임아웃 발생. `_render_source_page_images`가 최대 6페이지를 렌더 → cross_verify가 6장을 단일 codex exec 호출로 전달하는데, `DEFAULT_TIMEOUT=60.0`이라 6장×~17s ≫ 60s → codex 타임아웃 → graceful `cross_model_unavailable`. 회장 ABL생명 PDF에서 숫자 오독을 codex가 못 잡은 근본 원인.

## C (Complication)
- 타임아웃을 늘려도 되는가? → task-2912로 cross_verify가 백그라운드 실행되므로 타임아웃 상향이 업로드 응답 지연과 무관 (안전).
- 6장 이미지가 고해상도면 codex 처리 시간·토큰이 과도 → 타임아웃뿐 아니라 부하 자체를 줄여야 함.
- 제약: cross_verify.py + 그 테스트만 surgical 수정. main.py·review·validation·migration 불변. codex 검출 로직·플래그 스키마 불변.

## Q (Question)
codex 검출 로직·플래그 스키마·확증편향 방지·argv를 전혀 건드리지 않고, 타임아웃과 이미지 전처리만으로 다페이지 완주를 보장할 수 있는가?

## A (Answer) — 구현 (surgical, cross_verify.py만)

### 변경 1: DEFAULT_TIMEOUT 상향 (60.0 → 240.0)
`run_review_pipeline`이 timeout 미지정으로 호출하므로 DEFAULT 상향이 그대로 반영. 6장×~17s에 여유 있는 값.

### 변경 2: codex 전달 전 이미지 다운스케일 (긴 변 ≤ 1400px)
- 신규 상수 `DEFAULT_MAX_IMAGE_LONG_SIDE = 1400`
- 신규 헬퍼 `_downscale_image(src_path, max_side=1400) -> tuple[str, bool]`:
  - PIL **지연 import**(server/image_ai.py 패턴) — 모듈 import가 PIL 하드의존 안 함
  - 긴 변 ≤ 1400 → `(원본, False)` (임시본 없음)
  - 초과 → 종횡비 유지 LANCZOS 축소 → 임시 PNG(`codex_cv_img_*`) 저장 → `(temp_path, True)`
  - **어떤 예외(PIL 미설치/파일없음/디코딩·저장 실패)든 `(src_path, False)` graceful fallback** — 예외 전파 0. 원본 파일 절대 불변
- `_call_codex_cli`: `-i` 루프를 다운스케일 경유로 변경. `is_temp` 임시본 경로를 `downscaled_temp_paths`에 수집 → **`finally`에서 subprocess 성공/실패/타임아웃 무관하게 `os.unlink` 정리** (원본 미삭제)
- **codex_call_fn 주입 경로(테스트)는 불변** — 다운스케일은 오직 `_call_codex_cli`(실제 subprocess 경로)에만 적용 → 테스트 subprocess 0회 원칙 유지

### 불변 (미변경 확인)
graceful 3분기, 플래그 스키마, extractive-only, 확증편향 방지 프롬프트, codex argv 순서(-s read-only / -c approval_policy=never / -m / -C / -i 그룹 / -o / -), source_quote 강제, 인젝션 방어. **타임아웃·이미지 전처리만** 변경.

## 수정 파일
- `server/newsletter_cross_verify.py` (+56/-3)
- `server/tests/test_newsletter_cross_verify.py` (+88, 기존 테스트 삭제 0 = 순수 추가·무수정)

## 테스트 결과
- `pytest server/tests/test_newsletter_cross_verify.py` → **37 passed** (기존 32 회귀 0 + 신규 5 PASS)
- 신규 테스트(TestTimeoutAndImageDownscale 5종):
  1. `test_default_timeout_at_least_240` — DEFAULT_TIMEOUT ≥ 240
  2. `test_downscale_creates_temp_and_preserves_original` — 2000px 이미지 다운스케일 임시본 ≤1400 + 원본 2000px 불변
  3. `test_downscale_small_image_no_temp` — 800×600 → is_temp=False, 원본 경로 그대로
  4. `test_downscale_missing_file_fallback` — 존재하지 않는 경로 → `(원본, False)` graceful
  5. `test_call_codex_cli_passes_downscaled_temp_to_i` — argv의 `-i` 경로가 다운스케일 임시본(≤1400) + 호출 후 unlink 정리
- 기존 테스트 무수정 교차확인: `git diff`상 삭제된 기존 테스트 라인 0

## L1 스모크테스트 결과 (실측 — 실제 codex 다페이지 완주)
- **서버 재시작**: 해당없음 (모듈 단위 함수 — cross_verify는 아직 프로덕션 파이프라인 미배선, task-2904 참조. 배선·배포는 ANU/별도 phase)
- **다운스케일 실측**: 원본 페이지 이미지 3장(각 2200×1600) → `_downscale_image`로 각 **1400×1018**(긴 변 정확히 1400), 원본 3장 모두 2200×1600 **불변 확인**
- **실제 codex 교차검증 (clean cwd, 다페이지 3장, 실측)**:
  - 소요시간 **15.1s** (타임아웃 240s — 여유), **타임아웃 발생: False**
  - 정리본에 의도적 숫자 오류(`3,000만원`→`30,000만원`) 삽입 → **`number_misread` HIGH 검출 성공**
    - `summary_value=암 진단시 최대 30,000만원`, `source_quote=암진단시 3,000만원`, `verifier=codex`, `issue=금액 자릿수 불일치`
  - codex 완주·JSON 파싱·플래그 매핑·source_quote 강제 전 경로 정상 동작 실증
- **스크린샷**: 해당없음 (백엔드 서브프로세스 작업 — 콘솔 실측 로그로 대체, 상기 기재)
- codex CLI 0.106.0 / PIL 12.2.0 가용 확인

## 발견 이슈 및 해결
- 기존 argv 테스트가 존재하지 않는 `/tmp/page1.png`를 `-i`에 전달 → 다운스케일 실패 시 **반드시 원본 경로 fallback**이어야 회귀 0. `_downscale_image`의 graceful fallback(`(src_path, False)`)으로 보장 → 기존 argv 테스트 그대로 PASS 확인.

## 머지 판단
- **머지 필요**: Yes (단, 순서 B — 봇 push·**ANU 독립검증·머지·재배포는 ANU 소유**. merge_policy=none)
- **브랜치**: `task/task-2913-dev3`
- **워크트리 경로**: `/home/jay/projects/InsuRo/.worktrees/task-2913-dev3`
- **커밋**: `b28b3ec` (원격 HEAD 일치 확인: `git ls-remote origin task/task-2913-dev3` = b28b3ec)
- **base**: origin/main `b9a9429` (task-2911 배지 변경과 파일 무중첩 — cross_verify.py 동일)
- **머지 의견**: surgical 2파일만 수정, 회귀 0, L1 실측으로 다페이지 완주·number_misread 검출 실증. codex 검출 로직·플래그 스키마 불변. 프로덕션 파이프라인 미배선 상태라 live 영향 0(배선은 별도 phase). 저위험.

## 비고
- 병행 task-2912(main.py 백그라운드화)와 파일 무중첩.
- L1에서 codex가 입원일당을 "1만원"으로 읽은 것은 codex OCR 판독 차이(테스트 이미지 렌더 특성) — 핵심 검증 대상인 3,000→30,000 자릿수 오류는 정확 검출됨. 파이프라인 정상.

## 세션 통계
- 총 도구 호출: 0회

