# TRACK_B (Track B) — 서버·CRM 기반 (mock/fixture 전용, 실고객 write 금지)

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

## ★ ID 규약 (숫자 task ID 하드코딩 금지)
- **logical ID = `TRACK_B`** — 본 문서의 영구 식별자.
- **`<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)
- `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:
    - "server/**"
    - "supabase/migrations/**"
    - "server/tests/**"
  forbidden_paths:
    - "extension/**"       # Track A 영역 — 중첩 0
    - "src/**"
    - "package.json"       # 공통설정 — 양 track 수정 금지
    - "vitest.config.ts"
    - "tsconfig.json"
    - "docs/contracts/**"  # 계약 = 읽기 전용
    - ".github/**"
    - ".env*"
  new_file_limit: 8
  commands: ["git","python3","pytest","alembic"]
  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>-trackB.md
```
**타 track 중첩**: Track A(`extension/`)와 **0**.
**migration 은 serial_only 자원** — 다른 진행 task 와 동시 생성 금지(착수 시 재확인).

## ★ 이번 Track 의 절대 제약
- **실고객 데이터 write 0.** fixture/mock payload 만 사용.
- **production endpoint 활성화 금지.**
- Track A 없이 **독립 검증 성립**해야 함(mock payload 로 전 경로 테스트).

## ★ 이번 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 발사


## 구현 요구

### B1. ingest API — 계약 §2 envelope 수신
- **★ 신규 V1 ingest endpoint 1개를 만든다**(계약 §2-4-4). integration 이 여는 경로는 **이것 하나뿐**.
- **legacy write 4개는 통합·파일럿·운영 활성화 이후에도 계속 `FEATURE_DISABLED` 유지** — 재활성화는 별도 회장 승인 사안.
`docs/contracts/INSURO_LV4_PROGRAM_CONTRACTS_V1.md` §2-1 payload 를 그대로 수신 스키마로.
- **`external_subject_key` 를 클라이언트가 보내면 거부**(§2-1-a). **★ V1 은 서버도 이 키를 생성·저장하지 않는다**(§2-1-a-0 — 범위 밖).
- **금지 필드 수신 시 거부**: ohmymanager JWT 원문·`consultantid` 원문·API 전체 원본 응답·불필요 PII.
- **★ 멱등성 (회장 정정 2026-07-19 — 이전 초안 오류)**: `(fa_account_id, request_fingerprint, dom_selection_hash)` **단독으로 중복 판단 금지.** 조회조건이 같은 **서로 다른 고객·상담이 충돌**한다.
  - **★ DB unique scope (tenant 포함 — 이전 초안 누락)**:
    ```
    (fa_account_id, operation_scope, idempotency_key)
    ```
    - `fa_account_id` = **서버 인증세션 파생**. 빠지면 다른 FA 의 저장이 내 저장을 막는다.
    - `operation_scope` = 저장 연산 종류. **V1 값 = `"CONSULTATION_HISTORY_SAVE"` 단독.**
    - **★ `reference_type`·`reference_id`·`contract_version` 은 unique scope 에 넣지 않는다** — **body hash 안에서 검증**한다.
      → **같은 `idempotency_key` 로 이 값들이 바뀌면 반드시 `IDEMPOTENCY_CONFLICT`**(전용 테스트 필수).
      > unique key 에 넣으면 "같은 키인데 대상이 바뀐 요청"이 **별건으로 통과**한다 = 조용한 대상 변경.
  - **양방향 증명 필수**: (a) 같은 `idempotency_key` 재시도 → 중복 레코드 **0** (b) **다른 고객·다른 상담인데 조회조건이 동일** → **별건으로 정상 저장**(충돌 0)
  - fingerprint·hash 는 **동일 조회 검증용**으로만 유지.
- **★ 멱등 동작 규정** (계약 §2-3-a — 이전엔 "중복 차단"만 있고 동작 미정의):

  | 입력 | 서버 동작 |
  |---|---|
  | 동일 `idempotency_key` + **동일 payload bytes**(body SHA-256 일치) | **기존 결과 반환**. 추가 저장 **0** |
  | 동일 `idempotency_key` + **다른 payload bytes**(SHA-256 불일치) | **`IDEMPOTENCY_CONFLICT`**(409). 저장 **0** |
  | 새 `idempotency_key` | 정상 신규 저장 |

  - **★ 비교 규약 4항목 (계약 §2-3-b — 회장 지시 6번)**:
    ① **비교 대상 = 요청 body 전체(raw bytes)** — 개별 필드 목록 두지 않음
    ② **정규화 안 함** — 배열순서·숫자표기·null 전송된 그대로. **서버 재직렬화 금지**
    ③ **SHA-256(raw body bytes)** 를 idempotency record 와 함께 보관
    ④ **재조립 금지** — 클릭 시점 immutable bytes 그대로 재전송
  - **의도된 실패 모드**: 재직렬화로 bytes 가 달라지면 `IDEMPOTENCY_CONFLICT`. **버그 아님 — fail-closed 설계.**
  - 이 규정이 없으면 재시도가 **조용한 덮어쓰기**가 되어 상담 이력이 훼손된다.
- **★ payload 필수 필드**(계약 §2-1): **`reference_type`+`reference_id`**(discriminated 단일 쌍) · `contract_version` · `consent{granted_at,consent_version,scope,source}` · `idempotency_key`(integration 이 생성해 전송).
  - **`reference_type`/`reference_id` 누락·두 종류 동시·enum 위반 → `REFERENCE_SHAPE_INVALID`/400.**
  - **★ V1 허용값 = `"CUSTOMER"` 단독.** `"CONSULTATION"` 수신 시에도 **`REFERENCE_SHAPE_INVALID`/400**(실체 부재).
  - **`fa_account_id` 수신 시 → `CLIENT_AUTHORITY_FIELD_FORBIDDEN`/400·저장 0** (예외 없음).
  - **`external_subject_key` 는 V1 범위 밖**(§2-1-a-0) — 수신 시 거부.

### B2. ★ 신원·tenant (계약 §3 — CRM 안전 1순위)
- 권한 authoritative = **우리 InsuRo 로그인 FA 계정(`fa_account_id`)**.
- **★ `fa_account_id` 는 클라이언트 payload 에서 받지 않는다**(계약 §2-1-b). **인증 세션에서만 파생.**
  - **★ V1 예외 없음**: 수신 시 **무조건 거부**(`CLIENT_AUTHORITY_FIELD_FORBIDDEN`/400·저장 0). legacy 호환은 **별도 adapter/endpoint 로 격리** — V1 ingest 안에서 예외 열기 금지.
- **★ `external_subject_key` 는 V1 범위 밖**(§2-1-a-0): 서버가 외부 주체를 검증할 입력이 계약상 존재하지 않는다. **tenant authority = `fa_account_id` 단독.** **미검증 값으로 HMAC 생성 금지.** 외부 매핑은 별도 `EXTERNAL_SUBJECT_BINDING` task.
- **★ reference tenant 검증**(계약 §2-1-c): **`(reference_type, reference_id)` 조합**이 **인증된 `fa_account_id` tenant 소속인지 서버가 검증**. 불일치 → **`TENANT_REFERENCE_MISMATCH`** · **저장 0 · 조회 0**.
  - 없으면 FA 가 **타 FA 의 상담·고객에 레코드를 붙일 수 있다.**
- **클라이언트가 decode 한 `consultantid` 로 tenant·저장권한 결정 절대 금지.**
- ~~`external_subject_key` = 서버 HMAC~~ → **V1 범위 밖**(§2-1-a-0). **이번 Track 에서 생성·저장·매핑 구현 금지.** `EXTERNAL_SUBJECT_BINDING` task 로 이관.
- **★ 키 구분**(계약 §2-1-d): `local_owner_observation_key` 는 **클라이언트 메모리 전용·비신뢰·전송 0** — **서버는 수신·저장하지 않는다.** 클라이언트가 이 값이나 `external_subject_key` 중 **어느 것이든 보내면 거부.** **미검증 값으로 HMAC 생성 절대 금지.**
- ~~`fa_account_id` ↔ `external_subject_key` 매핑~~ → **V1 범위 밖**. V1 의 tenant 차단은 **`TENANT_REFERENCE_MISMATCH`**(reference 소속 검증)로 성립한다.

#### ★ B2 결정항목 — 실사 후 확정 (Codex 지정)
| # | 결정할 것 | 비고 |
|---|---|---|
| 1 | **live-write 차단 메커니즘** — 서버측 default-OFF 를 **정확히 어떤 방식**으로(feature flag / env / 라우터 분기 / 미들웨어) | 저장소 관례상 **default-disabled·fail-closed** 가 실효 안전경계 |
| 2 | **"live write" 로 간주할 경로 범위** — 어떤 endpoint 까지 default fail-closed 대상인가 | B4 write 4개가 최소. 재실사로 확정 |
| 3 | **★ 기배포 구버전 확장 가정** — 이미 설치된 구버전이 계속 write 를 시도하는가. **확인 불가면 STOP** | 이게 **B 를 먼저 merge** 하는 근거. 실사 필수 |
| 4 | **차단 성립 증거** — 무엇을 보여야 "막혔다"고 인정할 것인가(diff 리뷰 + 재실행 증거 포함) | 자동 CI 아님 — **ANU 체크포인트 검증** |

**read-path tenant 강제 쿼리 제약**은 실사 후 확정. (외부주체 매핑 저장소·유일성·생성시점은 **V1 범위 밖** — `EXTERNAL_SUBJECT_BINDING` task.)

### ★ B0. 착수 전 저장소 실사 선행조건 3건 (계약 §2-4 — Codex holistic 발견)
**확정 못 하면 STOP_REPORT. 추측 구현 금지.**

| # | 실사할 것 | 확정 기준 |
|---|---|---|
| 1 | **`customers` 기준 tenant 소속 검증 가능 여부** — `(reference_type="CUSTOMER", reference_id)` 가 인증 `fa_account_id` 소속인지 확인할 수 있는가 | 가능함을 근거와 함께 보고. **불가하면 STOP_REPORT**. ※ **`reference_type` 은 계약 §2-1-c 가 `CUSTOMER` 단독으로 확정** — CONSULTATION 도입 여부는 **B 의 결정사항 아님**(재논의·구현 금지) |
| 2 | **새 envelope 저장 모델 부재** — `OhmyCaptureRequest`/`ohmy_capture_history` 는 `matrix`·`capture_hash`·`customer_key_hash` 중심이라 `reference_type/id`·`idempotency_key`·`body_sha256`·`query_condition`·`selected_coverages`·`per_insurer`·`validation`·`consent` **수용 불가** | **기존 모델 재사용 불가** 전제. 신규 모델·migration 설계 보고 |
| 3 | **`FEATURE_DISABLED` 의 "deserialize 0"** — FastAPI 는 핸들러 진입 전 body 파싱 | **★ 방식 고정**: ① **ASGI/HTTP middleware 에서 body 처리 전 403 반환** 또는 ② **body model 없는 disabled stub route 등록 → 즉시 403** 중 택1. **~~route 미등록~~ 은 삭제**(404 가 나와 `FEATURE_DISABLED`/403 계약 위반). `router dependency` 는 **Pydantic model/validator 호출 0 행동 증명 시에만** 허용 |

### B3. 상담 히스토리 데이터 모델
조회조건 · 선택담보+가입금액 · 보험사별 결과 · `validation.overall_status`+`failure_codes` · **동의 시각** · **감사 로그** · 삭제·보존 정책.
- **★ `REQUIRES_REVIEW`**: 정상 상담 히스토리에 **저장 금지** → **별도 quarantine/review 상태**로 격리. 추가로 **고객용 UI 노출 건수 0** · **조합 계산 입력 포함 건수 0**(= 조합 계산에서 항상 제외) 을 쿼리·모델 레벨에서 강제(표시만 다르게 하는 것으로 불충분).
- **★ `BLOCKED`**: **전송 0 · 저장 0**. 실패 코드는 **클라이언트 local verdict/UI 에만** 남고 **서버 레코드 0**(계약 §4-4-a). 서버는 `BLOCKED` payload 를 받을 일이 없다.
- **★ 파일럿 롤백 식별 필드 2종 (계약 §2-4-0 — 책임 = B)**:
  | 필드 | 값·규칙 |
  |---|---|
  | **`rollout_stage`** | **`PILOT` \| `PRODUCTION`** (2값). **서버가 저장 시점 기록**. **NULL 저장 거부.** 인덱스 필수 |
  | **`rollout_batch_id`** | **서버 생성**. **파일럿 회차별로 다른 값** → 특정 배치만 선별 삭제 가능. 인덱스 필수 |
  - `rollout_stage` 만으로는 **여러 차례 파일럿 중 "이번 회차분만" 지울 수 없다.** `rollout_batch_id` 가 그 단위를 만든다.
  - 두 필드 없으면 **integration gate 통과 불가**(G-3).

### B4. ★ 기존 활성 endpoint 차단 (수용기준 — 재실사 필수)
착수 시 아래를 **재실사**(계약 부록 재현 명령 실행). 결과가 다르면 STOP_REPORT.
```bash
git show origin/main:server/main.py | grep -nE '^@app\.(post|get|put)\(.*(ohmy-capture|composite-design/ingest)'
```
**write 계열 4개** — `7225 /composite-design/ingest` · `7403 /ohmy-capture` · `7727 /ohmy-capture/{id}/link` · `7858 /ohmy-capture-history/{id}/reassign`

#### ★ 보장 대상 정정 (회장 2026-07-19) — "요청 0" 이 아니다
**기배포 구버전 확장은 회수 불가 → 요청 *시도* 는 계속 올 수 있다. 요청 도달을 0 으로 만들 수단은 없다.**
따라서 증명할 것은 아래 **3가지**다:
1. **accepted write 0** — 2xx 성공 응답 0. `FEATURE_DISABLED` 로 **fail-closed** 거부
2. **DB write 0** — 어떤 테이블에도 레코드·부분행 생성 0
3. **queue / payload log 0** — 큐·재시도 버퍼·임시저장·**원문 로깅** 어디에도 payload 잔존 0

⚠️ **"payload 를 읽어 검증한 뒤 거부"는 불충분.** 거부 경로에서 payload 가 **로그·에러객체·큐**에 남으면 **위반**이다.

#### ★ default-disable 정밀 정의 (계약 — 회장 지시 2026-07-19)
feature OFF 시 요청 body 에 대해 **아래를 전부 하지 않고 즉시 `FEATURE_DISABLED`(HTTP **403** 고정) 반환**:
- **애플리케이션 모델로 deserialize 하지 않음**(Pydantic/dataclass 파싱 자체 안 함)
- **비즈니스 검증 실행 안 함**
- **DB · queue · cache · temp file 기록 0**
- **APM · error log 에 payload 원문 기록 0**

**`403` 고정 근거**: `503` 은 "재시도하라"는 의미라 **기배포 구버전 확장의 backoff 재시도 폭주**를 부른다. `404` 는 경로 존재를 흐려 디버깅을 방해한다. **`403` 하나만 쓴다**(분기 금지).

#### ★ 전송계층 버퍼 vs 애플리케이션 저장 (구현 불가능한 절대문구 회피)
ASGI/웹서버가 요청 수신 과정에서 갖는 **전송계층 일시 버퍼**는 프레임워크 레벨에서 불가피하다 — 이를 0 으로 만들라는 요구는 **구현 불가능**이다.
→ 요구사항은 **"애플리케이션 계층 저장·기록 0"** 이며, **증명 대상**은 **payload 원문이 우리가 관측·보존하는 시스템(DB·queue·cache·temp·로그·APM)에 남지 않음** 이다.
→ **증명**: 거부 요청 후 **DB 레코드 0 · 큐 항목 0 · 로그/에러 출력에 payload 문자열 미포함** assert.

**read 계열 2개** — `7612 /ohmy-capture-history` · `7667 /ohmy-capture-history/{id}`
→ **`fa_account_id` tenant 경계 강제**(설계사 간 **교차조회 0**) 증명

**증명 3중**: 정적(경로/플래그) + 동적(요청 시 거부·DB write 0 관측) + **뮤테이션**(차단 되돌리면 테스트 실패)

### B5. 실패 enum (계약 §4)
A 와 **동일 문자열** 사용. 서버가 임의 에러명 생성 금지. `OK`/`REQUIRES_REVIEW`/`BLOCKED` 저장 게이트 구현.
- **★ V1 미사용 enum**: `TENANT_MAPPING_MISSING` · `EXTERNAL_SUBJECT_MISSING` 은 **구현·검증 대상 아님**(external subject 매핑이 V1 범위 밖이라 발생 경로 없음). **V1 구현체가 반환하면 계약 위반.**
- **★ 신규 5종 구현 필수**(계약 §4-3-a): **`FEATURE_DISABLED`**(403) · **`IDEMPOTENCY_CONFLICT`**(409) · **`TENANT_REFERENCE_MISMATCH`**(403) · **`CLIENT_AUTHORITY_FIELD_FORBIDDEN`**(400) · **`REFERENCE_SHAPE_INVALID`**(400)
- HTTP status 는 계약이 고정한 값만 사용. **구현체가 임의로 분기 금지.**

## 검증
- pytest: B1 거부 분기(금지필드·external_subject_key·`fa_account_id` 클라이언트 전송) · B2 tenant 경계·**`TENANT_REFERENCE_MISMATCH`**(★ external_subject_key 매핑 테스트는 **V1 범위 밖** — 요구하지 않음) · B3 상태별 저장 게이트 · **B4 3중 증명**
- **★ 멱등 4분기 전수**: 동일키+동일 bytes(기존 반환·저장0) / 동일키+다른 bytes(`IDEMPOTENCY_CONFLICT`·저장0) / **동일키인데 `reference_id`·`reference_type`·`contract_version` 변경(→ 반드시 `IDEMPOTENCY_CONFLICT`)** / 새키(정상저장)
- **★ FEATURE_DISABLED 방식 증명**: ① **middleware(body 처리 전 403)** 또는 ② **body model 없는 disabled stub route(즉시 403)** 중 채택한 쪽. **~~route 미등록~~ 은 후보에서 삭제됨**(404 → 403 계약 위반). dependency 방식이면 **Pydantic model/validator 호출 0** 행동 테스트 필수
- **★ rollout 필드**: `rollout_stage` NULL 저장거부 · `rollout_batch_id` 배치별 상이 · 두 필드 인덱스 존재
- **★ FEATURE_DISABLED 경로**: 403 반환 · deserialize 미실행 · DB/queue/cache/temp 0 · **로그·에러출력에 payload 문자열 미포함** assert
- **뮤테이션 검증 필수**
- **mock payload 만으로 전 경로 통과**(Track A 불필요) 증명
- **ANU 가 서버 테스트 독립 재실행**

## goal_assertions
- `python3 -m pytest server/tests -q`

## STOP_REPORT 조건
계약 SHA-256 불일치 · 계약 §2 envelope 으로 상담이력 표현 불가 · tenant 분리가 스키마상 불가 · 실고객 데이터 필요 · migration 이 타 task 와 충돌 · Track A 산출물 없이는 검증 불가

## 금지
실고객 데이터 write · production endpoint 활성화 · 클라이언트 `external_subject_key` 신뢰 · `consultantid` 로 tenant 결정 · 원문(JWT·consultantid·API 원본) 저장 · Track A 영역 수정 · 공통설정 수정 · **PR·merge·배포**(전부 회장 승인)

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