# TRACK_A (Track A) — 확장 production 이식 (로컬 전용, 서버 전송 OFF)

> **★ 개발 dispatch 승인됨(2026-07-19). PR·merge·배포·실고객 write·production 활성화는 여전히 미승인.**

## ★ ID 규약 (숫자 task ID 하드코딩 금지)
- **logical ID = `TRACK_A`** — 본 문서의 영구 식별자.
- **`<ALLOCATED_TASK_ID>`** = dispatch 시점에 **dispatch 시스템이 실제로 할당한 숫자 ID**로 치환한다.
  - ⚠️ ANU 가 미리 고른 번호·브랜치 선점은 **예약이 아니다**(2026-07-19 실증: 선점한 `task-2798` 이 타 task 에 할당됨).
  - 치환은 **등록 성공 후 실제 할당값을 조회해서** 수행한다. 추측 금지.
- **`<TEAM>`** = dispatch 시 확정되는 팀 슬러그(예: `dev5`).
- 브랜치·worktree·보고서 경로 모두 위 치환 결과를 사용한다.

## 레벨
Lv.4 프로그램의 하위 Track. **PR·merge·배포·운영활성화 전부 회장 승인 게이트.**

## base (★ 확정 — 계약 merge 를 기다리지 않는다)
- **분기 기준**: **`cc7476bfbd9f6b3ae3c99f9a7136cc9f94cd3ada`** (현재 `origin/main`)
- **계약 문서는 merge 되지 않은 브랜치에서 읽는다**(계약 merge 는 별도·후순위 승인 사안):
  ```bash
  git -C /home/jay/projects/InsuRo show bb1c219d0451cf3798aff624226b4f25d194f5d9:docs/contracts/INSURO_LV4_PROGRAM_CONTRACTS_V1.md | sha256sum
  git -C /home/jay/projects/InsuRo show bb1c219d0451cf3798aff624226b4f25d194f5d9:docs/contracts/OHMY_PPD_DATA_CONTRACT_V1.md | sha256sum
  ```
  → 아래 "단일 소스" 의 SHA-256 과 **정확히 일치**해야 한다. 다르면 **STOP_REPORT**.
- ⚠️ **계약 커밋 `bb1c219d0451cf3798aff624226b4f25d194f5d9` 를 브랜치에 merge·cherry-pick 하지 마라.** 읽기 전용 참조다.


## ★ 저장소·경로 기준 (모호성 제거)
- **대상 저장소**: **InsuRo** (`/home/jay/projects/InsuRo`, remote `origin`)
- 본 문서의 **모든 상대경로는 InsuRo 저장소 루트 기준**이다. 예: `docs/contracts/…` = `<InsuRo>/docs/contracts/…`
- 계약 PR merge 전에는 해당 파일이 **`main` 에 없다** — 브랜치 `contract/ohmy-ppd-v1` 에만 존재한다. 확인 방법:
  ```bash
  git -C /home/jay/projects/InsuRo show <계약커밋>:docs/contracts/INSURO_LV4_PROGRAM_CONTRACTS_V1.md | sha256sum
  ```
- ⚠️ **`/home/jay/workspace` 는 대상 저장소가 아니다.** 본 지시서 파일 자체가 거기 있을 뿐이며, **경로 해석 기준이 아니다.**

## 단일 소스 (저장소 내부 canonical — memory 사본 아님)
- `docs/contracts/INSURO_LV4_PROGRAM_CONTRACTS_V1.md` (SHA-256 `c7fe00a75606f94de218f27f65f44315bae97ef993be1d7b217ea322febca873`)
- `docs/contracts/OHMY_PPD_DATA_CONTRACT_V1.md` (SHA-256 `fc081a76da0c9213faa7cee1a64d183981b348e42ac788d81466dd9d3e720986`)
- **착수 시 두 파일의 SHA-256 을 재확인**. 다르면 **STOP_REPORT**.

## expected_files
```yaml
allowed_resources:
  paths:
    - "extension/content.js"
    - "extension/background.js"
    - "extension/manifest.json"
    - "extension/__tests__/**"
  forbidden_paths:
    - "server/**"          # Track B 영역 — 중첩 0
    - "supabase/**"        # Track B 영역
    - "src/**"
    - "package.json"       # 공통설정 — 양 track 수정 금지
    - "vitest.config.ts"   # 공통설정
    - "tsconfig.json"      # 공통설정
    - "docs/contracts/**"  # 계약 = 읽기 전용
    - ".github/**"
    - ".env*"
  new_file_limit: 3
  commands: ["git","npm","node","npx"]
  merge_policy: "none"
```
```yaml
external_control_artifacts:
  # ★ 아래는 **InsuRo 저장소 밖**(/home/jay/workspace)의 control-plane 산출물이다.
  #   - git stage · commit 대상 **0**
  #   - effective diff 산정 대상 **0**  (expected_files 위반 판정에 포함되지 않는다)
  #   - 절대경로로만 접근한다. 저장소 상대경로로 해석 금지.
  - /home/jay/workspace/memory/reports/<ALLOCATED_TASK_ID>-trackA.md
```
**타 track 중첩**: Track B(`server/`·`supabase/`)와 **0**. 공통설정 3개는 양쪽 수정 금지.

## ★ 진단브랜치 격리 (절대)
`task/task-2787-dev5` · `task/task-2787+2-dev5` · `task/task-2788-dev5` →
**merge · cherry-pick · rebase 이식 · 수동 복사 · 부분 포팅 · 코드 스니펫 붙여넣기 전부 금지.**
지식 전달은 **위 계약 문서를 통해서만**. 해당 브랜치 파일을 열어 코드를 옮기는 행위 자체를 금지.

## ★ 이번 dispatch 의 범위 (회장 확정 2026-07-19)
**개발까지만.** 아래는 **전부 미승인**이며 착수 금지:
- PR 생성 · main merge · 배포 · 실고객 write · production 활성화 · A↔B 실연결

**merge 순서(계약 merge 이후 적용될 계획 — 지금은 참고용)**:
계약 PR merge → B first merge → A 를 `B_MERGE_SHA` 위로 rebase·전체 재검증 → A merge → integration
※ **지금은 어느 것도 실행하지 않는다.** 브랜치 커밋까지가 이번 범위.

### ★ 봇 실행 안전 프로토콜 (필수)
1. **Edit·commit·finish 직전마다 cancel 마커 재확인** — `.cancelled` 존재 시 즉시 중단
2. **동일 worktree·branch 를 다른 task 가 쓰고 있지 않은지** 착수 시 확인
3. **`.done` 자동 마커는 완료 증거가 아니다** — ANU 독립검증이 권위. 산출물(커밋·보고서)을 반드시 남길 것
4. 실패·중단 시에도 **보고서를 남기고** ANU callback 발사


## 구현 요구

### A1. 사용자 명시 클릭 기반 background 직접 조회
- ohmymanager 페이지 버튼 **명시 클릭 시에만** 동작. 클릭 전 A-0 관련 네트워크 요청 **0**.
- content(ISOLATED) → 현재 URL 에서 **토큰 1회 읽기** → background 로 전달 → `fetch(API, {headers:{Authorization:"Bearer "+token}})`.
- **MAIN world 주입·fetch/XHR 인터셉터 신규 추가 절대 금지.**

### A2. 토큰 취급 (계약 §AMENDMENT-1)
- **저장·캐시·로깅·서버전송 0.** 함수 스코프 1회 사용 후 폐기.
- `token_source: "URL_QUERY" | "ABSENT"` 기록. **ABSENT → fail-closed**(과거 토큰 재사용·우회 조립 금지).
- 실패·거부 경로에서 토큰이 **에러객체·재시도큐·verdict·로그에 잔존 0**.

### A3. URL allowlist (fetch 전 게이트)
`protocol=https` · `hostname === "mmlfcp.ohmymanager.com"`(정확일치) · `pathname === "/api/ProductPremiums"`(정확일치) · **포트 = 생략 또는 443**.
+ **query key allowlist**: 허용 키만(`age`,`gender`,`insurance_type`,`plan_id` 등 실사 후 확정). **미지 키·중복 키·비정상 길이·enum 위반·나이범위 위반 거부.**
+ **sender 검증**: `sender.id === chrome.runtime.id` **및** `sender.url`/`sender.origin` 의 origin 이 `https://mmlfcp.ohmymanager.com`.
불일치 → **fetch 함수 자체를 호출하지 않고** 거부 코드 반환.

### A4. ★ 계정전환·stale URL 차단 (계약 §3-4, §3-5)
fetch 전 **아래 전부 일치**해야 진행. 하나라도 불일치 → **fail-closed** + "조회하기를 다시 눌러주세요":
- resource URL 의 `age/gender/insurance_type/plan_id` == **현재 화면 조건**
- resource entry 생성시각 **> 현재 세션 마지막 조회 시각**
- 현재 `local_owner_observation_key` == 조회 당시 `local_owner_observation_key`
**owner 변경 감지 시 (A 의 책임 범위)**: 이전 응답·계산·미리보기·**preview snapshot 즉시 폐기** + 미리보기 상태를 **`INVALIDATED`** 로 표시(계약 §3-4-a 상태 정의: `VALID` | `INVALIDATED`).
> ⚠️ **"저장 버튼 비활성화"는 A 의 책임이 아니다** — A 에는 저장 버튼이 없고 추가도 금지다(A6-a).
> **버튼 비활성화는 integration task 소유**이며, integration 은 **A 가 노출한 `INVALIDATED` 상태를 근거로** 버튼을 잠근다.
> A 는 **상태를 정확히 노출할 의무**까지, integration 은 **그 상태를 UI 에 강제할 의무**를 진다.
전환 판단은 **토큰 원문 비교가 아니라 `local_owner_observation_key` 비교**로 한다(같은 사용자도 토큰은 갱신되므로 토큰 비교는 오탐). **V1 에서 `external_subject_key` 는 쓰지 않는다.**

**★ 값 구분 (계약 §2-1-d — 혼동 시 보안사고)**:

| 값 | 소재 | 신뢰 | 전송 |
|---|---|---|---|
| **`local_owner_observation_key`** | 클라이언트 **메모리 전용** | **비신뢰**(관측용) | **전송 0** |
| **`external_subject_key`** | — | **V1 미사용**(§2-1-a-0) | 생성·전송 **금지** |

→ 계정전환 탐지는 **`local_owner_observation_key`** 로 한다. 이 값은 **payload 에 넣지 않는다.**

#### ★ A4 결정항목 — 지어내지 말고 **실사 후 확정**할 것 (Codex 지정)
> 검증 안 된 DOM 셀렉터를 미리 명시하면 그게 더 위험하다(계약상 `plan_id` DOM 위치는 **UNKNOWN**). 아래를 **실사 → 근거와 함께 확정 → 보고**하라.

| # | 결정할 것 | 판단 근거로 삼을 것 |
|---|---|---|
| 1 | **`plan_id` 권위 소스** — DOM / 페이지 내장 state / 네트워크 payload 중 어디인가 | 실제 화면 실사. 추측 금지 |
| 2 | **추출 타이밍·상태** — 그 값이 "안정적으로 읽을 수 있는" UI 시점은 언제인가 | 조회 전/중/후 관측 |
| 3 | **fallback 증거 경로** — 1의 경로가 없을 때 무엇을 허용하고 **어떤 증거를 남길 것인가** | resource timing 은 fallback. **인터셉터 복귀 금지** |
| 4 | **STOP 조건** — `plan_id` 의 **안정적·재현 가능한 소스를 실사로 확정하지 못하면 즉시 STOP** | 미확정 상태로 구현 진행 금지 |

같은 방식으로 **"현재 화면 조건"의 DOM 소스 · "마지막 조회 시각"의 기준 시점 · `local_owner_observation_key` 산출 방법**도 **실사 후 확정**하고 근거를 보고에 남긴다. (※ **저장 버튼 UI 요소는 A 범위 밖** — integration task.)

### A5. URL 구성 — DOM 1순위 / resource timing fallback
- `request_fingerprint` 4요소는 **화면 입력값**이므로 **DOM 기반 구성을 primary**로.
- resource timing 은 **교차검증·fallback**(버퍼 유실·SPA clear·첫 조회 전 공백 대비).
- 둘 다 실패 → **인터셉터 복귀 금지**. "조회하기를 먼저 눌러주세요" UX 로 흡수.

### A6. 로컬 분석·검증·미리보기 (여기까지만)
계약 §1 계산 + 3축 교차검증(계산값 / 화면 셀 / 화면 상단 총액) → **로컬 미리보기**.
**서버 전송 코드 미포함(추가 금지).** 실제 전송은 **A↔B 통합 task 에서만**.

#### ★ A6-a. payload builder 구조까지만 — **`idempotency_key` 생성 금지** (holistic 검토 정정 2026-07-19)

> **이전 초안 오류**: "저장 액션 1회 단위로 `idempotency_key` 생성"을 A 에 지시했다. **그런데 A 에는 저장 액션이 없다.**
> 없는 것을 단위로 삼으라는 지시라, dev 는 미리보기 시 / 조회 시 / 페이지 로드 시 생성하거나 **가상의 저장 버튼을 미리 추가**하게 된다(= A 의 "서버 전송 코드 미포함" 불변식 침해).

**★ Track A 의 산출물 = `ImmutablePreviewSnapshotV1`** (계약 §2-3-c — 회장 지시 3번)
- 내용: 조회조건·선택담보·보험사별 계산·검증결과 등 **미리보기 확정 데이터**
- **미포함**: `reference` · `consent` · `idempotency_key` — 미리보기 시점에 **아직 존재하지 않는 값들**이다
- **★ A 는 최종 `request_body_bytes` 를 만들지 않는다.** (만들 수 없다 — 위 3개가 없으므로)
- **`idempotency_key` 생성 금지.**

**Integration task 의 책임 (A 범위 밖)**: 첫 저장 클릭 시 `ImmutablePreviewSnapshotV1` + **`reference` + `consent` + `idempotency_key`** 를 합쳐 **`request_body_bytes` 를 정확히 1회 생성** → 재시도 시 **그 bytes 를 그대로 재사용**(재생성 금지).

**필드 규약 (계약 §2-1)**:
- **`reference_type` + `reference_id`** — **integration 이 채운다.** A 는 snapshot 에 넣지 않는다. (V1 허용값 = `"CUSTOMER"` 단독)
- `contract_version` 은 snapshot 에 포함 / **`consent{granted_at,consent_version,scope,source}` 는 integration 이 채운다.**
- **`fa_account_id` 를 payload 에 넣지 않는다**(계약 §2-1-b). 넣으면 서버가 `CLIENT_AUTHORITY_FIELD_FORBIDDEN`/400 으로 거부한다.
- **`external_subject_key` 는 V1 범위 밖**(§2-1-a-0) — 생성·전송 금지.
- **`BLOCKED` 상태면 payload 조립 자체를 하지 않는다.** 실패 코드는 **local verdict / UI 에만** 기록(계약 §4-4-a). **payload 에 넣으려고 조립하면 계약 위반.**

### A7. ★ 기존 활성 write 경로 구조적 차단 (수용기준)
`origin/main` 에 **살아있는** extension 서버 POST(`background.js` 의 `fetch(\`${base}/api/insuro/ohmy-capture\`, POST)`)와 레거시 캡처 버튼을 **제거**한다.
**증명 3중 (전부 0)**:
1. **정적**: 서버 POST **함수 정의 0** · 해당 경로로의 **호출 도달경로 0**
2. **동적**: 실행 중 **`insuro.biz` write 요청 0**(행동 테스트로 관측)
3. **뮤테이션**: 차단을 의도적으로 되돌리면 **테스트가 실패**해야 함
※ **방식만 참조**(A-0 브랜치에서 검증된 접근) — **코드 복사 금지**.

## 검증
- 단위+행동 테스트: A2~A5 각 거부 분기 · 토큰 미유출(전 경로) · 클릭 전 A-0 네트워크 0 · A7 3중 증명
- **뮤테이션 검증 필수**(결함 재주입 시 실패 확인)
- **ANU 가 `npx vitest run` 전체 독립 재실행**. 기존 테스트 감소 금지.

## goal_assertions
- `npx vitest run`

## STOP_REPORT 조건
계약 문서 SHA-256 불일치 · A4 차단이 구조적으로 불가 · 서버 전송 없이 미리보기 불가 · 진단브랜치 코드가 필요 · manifest 권한 확대 필요 · 공통설정 파일 수정 필요

## 금지
서버 전송 코드 추가 · 토큰 저장/캐시/로깅 · 자동 실행 · MAIN world 인터셉터 · 진단브랜치 이식(수동 복사 포함) · Track B 영역 수정 · 공통설정 수정 · **PR·merge·배포·운영활성화**(전부 회장 승인)

## 산출물 & 보고
1. 브랜치 커밋. **PR 생성도 회장 승인 후.**
2. 보고서는 **`/home/jay/workspace/memory/reports/<ALLOCATED_TASK_ID>-trackA.md`** 에 쓴다(저장소 커밋 대상 아님). 내용: diff / A1~A7 각 근거 / **A7 3중 증명 결과** / 뮤테이션 결과 / vitest 카운트 / MATCH·GAP·UNKNOWN
3. 봇 종료 전 ANU callback(envelope만·UTF-8 ≤3900B, collector=ANU key c119085addb0f8b7)