# task-2912 보고서 — 소식지 업로드 타임아웃 수정 (검증 파이프라인 백그라운드화 + published 소비 필터)

- 팀: dev1-team (헤르메스)
- 레벨: Lv.2 (URGENT 라이브 회귀)
- base: origin/main `0e601ad` (#187)
- 브랜치: `task/task-2912-dev1` → 원격 push 확인 (`53bc956`)
- PR: https://github.com/Jeon-Jonghyuk/InsuRo/pull/190
- merge_policy: **none** (ANU 독립검증·머지·재배포)

## S (Situation)
소식지 업로드가 라이브에서 "Failed to fetch"로 실패. 프론트 업로드 요청이 cloudflared 터널 origin 타임아웃(~100s)에 걸림.

## C (Complication)
업로드 핸들러(`parse_premium_file`, `upload_to_drive`)가 vision추출(Opus) + `_render_source_page_images`(원본 페이지 렌더) + `run_review_pipeline`(내부 codex 교차검증)을 **전부 동기 `await`**. 이전엔 codex가 이미지 부재로 즉시 스킵돼 빨랐으나 #187 렌더 수정으로 **codex가 실제 실행 → 총 2분+ → 터널 타임아웃**. (로그 실증 13:10:25~13:12:47.)

## Q (Question)
로직·상태기계를 바꾸지 않고 업로드 HTTP 응답만 타임아웃 이내로 끝내려면?

## A (Answer) — 구현 (server/main.py 단일 파일)
### 1) 검증 파이프라인 백그라운드화
- 신규 async 헬퍼 **`_run_newsletter_review_bg(sb, record_id, *, file_bytes, filename, insurer, title, body, source_text, file_url, model_version, parse_mode, current_review_status, month_key)`** 추가 — 렌더+`run_review_pipeline`+임시파일 정리를 수행하고 예외를 삼켜 로깅. `markitdown`/`table` 모드는 렌더 스킵 규칙 유지.
- `parse_premium_file` 시그니처에 `background_tasks: BackgroundTasks` 추가. 두 업로드 경로 모두 동기 렌더+파이프라인 블록을 **`background_tasks.add_task(_run_newsletter_review_bg, ...)`** 예약으로 교체.
- **vision 추출·레코드 저장(status="completed" 업데이트)은 동기 유지** → 응답은 추출 직후 즉시 반환. 소식지는 검토 대기 큐에 백그라운드 완료 후 codex 플래그와 함께 나타남.
- 상태기계·published fail-closed·flag-only·codex 로직 자체는 불변 — **호출 위치만 응답 후로 이동**.

### 2) 소비 게이트 필터
- `newsletter_chat`(POST /api/insuro/newsletter-chat) newsletters 조회에 **`.eq("review_status", "published")`** 추가 — 사람 승인된 소식지만 AI분석에 사용. 관리 목록/검토 대기 큐 조회는 불변(이 필터는 소비 경로만).

## 수정/생성 파일
- `server/main.py` (수정, 96+/61−) — 커밋 `04a1e13` (불칸)
- `server/tests/test_newsletter_bg_pipeline_task2912.py` (신규, 324줄) — 커밋 `53bc956` (아르고스)
- base 0e601ad 대비 HEAD diff = 위 2개 파일만 (allowed_resources: `server/main.py` + `server/tests/**` 준수, forbidden 경로 무변경)

## 테스트 결과
- **신규 7건 전부 PASS** (헤르메스 재실행 교차검증): 헬퍼 kwargs 정합/렌더 스킵/예외삼킴+os.unlink 정리, `parse_premium_file`·`upload_to_drive` add_task 예약(응답 시점 run_review_pipeline 호출 0회)+레코드 선저장+`await bg()` 시 실제 실행, newsletter-chat `.eq("review_status","published")` 삽입.
- **전체 `pytest server/tests/`: 1236 passed, 1 failed**. 유일 실패 `test_cors_fail_closed_when_ext_origin_unset` = worktree `.env`의 `INSURO_EXTENSION_ORIGIN` 오염에 의한 **기지(pre-existing) 실패**(dotenv 재로딩으로 env -u로도 미해소), test_consultation_history_get.py 소속으로 본 변경 경로(newsletter 업로드/채팅)와 무관. **회귀 0**.

## L1 스모크테스트 결과
- **서버 재시작: 성공** — worktree 코드를 별도 포트 8899로 uvicorn 부팅, `Application startup complete` (라이브 파일럿 systemd 서비스는 구버전·outward·미승인이라 재시작 안 함).
- **API 응답 확인:**
  - `GET /api/status` → **HTTP 200, time_total=0.028s**
  - `POST /api/insuro/parse-premium-file` (무인증) → 401, **0.003s** (행 없음, 추가한 `BackgroundTasks` DI 정상)
  - `POST /api/insuro/upload-to-drive` (무인증) → 401, **0.003s**
  - `POST /api/insuro/newsletter-chat` (무인증) → 401, **0.002s**
- **스크린샷: 해당없음** (백엔드 API 작업)
- **라이브 PDF 업로드 왕복 응답 소요시간**: 직접 측정은 프로덕션 Supabase/Drive/JWT/codex + 터널 필요라 이 샌드박스에서 미수행. 다만 느린 부분(렌더+codex 2분+)이 응답 경로에서 제거되어 **응답은 vision 추출 시간(수십초) 내로 수렴** — 동기-오프로드 동작은 함수레벨 테스트로 결정론적 검증(run_review_pipeline await 0회 + bg.tasks 예약). 라이브 실측은 ANU/회장 rollout 시 확인 권장.

## 발견 이슈 및 해결
- **base 원격 전진**: 작업 중 origin/main이 `0e601ad`→`b9a9429`로 전진(#189 task-2911). 확인 결과 #189는 `src/`만 변경, `server/main.py` 무변경 → **충돌 0**, PR 깨끗이 머지 가능.
- **CORS 테스트 1건 실패**: 위 기록대로 `.env` 오염 기지 실패로 확정, 회귀 아님.

## 머지 판단
- **머지 필요**: Yes (PR #190) — 단, merge_policy=none → **머지 주체=ANU**
- **브랜치**: task/task-2912-dev1 (`53bc956`, 원격 push 확인)
- **워크트리 경로**: /home/jay/workspace/projects/insuro/.worktrees/task-2912-dev1
- **머지 의견**: 순수 additive 백엔드 변경(로직 불변, 호출 위치 이동). 회귀 0, 신규 7건 PASS, L1 서버 기동/엔드포인트 응답성 확인. ANU 독립검증(clean worktree) 후 머지·재배포 권장. **후속(회장/ANU)**: 라이브 systemd 서비스 재배포 후 실제 PDF 업로드 응답시간 실측.

## 모델 사용 기록
- 불칸(백엔드 구현): sonnet
- 아르고스(테스트 작성): sonnet
- 헤르메스(팀장): 설계/검토/통합/L1 (직접 코딩 없음)
- haiku 미사용

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

