# task-2447 보고서 — Extension 자동 sync 매커니즘 (옵션 C 하이브리드)

**팀**: dev5-team (마르둑)
**작업 레벨**: Lv.3 (시스템 인프라)
**작업 시작**: 2026-05-04 20:31 KST
**작업 완료**: 2026-05-04 21:00 KST (예정)
**머지 정책**: `manual_after_full_enforcement` (admin bypass 금지)

---

## SCQA Summary

**S** : 회장 Edge에 v0.4.0 라벨이지만 task-2433 fix 미반영된 옛 zip이 박제. 추가로 InsuRo `/composite-design/setup` 페이지가 v0.1.0 박제, popup.html에 v0.1.0 하드코딩 — 같은 라벨 다른 내용 / 옛 버전 박제 사고가 task-2423/2429/2433에서 누적됨.

**C** : (1) `extension/manifest.json` version과 `extension/popup.html` 하드코딩 version, `server/config/extension_version.json` 사이 sync 강제 부재. (2) main에 extension/ 변경 push되어도 자동 zip release 메커니즘 부재. (3) 페이지가 정적 JSON 파일에 의존 → 누군가 수동 갱신 안 하면 박제. (4) task-2434/2440/2445 산출물(가드/ruleset/ci.yml) 변경 금지 + admin bypass 금지.

**Q** : "main의 `extension/` 변경 → 자동 zip release → InsuRo 페이지가 항상 최신 버전 표시 + 다운로드 제공"을 어떻게 수동 개입 없이 보장할 것인가?

**A** : 옵션 C 하이브리드 도입:
1. **CI 가드 (workspace)**: `extension_version_bump.py --check` — manifest/popup/config 일관성 검증, 불일치 시 exit 1
2. **자동 release (InsuRo .github)**: `extension-release.yml` — main push + extension/ 변경 시 manifest version 추출 → zip 빌드 → GitHub release 자동 생성
3. **백엔드 동적 sync (server/routes/extension.py)**: 5분 TTL 캐시 + manifest mtime 기반 invalidation + GitHub release URL fallback
4. **프론트 동적 표시**: popup.html 하드코딩 제거 → `chrome.runtime.getManifest()` / setup 페이지에 stale 경고 추가

---

## 작업 내용

### Phase 1 — 인프라 (닌기르수 + 팀장)
- `/home/jay/workspace/.worktrees/task-2447-dev5/scripts/extension_version_bump.py`: CI guard (--check / --sync 모드)
- `/home/jay/workspace/.worktrees/task-2447-dev5/tests/scripts/test_extension_version_bump.py`: 6 케이스 회귀 테스트
- `/home/jay/workspace/.worktrees/task-2447-dev5/.github/workflows/extension-release.yml`: workspace 미러 (workflow_dispatch only — 실 트리거는 InsuRo 측)

### Phase 2 — 백엔드 (엔키)
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/server/routes/__init__.py`: package init
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/server/routes/extension.py`: APIRouter + 5분 TTL 캐시 + helpers
  - `GET /api/extension/latest-version` (신규, 공개)
  - `GET /api/extension/download` (302 redirect, 공개)
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/server/main.py`: 3곳 수정
  - line 79: import `extension_router, get_latest_version_info`
  - line 315: `app.include_router(extension_router, tags=["extension"])`
  - line 7918: 기존 `/api/insuro/composite-design/extension-version` 본문을 `get_latest_version_info()`로 위임 (URL 유지, backward compat)
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/server/config/extension_version.json`: v0.1.0 → v0.4.1 + GitHub release URL (legacy fallback용)
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/server/tests/test_extension_routes.py`: 6 케이스 신규

### Phase 3 — 프론트 + Extension (이쉬타르)
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/extension/manifest.json`: version 0.4.0 → 0.4.1
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/extension/popup.html`: line 30 하드코딩 "v0.1.0" → `<div id="ext-version-footer">로딩중...</div>`
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/extension/popup.js`: DOMContentLoaded 핸들러 추가 (chrome.runtime.getManifest().version 기반 footer)
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/.github/workflows/extension-release.yml`: 실 트리거 워크플로
- `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5/src/pages/CompositeExtensionGuide.tsx`: stale 경고 카드 추가 (installed != latest 시)

### Phase 4 — 검증 & 통합 (팀장)
- 3doc 작성 (plan.md / context-notes.md / checklist.md)
- Codex G1 게이트 검증
- L1 스모크테스트 (TestClient + npm build + 가드 실행)
- pytest 12개 (6 스크립트 + 6 라우터) 전 PASS
- 보고서 작성

---

## 합격 6 케이스 검증

| # | 조건 | 검증 명령 | 결과 |
|---|---|---|---|
| A | manifest version bump 강제 | `python3 scripts/extension_version_bump.py --check` (실제 InsuRo 파일 대상) | **PASS** — `manifest version='0.4.1', extension_version.json 일치, popup.html 정상` |
| B | popup.html hardcoded version 제거 | `grep -E 'v[0-9]+\.[0-9]+\.[0-9]+' extension/popup.html` | **PASS** — 0건 |
| C | extension-release.yml workflow 추가 | `ls .github/workflows/extension-release.yml` (InsuRo + workspace) | **PASS** — 양쪽 모두 존재 |
| D | `/api/insuro/composite-design/extension-version` 정상 응답 | TestClient → `200`, version=0.4.1 | **PASS** — version, latest_version, download_url, released_at, release_notes_url 모두 포함 |
| E | InsuRo setup 페이지 동적 표시 | `npm run build` PASS + grep stale 경고 코드 | **PASS** — build 11.96s, stale 경고 2건 매치 |
| F | 본 task PR ruleset 통과 후 머지 | (PR 생성 + Gemini 리뷰 후 확인 예정) | **PENDING** — finish-task.sh 후 worktree_manager finish --action pr 실행 |

**A-E 5건 PASS, F는 PR 생성 후 확정.**

---

## 파일 목록

### 신규 (workspace)
- `scripts/extension_version_bump.py` (CLI guard)
- `tests/scripts/test_extension_version_bump.py` (6 cases)
- `.github/workflows/extension-release.yml` (workspace 미러)
- `memory/plans/tasks/task-2447/{plan,context-notes,checklist}.md` (3doc)

### 신규 (InsuRo)
- `server/routes/__init__.py`
- `server/routes/extension.py` (APIRouter + cache)
- `server/tests/test_extension_routes.py` (6 cases)
- `.github/workflows/extension-release.yml` (실 트리거)

### 수정 (InsuRo)
- `extension/manifest.json` (0.4.0 → 0.4.1)
- `extension/popup.html` (line 30 dynamic)
- `extension/popup.js` (DOMContentLoaded init)
- `server/main.py` (3곳: import + include_router + 위임)
- `server/config/extension_version.json` (sync to 0.4.1 + GitHub URL)
- `src/pages/CompositeExtensionGuide.tsx` (stale 경고)

### 변경 0건 (forbidden 준수)
- `extension/content.js`, `background.js`, `inject.js` ✓
- `scripts/task_scope.py`, `pre_push_guard.py`, `qc_report_guard.py`, `guard.sh` ✓
- workspace `.github/workflows/ci.yml` ✓
- `dispatch.py`, `dashboard/`, `teams/shared/`, `CLAUDE.md` ✓

---

## 테스트 결과

### pytest
```
[workspace] tests/scripts/test_extension_version_bump.py
============================== 6 passed in 0.34s ===============================

[InsuRo] server/tests/test_extension_routes.py
============================== 6 passed in 2.53s ===============================
```

### npm build (InsuRo)
```
✓ built in 11.96s
PWA v1.2.0  precache 172 entries (5868.49 KiB)
```
TypeScript 오류 0건. chunk size 경고는 기존 코드베이스 수준.

### CI guard 실행 (실제 worktree 파일 대상)
```bash
$ python3 scripts/extension_version_bump.py --check \
    --extension-dir extension \
    --config-file server/config/extension_version.json \
    --popup-file extension/popup.html
PASS: manifest version='0.4.1', extension_version.json 일치, popup.html 정상
$ echo $?
0
```

---

## L1 스모크테스트 결과 (필수)

- **서버 재시작**: 해당없음 (TestClient 사용 — 코드 레벨 검증)
- **API 응답 확인**:
  - `GET /api/extension/latest-version` → 200, body=`{"version": "0.4.1", "latest_version": "0.4.1", "download_url": "https://github.com/JonghyukJeon/InsuRo/releases/download/extension-v0.4.1/insuro-helper-0.4.1.zip", "released_at": "2026-05-04T11:39:04...", "release_notes_url": "...releases/tag/extension-v0.4.1"}`
  - `GET /api/insuro/composite-design/extension-version` (legacy) → 200, 동일 schema (frontend backward compat 보존)
  - `GET /api/extension/download` → 302, Location=`https://github.com/JonghyukJeon/InsuRo/releases/download/extension-v0.4.1/insuro-helper-0.4.1.zip`
- **스크린샷**: 해당없음 (서버 미기동 환경 — TestClient로 동등 검증). 빌드 PASS + 코드 grep 검증으로 대체.
- **CI guard**: 실제 InsuRo worktree 파일 대상 `--check` PASS

---

## 모델 사용 기록

| 팀원 | 모델 | 작업 | 정당성 |
|---|---|---|---|
| 엔키 | sonnet | server/routes/extension.py + main.py 통합 + 6 테스트 | 일반 백엔드 코딩 |
| 이쉬타르 | sonnet | extension/* 3 + CompositeExtensionGuide.tsx | 일반 프론트 + Chrome Extension API |
| 닌기르수 | sonnet | extension_version_bump.py + 6 테스트 + workflow yml × 2 | 인프라/CLI 스크립트 + GitHub Actions |
| 마르둑(팀장) | opus | 설계 + 분배 + 통합 + 검증 + 보고서 | 직접 코딩 0건 (Edit 1건 — Pyright 타입 fix만) |

3 페르소나 병렬 dispatch (단일 메시지 3 Task 호출). haiku 0건.

---

## 발견 이슈 및 해결

### 이슈 1 — 신규 테스트 파일 Pyright 경고
**증상**: `test_extension_version_bump.py:52` `download_url: str = None` → reportArgumentType.
**원인**: PEP 484 strict 모드 위반.
**해결**: `download_url: str | None = None` + `_run` 시그니처에서 미사용 `tmp_path` 제거. 6 케이스 PASS 유지.

### 이슈 2 — 워크스페이스가 다른 task 작업으로 dirty
**증상**: workspace `git status`가 `task-2439-dev1` 브랜치 + 5+ 수정 파일 (config/constants.json 등) — 본 task와 무관.
**원인**: 다른 팀의 미완료 작업 진행 중.
**해결**: workspace 신규 파일은 `.worktrees/task-2447-dev5` 신규 worktree에서 commit (task-2439-dev1 브랜치 미오염). 메인 workspace의 `memory/plans/tasks/task-2447/`는 worktree로 symlink (QC 스크립트 호환).

### 이슈 3 — extension-release.yml 위치 모순
**증상**: task spec `allowed_resources`가 `/home/jay/workspace/.github/workflows/extension-release.yml`을 명시하나, `extension/`은 InsuRo repo에 있어 workspace 워크플로 트리거 불가.
**원인**: 명세 paths의 repo 경계 불일치.
**해결**: 양쪽 모두에 작성. workspace 버전은 상단 주석으로 동작 안내 + workflow_dispatch만 가능. 실 트리거는 InsuRo `.github/workflows/extension-release.yml`이 담당. (context-notes.md 결정 1 참조)

### 이슈 4 — Codex G1에서 critical 4건 보고
**증상**: 사전 게이트 `pass: false`.
**원인**: Codex가 현재 코드 상태(아직 미구현)를 평가 — 본 task가 fix할 항목들과 정확히 일치.
**해결**: 본 task 구현이 정확히 그 4건을 해소. 게이트 해석은 "design intent 검증" — 4건 risk가 모두 task scope 내. G3 재검증으로 PASS 확인 예정.

### 이슈 5 — 기존 main.py Pyright import 미해결 경고
**증상**: `main.py`에서 `sb_helpers`, `composite_calculator`, `pii_crypto`, `customer_match` import 미해결.
**원인**: 기존 코드베이스의 PYTHONPATH 설정 이슈 (서버 실행 시 server/가 sys.path에 추가됨).
**해결**: 본 task scope 외 (사전 존재). 본 task가 추가한 `routes.extension` import도 같은 패턴 — pytest는 conftest.py에서 sys.path 처리하므로 런타임 PASS.

---

## 머지 판단

- **머지 필요**: Yes
- **InsuRo 브랜치**: `task/task-2447-dev5` (commits: 9bbe828, fdb62a1, 81dab03)
- **InsuRo 워크트리**: `/home/jay/projects/InsuRo/.worktrees/task-2447-dev5`
- **Workspace 브랜치**: `task/task-2447-dev5` (commit: dev5 일괄)
- **Workspace 워크트리**: `/home/jay/workspace/.worktrees/task-2447-dev5`
- **머지 의견**: 
  - 12 pytest 전 PASS, npm build PASS, CI guard 실 파일 대상 PASS, L1 TestClient 응답 schema 일치
  - `manual_after_full_enforcement` 정책 준수: PR 생성 후 Gemini 리뷰 + ruleset 통과 후 머지
  - 회장 절대 기준 "버전 라벨이 같아도 내용이 다른 사고 재발 X" 충족 — CI 가드가 manifest/popup/config 3중 sync 강제

---

## PR 링크

- **InsuRo PR #102**: https://github.com/JonghyukJeon/InsuRo/pull/102 (Backend + Extension + Frontend + Workflow)
- **Workspace PR #20**: https://github.com/Jeon-Jonghyuk/dev_workspace/pull/20 (CI guard + 3doc + capability + scope)

## 다음 단계

1. Gemini PR 리뷰 대응 (양 PR, 5분 대기, High 0건 시 머지)
2. F 케이스 (ruleset 통과 + admin bypass 0건) 확인
3. 머지 후 GitHub Actions extension-release.yml 트리거 → `extension-v0.4.1` release 자동 생성 검증
4. `finish-task.sh` 실행 (.done 자동 생성 + timer end + notify-completion)
5. 회장 게이트키퍼 6 케이스 모두 PASS → `.done.acked`

---

## 참조

- task 명세: `/home/jay/workspace/memory/tasks/task-2447.md`
- 3문서: `/home/jay/workspace/memory/plans/tasks/task-2447/`
- 선행 task: task-2423/2429/2433 (content.js mmlfcp fix), task-2440/2445 (ruleset)
- 회장 거버넌스: `system_governance_4layer.md`
- 회장 결정: 옵션 C 하이브리드 (백엔드 캐시 + GitHub release 동기화)
