# [fix] 계약 정합 후속 — 실제 동의 이벤트 캡처(granted_at 진위) + canary 새 shape + 골든 provenance

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "extension/content.js"
    - "extension/background/ingest.js"
    - "extension/__tests__/**"
    - "tests/contract/**"
  forbidden_paths:
    - "src/**"
    - "server/**"
    - ".github/**"
    - "extension/manifest.json"
    - "extension/background/crm.js"
  commands: ["npx vitest","python","git"]
  merge_policy: "none"
  ttl_hours: 8
```
> ★ merge_policy=none. 브랜치 **`task/task-2849-dev2`**(계약 정합 PR)에 이어서 커밋(새 PR 금지). 서버 불변.

## 배경 (Codex 계약검토 3 미결)
계약 정합(flat OHMY_PPD_V2)은 파싱 성공(골든 통과)이나 Codex 검토가 3건 지적:
1. 🔴 **consent 조작**: content.js가 `consent:true`(bool)만 보내고, ingest.js가 bool→consent 객체를 합성. `granted_at`=**조립 시각**(실제 동의 클릭 시각 아님), version/scope/source 하드코딩. → "실제 동의 없이 ingest 없음" 감사 근거 약함.
2. 🟡 **canary sink-spy 테스트가 옛 shape**(reference_type:"unmatched", 옛 analysis_matrix) → 새 flat 계약 미검증.
3. 🟡 **골든 fixture provenance 불명** — assembleRequestBody 산출 증거 없음(수기 의심, 명세는 실산출 요구).

## 작업
1. **실제 동의 이벤트 캡처(content.js)**: FA 가 동의/저장 승인하는 **바로 그 시점**에 `granted_at` = 실제 클릭 시각(ISO8601 tz-aware, 예 `new Date().toISOString()` 를 **동의 핸들러 안에서** 1회) 를 기록하고, consent 객체 `{granted_at, consent_version, scope, source}` 를 **content.js 에서 구성**해 storage.session blob 에 동결(조립 bytes 와 함께). ingest.js 는 bool→객체 합성 **금지** — content.js 가 준 consent 객체를 그대로 싣는다. (조립/재시도가 나중이어도 granted_at 은 동의 시점 값으로 고정.)
   - consent_version/scope/source 는 **실제 동의 UI 를 정직히 기술하는 상수** 허용(예 version="v1", scope="consultation_history_ingest", source="extension_popup") — 단 granted_at 은 반드시 실 클릭 시각.
   - consent 게이트(동의 없으면 ingest 0) 규율 유지: consent 객체 부재/granted_at 부재 시 전송 거부(fail-closed).
2. **canary sink-spy 테스트 새 shape 로 갱신**: `reference_type:"CUSTOMER"` + 새 flat + consent 객체 + 새 analysis_matrix 형태로 대표입력을 바꿔, **새 계약 bytes 로 PII/JWT sink 0** 을 검증(옛 unmatched/옛 matrix 제거). taint-guard 가 새 shape 에서도 canary 0 임을 실증.
3. **골든 fixture provenance**: `tests/contract/ohmy_ppd_v2.golden.json` 이 **assembleRequestBody 실산출**임을 보증 — 생성 스크립트(예 `tests/contract/generate_golden.mjs` 또는 vitest 내 생성-비교) 추가 + 파일 상단 주석/메타로 provenance 명시. 수기 작성 아님을 CI 가 강제(생성값 == 커밋된 골든 assert).

## 검증
- `assembleRequestBody` 산출 consent.granted_at == 동의 핸들러가 기록한 실 클릭 시각(조립 시각 아님) 단위테스트.
- 동의 없이(또는 granted_at 없이) → 전송 0(fail-closed) 회귀 유지.
- canary sink-spy(새 shape): PII/JWT 비허용 sink 0.
- 골든 provenance: 생성 스크립트 산출 == 커밋 골든(드리프트 시 실패).
- 확장 전체 vitest 회귀 0(387 기준). 서버 골든테스트 여전히 PASS(계약 유지).

## 완료 (★ 순서 B)
- 변경 = allowed_resources 내부만. 서버/manifest/crm.js 불변.
- **dev6 금지** — dev2(브랜치 소유). 커밋 → push → finish-task **foreground 1회** → .done 즉시 종료. **background+wait 금지.** ANU 원격 검증·머지.