---
task_id: task-2981
role: ANU 독립검증 (collector_role=ANU)
date: 2026-08-20
pr: 233
verdict: PASS (머지 HOLD 유지 — merge_authority=ANU_ONLY)
---

# task-2981 ANU 독립검증 보고서

검증 원칙: **봇 자기보고 전건 미승계.** 모든 수치를 ANU가 직접 재측정하고,
적대적 우회는 봇 probe를 재실행하지 않고 **ANU가 새로 고안**했다.

## 1. 원격 사실 (gh api 직접 조회)

| 항목 | 봇 주장 | ANU 실측 | 판정 |
|---|---|---|---|
| PR state | OPEN·HOLD | `state=open`, `merged=false`, `mergeable_state=clean` | 일치 |
| head / base | 4634c3a7 / 49c17a92 | 동일 | 일치 |
| 변경 규모 | 12파일 2127+/6- | `changed_files=12, additions=2127, deletions=6` | 일치 |
| 변경 파일 목록 | expected_files 12개 | 12개 전부 일치, 초과 0 | 일치 |
| CI | 11/11 GREEN | check-runs **11건 전부 success** | 일치 |

## 2. 봉인 무손 (forbidden_paths 12경로)

`git diff 49c17a92 4634c3a7 -- <각 경로>` → **12경로 전부 0라인**. 침범 0.

## 3. pytest — ★ 봇의 "수치 정정"이 오히려 오정정

라운드3은 1차 보고(2380/2347)를 **"부정확"으로 낙인**하고 2389/2356으로 "정정"했다.
ANU가 clean worktree에서 양측을 직접 돌린 결과 **두 수치 모두 정확**하며, 차이는 **scope**다.

| scope | baseline 49c17a92 | HEAD 4634c3a7 | delta |
|---|---|---|---|
| `pytest server/tests` | **2347 passed, 3 skipped** | **2380 passed, 3 skipped** | +33 |
| `pytest server` | 2356 (collect 2359) | **2389 passed, 3 skipped** (collect 2392) | +33 |

- 오프셋 **+9의 정체 = `server/scripts/tests/test_standby_fallback.py` 단 한 파일**
  (`server/tests/` 밖에 존재하는 유일한 테스트 파일, 정확히 9건).
- 봇은 "+9 오프셋 근본원인은 범위 밖·후속 조사 대상"으로 남겼으나 **본 검증에서 확정**했다.
- **결론(회귀 0 · delta +33 = 신규분)은 양 scope 모두에서 성립** → 판정 무손.
- 다만 `qc-result` 의 `"1차 보고 2380 은 부정확"` 문구는 **그 자체가 부정확**하므로 정정 대상.

기타 직접 실행: 봉인 8종 **224 passed** · 신규 2파일 **33 passed** (224+33=257 산술 일치).

## 4. ★ ANU 자체 고안 적대적 우회 10종 — 전건 fail-closed

봇이 만든 probe를 재실행하지 않고 ANU가 새 공격을 설계했다. (형식이 올바른 위조 위주)

| # | 공격 | 결과 |
|---|---|---|
| A1 | 동일 cov_id·다른 원문 2조항 | unresolved / code_ambiguous_multiple_clauses |
| A2 | 교차필드 — 한쪽은 `code`, 다른쪽은 `cov_id` 로 동시 매칭 | unresolved / ambiguous |
| A3 | product_id = `"   "` (공백만) | unresolved / missing_product_scope |
| A4 | 전각 `Ａ１００` 위조 | code / exact_match (의도된 NFKC 관용) |
| A5 | **ZWSP(U+200B) 주입** `A​100` | unresolved / no_structural_key |
| A6 | 소문자 `a100` | code / exact_match (의도된 관용) |
| A7 | 완전 동일 조항 중복 등장 | code / exact_match (정상) |
| A8 | **위조 content_hash == 타 조항 clause_text** (실체 충돌 유도) | unresolved / ambiguous |
| A9 | product_id = None | unresolved / missing_product_scope |
| A10 | soft-hyphen 코드 | unresolved / no_structural_key |

**뚫리지 않았다.** A8이 막힌 구조적 이유: `ClauseRecord.__post_init__` 이 `clause_text`
로부터 `content_hash` 를 **자동 sha256 계산**하므로, 원문과 해시가 같은 슬롯에서
충돌하는 경우가 현실적으로 생성 불가.

## 5. ★ ANU 신규 발견 결함 (봇 3라운드가 못 잡음) — 비차단

`server/policy_analysis_table.py:267-270`

```python
try:
    bindings = bind_coverages(flat_coverages, clauses, product_id=product_id)
except TypeError:
    # "task-2981 시그니처 마이그레이션 과도기 대응(모듈 docstring §3)"
    bindings = bind_coverages(flat_coverages, clauses)   # ← product_id 없음
```

실측 근거:
- `terms_binding.py` 는 **이 PR 에서 신설된 파일**이다 — `git cat-file -e 49c17a92:...` →
  `does not exist`. 따라서 **구 시그니처는 존재한 적이 없고**, "과도기"라는 주석 근거는 사실이 아니다.
- 참조한 **`모듈 docstring §3` 은 파일에 없다** (`grep -c "§3"` → **0**).
- 현재 이 fallback 은 자기 자신을 인자 없이 재호출해 **다시 TypeError 를 낸다** →
  **도달 불가 dead code**. 즉 **지금은 오연결이 발생하지 않는다**(그래서 비차단).

왜 그래도 고쳐야 하나:
- 누군가 `product_id` 에 기본값을 부여하는 순간, 이 경로가 **조용히 살아나
  product scope 없이 결합**한다 = 팀장이 이번에 막은 **구멍②(타사 오연결)를 재개방**하는 부비트랩.
- 워크스페이스 CLAUDE.md **`죽은 코드 금지`** 위반.

권고: **해당 try/except 3줄 삭제**(단순·저위험). 별도 소규모 PR 또는 본 PR 머지 전 수정.

## 6. E2E 스크린샷 — 파일명 불신·이미지 직접 열람

`AUTHINJECTED` 2장을 ANU가 **이미지로 직접 열어** 확인했다(로그인월 아님):
- `L1-policy-analysis-terms-set-AUTHINJECTED.png` — `증권 PDF 업로드` + `약관 PDF (선택)`
  카드 공존, `약관 PDF 파일을 선택하세요 (복수 선택 가능, 파일당 50MB 이하)`,
  하단 amber `약관 미첨부 시 기존 담보의 판별축(axes)을 붙일 수 없어 자동 매칭이 제한됩니다` **렌더 확인**.
- `L1-policy-terms-upload-AUTHINJECTED.png` — `약관 업로드` 화면(보험사*/상품명*/상품코드/
  약관 버전/약관 PDF*), 좌측 `분석 & 도구` > **`약관 업로드`** 활성 하이라이트 **확인**.
- 로그인월 3장은 `LOGINWALL-*` 로 **정직하게 명명**되어 있음 — 오표기 없음.
- 한계 정직 표기 적정: 세션 주입은 **렌더 검증 전용**, 인증·API 왕복은 미검증(JWT 부재).

## 7. 위생

- 타이머 `status=completed`, `end_time` 기록됨 → **고스트 태스크 없음**.
- `task-2981.done` → `task-2981.done.acked` 처리 완료 → `.done.escalated`(stale 오탐) 해소.
- `task-2981.failure-handoff-marker.json` 은 **dev4-team/vishnu 의 dispatch_runtime_crash**
  기록(02:44)으로, 최종 산출을 낸 dev2 라인과 무관 — 별도 인프라 이슈로 분리 인계.

## 8. 최종 판정

**독립검증 PASS. 단 머지는 HOLD 유지** (merge_authority=ANU_ONLY, 회장 결정 대기).

HOLD 유지 사유:
1. 완료조건 2건(제안서 추출 실측·axes 자동매칭 발현)이 **구조적으로 미달**이며,
   해소에 **회장 샘플 PDF 2종(가입제안서 1건·증권 1건)** 이 선결.
2. 위 §5 dead fallback 3줄을 **머지 전 제거**하는 편이 안전.

봇의 정직성 평가: 미달 2건을 **은폐하지 않고 UNKNOWN 으로 degrade** 시킨 판단은 옳다.
위조된 수치·위조된 CONFIRMED 비교는 발견되지 않았다.
