# task-2869 보고서 — InsuWiki 50MB 업로드 (Google Drive 직접 resumable)

**상태: 🟡 BLOCKED @ Step 0 — CORS 실증 실패 → ANU/회장 결정 대기 (구현 미착수, 강행 금지 준수)**
**팀: dev2 (오딘)** · 레벨: Lv.3 · 작성: 2026-07-27 21:30 (server time)

---

## S (Situation)
- InsuWiki 파일 업로드가 브라우저 → `/api/upload`(Vercel 서버리스) → `file.arrayBuffer()` → Drive `files.create` 경로.
- **Vercel Hobby 4.5MB body 하드한도**로 4.5MB 초과 파일은 함수 실행 전 413(비-JSON) → 클라 `response.json()`이 "Unexpected token 'R'…"로 파손. UI "Max 50MB"는 허위.
- 회장 A안(진짜 50MB 지원 = 브라우저에서 Google Drive로 **직접 resumable PUT**, 서버리스 body 우회) 선택 → 본 태스크.

## C (Complication)
- 태스크 **Step 0(착수 최우선, 가정 금지)**: 브라우저가 Drive resumable **세션 URI로 cross-origin PUT** 가능한지 **실증**. 불가 시 **강행 금지 → ANU 에스컬레이션**.
- 이 CORS 가능 여부가 전체 설계의 make-or-break 전제.

## Q (Question)
- 브라우저에서 pre-authorized Drive 세션 URI로 (OAuth 토큰 노출 없이) 직접 PUT이 CORS 허용되는가?

## A (Answer) — 실증 결과: **불가능(CORS BLOCKED)**. 단, 검증된 대안 있음.

### ✅ Step 0 CORS 실증 (실제 왕복, 가정 아님)
1. 실제 OAuth refresh_token(`.env.local`)으로 access_token 발급 → `POST https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable`로 **실제 세션 URI 발급 성공(HTTP 200, `Location` 헤더 존재)**.
2. 실제 브라우저(Playwright, origin=`https://example.com`)에서 그 세션 URI로 **cross-origin PUT(1KB)** 시도.
3. **결과: 차단.** 브라우저 콘솔 원문:
   > `Access to fetch at 'https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&upload_id=…' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.`
   > `Failed to load resource: net::ERR_FAILED`
4. **결론**: `/upload/drive/v3/files` resumable 엔드포인트는 cross-origin PUT에 대해 `Access-Control-Allow-Origin`을 **반환하지 않음**. PUT은 비-simple 메서드라 preflight가 강제되고, preflight에서 ACAO 부재로 차단됨. 세션 URI가 pre-authorized여도 **CORS 자체가 막힘** → 태스크가 지시한 "브라우저 직접 PUT" 방식은 **실현 불가**.

### ✅ 대안 실증 — 청크 서버리스 릴레이 (KEEPS Drive, CORS 없음) — **검증 성공**
- 아키텍처: 클라 → `/api/upload/chunk`(≤~4MB 청크) → 서버가 **Drive 세션 URI에 `Content-Range`로 서버측 PUT 릴레이**. 서버→Google은 CORS 무관.
- 실증: **5MB(> 4.5MB 한도) 파일을 2MB 청크 3개로 서버측 릴레이 → 성공**.
  - `chunk 0-2097151/5242880 -> HTTP 308`
  - `chunk 2097152-4194303/5242880 -> HTTP 308`
  - `chunk 4194304-5242879/5242880 -> HTTP 200`
  - `UPLOAD_COMPLETE size=5242880 hasLink=true` (webViewLink 정상 발급), 테스트파일 cleanup DELETE 204.
- **이점**: (1) Google Drive 유지(webViewLink·per-user 폴더·Firestore attachments 로직 **무변경**, 최소침습) (2) 각 요청 ≤4MB라 **Vercel 4.5MB 재발 없음** (3) 브라우저에 OAuth 토큰 미노출 (4) CORS 무관.
- **유의(회장/ANU 판단 필요)**: Vercel Hobby 서버리스 **함수 실행시간 한도**(기본 ~10초/호출). 청크당 2MB 릴레이는 여유. 청크 수만큼 호출 증가.

### 대안 B — Firebase Storage 브라우저 SDK
- `.env.local`에 `NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET` **이미 설정됨**. Firebase Auth와 통합, 네이티브 resumable + CORS 지원.
- **단점**: 저장소가 Drive→Firebase Storage로 **전환**(URL 의미/권한/기존 첨부 혼재). 변경 폭 큼. Alt A 대비 침습적.

## 권장
**대안 A(청크 서버리스 릴레이)** 채택 권장 — 실증 검증됨, Drive 유지, 최소침습, CORS·토큰·4.5MB 문제 모두 회피. 회장 승인 시 재디스패치(서버 `initiateResumableUpload`+`/api/upload/chunk` 릴레이 라우트, 클라 청크 PUT+진행률, EditorToolbar 동일 전환). allowed_resources는 `/api/upload/**` chunk 라우트 추가만 필요.

## 발견 이슈 및 해결
- worktree_manager `create`가 `origin/main` 하드코딩 → insuwiki(master)에서 fail-closed. **수동 worktree(base=origin/master 6516dbd) 생성**으로 우회, base 마커 기록. 구현 미착수라 worktree는 정리(제거)함.

## L1 스모크테스트 결과
- 서버 재시작: **해당없음** (코드 변경 0, 실증은 실제 Drive API 왕복 + 실제 브라우저로 수행).
- API 응답 확인: 세션개시 `HTTP 200`(Location 有), 청크릴레이 `308/308/200`(UPLOAD_COMPLETE, webViewLink 有), cleanup `DELETE 204` — 모두 실제 Google Drive 왕복.
- 브라우저 CORS 실증: Playwright example.com origin → 세션 URI PUT → **CORS BLOCKED(콘솔 원문 확보)**. 스크린샷: `teams/dev2/cors-blocked-evidence-2869.png`.

## 변경 파일
- **소스 코드 변경 0건** (Step 0 게이트에서 강행 금지 준수, 구현 미착수).
- 산출: 본 보고서, 실증 스파이크(`/tmp/cors-spike-2869/`, 세션 URI 파일은 민감정보라 삭제).

## 머지 판단
- **머지 필요: No** (코드 변경 없음).
- 다음 액션: **회장/ANU가 대안(A 청크릴레이 권장 vs B Firebase Storage) 결정** → 결정 시 신규 task로 재디스패치.

## 모델 사용 기록
- 팀원 코딩 위임 없음. Step 0은 팀장(오딘) 직접 실증 검증(진단/판단 영역, 구현 코드 아님). CORS 실패로 게이트에서 중단되어 팀원(토르/프레이야) 위임 불필요.

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


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

