# task-2931 완료 보고 — InsuRo P0-1 Drive 프록시 Phase2 폴리시 (라벨-동작 일치 + 이미지 ticket 캐싱)

- 작업 ID: task-2931 / 팀: dev1-team (헤르메스)
- 레벨: Lv.2 / 유형: finalize-only (머지=ANU, 배포=CF Pages ANU 조율)
- 완료 시각: 2026-08-09 20:16 (KST)

## Situation (상황)
P0-1 Drive 공개제거→서버프록시 전환의 Phase2(task-2930 프론트 배선, 8곳 프록시 교체)가 완료·머지대기 상태. Codex 교차검증에서 **소소 2건**이 도출되어 본 폴리시(task-2931)로 보완 후 재조율→GO 예정.

## Complication (문제)
Codex 지적 2건:
1. **라벨-동작 불일치**: PolicyAnalysis "원문 PDF 보기" 버튼이 `mode="download"`를 호출 → 라벨(보기)과 동작(다운로드)이 어긋나고 나머지 7곳(view)과도 불일치.
2. **이미지 ticket 중복 발급**: view 모드 ticket이 매 mount/URL변경마다 재발급 → 백엔드 file-ticket 엔드포인트 불필요 호출 반복.

## Question (핵심 질문)
스코프 3파일(driveFile.ts / __tests__ / PolicyAnalysis.tsx)만 건드려, 회귀 0·과설계 없이 2건을 보완할 수 있는가?

## Answer (해결)

### 수정 내역 (2건)
1. **PolicyAnalysis 라벨-동작 일치** — `src/pages/PolicyAnalysis.tsx:602`
   - `openFileViaProxy(result.file_url, "download")` → `"view"` (브라우저 inline 표시). 이 파일 유일 호출부, 라벨 "원문 PDF 보기"(609)와 일치, 다른 7곳과 view 일관.
2. **requestFileTicket 티켓 캐싱** — `src/lib/driveFile.ts`
   - 모듈 레벨 `Map<string,{url,expiresAt}>`, 키 `${mode}::${fileUrl}`, TTL `TICKET_CACHE_TTL_MS = 90_000`(백엔드 120초보다 30초 안전마진).
   - 함수 시작부 캐시 조회(hit & 미만료 → 세션조회/fetch skip, 캐시 URL 반환). 미스/만료 시 기존 로직 → 성공 발급만 캐시. throw(세션없음/발급실패) 경로는 미저장(기존 throw 유지).
   - `openFileViaProxy`는 미변경(내부적으로 requestFileTicket 호출 → 자동 캐시 혜택. 이미지 <img src>도 동일 함수 경유하여 중복 발급 방지).
   - 테스트 전용 `export function clearFileTicketCache()` 추가(모듈 캐시 초기화).

### 생성/수정 파일 (3개, 스코프 정확 일치)
- `src/pages/PolicyAnalysis.tsx` (+1/-1)
- `src/lib/driveFile.ts` (+19)
- `src/lib/__tests__/driveFile.test.ts` (+68/-4): beforeEach `clearFileTicketCache()` 격리 + 신규 3 캐시 테스트

### 테스트 결과
- **vitest 전체: 79 files / 1177 passed (회귀 0)** — task-2930 base 1174 + 신규 캐시 3 = 1177.
- 신규 캐시 테스트 3건(모두 실제 requestFileTicket 실행):
  - A: 같은 (fileUrl,mode) 2회 호출 → fetch 1회, 반환 동일(dedup)
  - B: TTL(90초) 만료 후 재호출 → fetch 2회 재발급 (`setSystemTime` +90_001ms)
  - C: 같은 fileUrl이라도 view/download는 별도 캐시 → fetch 2회
- **`npm run build` PASS** (goal_assertion) — built in 12.03s, dist 생성(20:14), PWA precache 176 entries.
- `npx tsc --noEmit` exit 0.

### L1 스모크테스트 결과 (실동작 확인)
- **서버 재시작**: 해당없음 (프론트 전용, 백엔드 미변경). 대신 프로덕션 빌드 산출물로 preview 서버 기동.
- **API 응답 확인**: `npm run preview`(:4931) → `curl` **HTTP 200**.
- **스크린샷**: Playwright로 실제 브라우저 로드 → 앱 정상 렌더(로그인 리다이렉트, 콘솔 에러 **0건**). 경로: `memory/reports/task-2931-smoke.png`
- 브라우저 정리(browser_close) + preview 서버 종료 완료.
- ※ PolicyAnalysis 페이지는 인증 필요로 라이브 접근 불가하나, view 모드 변경/캐시 로직은 vitest가 실함수 실행으로 검증하고 프로덕션 번들이 무크래시 로드됨을 확인.

## 발견 이슈 및 해결
- **기존 driveFile 테스트 캐시 오염 1건**(이리스 발견): 기존 6테스트가 동일 `(u,view)` 키 재사용 + beforeEach가 모듈 캐시 미초기화 → "정상 흐름"이 캐시한 URL 때문에 "발급 실패 close()" 테스트가 캐시 히트로 resolve. **해결**: 이 목적으로 export한 `clearFileTicketCache()`를 beforeEach에 추가(아르고스)하여 격리 → 9/9 PASS. driveFile.ts/PolicyAnalysis.tsx는 무수정.

## 머지 판단
- **머지 필요**: Yes (단, finalize-only — 머지=ANU)
- **브랜치**: `task/task-2931-dev1` (head **53b8030**, 원격 동기 확인)
- **base**: `task/task-2930-dev1` head **067815b** (task-2930 미머지 상태이므로 그 위에 스택. 현재 main=c880cef=Phase1.5)
- **워크트리 경로**: `/home/jay/projects/InsuRo/.worktrees/task-2931-dev1`
- **머지 의견**: 회귀 0·build PASS·스코프 3파일 정확 일치·과설계 없음(간단 Map+timestamp). 독립 머지 아닌 **P0-1 스택 coordinated 머지** 대상 → ANU가 Codex 재조율(2건 해결 확인) 후 task-2930+2931 함께 CF Pages 배포. 백엔드/계약 변경 없음(프론트 전용, 무중단).

## 모델 사용 기록
- 이리스(프론트 구현): sonnet — driveFile 캐시 + PolicyAnalysis view 변경
- 아르고스(테스트): sonnet — 캐시 3테스트 + 오염 격리
- 팀장(헤르메스, Opus): 설계/분배/교차검증(grep·전체 vitest·build·L1)/통합. 직접 코딩 없음.

## 다음 절차
ANU 독립검증 → Codex 재조율(라벨-동작/캐시 2건 해결 확인) → 합의 시 **task-2930+2931 함께 CF Pages 배포(GO)**. Phase3=신규 anyone 제거 + 소급 회수(회장 결정 대기).

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

