---
task_id: task-2342
team: dev5
level: Lv.2
status: done
report_path: /home/jay/workspace/memory/reports/task-2342.md
generated: 2026-05-02
---

# task-2342 — InsuRo PWA SW `/downloads/*` 404 fallback 수정

## SCQA 요약

### Situation (상황)
2026-05-01, 사용자가 `https://insuro.biz/downloads/insuro-helper-0.1.0.zip` 직접 접근 시 SPA 404 페이지("Oops! Page not found")가 표시. curl 호출은 200 정상이며 dist/downloads/ 산출물도 존재했으므로 서버 측은 문제없음.

### Complication (문제)
PWA Service Worker가 `/downloads/*` 경로를 SPA navigation request로 잘못 분류 → `navigateFallback: '/index.html'` 적용 → SPA Router가 라우트를 못 찾아 404 페이지 렌더링. 원인은 `vite.config.ts`의 `workbox.navigateFallbackDenylist`가 `[/^\/~oauth/, /^\/api\//]`만 포함하고 `/downloads/`가 빠져 있던 것.

### Question (질문)
SW navigation fallback에서 `/downloads/*`와 다운로드형 정적 자원을 어떻게 명시적으로 제외할 것인가?

### Answer (해답)
`navigateFallbackDenylist`에 `/^\/downloads\//`와 `/\.(?:zip|pdf|exe|dmg|tar|gz)$/`를 추가하고, Cloudflare Pages `_headers`에 `/downloads/*.zip`의 `Content-Disposition: attachment`를 명시. 빌드된 `dist/sw.js`와 프로덕션 SW 모두에서 새 denylist 반영을 확인.

---

## 작업 내용

### 수정 파일
1. `/home/jay/projects/InsuRo/vite.config.ts` (worktree 경로: `.worktrees/task-2342-dev5/vite.config.ts`)
   - `workbox.navigateFallbackDenylist`를 2 regex → 4 regex로 확장
2. `/home/jay/projects/InsuRo/public/_headers`
   - `/downloads/*.zip` 블록 추가 (`Content-Disposition: attachment`, `Cache-Control: public, max-age=3600`)

### 변경 전후 비교

**변경 전 (vite.config.ts:47):**
```ts
navigateFallbackDenylist: [/^\/~oauth/, /^\/api\//],
```

**변경 후 (vite.config.ts:47-52):**
```ts
navigateFallbackDenylist: [
  /^\/~oauth/,
  /^\/api\//,
  /^\/downloads\//,
  /\.(?:zip|pdf|exe|dmg|tar|gz)$/,
],
```

### 커밋
- `874f79e [task-2342] 이쉬타르: PWA SW navigateFallbackDenylist에 /downloads/ 및 정적 다운로드 확장자 추가`
- 브랜치: `task/task-2342-dev5` → main (PR #74 머지)
- PR: https://github.com/JonghyukJeon/InsuRo/pull/74

### 게이트 통과 기록
- **G1 (Codex 사전 검증)**: PASS — pass:true, critical:false. 2개 high finding은 모두 이번 수정으로 해소(`/downloads/` denylist 부재, affected_files 범위).
- **G2 (Gemini PR 리뷰)**: PASS — high_severity_count: 0. medium 2건은 DEFER:
  - `vite.config.ts` regex case-insensitive 플래그 권장 → 현재 코드는 zip/png 등 모두 소문자 입력만 다루므로 운영상 문제 없음. 다음 task에서 검토 가능.
  - `_headers` 다른 확장자(pdf/exe/dmg/tar/gz)도 헤더 설정 추가 권장 → 현재 다운로드 자원이 zip뿐이라 실제 추가 필요 시점에 함께 처리.

### 모델 사용 기록
- 마르둑(팀장, Opus 4.7 1M): 설계, 위임, 게이트 통과, 프로덕션 검증
- 이쉬타르(프론트, Sonnet): vite.config.ts + _headers 수정, 빌드, dist/sw.js 검증
- 닌기르수(테스터, Sonnet): Playwright L1 스모크테스트
- haiku 미사용 (단순 작업이지만 빌드 검증 + Playwright 사용으로 sonnet 적합)

---

## L1 스모크테스트 결과

### 로컬 검증 (닌기르수)
- 서버 재시작: 성공 — `python3 -m http.server 4173` (PID 2011984), `dist/` 디렉토리 서빙
- API/curl 응답:
  - `curl -sI http://localhost:4173/downloads/insuro-helper-0.1.0.zip` → HTTP 200, Content-Length 11453
  - `curl -sI http://localhost:4173/sw.js` → HTTP 200
- Playwright 검증:
  - SW 등록: scope=`http://localhost:4173/`
  - 콘솔 에러 0건
  - `fetch('/downloads/insuro-helper-0.1.0.zip')` → status 200, content-type `application/zip`, content-length 11453, **firstBytes `50 4b 03 04`** (ZIP magic number)
  - navigation 테스트: 브라우저가 파일 다운로드 트리거. SPA 404 페이지 미노출.
- 스크린샷: `/home/jay/.cokacdir/workspace/23A9399E/task-2342-sw-zip-test.png`

### 프로덕션 검증 (마르둑)
- Cloudflare Pages 자동 배포 후 (캐시 무효화 후):
  ```bash
  curl -s "https://insuro.biz/sw.js?cb=$(date +%s%N)" | grep -oE 'denylist:\[[^]]*\]'
  ```
  결과: `denylist:[/^\/~oauth/,/^\/api\//,/^\/downloads\//,/\.(?:zip|pdf|exe|dmg|tar|gz)$/]` ✅
- 프로덕션 zip 다운로드:
  ```bash
  curl -s "https://insuro.biz/downloads/insuro-helper-0.1.0.zip" -o /tmp/prod-helper.zip
  file /tmp/prod-helper.zip
  ```
  결과: `Zip archive data, at least v2.0 to extract, compression method=deflate`, 11453 bytes ✅
  ZIP magic 확인: `504b 0304` (PK\x03\x04)

### 빌드 결과
- `npm run build` (worktree): **성공**, 15.84s
- PWA generateSW 모드, precache 168 entries (5833.85 KiB)
- `dist/sw.js` + `dist/workbox-5d66ad07.js` 생성
- 경고: pdf-libs 청크 883 kB > 500 kB warning — 기존부터 있던 알려진 경고, 이번 변경과 무관

---

## 머지 판단

- **머지 필요**: Yes (이미 머지됨)
- **브랜치**: `task/task-2342-dev5` → main (PR #74 머지 + 브랜치 삭제)
- **워크트리 경로**: `/home/jay/projects/InsuRo/.worktrees/task-2342-dev5`
- **머지 의견**: 단일 파일(vite.config.ts) 한 줄 수준의 변경 + 보조 _headers 추가. Codex/Gemini 모두 PASS. 로컬+프로덕션 검증으로 zip 다운로드 정상 동작 확인. 회귀 위험 매우 낮음.

---

## 발견 이슈 및 해결

### 이슈 1: Cloudflare Pages 배포 지연
- 증상: PR 머지 직후 첫 `curl https://insuro.biz/sw.js`에서 옛 denylist(2 regex) 반환
- 원인: PR 머지 후 Cloudflare Pages가 main 브랜치를 빌드+배포하는 데 1-3분 소요. 초기 응답은 옛 빌드의 캐시된 sw.js
- 해결: until-loop으로 새 denylist가 응답에 나타날 때까지 폴링 (`cache-busting` 쿼리 파라미터 사용). 새 SW 배포 확인 후 zip 다운로드 검증

### 이슈 2: worktree_manager.py finish 보고 상태 불일치
- 증상: `--action pr` 결과가 `"status": "merge_failed"`였지만 `gh pr view`로 확인 시 PR state=MERGED
- 원인: gh CLI의 비동기 머지 + 스크립트의 상태 폴링 타이밍 이슈로 추정. 실제 GitHub 상에선 이미 머지 완료
- 해결: gh로 직접 상태 재확인하여 머지 완료 검증. 차후 worktree_manager.py 머지 후 폴링 로직 점검 필요 (별도 task로 분리 가능)

---

## 운영 안내 (사용자 대상)

기존 사용자 브라우저는 옛 SW가 활성 상태일 수 있어 새 denylist가 즉시 적용되지 않습니다. 다음 중 하나로 갱신:
1. **하드 리로드**: `Ctrl+Shift+R` (Windows/Linux), `Cmd+Shift+R` (Mac)
2. **시크릿 모드**: 새 시크릿 창에서 사이트 접속
3. **앱 재시작**: PWA로 설치한 경우 앱 종료 후 재실행 (vite-plugin-pwa `registerType: "autoUpdate"`가 자동 갱신을 시도)

---

## 비고

- 디자인 작업 없음 (코드/설정 수정만)
- 다른 팀 디렉토리 미변경
- 메모리 피드백 적중: 2026-04-29 "PWA 배포 후 구버전 로드 시 SW 캐시 우선 의심" — 이번 사례도 같은 패턴(SW 옛 버전이 새 동작을 막음)

---

## ⚠️ QC ESCALATE 사유 (아누 판단 요청)

**실제 작업은 완료되었으나 finish-task.sh의 QC 게이트가 재시도 3회 초과로 ESCALATE 처리되었습니다.**

### 실패한 검증
- `git_evidence.NO_UNCOMMITTED`: workspace 리포지토리에 uncommitted 변경 8건 잔존

### 8건의 정체
모두 **이번 작업과 무관**한 다른 자동 프로세스가 남긴 변경:
1. `config/constants.json` — 시스템 설정 자동 갱신
2. `memory/specs/.spec-state-cache.json` — 스펙 상태 캐시
3. `memory/specs/anu-system-spec-changelog.md` — 시스템 스펙 changelog 자동 추가
4. `memory/specs/anu-system-spec.md` — 시스템 스펙 자동 갱신
5-8. `memory/backups/system-spec/2026-04-{07,15,23}*/anu-system-spec.md` — 6시간 주기 백업 회전(오래된 4개 삭제)

### 근거
`/home/jay/workspace/teams/shared/verifiers/git_evidence.py`의 `SYSTEM_AUTO_FILES` 화이트리스트에 `memory/specs/`, `memory/backups/`, `config/constants.json`이 등재되지 않아 verifier가 시스템 자동 변경을 사용자 작업 변경으로 오분류.

### 실제 작업 결과
- **본 task의 코드 변경**: PR #74 머지 완료, main 정상 반영 (커밋 874f79e)
- **workspace task-2342 커밋**: 33279862 (보고서 + 3문서)
- **프로덕션 검증**: `https://insuro.biz/sw.js` denylist 4 regex 반영, zip 다운로드 정상 (`PK\x03\x04` magic, 11453 bytes)
- **L1 스모크테스트**: PASS

### 권고 조치
1. 본 task는 사용자 시나리오 측면에서 해결 완료 — 아누가 .done 생성 가부 판단
2. 별도 task로 `git_evidence.py`의 `SYSTEM_AUTO_FILES`에 `memory/specs/`, `memory/backups/`, `config/constants.json`, `memory/specs/.spec-state-cache.json` 추가 검토 필요
