# [Lv.4] Integration Phase2 PR-D1 — 확장 명시적 동의 저장 수직 슬라이스 (V ingest, flag OFF)

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "extension/**"
  forbidden_paths:
    - "src/**"
    - "server/**"
    - ".github/**"
    - "docs/**"
  commands:
    - "npx vitest"
    - "git"
  merge_policy: "none"
  ttl_hours: 24
```
> ★ merge_policy=none — PR OPEN 유지(자동머지 금지). 머지 판정=아누 Codex+독립검증.

## 배경·단일소스 (반드시 읽어라)
- 합의 미팅록: `/home/jay/workspace/memory/meetings/2026-07-22-insuro-extension-prD-design.md` (Codex 포함 5자 CONSENSUS YES).
- 설계: `/home/jay/workspace/memory/plans/insuro-composite-design/integration-design-260721.md`.
- 리포 `/home/jay/projects/InsuRo`, base=현 origin/main. Phase2 서버(PR-D0: V GET·CORS) 반영됨. 서버 V ingest = `POST /api/insuro/consultation-history/v1`(멱등·per-FA·금지필드·feature-flag).
- **아키텍처 X**: 확장이 캡처→동의→조립→전송. 웹앱은 나중 조회(PR-E). **확장=dumb carrier(freeze만), 매칭·dedup·canonical 진실은 서버**(오딘 원칙).
- **이 task = D1(수직 슬라이스): 기존 Track A preview 위 integration 레이어.** near-match UI·계정전환잠금·재시도 풍부화·A7 뮤테이션 재범위화는 **D2**(별도).

## 작업 (합의 8항 — 이 PR 범위)
1. **명시적 동의 저장 액션**: Track A `previewState===VALID` 이후 **2차 명시 버튼(동의+저장)**. 캡처직후 자동전송 0. content.js 는 preview 확정까지만, **전송·조립은 background 신규 모듈**(예: `background/ingest.js`) — MV3 storage.session access=TRUSTED_CONTEXTS 라 background 필수.
2. **request_body_bytes 1회 조립**(background): ImmutablePreviewSnapshotV1 + reference_id(이 PR 에선 서버 후보검색 미연동이면 최소 형태로; 실제 매칭 연동은 D2) + consent + idempotency_key + **analysis_matrix**(계약V2). **base64+sha256 로 굳혀** `chrome.storage.session` 저장. idempotency_key 는 조립 시 1회 생성·bytes 와 함께 보존.
3. **V ingest POST**(background, **flag OFF 유지** — 프로덕션 노출 0, 테스트에서만 enable). 전송은 **단일 egress choke point 함수** 경유(consent 게이트 그 함수 안). URL allowlist 로 ingest 엔드포인트만.
4. **재시도 = 명시 버튼 + 같은 key+frozen bytes 동일 POST 재전송**(재직렬화 0). 서버가 원결과 반환(기존 멱등). **상태조회 엔드포인트 신설 금지.** 조용한/자동 재시도 0(옛 auto-queue 삭제 유지).
5. **저장소/수명**: PII bytes+idempotency_key = storage.session (**JWT 는 storage.local 유지·이동 금지**, ohmy 토큰 무저장 유지). 폐기 = **terminal durable ack(2xx) 또는 owner-change/만료 시**(첫 응답 즉시폐기 아님, 재시도 계약 §2-3-b/c). `chrome.alarms`+read-time lazy expiry(setTimeout 금지).
6. **만료/INVALIDATED**: operation 종료 → 폐기. 재저장=**명시적 재-preview·재-동의(새 operation·새 key)**. 투명 re-fetch·diff 없음.
7. **★ taint-guard(load-bearing) = 수명최소화 + allowlist-직렬화 scrubber + sink-spy canary + single egress choke + URL allowlist**. opaque 래퍼는 **fail-loud tripwire**(toJSON/String/valueOf → throw). "leak-proof 완전성" 주장·테스트 금지.

## 검증 (전부 통과 — canary sink-spy 필수)
- **canary sink-spy**: PII·**JWT** 마커를 필드에 주입 후 전 플로우(조립→전송→재시도→만료) 구동, 아래 sink 인자 스캔 = **0**(단, 허용된 단일 ingest body + 단일 storage.session base64 blob 만 예외): `console.*` / `fetch`·XHR(비허용 호스트) / localStorage·sessionStorage·IndexedDB·chrome.storage.local / URL·history·tabs.update / window.onerror·unhandledrejection·telemetry / runtime.sendMessage·**postMessage(main-world 경계)** / dataLayer / structuredClone.
- **tripwire**: PII 래퍼 `JSON.stringify`/`String()` → throw.
- **consent-gate**: consent 부재/false → 전송 `fetch` 호출 0 + fail-closed. consent=true → 정확히 1회·허용 endpoint·consent 포함. 뮤테이션: consent 제거 → 전송 실패.
- **byte 보존**: 조립 bytes → base64 저장 → 디코드 → 원본 바이트 동일(sha256 일치). 재시도 = 동일 bytes.
- **수명**: terminal ack 후 storage.session PII 부재, 계정전환 시 전송차단→삭제 순서, alarms+lazy expiry 폐기, 재시작 자동소멸. JWT 는 여전히 local(회귀 0).
- **single egress**: ingest 호스트로 fetch 호출부 유일성(정적 검사).
- **Track A 회귀 0**: 기존 track-a-* 테스트 green. **legacy 자동전송 차단(a7-write-path) 유지**(이 PR 은 V-ingest 만 추가 — "V 밖 write 0" 여전히 참, A7 뮤테이션 재범위화는 D2).
- `npx vitest run extension/__tests__/` 기존 통과 + 신규.

## 제약
- extension/ 밖 변경 0. **JWT storage 이동 금지**(local 유지). 서버·웹앱 미변경(PR-D2/E).
- flag(INSURO_CONSULTATION_HISTORY_V1_ENABLED) 켜지 마라(테스트만 enable).
- 확장은 dumb carrier: matrix 구조/검증·매칭결정·dedup canonical 은 서버 몫(여기선 freeze·전송만).

## 완료
- extension/ 외 변경 0 (`git diff --stat`)
- **dev6 금지** — dev1. worktree finish(STRICT+정확base+worktree cwd) → PR(**OPEN 유지·머지 금지** merge_policy none) → ANU 독립검증.
- **ANU callback**(UTF-8 ≤3900 bytes, envelope). scope-guard 막힘 시 result.json+ANU callback.

## goal_assertions (auto-generated)
- `npx vitest run extension/__tests__/`
