---
task_id: task-2981
project: insuro-composite-workbench-phase1
team: dev2 (오딘)
date: 2026-08-20
status: completed
scope: task
project_path: /home/jay/projects/InsuRo-worktrees/task-2981-dev2
pr: 233
merge_state: HOLD
---

# task-2981 — 인슈로 복합설계 워크벤치 Phase 1 (입력·파싱 계층)

## S — 상황

복합설계 워크벤치(설계사 전용)를 짓기 위해 보장분석·신규설계 **양측 입력을 (문서+약관) 세트로 통일**하고, 양측 담보에 `clause_id`/`axes`를 부착해 자동 매칭을 성립시키는 것이 Phase 1 목표다. repo `/home/jay/projects/InsuRo`, base `origin/main 49c17a92`. 회장 2026-08-20 (a)안 확정 사항.

## C — 문제

보장분석은 증권 PDF만 받고 `CoverageRow`에 `clause_id`/`axes`가 없어, 비교 시 기존측 담보가 **구조적으로 CONFIRMED에 도달하지 못한다**. 담보명 매칭은 T3 최상위 금지 불변식이라 우회 불가.

## Q — 질문

게이트 봉인(t2970/2974/2976/2977/2979)을 침해하지 않고, name-match 없이 기존측 담보에 axes를 부착할 수 있는가?

## A — 답

**부착 배관은 완성했다. 단, 실제 발현에는 두 가지 선결 조건이 남았고 둘 다 정직하게 UNKNOWN으로 떨어지도록 만들었다.**

핵심 발견: 회장 (a)안 결정 시점에 드러나지 않았던 갭이 있었다 — **증권 담보를 약관 조항에 이름 없이 이을 구조적 키가 코드에 존재하지 않는다**. 증권측 `CoverageRecord`에는 코드 식별자 필드가 아예 없었고(`name`/`payout_condition`만), 약관측 `ClauseRecord`에만 `cov_id`/`code`가 있었다. 그래서 **명시적 결합 계층을 신설하고, 못 이은 건은 추측 없이 `unresolved`로 남기는** 설계를 택했다.

---

## Phase 1a — 구조 선행 파악 (실측)

| 항목 | 실측 | 근거 |
|---|---|---|
| analyze-policy 엔드포인트 | `server/main.py:7021` · multipart, `files` 다중, 수동 `request.form()` | 코드 |
| 약관 배선 | **0건** (terms_sha256/clause/lookup_group 호출 전무) | grep |
| CoverageRow | `server/policy_normalizer.py:255-266` · 7필드 · **clause_id/axes 부재 확정** | 코드 |
| 결함 자백 docstring | `server/new_design_comparison/engine.py:9-20` | verbatim |
| grouping 재사용 인터페이스 | `server/policy_extract/grouping_bridge.py:133` `lookup_group(store, coverage, *, clause_id, axes, clause_text, read_only)` — **이미 수신 준비 완료** | 코드 |
| 약관 파이프라인 | `split_clauses()` → ClauseRecord → 정독 → CoverageAxes → mapper/hitl | `policy_grouping/__init__.py` |
| 약관 업로드(t2975) | `server/routes/policy_terms_v1.py:90` · sha256(raw bytes) 캐시키 | 코드 |
| 약관 업로드 프론트 UI | **존재하지 않음**(t2975 백엔드 전용) | grep 0건 |
| 가입제안서 파서 | **존재하지 않음** | grep 0건 |
| 제안서·증권 샘플 PDF | **디스크 전역 0건**(약관 2건만) | find |

---

## 수행 내용

### 1b — 세트 업로드 배선
`analyze-policy`가 `terms_files`(선택)를 수신. **LLM 약관 재정독 없음** — `terms_sha256`으로 이미 적재된 약관에 **조인만** 한다. 미적재 시 `binding_reason="terms_not_ingested"`로 정직하게 남긴다.

`clause_text`는 DB `excerpt`(2000자 절단)가 아니라 **업로드 원본 PDF**를 `split_clauses`로 재분할해 조달했다 — `comparison_gate`의 생산자 ≠ 검증자 원칙상 `content_hash` 재계산에 원문이 필요하기 때문.

★ `terms_files` 미첨부 시 분기 자체가 실행되지 않아 **기존 응답과 동일**(라이브 회귀 방어).

### 1c — 가입제안서 파싱 **보류**
제안서 파서·샘플 PDF 모두 리포에 0건. 실제 표기 형태 없이 추출 로직을 넣으면 검증 불가하므로 착수하지 않았다. **회장님 샘플 제공 필요.**

### 1d — 보장분석 확장
신규 `server/policy_extract/terms_binding.py`. 허용 규칙은 **코드 완전일치 하나뿐**(NFKC/공백/대소문자만 관용, 부분일치·유사도 전면 금지). 담보명은 판정에 **한 줄도** 쓰지 않으며 grep 가드 테스트로 강제. `product_id` 키워드 필수(미제공 시 전건 fail-closed).

결합 성공건만 `lookup_group(..., read_only=True)` 호출. **`map_state`는 오직 `lookup_group` 반환값에서만** 나온다(직접 생성·위조 없음).

스키마 additive: `CoverageRecord.code`, `CoverageRow`/`CoverageRowIn`에 `clause_id`/`axes`/`map_state`/`binding_method`/`binding_reason` — 전부 맨 끝 + 기본값.

### 1e — UI 세트 업로드
보장분석 화면에 약관 세트 입력 additive(`terms_files`), 약관 업로드 화면 `src/pages/PolicyTermsUpload.tsx` 신설 + 라우트/내비 등록. 프론트는 순수 소비자.

---

## ★ 팀장 적대적 검증 — 봉인 구멍 2건 실측 발견 및 수정

초기 구현은 신규 테스트 15건이 **전부 PASS**였으나, 팀장이 **형식이 올바른 위조 입력**으로 두 건을 실제로 뚫었다.

**구멍① 동일 `cov_id` 중복 조항 → 조용한 임의 선택**
모호성을 `cov_id` 집합 크기로 판정해 걸리지 않았고, 원문이 전혀 다른 두 조항 중 첫 번째를 말없이 채택했다(`clause_text='삼성 원문 텍스트 AAA'`).
→ 조항 실체(`cov_id`, `content_hash`) 기준 판정으로 수정.

**구멍② product scope 부재 → 타사 오연결**
삼성 증권 담보(`A100`)가 **메리츠 약관 조항(`MZ-9`)에 결합**됐다. `seed_binding.py`가 `is_seed_product()`로 명시적으로 막는 바로 그 오연결.
→ `product_id` 키워드 필수화 + `CoverageBinding.product_id` 감사 필드로 수정.

**수정 후 재현 실측**: 구멍① → `unresolved` / `code_ambiguous_multiple_clauses`, 구멍② → `unresolved` / `missing_product_scope`, 정상 케이스(단일 상품 스코프 + 코드 완전일치) → 계속 결합. 이름 동일·코드 상이 → `unresolved` 유지.

이는 과거 교훈(fail-closed 테스트를 malformed 입력만으로 짜면 **형식이 올바른 위조**를 놓친다)의 정확한 재현이다.

---

## 수정 파일별 검증 상태

| 파일 | 변경 | 검증 상태 |
|---|---|---|
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/policy_extract/terms_binding.py | 신규 결합 계층 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/policy_extract/models.py | CoverageRecord.code additive | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/policy_normalizer.py | CoverageRow additive 5필드 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/new_design_comparison/models.py | CoverageRowIn 미러 additive | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/policy_analysis_table.py | 약관 조인·바인딩 배선 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/main.py | terms_files 수신 배선 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/tests/test_coverage_terms_binding_task2981.py | 결합 봉인 테스트 24건 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/server/tests/test_analyze_policy_terms_set_task2981.py | 배선 테스트 9건 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/src/pages/PolicyAnalysis.tsx | 약관 세트 입력 additive | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/src/pages/PolicyTermsUpload.tsx | 약관 업로드 화면 신설 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/src/config/routes.ts | 라우트 등록 | verified |
| /home/jay/projects/InsuRo-worktrees/task-2981-dev2/src/components/navigation/navigationConfig.ts | 내비 등록 | verified |

## 검증 실측 결과

> ⚠️ **아래 절대 수치 3건은 재검증 라운드에서 부정확으로 판명됨** — 정정치는 `## 독립 재검증(토르)` 표 참조. **결론(회귀 0)은 유지**.

| 항목 | 1차 실행 보고 | 독립 재실측 (정정) |
|---|---|---|
| 전체 pytest (변경 후) | ~~2380 passed, 3 skipped~~ | **2389 passed, 3 skipped** (167.91s) |
| 전체 pytest (baseline `49c17a9`) | ~~2347 passed, 3 skipped~~ | **2356 passed, 3 skipped** (181.06s) |
| 증감 | +33 | **+33 = 신규 테스트분 정확히 일치, 회귀 0** (양측 동일 재확인) |
| 봉인 8종 | 224 passed | **224 passed** (일치) |
| 봉인 8종 + 신규 결합 | ~~248 passed~~ | **257 passed** (224+33) |
| 배선 + 결합 테스트 | 33 passed | **33 passed** (일치) |
| 봉인 파일 변경 | 0건 | **0건 — 봉인 12경로 전부 blob 완전 무변경(diff 0바이트)** |
| tsc --noEmit | 0 errors | **0 errors** |
| vitest | 1194 passed (81 files) | **1194 passed (81 files)** |
| vite build | 성공 | **성공** (dist 재생성 2026-08-20 03:08) |

## 근본 원인 분석

- **1st Why**: 보장분석 기존측 담보가 왜 CONFIRMED에 못 가나? → `CoverageRow`에 `clause_id`/`axes` 필드가 없어서.
- **2nd Why**: 왜 그 필드를 못 채우나? → 약관을 입력으로 받지 않아 axes 근거가 없어서.
- **3rd Why**: 약관을 받으면 바로 채워지나? → **아니다.** 증권 담보를 약관 조항에 잇는 구조적 키(담보코드)가 증권 파서 산출물에 없고, 이름 매칭은 금지 불변식이라 우회 불가. 이것이 이번에 드러난 진짜 병목.

## ⚠️ 알려진 한계 (정직 degrade — 은폐 아님)

1. **증권 파서가 담보코드를 뽑지 않는다** — `coverage.code`가 항상 `None`이라 실사용에서 결합이 전건 `unresolved`. 증권 샘플로 코드 표기 유무를 실측한 뒤 파서를 확장해야 발현.
2. **`clause_id` 불일치** — `split_clauses`가 주는 `cov_id`는 DB `coverage_clause.id`(UUID)가 아니라서 `store.get_mapping`이 대체로 miss → cold-start로 `UNKNOWN`. `(product_id, code) → clause_id` 리졸버가 `policy_grouping/knowledge.py`에 필요하나 봉인 영역이라 미접촉.
3. **`product_id`는 감사 라벨** — 런타임에 `clauses` 목록이 실제 그 상품 하나에서만 왔는지 검증하지는 않는다(모델에 상품 식별 필드 부재). docstring에 계약으로 명시.

세 한계 모두 **위조 대신 정직하게 UNKNOWN으로 떨어지는** 방향이라 오비교 위험은 없다.

## 완료조건 대비 자기평가

| 완료조건 | 상태 |
|---|---|
| 세트 업로드 E2E (신규·보장분석) | 배선·UI 완료, 유닛/TestClient 검증. ~~실 브라우저 E2E 미실시~~ → **재검증 라운드에서 실 브라우저 렌더 실측 완료**(하단 `## L1 스모크테스트`). **API 왕복은 JWT 부재로 여전히 미검증** |
| 제안서 담보별 (한도, 보험료) 추출 실측 | **미달 — 1c 보류**(샘플 부재) |
| 보장분석 담보 axes 부착·자동 매칭 성립 실측 | **미달 — 배관 완성, 발현은 위 한계 1·2 해소 필요** |
| 게이트 봉인 회귀 GREEN · 오비교 0 | **달성** (봉인 파일 0건 변경, 224 passed, 회귀 0) |
| report + .done + ANU 콜백 + 머지 HOLD | 달성 |

## 회장님 결정 필요

1. **가입제안서 샘플 PDF 1건** — 1c 착수 전제
2. **증권 샘플 PDF 1건** — 담보코드 표기 유무 실측 → 파서 확장 전제

## 다음 Phase 인계

- **Phase 1 잔여**: 증권 파서 담보코드 추출 + `(product_id, code) → clause_id` 리졸버 + 1c 제안서 파싱
- **Phase 2**: 가성비 계산(단위한도당 보험료)
- **Phase 3**: 복합설계 조립 UI + 비교표 + 최종안 ↔ 기존 비교

## 레벨: Lv.3

## 산출물

- PR **#233** https://github.com/Jeon-Jonghyuk/InsuRo/pull/233 (머지 **HOLD** — ANU 독립검증 후)
- 커밋 `bf5cfdd` → `4634c3a` (branch `task/task-2981-dev2`)

---

# ★ 재검증 라운드 (재디스패치 2026-08-20 03:05~, 오딘)

> 첫 실행(`.done` 03:02) 이후 **동일 task 재디스패치**(one-time schedule `A35B1EE6`)로 재기동됨.
> PR #233은 이미 생성·HOLD 상태였으므로 **작업을 다시 하지 않고**, 첫 실행이 남긴 **미달 항목만 실측으로 닫았다.**

## L1 스모크테스트

- **서버 재시작**: **성공** — 테스트 전용 포트만 사용(백엔드 8099, 프론트 preview 4199).
  ★ 라이브 `insuro-api`(user systemd, 포트 **8001**)는 **재시작하지 않음**. 검증 전·후 `curl /api/status` → **200**, `systemctl --user is-active insuro-api` → **active**. 대시보드(8000)도 **200** 유지.
- **프론트 빌드**: **성공** — `npm run build` exit 0, `dist/` 생성(2026-08-20 03:08), `PolicyAnalysis-*.js` / `PolicyTermsUpload-*.js` 청크 정상 생성.
- **API 응답 확인**: `curl http://127.0.0.1:8099/api/status` → `{"status":"ok","sha":"4634c3a77047aecba0a9762748f5cd9f39560db9",...}` — **PR head sha와 일치**(빌드·기동 대상이 이 브랜치임을 실측).
- **스크린샷**:
  - `/home/jay/workspace/memory/reports/screenshots/task-2981/L1-policy-analysis-terms-set-AUTHINJECTED.png`
  - `/home/jay/workspace/memory/reports/screenshots/task-2981/L1-policy-terms-upload-AUTHINJECTED.png`
  - (참고·**로그인월**) `LOGINWALL-unauthenticated-redirect-{1,2,3}.png`

### 실 브라우저 실측 결과 (이전 `real_browser_e2e: SKIP` 해소)

| 검증 대상 | 결과 | 근거 |
|---|---|---|
| 보장분석 화면 약관 세트 입력 | **렌더 확인** | `약관 PDF (선택)` 카드 + `약관 PDF 파일을 선택하세요 (복수 선택 가능, 파일당 50MB 이하)` + 독립 file input |
| 증권+약관 **세트** 구성 | **렌더 확인** | `증권 PDF 업로드` 카드와 `약관 PDF (선택)` 카드가 한 화면에 공존, `일괄 분석 시작` 별도 카드 |
| 설계사 오판 방지 경고 | **렌더 확인** | `약관 미첨부 시 기존 담보의 판별축(axes)을 붙일 수 없어 자동 매칭이 제한됩니다` (amber) |
| 약관 업로드 화면 신설 | **렌더 확인** | `/tools/policy-terms-upload` — 보험사*/상품명*/상품코드/약관 버전/약관 PDF* + `약관 업로드` 버튼 |
| 내비 등록 | **렌더 확인** | 좌측 `분석 & 도구` 섹션에 **`약관 업로드`** 항목 표시·활성 하이라이트 |
| 브라우저 콘솔 에러 | **신규 코드 기인 0건** | 18건 전부 외부 기인 — Supabase REST **401** 16건(주입 토큰 미서명) + `api.insuro.biz` **CORS/ERR_FAILED** 2건(preview origin 미허용) |

### ★ 검증 방법의 한계 (정직 표기 — 은폐 아님)

- 팀 계정에 **로그인 가능한 테스트 계정이 없다.** `src/components/AuthGuard.tsx`가 `supabase.auth.getSession()`의 `!!session`으로 전 라우트를 보호하여, 최초 시도의 스크린샷 3장은 **전부 `/login` 리다이렉트 화면**이었다(→ `LOGINWALL-*.png`로 재명명해 오인 방지).
- 따라서 **localStorage에 세션 객체를 주입**해 AuthGuard를 통과시킨 뒤 렌더를 확인했다. 이는 **UI 렌더링 검증 전용**이며, 인증 자체를 검증한 것이 아니다. 스크린샷 파일명에 `AUTHINJECTED`를 명시했다.
- **세트 업로드 API 왕복은 미검증(미통과)**: `analyze-policy`는 `Depends(verify_jwt)`로 Supabase JWKS 서명 검증을 강제하므로 주입 토큰(미서명)으로는 **401**. 실제 서명 JWT를 위조하지 않았다.
  - 약관 PDF 샘플은 2건 확인(`terms_source/meritz_2607_*.pdf`, `terms_source/samsung_newnaedonnaesam_*.pdf`).
  - **증권·가입제안서 샘플은 디스크 전역 0건**(재확인) — 설령 인증이 있었어도 `files`(증권, 필수) 부재로 400이 났을 것.
  - → `binding_reason`/`map_state`/`clause_id` **런타임 실측은 여전히 미달**. 유닛/TestClient 레벨로만 검증됨.

## 독립 재검증 (토르) — 자기보고 불신 원칙 적용

1차 실행이 보고한 수치를 신뢰하지 않고 **clean 동일조건에서 재실측**했다.

| 항목 | 1차 보고 | 독립 실측 | 판정 |
|---|---|---|---|
| 봉인 12경로 변경 | 0건 | `git diff 49c17a92 HEAD -- <각 봉인경로>` **전부 빈 출력(0줄)** | 일치 |
| 봉인 8종 | 224 passed | `224 passed, 4 warnings in 0.53s` | 일치 |
| 봉인 8종 + 신규 2파일 | 248 passed | `257 passed` (224+33) | **불일치(+9)** |
| 신규 2파일 단독 | 33 passed | `33 passed` | 일치 |
| 전체 pytest (HEAD) | 2380 passed / 3 skipped | `2389 passed, 3 skipped in 167.91s` | **불일치(+9)** |
| 전체 pytest (baseline) | 2347 passed / 3 skipped | `2356 passed, 3 skipped in 181.06s` | **불일치(+9)** |
| **증감** | +33 | **2389 − 2356 = +33**, skip 3→3 불변 | **일치** |

★ **오프셋 +9가 baseline·HEAD 양쪽에 동일하게 걸린다** → task-2981 변경과 무관한 컬렉션/환경 차이. 따라서 절대 수치는 1차 보고가 부정확하지만, **증감(+33)과 회귀 0 결론은 독립 실측으로 재확인**됨. (오프셋 근본원인은 이번 범위 밖 — 후속 조사 대상으로 인계)

★ **환경 오염 재확인**: `.env`를 가진 main checkout에서 그대로 pytest를 돌리면 `5 failed`(`test_standby_fallback.py`, `test_cors_fail_closed_when_ext_origin_unset` 등). 과거 교훈("worktree `.env`가 CORS 테스트를 깨뜨림") 재현. 그래서 baseline은 **`.env` 없는 임시 detached worktree**에서 측정해 조건을 맞췄다.

### 불변식 코드 실재 확인 (grep 근거)

| 불변식 | 판정 | 근거 |
|---|---|---|
| 담보명 판정 미사용(name-match 금지) | **확인** | 판정 본문(`terms_binding.py:150-279`)은 `coverage.code`만 사용 · 정적 회귀 `test_coverage_terms_binding_task2981.py:250-262`가 `"coverage.name =="` 부재를 grep assert |
| `product_id` 미제공 → fail-closed | **확인** | `terms_binding.py:192-203` → 전건 `missing_product_scope`(:99) |
| 중복/모호 조항 → fail-closed | **확인** | `_clause_identity()`(:139-147)가 `(cov_id, content_hash or clause_text)` 실체 판정 · `:252-265` distinct>1 → `code_ambiguous_multiple_clauses`(:95) |
| `map_state` 위조 없음 | **확인** | `policy_analysis_table.py:300` `link = lookup_group(...)` → `:311` 유일 대입 → `:331` `link.map_state if link is not None else "UNKNOWN"` |

## 트립와이어 5종 (실측)

| 항목 | 실측 | 근거 |
|---|---|---|
| Critical7 net-new | **0** | red-team scan: HEAD 5/12/5 = base 5/12/5 **동일** · 전건 `re.compile` 정규식 리터럴(알려진 오탐). 미변경 봉인 모듈 대조군도 동일 패턴 |
| PII net-new | **0** | 추가 라인 2,127줄 전수 스캔(주민번호/JWT/AWS키/PEM/시크릿 패턴) 0건 |
| 회귀 실패 | **0** | baseline 2356 → HEAD 2389, 증감 +33 = 신규분, 실패 0, skip 3→3 |
| forbidden_paths 침범 | **0** | 봉인 12경로 blob 완전 무변경 |
| nonce = task_id | **일치** | `task-2981` |

## 재검증 라운드 결론

- **닫은 것**: 실 브라우저 E2E(이전 SKIP) · 3문서 미작성(템플릿 draft → 실내용 completed) · 1차 보고 pytest 절대수치 오류 정정 · result.json 미생성
- **여전히 미달(정직 표기)**: 세트 업로드 **API 왕복**(JWT 부재) · **1c 제안서 파싱**(샘플 부재) · **axes 부착 런타임 발현**(증권 담보코드 부재 — 한계 1·2)
- **머지**: **HOLD 유지** (merge_authority=ANU_ONLY). CI **11/11 success**, `mergeable_state=clean`, PR #233 OPEN.

## S — 상황
라운드 2가 03:30 종료되며 `.done`·보고서·`result.json`·`qc-result`·CI_WATCH_HANDOFF 를 남겼으나, 핸드오프 시점(03:00)에 CI 3종(`ci`/`e2e-test`/`diagnostic`)이 **진행 중**이어서 종단 상태가 미확정이었다. 03:30 라운드 3이 스폰됨.

## C — 문제
(1) CI 종단 상태 미확정 → CI_WATCH_HANDOFF 의 terminal_state 미기록.
(2) 라운드 2의 실측 주장(봉인 무손·PII 0·스크린샷 진위·샘플 부재)을 **승계하면 안 됨**(판정 승계 금지 원칙).

## Q — 질문
CI 는 종단에서 GREEN 인가? 라운드 2 주장은 독립 재측정에서도 성립하는가? 신규 구현이 더 필요한가?

## A — 답
**CI 11/11 GREEN·PR CLEAN/MERGEABLE. 라운드 2 주장 전건 독립 재측정으로 성립. 잔여 2건은 구조적 차단이라 신규 구현 없음.** 머지 HOLD 유지.

## CI 종단 결과 (`gh pr checks 233`, head_sha `4634c3a7`)

| 체크 | 결과 | 소요 |
|---|---|---|
| cancel-kill-switch | pass | 6s |
| ci | pass | 8m7s |
| ci/guard | pass | 4s |
| diagnostic | pass | 4m48s |
| e2e-test | pass | 1m43s |
| gemini-review-gate | pass | 3s |
| guard | pass | 5s |
| hidden-path-audit | pass | 8s |
| lock-in-check | pass | 6s |
| merge-safety-check | pass | 6s |
| qc-check | pass | 10s |

- **11/11 pass.** 핸드오프 시 진행 중이던 `ci`·`e2e-test`·`diagnostic` **3종 모두 GREEN 으로 종단**.
- PR #233: `state=OPEN`, `mergeable=MERGEABLE`, `mergeStateStatus=CLEAN`, `mergedAt=null`.
- **자동 수정 미사용**(auto_remediation_used=false) — 수정할 것이 없었음.
- **워처는 머지하지 않음.** `merge_authority=ANU_ONLY` 준수.

## 독립 재검증 — 라운드 2 판정 승계 금지

| 항목 | 라운드 2 주장 | 라운드 3 독립 재측정 | 일치 |
|---|---|---|---|
| 봉인 12경로 무손 | 0건 변경 | `git diff 49c17a92 4634c3a7 -- <path>` 12경로 전부 **0 라인** | ○ |
| 변경 파일 범위 | expected_files 12개 | diffstat **12 files, 2127 insertions, 6 deletions** — expected 와 완전 일치 | ○ |
| PII net-new 0 | 0건 | 추가 2127줄 전수 재스캔(주민번호/sk-/AKIA/PEM/JWT/시크릿 대입) **0 hit** | ○ |
| 실 브라우저 E2E | 렌더 실측 PASS | **스크린샷 2장을 이미지로 직접 열어 육안 확인** — 실제 앱 UI, 로그인월 아님 | ○ |
| 1c 샘플 부재 | 제안서/증권 0건 | 디스크 전역 재탐색: InsuRo PDF **2건뿐(약관 meritz·samsung)**, 증권·제안서 **0건** | ○ |

### 스크린샷 육안 검증 (파일명 신뢰 금지 원칙 적용)
- `L1-policy-analysis-terms-set-AUTHINJECTED.png` — **실제 앱 UI 확인**. `증권 PDF 업로드` 카드와 `약관 PDF (선택)` 카드 공존, 하단 amber 경고 `약관 미첨부 시 기존 담보의 판별축(axes)을 붙일 수 없어 자동 매칭이 제한됩니다` 렌더 확인.
- `L1-policy-terms-upload-AUTHINJECTED.png` — **실제 앱 UI 확인**. `약관 업로드` 화면(보험사*/상품명*/상품코드/약관 버전/약관 PDF*), 좌측 내비 `분석 & 도구` > `약관 업로드` 활성 하이라이트 확인.
- 로그인월 3장은 `LOGINWALL-*` 로 정직 명명되어 있음 — 오표기 없음.

## 신규 구현을 하지 않은 이유 (범위 판단)
잔여 미달 2건은 **샘플/봉인이라는 구조적 차단**이며 이번 라운드에 해소 불가:
1. **1c 가입제안서 파싱** — 실제 제안서 PDF 디스크 전역 0건. task 스펙이 `없으면 1c 보류·구조/스텁까지` 로 **명시 승인**한 경로.
2. **axes 부착 런타임 발현** — `(product_id, code) → clause_id` 리졸버가 필요하나 위치가 `server/policy_grouping/knowledge.py` = **forbidden_paths 봉인 영역**. 침범 시 트립와이어 위반.

→ 억지 구현은 봉인 침해 또는 수치 위조로 귀결. **정직하게 UNKNOWN 으로 떨어지는 현 구현 유지**가 옳다고 판단.

## L1 스모크테스트 (라운드 3)
- **서버 재시작**: **해당없음** — 라운드 3은 검증 전용, 코드 변경 0줄. 라이브 `insuro-api` 무영향.
- **API 응답 확인**: **해당없음**(코드 무변경). 대신 **원격 CI 11/11 GREEN** 이 로컬 재실행보다 강한 증거로 대체 — 라운드 2의 로컬 L1(포트 8099 `/api/status` sha=4634c3a7 일치)은 이미 기록됨.
- **스크린샷**: 신규 촬영 없음. 기존 2장을 **이미지로 직접 열어 육안 검증**(위 표) — 파일 존재 확인이 아닌 내용 확인.

## 트립와이어 5종 (라운드 3 실측)

| 항목 | 실측치 | 근거 |
|---|---|---|
| Critical7 net-new | **0** | CI `qc-check`·`hidden-path-audit`·`guard` pass. 코드 변경 0줄 |
| PII net-new | **0** | 추가 2127줄 독립 재스캔 0 hit |
| 회귀 실패 | **0** | 원격 CI `ci` job 8m7s **pass** (full suite) |
| forbidden_paths 침범 | **0** | 봉인 12경로 diff 0라인 |
| nonce == task_id | **일치** | `task-2981` |

## 라운드 3 결론
- **CI 종단 GREEN·PR CLEAN → terminal_state = MERGE_READY.** CI_WATCH_HANDOFF 에 기록 완료.
- 라운드 2 실측 주장은 **독립 재측정에서 전건 성립**. 과대보고 없음.
- **머지는 하지 않음** — `merge_authority=ANU_ONLY`, 머지 HOLD 유지. **ANU 독립검증 후 판단**.
- 완료조건 중 2건(제안서 추출 실측·axes 자동매칭 발현)은 **여전히 미달**이며, 이는 은폐 없이 위 사유로 보고됨. 회장님 샘플 제공이 선결.

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

