# 작업 보고: task-3002 — 묵은 PR #215 · #202 심층분석 및 정리

- **팀**: dev3-team (다그다 / Dagda)
- **레벨**: Lv.2 · **검증 레벨**: normal
- **저장소**: `/home/jay/projects/InsuRo` (`Jeon-Jonghyuk/InsuRo`)
- **기준**: `origin/main` = `51718e8d7814540c64b0f48bd43e89e860af04b0` · 프로덕션 배포 sha = `eadd0c9780b180c86a7554045be2d5c5c96751ff`
- **결과**: **#202 → (a) 흡수됨 · 닫음** / **#215 → (a) 흡수됨(잔여 갭 1건) · 닫음** · **코드 0줄**

## 레벨: 코드 수정 없음

분석 전용 태스크다. 근거는 자가부여가 아니라 **태스크 md 가 부여**했다 —
`task-3002.md` 의 `merge_policy: "analysis_only"` 및
`forbidden_paths: ["src/**","server/**","supabase/**","ops/**",".github/**"]`,
그리고 본문 "★ 분석 태스크라 코드 경로를 전부 forbidden 으로 뒀다".
산출물은 보고서 1개뿐이며 `InsuRo` 저장소에 커밋·푸시한 것은 **0건**이다.

---

## S — 상황

InsuRo 에 묵은 open PR 7건이 남아 있었다. 회장 지시로 그중 **#215 · #202** 두 건만
심층분석 대상이 됐다(나머지 #106 · #105 · #94 · #92 · #8 은 "무시 가능"으로 분류 = 범위 밖).

ANU 선행 실측:

| PR | 브랜치 | head | state | 규모 | main 대비 |
|---|---|---|---|---|---|
| #215 | task/task-2954-dev2 | 64111fbd | dirty | +995/-0 · 6파일 | 34커밋 뒤처짐 |
| #202 | task/task-2927-dev1 | 8f6692a6 | dirty | +941/-22 · 9파일 | 45커밋 뒤처짐 |

두 PR 이 건드리는 파일이 **전부 `origin/main` 에 이미 존재**했다.

## C — 문제

파일이 main 에 존재한다는 사실만으로는 아무것도 판정할 수 없었다.
"후속 작업이 흡수했을 것"은 **정황이지 결론이 아니었고**, 세 가지 함정이 있었다.

1. **이름이 같아도 구현이 다를 수 있다** — grep 존재 확인만으로 (a) 를 선언하면 오판이다.
2. **코드 존재 ≠ 라이브 동작** — main 에 있어도 프로덕션에 안 떠 있을 수 있다.
3. **낡은 쪽이 반드시 PR 이라는 보장이 없다** — main 이 앞서 있어도 단정은 금물이다.

여기에 더해 34~45커밋 뒤처진 `dirty` 브랜치라 rebase 부활은
**의미 충돌**(텍스트는 합쳐지는데 동작이 깨지는 것) 위험이 컸다.

## Q — 질문

각 PR 은 (a) 흡수됨 / (b) 고유 가치 있음 / (c) 일부만 중 무엇인가?
**파일 diff 가 아니라 "이 PR 이 하려던 일이 지금 되는가"** 로 판정할 수 있는가?

## A — 답

두 건 모두 **(a) 흡수됨**. 옮길 코드는 **0줄**이며 두 PR 을 닫았다.
#215 에는 되살릴 사유는 아니나 기록할 **잔여 갭 1건**이 있다.

---

## 판정 1 — PR #202 (task-2927, Drive 프록시 Phase1) → **(a) 흡수됨**

**흡수 경로**: PR #203 `b21b04b80475f39c12756bdc9c38ccae0cf0faf0` (2026-08-09 머지)

결정적 증거는 정황이 아니라 **커밋 본문의 명시적 자기선언**이었다:

```
$ git log -1 --format='%b' b21b04b
* [task-2927] 불칸: Drive 프록시 백엔드 코어 — drive_file_id 마이그레이션+백필, file_id 파서,
  ticket store, file-ticket/프록시 스트리밍 엔드포인트+IDOR 인가, parse_premium file_id 전환,
  ES256 전용, 예외 감사로그
* [task-2928] 불칸: drive_file_id 신규경로 배선 + file-ticket view모드 …
* [task-2929] 불칸: FCPA drive_file_id 일관성 …
```

즉 `b21b04b` 는 이 PR 을 흡수했을 뿐 아니라 **이 PR 이 남긴 갭까지(t2928·t2929) 마저 메웠다.**

### 기능 10종 전수 대조 — main 미존재 0건

| # | 기능 | main 존재 | 증거 | 판정 |
|---|---|---|---|---|
| 1 | POST /api/insuro/file-ticket IDOR 인가 3단계 | 있음 | server/main.py 5911-5997 | 동등 + mode 파라미터로 우월 |
| 2 | GET /api/insuro/file/{ticket_id} 스트리밍+보안헤더 | 있음 | server/main.py 5997-6079 | main 우월 (Range 206/416) |
| 3 | file_ticket_store TTL·1회소비 | 있음 | create_ticket/consume_ticket 동일 | main 상위집합 (peek_ticket) |
| 4 | extract_drive_file_id 4패턴 파서 | 있음 | blob **byte-identical** | 동등 |
| 5 | iter_drive_file 청크 스트리밍 | 있음 | server/gdrive.py 271-287 | 동등 + fetch_drive_file_range |
| 6 | parse_premium_file file_id 전환 | 있음 | 함수 본문 diff **출력 없음** | 동등 |
| 7 | JWT ES256 전용화 | 있음 | 아래 별도 표 참조 | **main 우월** |
| 8 | drive_file_id 마이그레이션 | 있음 | blob **byte-identical** | 동등 + 후속 2건 추가 |
| 9 | except pass 5곳 → 감사로그 | 있음 | parse_premium_file 내 bare pass **0건** | 동등 |
| 10 | _validate_file_url SSRF 화이트리스트 | 있음 | server/main.py 223/233 | 동등 |

**main 이 이 PR 보다 앞서 있는 지점 4가지** (역방향 갭):

| 항목 | 이 PR | origin/main |
|---|---|---|
| multi_account_detect_middleware JWT | ES256+HS256 잔존 (미처리, 보고서에도 기록됨) | ES256 통일 완료 |
| 신규 업로드 시 drive_file_id 배선 | **없음** — 마이그레이션 백필뿐이라 신규 파일은 file-ticket 조회 시 계속 404 | task-2928 로 실시간 배선 |
| file-ticket 모드 | download 1회소비만 | view 모드 + Range 206/416 |
| FCPA drive_file_id | 없음 | 20260809120000 마이그레이션 존재 |

### ★ 이 PR 을 되살리면 보안 회귀가 난다 (팀장 직접 재확인)

팀원 보고를 그대로 받지 않고 직접 대조했다.

```
$ git show 8f6692a6:server/gdrive.py | grep -n 'anyone'
143:        body={"type": "anyone", "role": "reader"},     # 잔존
199:        body={"type": "anyone", "role": "reader"},     # 잔존

$ git show origin/main:server/gdrive.py | grep -n '공개 권한'
141:    # P0-1 Phase 3a: 공개 권한(type=reader for all) 생성 제거 — 신규 업로드는 소유자 전용(비공개).
185:    # P0-1 원칙 유지 — 공개 권한(type=reader for all) 생성 금지. …
239:    # 4. P0-1 Phase 3a: 공개 권한(type=reader for all) 생성 제거 …
```

이 브랜치를 rebase 로 되살리면 **P0-1 이 이미 해소한 Drive 공개공유 취약점이 재유입**된다.
"낡았으니 버린다"가 아니라 **"되살리면 위험하다"** 가 이 PR 을 닫는 더 강한 사유다.

---

## 판정 2 — PR #215 (task-2954, 실손 세대지식 배선) → **(a) 흡수됨** (잔여 갭 1건)

**흡수 경로**: PR #216 task-2955 `6eeb5b6845e1b94fae07357c38b2f2131bfc178e` (2026-08-14 머지)

이 PR 의 **의도**는 "확정된 실손 세대지식을 설계사 화면에 도달시킨다"였다.
직전 병목은 지식이 아니라 **배선**이었다(`analysis_bridge` 호출부 0 = 휴면).
그 의도는 현재 **라이브에서 달성돼 있다.**

### 리스크 가드 3종

| 가드 | main 상태 | 판정 |
|---|---|---|
| PII | insured_name_masked=None / policy_number_masked=None **문자 단위 일치** | 동등 |
| 금소법 고지문 | "설계사 참고용입니다. 실제 증권·약관을 확인하세요." **문자 단위 일치** | 동등 |
| 계약일 fallback caveat | 정보는 전달되나 빨간 Alert → 회색 muted 로 **시각 강조 약화** | 열등(스타일) |

### 6파일 전부 main 이 더 새것

`analysis_summary.py` 는 전면 재작성(+157/−185)으로 **스키마 자체가 다르다**
(`applicable`/`highlights` → `generation_id`/`effective_period`/`key_coverages`/`sources`).
`src/pages/PolicyAnalysis.tsx` 는 **863줄**로 확장됐고 최근 커밋은 `c3434bb`(task-2981 워크벤치 Phase 1)다.
이 PR 의 diff 재적용은 불가능하며 무의미하다.

### ★ 잔여 갭 1건 — 팀장 직접 재확인

모리건 보고를 그대로 받지 않고 양쪽 소스를 직접 읽었다. **보고가 맞았다.**

```
# PR (64111fbd) _detect_silson() — 담보명 매칭 있음
122:        elif any(keyword in name for keyword in _SILSON_KEYWORDS):
123:            basis.append(f"담보명에 실손 키워드 포함: {name}")

# origin/main is_silson_relevant() — cov.category 와 meta.product_name/product_type 만 검사
100:            if isinstance(cov, dict) and cov.get("category") == "실손":
107:                if isinstance(value, str) and "실손" in value:
```

→ `category` 도 `product_name/type` 도 비어 있고 **담보명에만 "실손" 이 있는** 증권에서
main 이 미탐(false negative)할 수 있다. **실데이터 재현 검증은 하지 않았다 — 미확인.**

나머지 3건은 되살릴 대상이 아니다:
날짜 caveat 강조 스타일(스타일 차이) · `sources` 노출(main 이 **회귀테스트로 의도적 차단**) ·
`main.py` 예외 타입명 로깅(main 은 모듈이 예외를 안 던지는 설계).

### 판정 근거를 밝힌다 — (a) 인가 (c) 인가

엄격히 읽으면 미반영분이 있으니 **(c)** 로 볼 여지가 있다. 그러나 태스크 md 는
**"파일 diff 가 아니라 이 PR 이 하려던 일이 지금 되는가로 판정하라"** 고 지시했고,
그 기준에서 이 PR 의 목적은 **전부 달성**됐다. 잔여 갭은 이 PR 의 부활 사유가 아니라
**현재 main 기준의 독립적 개선 항목**이다. 따라서 **(a)** 로 판정하되 갭을 명시 기록했다.
★ 어느 판정이든 **낡은 PR 은 닫는다**는 점은 동일하므로 닫기 조치에는 영향이 없다.

---

## 라이브 실측 (코드 존재로 판단하지 않았다)

**API base 실측** — `insuro.biz` 는 프론트 정적 SPA 다. 여기에 API 경로를 찌르면
`POST → 405`, `GET → 200`(SPA fallback)이 나와 **오판을 부른다.** 실제 API 는 `api.insuro.biz`.

```
$ curl -s -X POST https://api.insuro.biz/api/insuro/file-ticket -H 'Content-Type: application/json' -d '{}'
{"detail":"Missing or invalid authorization"}                          # 401
$ curl -s https://api.insuro.biz/api/insuro/file/nonexistent-xyz
{"detail":"유효하지 않거나 만료된 티켓입니다"}                            # 404
$ curl -s https://api.insuro.biz/api/status
{"status":"ok","sha":"eadd0c9780b180c86a7554045be2d5c5c96751ff","ready":true,"db":"ok",…}
```

**프론트 번들 실측** — minified `grep -c` 함정(항상 1)을 피해 `grep -o | wc -l` 을 썼고,
lazy chunk 미조회 오판을 피해 엔트리 `assets/index-DxwzQ0mr.js` 에서 **청크 152개를 전수 추출**해
`assets/PolicyAnalysis-BvScngmQ.js` 를 특정했다.

```
silson_summary → 2   generation_label → 1   verification_notice → 1
key_coverages  → 1   effective_period → 1   실손 세대정보 → 2
"가입일 기준(계약일과 다를 수 있음)" → 1
```

**조상 관계** (반영 판정의 권위):

```
b21b04b → eadd0c978 : ANCESTOR      6eeb5b6 → eadd0c978 : ANCESTOR
eadd0c978 → origin/main : ANCESTOR
```

두 흡수 커밋 모두 **현재 서비스 중인 프로덕션 sha 의 조상**이다.

---

## L1 스모크테스트

- **서버 재시작: 해당없음** — 코드 0줄 변경. 재시작 사유가 없고, 오히려 재시작은 무관한 위험만 만든다.
- **API 응답 확인: 성공** — 위 `api.insuro.biz` curl 3종 (401 / 404 / 200+sha). 원문 인용은 위 절 참조.
- **스크린샷: 해당없음** — 증권분석 화면은 **로그인 + 파일 업로드**가 필요한 라우트다.
  인증 없이 찍으면 로그인월 스샷이 되어 아무것도 증명하지 못하므로 찍지 않았다.
  대신 **프로덕션 번들 문자열 실측**(위)으로 대체했고 그 사실을 여기 밝힌다.
  ★ **로그인 후 실제 렌더 화면은 미확인이다.**

## 수정 파일별 검증 상태

| 파일 | 구분 | 검증 방법 | status |
|---|---|---|---|
| /home/jay/workspace/memory/reports/task-3002.md | 신규 | 본 보고서. 기재된 모든 명령을 실행하고 출력 원문을 인용 | verified |

**InsuRo 저장소 코드 변경: 0파일 / 0줄.** `git status` 상 InsuRo 워킹트리에 본 태스크가 만든 변경은 없다.

## 실행한 조치 (되돌리기 쉬운 것만)

| 조치 | 대상 | 결과 |
|---|---|---|
| 사유 코멘트 | PR #202 | issuecomment-5389252066 |
| 사유 코멘트 | PR #215 | issuecomment-5389255151 |
| PR 닫기 | PR #202 | state=closed · merged=false · 2026-08-24T00:08:43Z |
| PR 닫기 | PR #215 | state=closed · merged=false · 2026-08-24T00:08:44Z |

`gh` 는 전부 `GH_TOKEN="$BOT_GITHUB_TOKEN"` 으로 호출했다(회장 개인 PAT 미사용 — 감사기록 오염 방지).

## 배포 필요 여부

**배포 불필요.** 두 PR 의 기능은 이미 프로덕션 sha `eadd0c978` 에 반영돼 서비스 중이다.
코드 변경이 0줄이므로 빌드·배포·재기동 대상이 없다.

---

## ANU 판단 요청 (2건)

### 1. 실손 담보명 감지 갭 — 별도 마이크로 태스크 발급 여부

`server/silson/analysis_summary.py` 의 `is_silson_relevant()` 에 담보명 기반 보조 감지를 추가하는 건.
**PR #215 를 되살리는 게 아니라 현재 main 기준 신규 작업**이다.
판단 재료: 과탐 > 미탐 위험(금소법 민감 기능)이라 원저자가 키워드를 `("실손",)` 하나로 좁혀 뒀다.
담보명까지 넓히면 recall 은 오르나 과탐 위험도 함께 오른다. **실데이터 재현 검증이 선행돼야 한다.**
★ 태스크 md 의 "명세와 실측이 다르면 임의 판단하지 말고 ANU 에 보고하라"에 따라 착수하지 않았다.

### 2. 날짜 caveat 시각 강조 — 회귀인지 의도인지

PR 은 빨간 `Alert variant="destructive"` + "추정 — 확정 판정 아님", main 은 회색 muted 한 줄.
정보 자체는 전달되나 금소법 민감 기능에서 강조가 약해진 것이 **의도된 UX 조정인지 회귀인지 확인 불가**.
task-2955 리뷰 이력 확인이 필요하다. 판단 시 **UX(아네) 관여 권고.**

### (b)(c) 재작성 실행안

**없음.** 두 PR 모두 (a) 로 판정돼 옮길 코드가 0줄이다.
`PolicyAnalysis.tsx` 는 손대지 않았다 — 후속 "신규설계 비교 입력 UI" 작업과 충돌 가능성 0.
해당 후속 작업은 **현재 main(863줄, silson 배선 포함) 기준으로 새로 설계**하면 된다.

## 남은 묵은 PR (범위 밖 · 참고)

정리 후 open 5건: #106 · #105 (2026-05-07) · #94 · #92 (REVERT, 2026-05-03) · #8 (2026-04-20).
회장이 "무시 가능"으로 분류해 **손대지 않았다.**

## 3 Step Why

**1st Why — 왜 두 PR 은 묵었나?**
후속 작업이 같은 영역을 더 넓은 범위로 재구현해 머지했는데, **원본 PR 을 닫는 절차가 없었다.**
`b21b04b` 는 커밋 본문에 `[task-2927]` 을 적어 두고도 #202 를 닫지 않았다.

**2nd Why — 왜 닫는 절차가 없었나?**
"흡수됐다"를 판정하려면 blob·심볼·라이브 3중 대조가 필요한데 그 비용이 커서 아무도 손대지 않았다.
그 사이 브랜치가 34~45커밋 뒤처져 **판정 비용이 더 커지는 악순환**이 생겼다.

**3rd Why — 그래서 무엇을 바꿔야 하나?**
후속 PR 이 선행 PR 의 task_id 를 커밋 본문에 적을 때, **선행 PR 을 닫는 것을 머지 체크리스트에 넣어야 한다.**
`b21b04b` 처럼 커밋 본문에 `[task-NNNN]` 이 있으면 해당 task 의 open PR 을 자동 검출해
경고하는 것만으로도 이번 같은 6~15일 방치는 막을 수 있다. ANU 검토 대상으로 제안한다.

## 셀프 QC

| 항목 | 결과 |
|---|---|
| 하드코딩 | 해당없음 (코드 0줄) |
| 테스트 | 해당없음 (코드 0줄) — 대신 라이브 실측 curl 3종 + 번들 문자열 실측 |
| 회귀 | 없음 — 저장소 파일 변경 0 |
| 범위 준수 | forbidden_paths(src/server/supabase/ops/.github) 침범 **0건** · PR #106·#105·#94·#92·#8 **미접촉** |
| 타 작업 침범 | scripts/youtube-pipeline/** 미접촉 · supabase/functions/send-push/**(task-3003) 미접촉 |
| origin/main 직접 커밋 | 시도조차 안 함 (0건) |
| 토큰 위생 | BOT_GITHUB_TOKEN 사용 · 회장 PAT 미사용 · 보고서에 토큰값 미노출 |
| PII | net-new 0건 |

## trip-wire 5종 (실측)

| 항목 | 실측치 |
|---|---|
| Critical7 | 0 |
| PII net-new | 0 |
| 회귀 실패 | 0 |
| forbidden_paths 침범 | 0 |
| nonce = task_id | task-3002 일치 |

## 모델 사용 기록

| 팀원 | 역할 | 모델 | 담당 |
|---|---|---|---|
| 루 (Lugh) | 백엔드 | sonnet | PR #202 백엔드 프록시 전수 대조 |
| 모리건 (Morrigan) | 테스트/QA | sonnet | PR #215 기능 단위 검증 + 번들 실측 |
| 다그다 (Dagda) | 팀장 | opus | 설계·분배·**결정적 주장 직접 재확인**·판정·PR 닫기·보고서 |

haiku 미사용 — 분석·리서치 작업에는 금지돼 있다.
브리짓(프론트)·아네(UX/UI) 미소집 — 코드 0줄 분석 태스크라 배정할 작업이 없었다.
★ 팀원 보고를 그대로 신뢰하지 않고, 판정을 뒤집을 수 있는 2가지 주장
(#202 anyone/reader 잔존 · #215 담보명 감지 갭)은 **팀장이 원본 소스를 직접 대조**해 확인했다. 둘 다 사실이었다.

## 정직성 메모 (미확인 항목)

- 마이그레이션의 **프로덕션 DB 실제 적용 여부** — DDL 조회를 하지 않았다(쓰기 금지 규율 준수). **미확인.**
- Drive 파일의 실제 `anyone/reader` 권한 해제 상태 — Drive API 권한이 없다. **미확인.** (Phase 2/3 범위)
- 실손 담보명 미탐 엣지케이스의 **실데이터 재현** — 하지 않았다. **미확인.**
- `sources` 노출 제거의 정확한 사유(커밋·리뷰 이력) — 추적하지 못했다. 회귀테스트 존재로 **추정**만 제시.
- 로그인 후 실제 렌더 화면 — **미확인** (번들 문자열 실측으로 대체).

## 비고

- **GLM 코드 품질**: 해당없음 (GLM 미사용, 코드 0줄)
- **버그 발견/수정**: 수정 없음. 다만 **발견 2건** — (1) PR #202 를 되살리면 Drive 공개공유 취약점 재유입,
  (2) main 의 실손 담보명 감지 recall 갭. 둘 다 위에 기록했다.

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

