# task-2999 (초안 — 회장 승인 대기)

## 배경
task-2998 검증 중 ANU 가 판정한 **후속 2건**. 둘 다 2998 범위 밖(forbidden path / 명시적 범위 제외)이라
봇이 올바르게 손대지 않고 보고했다. 별도 태스크로 처리해야 한다.

## 항목 A — `/tools/policy-terms-upload` 라우트 가드 부재 (우선순위 높음)

**실측 근거 (ANU)**:
- `src/config/routes.ts` 의 `/tools/policy-terms-upload` 항목에 **`adminOnly: true` 없음**
- `src/App.tsx:73` 이 `route.adminOnly === true` 인 경우에만 `AdminRouteGuard` 로 감싼다
- 현재 `adminOnly: true` 라우트는 14개 — 이 라우트는 미포함
- → 메뉴에서는 관리자 전용으로 옮겼으나(2998), **URL 직접 입력 시 비관리자도 페이지 본문 접근 가능**

**중요 — 악화 아님**: 이 결함은 2998 **이전부터** 존재했다. 2998 이전에는 약관 업로드가
`analytics-tools`(전 설계사 노출) 메뉴에 있었으므로, 2998 은 노출면을 **줄였다**.
따라서 2998 머지를 막을 사유가 아니다.

**조치**: `adminOnly: true` 추가 + `AdminRouteGuard` 결선 회귀 테스트
(`src/components/__tests__/AdminRouteGuard.test.tsx` 가 이미 결선을 단언하므로 자동 커버)
+ 기존 `tests/routes.section.test.ts` 가 `adminOnly === undefined` 를 단언 중이라 **그 단언 갱신 필요**.

## 항목 B — `server/new_design_comparison/engine.py` 잔존 용어 (우선순위 낮음)

**ANU 판정 — 봇 판단 오류 정정**: 봇은 "실제로 견적을 뜻하므로 용어 오류 아님" 이라 보고했으나,
**같은 함수 `_validate` 의 252행이 "신규 설계 담보가 없습니다" 를 쓴다.** 즉 이 파일은 이미
new_design 을 "신규 설계" 로 부르고 있고, 137/254 의 "신규 견적 보험료" 는 **잔존 용어**다.

| 위치 | 현재 문구 | 제안 |
|---|---|---|
| `engine.py:137` | `"NO_PREMIUM_DATA": "신규 견적 보험료가 없어 비교할 수 없습니다."` | 신규 설계 보험료 |
| `engine.py:254` | `"신규 견적 보험료가 전 담보에서 비어있습니다."` | 신규 설계 보험료 |

**유지할 것** (진짜 견적 의미):
- `engine.py:198` "견적 기준금액 대비 선형 환산" → 유지
- `NewDesignComparisonTable.tsx:142` "견적 보험료 없음" → 유지

**노출 경로**: `engine.py:254` → `NewDesignComparison.tsx:121-125` 가 `detail.detail` 로 꺼내
212행 `AlertDescription` 렌더. **전 담보 보험료가 빈 에러 경로에서 고객/설계사 화면에 노출된다.**

**주의**: `server/**` 변경이므로 pytest 회귀 필수. 계약 §7 enum 문구라 서버 테스트 픽스처가
문자열을 하드코딩했을 수 있음 — `grep -rn "신규 견적" server/tests/` 선확인.

## 범위 밖
- `routes.ts` 의 죽은 `section` 필드 **삭제**는 이번에도 하지 않는다(2998 이 주석만 달았다).
  삭제하려면 `AppSidebar.tsx` 자체의 존치 여부를 먼저 결정해야 한다 — 별도 판단.
