# Extension 자동 sync 매커니즘 (옵션 C 하이브리드) ★ Lv.3

## ★★★ 본 task 절대 원칙 (회장 직접 명시 2026-05-04)

> **"버전이 달라지면 InsuRo 페이지의 확장 다운로드 기능이 함께 업데이트되어야 한다."**
> **"task.md 수정 금지. 중간 정정 = 100% 무효. 봇은 본 payload만 실행."**
> **"본 task PR도 ruleset 통과 후 merge. admin bypass 금지."**

본 task는 단일 payload. dispatch 후 task.md 수정 무효. 봇은 본 파일만 실행.

## 작업 레벨: Lv.3 (시스템 인프라 — Extension 자동 sync 하이브리드)

## 위임 배경 (회장 직접 진단)

### 결함 현황 (회장 Edge 시연 결과)
1. 회장 Edge Extension v0.4.0 (`C:\Users\drumb\Desktop\insuro-helper-0.4.0`) — task-2423/2429/2433 fix **미반영**
2. main `extension/` v0.4.0 라벨 동일하지만 **content.js는 task-2433 trace 포함** (같은 라벨 다른 내용)
3. InsuRo 페이지 `https://insuro.biz/composite-design/setup`:
   - 상단: "확장 설치됨 (v0.4.0)" — 회장 Edge 인식 ✅
   - 최신 버전: **v0.1.0** + "확장 다운로드" 버튼 — **옛 버전 박제, 페이지 신뢰도 깎음**
4. `extension/popup.html` line 30 `<div class="footer">v0.1.0 · 인카 FA 전용</div>` — **하드코딩** (manifest version 동기화 X)
5. manifest version bump 정책 부재 — task-2420 이후 task-2423/2429/2433 모두 version bump 누락

### 회장 결정 (옵션 C 하이브리드)
- 백엔드 캐시 + GitHub release 동기화
- main commit 시 자동 zip 빌드 + release tag
- InsuRo 페이지가 백엔드 API → 캐시/release fallback으로 항상 최신 제공
- 페이지 "최신 버전" 텍스트도 manifest 동적 읽기

## 핵심 합격 기준 (한 줄)

> **"main의 extension/ 변경 → 자동 zip release → InsuRo 페이지가 항상 최신 버전 표시 + 다운로드 제공."**

GitHub 원본 + 실 다운로드 zip 검증 기준.

---

## [1] manifest.json version bump 강제 정책

### 신규 파일: `scripts/extension_version_bump.py`
- main의 `extension/manifest.json` version과 `extension/popup.html`의 hardcoded version을 동시 검사
- 불일치 시 CI에서 FAIL
- 또는 `extension/` 파일 변경 시 manifest version auto-bump (선택)

### CI 통합: `.github/workflows/ci.yml` 신규 step (또는 기존 lock-in-check 확장)
```yaml
extension-version-check:
  if: always()
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - run: python3 scripts/extension_version_bump.py --check
    # extension/ 변경 commit인데 manifest version bump 없으면 FAIL
```

## [2] popup.html hardcoded version 제거

### 대상: `extension/popup.html` line 30
**기존**:
```html
<div class="footer">v0.1.0 · 인카 FA 전용</div>
```

**변경**:
```html
<div class="footer" id="ext-version-footer">로딩중...</div>
```

### 대상: `extension/popup.js`
신규 init 코드:
```javascript
document.addEventListener("DOMContentLoaded", () => {
  const m = chrome.runtime.getManifest();
  const v = m.version || "?.?.?";
  const footer = document.getElementById("ext-version-footer");
  if (footer) footer.textContent = `v${v} · 인카 FA 전용`;
});
```

## [3] GitHub Actions — main commit 시 extension zip release 자동 생성

### 신규 workflow: `.github/workflows/extension-release.yml`
```yaml
name: Extension Release
on:
  push:
    branches: [main]
    paths:
      - "extension/**"
  workflow_dispatch:

permissions:
  contents: write

jobs:
  build-and-release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Extract manifest version
        id: ver
        run: |
          VERSION=$(python3 -c "import json; print(json.load(open('extension/manifest.json'))['version'])")
          echo "version=$VERSION" >> $GITHUB_OUTPUT
      - name: Build extension zip
        run: |
          cd extension && zip -r ../insuro-helper-${{ steps.ver.outputs.version }}.zip . -x '*.DS_Store' '__tests__/*'
      - name: Create / Update Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: extension-v${{ steps.ver.outputs.version }}
          name: InsuRo Helper Extension v${{ steps.ver.outputs.version }}
          files: insuro-helper-${{ steps.ver.outputs.version }}.zip
          generate_release_notes: true
```

### 효과
- main에 `extension/` 변경 push → 자동 zip → GitHub release tag `extension-v{version}`
- release 자산: `insuro-helper-{version}.zip`

## [4] InsuRo 백엔드 API (캐시 + GitHub release fallback)

### 신규 파일: `server/routes/extension.py` (또는 적절한 InsuRo 백엔드 위치)

#### Endpoint 1 — `/api/extension/latest-version` (GET)
**캐시 전략**:
- TTL 5분 메모리 캐시
- 캐시 만료 시 `https://api.github.com/repos/Jeon-Jonghyuk/dev_workspace/releases/latest` 호출 → tag 추출
- 또는 main의 `extension/manifest.json` version 직접 읽기 (server 환경에 따라)

**Response**:
```json
{
  "version": "0.4.1",
  "download_url": "https://github.com/Jeon-Jonghyuk/dev_workspace/releases/download/extension-v0.4.1/insuro-helper-0.4.1.zip",
  "released_at": "2026-05-04T15:00:00Z",
  "release_notes_url": "https://github.com/Jeon-Jonghyuk/dev_workspace/releases/tag/extension-v0.4.1"
}
```

#### Endpoint 2 — `/api/extension/download` (GET, optional redirect)
- 옵션 A: GitHub release zip URL로 302 redirect
- 옵션 B: 백엔드가 GitHub에서 zip 받아서 직접 stream (CORS / private repo 시)
- 본 task는 옵션 A 선택 (단순)

### 캐시 invalidation
- GitHub Actions의 release publish webhook → InsuRo 백엔드 cache invalidation API 호출 (선택, 즉시성 필요 시)
- 또는 5분 TTL로 충분 (자체 트리거 없이 자동 갱신)

## [5] InsuRo 페이지 동적 표시

### 대상: `src/pages/CompositeExtensionGuide.tsx` (또는 setup 페이지 컴포넌트)

#### 변경 1: 최신 버전 동적 표시
```typescript
const [latest, setLatest] = useState<{version: string; download_url: string} | null>(null);

useEffect(() => {
  fetch("/api/extension/latest-version")
    .then(r => r.json())
    .then(setLatest)
    .catch(() => setLatest(null));
}, []);

// 렌더
{latest && <p>v{latest.version}</p>}
<a href={latest?.download_url} download>확장 다운로드</a>
```

#### 변경 2: "확장 설치됨" 영역에 신/구 비교 + 갱신 안내
```typescript
const installed = window.chrome?.runtime?.getManifest()?.version; // (extension messaging로 받아오기, 별도 logic)
const isStale = installed && latest && installed !== latest.version;

{isStale && (
  <Alert variant="warning">
    설치된 버전 v{installed}는 최신(v{latest.version})보다 옛 버전입니다.
    아래 "확장 다운로드"로 새 zip을 받고 Edge에서 ↻ reload 해주세요.
  </Alert>
)}
```

## [6] 회귀 테스트

### 신규 파일: `tests/scripts/test_extension_version_bump.py`
- manifest 변경 시 version bump 안 되면 FAIL
- popup.html hardcoded "v0.1.0" 패턴 남아있으면 FAIL

### 신규 파일: `server/__tests__/test_extension_routes.py` (또는 적절한 위치)
- /api/extension/latest-version 응답 schema 검증
- 캐시 hit / miss 동작
- GitHub API mock

## [7] 합격 조건 (★ 회장 절대 기준)

| # | 조건 | 검증 |
|---|---|---|
| A | manifest version bump 강제 (CI guard) | extension 변경 + version 그대로 → CI FAIL |
| B | popup.html hardcoded version 제거 + 동적 읽기 | grep popup.html "v0\.1\.0" → 0건 |
| C | extension-release.yml workflow 추가 | main push 후 GitHub release 자동 생성 확인 |
| D | InsuRo `/api/extension/latest-version` 정상 응답 | curl response schema PASS |
| E | InsuRo setup 페이지 "최신 버전" 동적 표시 | playwright 스크린샷 (or curl 후 렌더 결과 검증) |
| F | 본 task PR ruleset 통과 후 merge | gh pr view → merged=true + admin/bypass 0건 |

**모두 PASS = 합격. 한 가지라도 FAIL = task FAIL.**

## [8] 절대 금지 (회장 정책)

- ruleset 변경 금지
- admin bypass merge 금지
- main direct push 금지
- 봇 자체 머지 금지 (manual_after_full_enforcement)
- task-2434/2440/2445 산출물 (가드 / ci.yml 7-step / Ruleset / 기존 controller) 변경 금지

## affected_files

### 수정
- `extension/manifest.json` (version bump — 본 task 머지 시점 0.4.1 또는 0.5.0)
- `extension/popup.html` (line 30 hardcoded version 제거)
- `extension/popup.js` (manifest version 동적 읽기)
- `src/pages/CompositeExtensionGuide.tsx` (또는 적절한 setup 페이지 컴포넌트)

### 신규
- `scripts/extension_version_bump.py`
- `.github/workflows/extension-release.yml`
- `server/routes/extension.py` (또는 적절한 InsuRo 백엔드 라우터)
- `tests/scripts/test_extension_version_bump.py`
- `server/__tests__/test_extension_routes.py` (적절한 위치)
- `memory/reports/task-XXXX.md`

### 변경 금지 (forbidden)
- `extension/content.js` (task-2433 산출물 keep)
- `extension/background.js`
- `extension/inject.js`
- `scripts/task_scope.py` / `scripts/pre_push_guard.py` / `scripts/qc_report_guard.py` / `scripts/guard.sh`
- `scripts/anu_confirm_bot/main.py`
- `scripts/git-hooks/pre-push`
- `scripts/gemini_review_gate.py` / `scripts/gemini_feedback_loop.py` / `scripts/lock_in_verify.py`
- `scripts/auto_merge_controller.py` / `scripts/auto_merge_lock.py`
- `.github/workflows/ci.yml` (task-2445 산출 — 변경 금지)
- `dispatch.py`
- `dashboard/**`
- `teams/shared/**`
- `CLAUDE.md`

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "/home/jay/projects/InsuRo/extension/manifest.json"
    - "/home/jay/projects/InsuRo/extension/popup.html"
    - "/home/jay/projects/InsuRo/extension/popup.js"
    - "/home/jay/projects/InsuRo/src/pages/CompositeExtensionGuide.tsx"
    - "/home/jay/projects/InsuRo/server/routes/extension.py"
    - "/home/jay/projects/InsuRo/server/__tests__/**"
    - "/home/jay/workspace/scripts/extension_version_bump.py"
    - "/home/jay/workspace/.github/workflows/extension-release.yml"
    - "/home/jay/workspace/tests/scripts/test_extension_version_bump.py"
    - "/home/jay/workspace/memory/reports/task-XXXX.md"
    - "/home/jay/workspace/memory/plans/tasks/task-XXXX/**"
  forbidden_paths:
    - "/home/jay/projects/InsuRo/extension/content.js"
    - "/home/jay/projects/InsuRo/extension/background.js"
    - "/home/jay/projects/InsuRo/extension/inject.js"
    - "/home/jay/workspace/scripts/task_scope.py"
    - "/home/jay/workspace/scripts/pre_push_guard.py"
    - "/home/jay/workspace/scripts/qc_report_guard.py"
    - "/home/jay/workspace/scripts/guard.sh"
    - "/home/jay/workspace/scripts/anu_confirm_bot/main.py"
    - "/home/jay/workspace/scripts/git-hooks/pre-push"
    - "/home/jay/workspace/scripts/gemini_review_gate.py"
    - "/home/jay/workspace/scripts/gemini_feedback_loop.py"
    - "/home/jay/workspace/scripts/lock_in_verify.py"
    - "/home/jay/workspace/scripts/auto_merge_controller.py"
    - "/home/jay/workspace/scripts/auto_merge_lock.py"
    - "/home/jay/workspace/.github/workflows/ci.yml"
    - "/home/jay/workspace/dispatch.py"
    - "/home/jay/workspace/dashboard/**"
    - "/home/jay/workspace/teams/shared/**"
    - "/home/jay/workspace/CLAUDE.md"
  commands:
    - "python3 -m py_compile"
    - "pytest"
    - "npm run build"
    - "npm test"
    - "git status"
    - "git log"
    - "git diff"
    - "git fetch"
    - "gh api"
    - "gh pr"
    - "gh release"
    - "curl"
  merge_policy: "manual_after_full_enforcement"
  ttl_hours: 8
```

## 운영
- ★ Lv.3 (Extension 자동 sync 하이브리드)
- TTL 8h
- 위임: dev5-team (마르둑) — InsuRo Helper Extension 직접 컨텍스트 (task-2423/2429/2433 작업 이력)
- ★ 봇 자체 머지 금지 — manual_after_full_enforcement
- 회장 게이트키퍼 6 케이스(A~F) 모두 PASS 시에만 .done.acked

## 참조
- 회장 시연 결과: 매트릭스 alert "매트릭스를 찾을 수 없습니다"
- 회장 진단: insuro.biz/composite-design/setup 페이지가 v0.1.0 박제
- task-2433: content.js mmlfcp 셀렉터 fix (회장 PC 미반영, 본 task 산출물로 자동 sync)
- task-2440 enforcement: ruleset 8 required + manual_after_full_enforcement
- task-2445 ci.yml 1:1 매칭 fix
- 회장 거버넌스: `system_governance_4layer.md`

---

## ★ 한 줄 절대 기준 (재고정)
> **"main의 extension/ 변경 → 자동 zip release → InsuRo 페이지가 항상 최신 버전 표시 + 다운로드 제공."**
> **"버전 라벨이 같아도 내용이 다른 사고 재발 X."**