# task-2924 보고서 — 표 육안확인 신호를 본문 마커→메타데이터로 (extracted_text 오염 제거)

## Situation
task-2921이 저신뢰 표 페이지에 `[[TABLE_REVIEW page=N]]` 마커를 **본문 마크다운(full_markdown)에 삽입**했다. `main.py`가 이 markdown을 `newsletters.extracted_text`로 저장 → newsletter_chat(Q&A)·발행물 컨텍스트로 **그대로 노출**. 내부 육안확인 신호가 최종 발행물/Q&A에 문자열로 새어나가는 상태였음. **Codex 묶음배포 반증검토에서 NO-GO 블로커**로 확정 → 배포 선결.

## Complication
- 마커는 `pdf_extract.py:576-578`에서 본문에 prepend되어 `full_markdown`에 섞임.
- flags 병합부(`run_review_pipeline`)는 `newsletter_review.py`에 있으나 **forbidden_paths** (수정 불가).
- 허용 파일은 `server/pdf_extract.py`, `server/main.py`, `server/tests/**` 3개뿐.

## Question
extracted_text 본문 오염을 제거하면서 육안확인 신호를 검토 화면에는 유지하려면?

## Answer (surgical)
1. **pdf_extract.py**: 마커 본문삽입 블록(576-578) **완전 제거**. 신호는 페이지 dict 플래그 + top-level 메타데이터로만 노출.
   - 페이지 dict에 `needs_table_review: bool` 키 추가(3곳 — OCR/정상/예외폴백, 하위호환 기본 False, 기존 `table_review` 키 유지).
   - `table_review_pages: list[int]`(0-indexed)는 이미 top-level에 존재 → 유지, docstring에 0-idx 명시.
   - 저신뢰 표는 여전히 줄바꿈 보존 텍스트(마커만 제거) — 기존 `_region_to_markdown` 출력 byte-identical.
2. **main.py**: 본문(extracted_text)은 깨끗하게 저장, table_review 신호는 **validation_flags에 결정론 MED 플래그**로 병합.
   - `_try_text_layer_extract` 반환에 `table_review_pages` 추가(타입 힌트 `dict[str, Any] | None`).
   - 두 호출부(`parse_premium_file`·`upload_to_drive`)에서 `_table_review_pages` 캡처 → `_run_newsletter_review_bg`로 스레딩.
   - `_run_newsletter_review_bg`: `run_review_pipeline` 반환값 캡처 후, table_review_pages가 있으면 `{"rule":"table_review_needed","level":"MED","field":"body","message":"표 페이지 육안 확인 필요(자동 격자화 저신뢰)","detail":{"pages":[...]}}`를 validation_flags에 병합 후 `newsletters` update. **newsletter_review.py 불변** — 파이프라인이 기록한 flags를 배선부에서 병합.
   - MED 레벨이므로 HIGH-ack 발행 게이트(fail-closed, 금소법 P0-A)에 **영향 없음**. codex `structure_broken`과 공존(중복 경고 허용).

## 수정 파일 목록
- `server/pdf_extract.py` (+9 / -3): 마커 삭제 + needs_table_review 3곳 + docstring
- `server/main.py` (+31 / -2): table_review_pages 스레딩(5곳) + flag 병합(1곳)
- `server/tests/test_pdf_extract.py` (+50 / -13): 마커 assertion → 메타데이터 계약, 신규 계약 테스트 1건

## 테스트 결과
- **`pytest server/tests/test_pdf_extract.py`**: 22 passed (마커 검증 5클래스 갱신 + 신규 `TestTableReviewMetadataContract`)
- **전체 회귀 `pytest server/tests/` (clean env, CI parity)**: **1299 passed, 0 failed** — 회귀 0. (INSURO_EXTENSION_ORIGIN 미설정 clean worktree, CORS 오탐 없음 — [[feedback_regression_verify_clean_worktree_not_polluted_260726]])
- `py_compile server/main.py server/pdf_extract.py`: COMPILE OK
- grep 검증: 소스(테스트 제외) `TABLE_REVIEW` 0건, `needs_table_review` 5건(pdf_extract), `table_review_pages` 10건(main), `table_review_needed` 2건(main)

## L1 스모크테스트 결과 (실 데이터 검증)
- **서버 재시작**: 해당없음 (순수 추출 파이프라인 로직 — 서버 상태 무관)
- **실 추출 검증**: 실 파일 `DB생명 위드유매거진 26.08.pdf`(19p, text_based) → `pdf_extract.extract_markdown` 직접 실행
  - full_markdown(=extracted_text) length 20965
  - **`TABLE_REVIEW` 문자열 0건**, `[[` 0건, 페이지별 마커 누출 0건 ✅ (이전엔 11개 표페이지에 마커 삽입되던 것)
  - 메타데이터로만 신호: `table_review_pages`(0-idx) `[1,2,3,4,7,9,10,14,15,17,18]`, 페이지별 `needs_table_review=True` 동일 집합 ✅
  - **회장 지적 p16(idx15)**: `needs_table_review=True`·`table_review=True` — 메타로만 표시, 본문 오염 없음 ✅
- **스크린샷**: 해당없음 (백엔드 추출 로직, UI 무관 — 검토화면 validation_flags 노출은 DB 상태 의존이라 Supabase 실호출 필요, 프론트 렌더는 ANU 통합검증 범위)

## 발견 이슈 및 해결
- pyright `page_idx is not accessed`(pdf_extract.py:525): 마커 제거로 `_page_markdown`의 `page_idx` 파라미터가 미사용화. **호출부 변경 방지 위해 시그니처 유지**(호출부 695줄에서 positional 전달 중) — 무해한 unused-param 경고. 시그니처 변경 시 호출부 회귀 위험이 더 커서 의도적 보류.
- pyright import 미해결(sb_helpers/composite_calculator/pii_crypto 등): **전부 pre-existing** — 런타임 `PYTHONPATH=server`로 해석되는 상대 import, 이번 변경과 무관.

## 머지 판단
- **머지 필요**: Yes
- **브랜치**: `task/task-2924-dev1`
- **워크트리 경로**: `/home/jay/projects/InsuRo-worktrees/task-2924-dev1`
- **base**: origin/main `f3e801b` (task 지시 명시)
- **원격 head**: `7aaf375` (push 확인 — 로컬=원격 일치)
- **merge_policy**: none — 머지는 ANU 독립검증 후 수행. **순서 B**: 봇=push+.done, 검증·Codex 재확인·머지·전체 단일 재배포는 ANU.
- **머지 의견**: surgical 3파일. 회귀 0(1299 passed clean env). 실 데이터 L1로 본문 오염 0건·메타 신호 확증. newsletter_review/validation/src 불변. **소식지 스택(2916/2921/2918/2914/15/2923 등)과 coordinated 단일 재배포**(systemd insuro-api) 대상 — 회장 go 대기.

## 모델 사용 기록
- 불칸(백엔드): sonnet — 일반 코딩/로직 구현(3파일 surgical 수정 + 테스트 갱신)
- 팀장(헤르메스, Opus): 설계/코드파악/검증/L1/보고만. 직접 코딩 없음.

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

