본문으로 건너뛰기

ADR 0051: HWP born-digital 텍스트 추출

배경

한국 법률 사무소 문서의 사실상 표준은 HWP 다. 그런데 사무소 기억 (legacyDocuments) 학습 파이프라인은 Cloud Vision OCR 단일 경로였고, Vision 은 HWP 를 읽지 못한다. "대부분 HWP" 인 사무소의 현실 (2026-06-10 진단):

경로HWP 동작
사건 서류 업로드허용 — 보관만 되고 학습 0
사무소 기억 스캔 업로드accept 자체가 image/*,.pdf — 정문 없음
사건 종결 자동 유입contentType 필터 없이 Vision OCR 에 투입 → 전건 실패

자동 유입 실패는 단순 누락이 아니라 한도 잠식이다 — failed legacyDoc 도 legacyDocuments count() 기반 한도 슬롯을 차지한다. HWP 사무소는 종결할 때마다 실패 문서만 쌓이고, 한 줄 본질 ("자기 데이터로 자기 AI 학습") 의 입력 파이프가 통째로 끊긴다.

핵심 통찰: HWP 는 스캔 이미지가 아니라 born-digital 텍스트다. OCR 이 필요 없고, 직접 추출하는 쪽이 더 정확하며 (오인식 0) Vision 호출 비용도 0 이다. 스캔본보다 오히려 고품질 학습 입력이다.

결정

1. 순수 추출 모듈 — functions/src/born-digital-extract.ts

진입점 extractBornDigitalText(buf, fileName) 하나. 바이트 시그니처 우선 판별 (확장자는 TXT 만 보조):

포맷판별추출
HWP 5.xCFB 시그니처 + FileHeader 스트림 "HWP Document File" 서명BodyText/Section* raw deflate 해제 → HWPTAG_PARA_TEXT 레코드의 UTF-16LE 디코딩 (제어문자 스펙 처리 — 인라인/확장 컨트롤 8-WCHAR skip, 탭/하이픈/빈칸 치환)
HWPXZIP + mimetype=application/hwp+zip 또는 Contents/content.hpfContents/section*.xml 의 <hp:t> 문단 단위 수집 + 엔티티 복원
DOCXZIP + word/document.xml<w:t> 동일 방식
TXT.txt 확장자 (알려진 바이너리 매직 차단)UTF-8 / UTF-16 BOM / EUC-KR(CP949) 순서 시도

근거 스펙: 한컴 공개 "한글문서파일형식 5.0", KS X 6101 (OWPML). 의존성은 cfb 1개 (순수 JS, CFB·ZIP 양쪽 파싱 — SheetJS 계열) + Node 내장 zlib/TextDecoder (Node 24 full-ICU 로 EUC-KR 지원).

2. 반환 계약 — null 폴백 vs fail-loud throw

  • born-digital 미식별 (이미지·PDF·일반 zip·.doc 등 비 HWP OLE) → null 반환. 호출부가 기존 Vision OCR 경로로 폴백 — 현행 동작 보존.
  • 포맷은 식별됐으나 추출 불가BornDigitalExtractError throw (ADR 0029 fail-loud). 사유별 한국어 메시지가 ocrError 로 그대로 노출:
    • hwp5-encrypted — 암호 설정 문서 (암호 해제 사본 재업로드 안내)
    • hwp5-distribution — 배포용(보기 전용) 문서
    • hwp5-malformed — 레코드/압축 손상
    • txt-unknown-encoding — UTF-8/EUC-KR 외 + 제어문자 감지 (바이너리 위장)
    • empty-text — 본문 0자 (이미지 전용 문서)

silent 부분 추출 (PrvText 미리보기 폴백 등) 은 채택하지 않는다 — 잘린 텍스트가 전문인 양 학습되는 쪽이 더 위험하다.

3. 보안 — 파싱만, 실행 없음

HWP 는 국내 APT 공격의 단골 벡터 (OLE/EPS 개체 삽입) 다. 본 모듈은 렌더링·매크로·OLE 개체 해석 없이 텍스트 레코드만 파싱하고, ICU 디코더가 fatal 모드에서도 통과시키는 C1 제어문자 (실측: 미매칭 EUC-KR lead byte → U+0080~U+009F) 까지 가드해 바이너리 위장 입력을 차단한다.

4. 검증 — 합성 픽스처 + 실파일 이중

  • 단위 테스트 31종: 스펙대로 CFB 컨테이너·레코드·zip 을 직접 조립한 합성 픽스처 (외부 파일 의존 0 — 목 데이터 원칙)
  • 실파일 smoke: 한컴 산출 HWP 5.x 3종 (pyhwp OSS 픽스처 — aligns · charshape · footnote-endnote) 본문/각주/미주 추출 확인 (2026-06-10)

구현 (3 PR)

  1. PR-1 (본 ADR): 추출 모듈 + 테스트 + cfb 의존성 — 행동 변화 없음
  2. PR-2: processFileForLegacy 분기 wiring — born-digital 이면 Vision 우회 (confidence=1.0, extractionMethod 기록), 자동 유입 fileEntries 확장자 필터 (.doc 등 미지원 → skip + 알림 가시화, 한도 잠식 차단)
  3. PR-3: 스캔 업로드 모달 accept 확장 (.hwp,.hwpx,.docx,.txt) + 카피

비범위 (후속)

  • 출력측 HWP 네이티브 생성 — ADR 0012 S2 백로그 유지 (법원 전자소송은 PDF 변환 제출이라 docx+PDF 로 운영 가능)
  • 포털 의뢰인 업로드 HWP 허용 — 비신뢰 입력 경로라 별도 보안 검토 필요
  • 수신 서류 분류 파이프라인 (incoming-doc-pipeline) 의 HWP 연결
  • incoming-content-gate 의 hwp 바이트 검증 (현재 hwp 는 스니퍼 미지원 통과 — magic-bytes.ts 확장 후속)
  • .doc (구형 Word 바이너리) 추출 — 수요 확인 후

영향

  • HWP 사무소의 기존 서면이 사무소 기억 학습에 진입 가능해짐 — 도입 온보딩 시나리오 ("진행 중 사건 + 기존 서류 반입") 의 선행 조건 해소
  • Vision 호출 절감 (born-digital 은 추출이 OCR 보다 싸고 정확)
  • 실패 legacyDoc 의 한도 슬롯 잠식 차단 (PR-2 필터)