Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.1] — 2026-07-07

PATCH release. 상류 `edwardkim/rhwp` 의 HWPX serializer fidelity 개선을 흡수한다 — 특히 본 프로젝트가 보고한 이슈 [#1451](https://github.com/edwardkim/rhwp/issues/1451) (legacy 도형 `shapeComment` 미직렬화) 이 상류에서 해결되어, `verify_hwpx_roundtrip` 의 보존 boundary 가 도형 shapeComment 까지 실질 확대됐다. 공개 API / IR schema (`"1.1"`) 불변, 표면 신규 0. spec / ADR: [docs/roadmap/v0.8.1/hwpx-fidelity-sync.md](docs/roadmap/v0.8.1/hwpx-fidelity-sync.md) / [docs/design/v0.8.1/hwpx-fidelity-sync-research.md](docs/design/v0.8.1/hwpx-fidelity-sync-research.md).

### Changed

- `verify_hwpx_roundtrip()` 의 round-trip 무손실 범위가 legacy 도형 (polygon / ellipse 등) `shapeComment` 까지 넓어졌다 — 상류 #1451 해결을 pin 으로 흡수한 결과. `verify_hwpx_roundtrip` 시그니처·반환 타입 (`RoundtripReport`) 은 불변이고, 상류 `diff_documents` 가 비교하는 필드 집합만 넓어진 하위호환 확대다. 이전에 `table-vpos-01.hwpx` 의 도형 캡션이 round-trip 에서 소실되던 것이 이제 보존된다.

### Tests

- `tests/test_hwpx_writeback.py` 의 `test_lossy_document_reports_human_readable_differences` 재설계 — 상류 #1451 해결로 `table-vpos-01.hwpx` 가 무손실 round-trip 이 되어 자연 발생 손실 fixture 가 소멸했다. (1) 도형 shapeComment 무손실 positive (`test_shapecomment_document_roundtrips_lossless` — 손실 재발 시 red 회귀 가드) + (2) `RoundtripReport(ok=False, ...)` unit-construct 로 손실 리포트 계약 (non-empty `list[str]` + False 방향 invariant) 을 검증하는 `test_report_surfaces_differences_when_constructed_lossy` 로 교체. 손실 detection 자체는 상류 `diff_documents` 계약에 위임 (현 fixture 코퍼스 무손실).

### Build

- `external/rhwp` submodule pin `7d9aae7f` (v0.7.16 + 36) → `10f5c51e` (v0.7.17 + 68, 상류 `main` HEAD). v0.8.0 GA 기록 이후 흡수된 238 commit 을 확정. **본 binding 관점 회귀 0** — 공개 API / IR schema (`"1.1"`) / wheel 의존성 불변. 우리가 소비하는 상류 심볼 (`DocumentCore` 메서드 / `serialize_hwpx` / `diff_documents` / `svgs_to_pdf` / `RasterRenderOptions` / `LayerRasterRenderer` / `Paragraph::control_text_positions` / `utf16_pos_to_char_idx`) 시그니처 전부 불변. 검증: `cargo check` clean, `maturin develop --release` clean, `pytest -m "not slow"` 회귀 0, IR baseline byte-equal (`aift.hwp` + `table-vpos-01.hwpx`).
- 흡수한 핵심 상류 개선: **legacy 도형 shapeComment 직렬화 (#1451** — 본 프로젝트 보고, commit `2d6d0cf9` + `c0f94fbb`). 이로써 `verify_hwpx_roundtrip` 의 `diff_documents` 위임 검증이 도형 shapeComment 손실을 자동 반영한다. 그 외 바탕쪽 MasterPage 직렬화 / 쪽 테두리 pageBorderFill / 묶음 개체 자식 좌표 등 serializer emit 개선이 동반되나, 상류 `diff_documents` 가 비교하지 않아 `verify_hwpx_roundtrip` 보장 범위에는 미포함 (emit ≠ verify).
- 상류 이슈 문서 [docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md](docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md) 를 RESOLVED (in-place Frozen) 로 전환 — 본 프로젝트 보고 → 상류 머지 경로 완결.
- `Cargo.toml` 의 `version` `0.8.0` → `0.8.1`. `pyproject.toml` 은 `dynamic = ["version"]` 으로 자동 추종.

## [0.8.0] — 2026-06-21

MINOR release. parse 한 `Document` 를 HWPX 로 저장했을 때 IR 의미가 보존되는지 검증하는 `Document.verify_hwpx_roundtrip()` 표면을 추가하고, 보존 boundary 를 v0.7.0 의 텍스트·문단에서 상류 `diff_documents` 가 실제 비교하는 필드 집합 (표 cell·캡션·page_break, 그림 크기·캡션, char_shape·lineseg, PageDef, 리소스·BinData count) 으로 확대한다. 직렬화·진단 모두 상류 위임 — 추가만 있고 기존 표면 보존, IR schema (`"1.1"`) 변경 0. 동시에 상류 v0.7.13 ~ v0.7.16 sync 를 흡수한다.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rhwp-python"
version = "0.8.0"
version = "0.8.1"
edition = "2021"
# ^ rust-version 미명시 — 상위 rhwp crate 정책(stable Rust, MSRV unclaimed) 준수.
# PyO3 0.28 이 Rust 1.83+ 요구하지만, 이는 README 에 문서로 안내
Expand Down
98 changes: 98 additions & 0 deletions docs/design/v0.8.1/hwpx-fidelity-sync-research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
status: Draft
description: "v0.8.1 hwpx-fidelity-sync ADR — 'test_lossy' 재설계 방식 / PATCH 등급 판단 / pin 추적 브랜치 3건의 옵션 비교와 근거"
target: v0.8.1
last_updated: 2026-07-07
---

# v0.8.1 hwpx-fidelity-sync — 설계 의사결정 리서치 요약

[v0.8.1/hwpx-fidelity-sync.md](../../roadmap/v0.8.1/hwpx-fidelity-sync.md) §결정 사항 중 외부 독자가 "왜?" 를 던질 만한 **3**건의 업계 선례·대안·실패 시나리오를 기록한다. spec 본문이 최종 결정을 기술하고, 본 문서는 그 결정의 근거를 담는다.

## 결정 매트릭스

| # | 항목 | 옵션 비교 | 채택 | 1차 근거 |
|---|---|---|---|---|
| 1 | `test_lossy` 재설계 | A: 새 손실 fixture 탐색 / B: xfail 대기 / C: 손실 e2e 제거 → invariant 흡수 / D: C + `RoundtripReport` unit-construct 계약 검증 | D | detection 은 상류 `diff_documents` 위임, binding 의 `ok=False` 리포트 분기는 unit 으로 가드 |
| 2 | 버전 등급 | A: PATCH v0.8.1 / B: MINOR v0.9.0 | A | 공개 표면 신규 0 · breaking 0, v0.6.1 전례 동형 |
| 3 | pin 추적 브랜치 | A: `main` (`10f5c51e`) / B: `devel` (`bca79ee5`) | A | `devel` 미릴리스, upstream 자체가 `main` 으로만 GA |

## 1. `test_lossy` 재설계

### 팩트

- `table-vpos-01.hwpx` 는 v0.8.0 시점 (pin `7d9aae7f`) 에서 polygon 2개의 `<hp:shapeComment>다각형입니다.</hp:shapeComment>` 가 `serialize_hwpx` 출력에서 소실되어 `verify_hwpx_roundtrip().ok == False` 였다 (`docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md` 재현 절).
- 상류 #1451 해결 (commit `2d6d0cf9` legacy 도형 `shapeComment` 직렬화 1단계, `c0f94fbb` Polygon 보존 가드 2단계) 로 현재 pin `10f5c51e` 에서 동일 문서가 `report.ok is True` / `report.differences == []` (실측: `pytest` 에서 `test_lossy_document_reports_human_readable_differences` FAILED — `RoundtripReport(ok=True, differences=[])`).
- 현 fixture 코퍼스는 `aift.hwpx` (HWP5→HWPX 변환) 와 `table-vpos-01.hwpx` 둘 뿐이고 양쪽 모두 무손실 round-trip.
- `verify_hwpx_roundtrip` 은 `self.inner.document()` 를 serialize → reparse → `diff_documents(원본, 재파싱)` 하는 self round-trip 이라 외부 손상 주입 지점이 없다 — 손실은 오직 상류 serializer 가 아직 못 쓰는 요소에서만 자연 발생.
- 상류 `diff_documents` 가 비교하지 않는 요소 (수식 script / cell rowspan·colspan / BinData byte / 도형 raw) 는 차이가 있어도 검출되지 않으므로 손실 유도 fixture 로 부적합.

### 검증자 반박

- "손실 검출 e2e 를 없애면 verify 가 실제 손실을 잡는지 누가 보증하나?" → 상류가 `IrDifference` 를 방출하는지는 상류 회귀 테스트 (`task1392_shape_comment_loss_in_gate` 등 `ObjectComment` 게이트) 가 가드한다. 본 binding 의 변환 (`IrDifference` → `d.to_string()`) 은 순수 매핑이라 상류 계약이 성립하면 표면도 성립.
- "상류가 회귀하면 무손실 positive 가 놓치지 않나?" → 회귀 시 `table-vpos-01.hwpx` 의 AC-1 이 `ok is True` 위반으로 red 전환되어 자동 검출된다 — negative 를 positive 로 뒤집었을 뿐 검출력은 보존.
- "새 손실 fixture (A) 가 더 견고하지 않나?" → 상류가 143 HWPX 샘플 xfail 0 에 도달해 자연 손실 문서 확보가 난망하고, 확보하더라도 상류의 다음 fidelity 개선에 또 무효화되어 테스트가 상류 구현 진척에 결합된다 (동일 실패의 반복).
- "xfail (B) 로 두면 의도가 보존되지 않나?" → xfail 은 "언젠가 xpass 기대" 의미인데 여기선 반대 (상류 회귀 시 손실 재발) 라 의미가 뒤집힌다. invariant 흡수가 semantics 정합.
- "손실 e2e 를 지우면 binding 의 `ok=False` 분기가 아무 테스트도 안 타지 않나?" → 맞다 — 이것이 옵션 C 의 잔여 공백이다. `RoundtripReport` 는 invariant 를 model-enforce 하지 않고 (`python/rhwp/document.py` plain Pydantic) Rust 가 `ok = differences.is_empty()` (`src/document.rs:341`) 로 설정하기에만 성립하므로, 이 분기를 미검증으로 두면 리포트 계약이 무가드가 된다. 옵션 D 는 `RoundtripReport(ok=False, differences=[...])` 를 unit 으로 구성·assert 하여 non-empty str list + False 방향 invariant 를 e2e 없이 가드한다 (detection 위임은 유지, binding 표면 계약만 추가 검증).

### 최종 결정

D 채택. 손실 검출 e2e (`test_lossy...`) 를 제거하고 무손실 positive (AC-1/2) + invariant (AC-3) 로 재편하되, binding 의 `ok=False` 리포트 분기는 `RoundtripReport(ok=False, differences=[...])` unit-construct 로 계약 (AC-4) 을 가드한다. detection 자체는 상류 `diff_documents` 계약에 위임하고, 상류 회귀는 positive 테스트의 red 전환으로 자동 노출된다.

### 1차 소스

- 상류 이슈: <https://github.com/edwardkim/rhwp/issues/1451>
- 상류 해결 commit: `2d6d0cf9` (legacy 도형 shapeComment 직렬화 1단계), `c0f94fbb` (Polygon 보존 가드 2단계)
- upstream 이슈 초안 (재현 절 포함): `docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md`

## 2. 버전 등급

### 팩트

- 현재 pin `10f5c51e` 에서 `cargo check` clean, `pytest -m "not slow"` 604 passed. 실패 2건은 (i) `test_version_is_package_version` — `.venv` 의 낡은 `rhwp_python-0.7.0.dist-info` 로 인한 로컬 메타데이터 불일치 (upstream 무관, 재설치로 해소) (ii) 본 spec 대상 `test_lossy`.
- 우리 소비 심볼 (`DocumentCore` 6 메서드 / `serialize_hwpx` / `diff_documents` / `svgs_to_pdf` / `RasterRenderOptions` / `LayerRasterRenderer` / `Paragraph::control_text_positions` / `utf16_pos_to_char_idx`) 시그니처 불변.
- IR schema 는 `"1.1"` 유지, `verify_hwpx_roundtrip` / `RoundtripReport` 시그니처·의미 불변, 신규 public 메서드·모듈 0.
- v0.6.1 은 "상류 v0.7.11~v0.7.12 GA 흡수 + polish" 를 PATCH 로 발행 (CHANGELOG `[0.6.1]`).

### 검증자 반박

- "보존 boundary 확대는 사용자 계약 강화라 MINOR 아닌가?" → 확대는 상류 `diff_documents` 위임의 자동 반영이지 binding 표면 추가가 아니다. 사용자가 호출하는 계약 (`verify_hwpx_roundtrip` 시그니처·반환 타입·의미) 은 불변이고 보장 범위만 상류를 따라 넓어진다 — SemVer 상 하위호환 확대라 PATCH 가 정합.
- "로드맵 v0.9.0 예약과 충돌하지 않나?" → v0.9.0 (HWP5 binary writeback) 은 별개 스코프이고, v0.8.1 은 그 사이 삽입되는 PATCH 라 SemVer 단조 증가를 깨지 않는다.

### 최종 결정

A 채택 (v0.8.1 PATCH). 공개 표면 신규 0 · breaking 0 · IR schema 불변이라 상류 GA 흡수 성격의 PATCH 로 발행하고, MINOR 예약 (v0.9.0) 은 보존한다.

### 1차 소스

- SemVer 2.0.0: <https://semver.org/spec/v2.0.0.html>
- 전례: `CHANGELOG.md` `[0.6.1]` 섹션 (상류 GA 흡수 PATCH)

## 3. pin 추적 브랜치

### 팩트

- 상류 원격 상태 (`git ls-remote`): `main` = `10f5c51e` (2026-06-25), `devel` = `bca79ee5` (2026-07-07). `main..devel` = 959 commit, `devel..main` = 14 commit.
- `.gitmodules` 는 `branch = main` 으로 고정. 우리 pin `10f5c51e` 는 `main` HEAD 와 동일하고 v0.7.17 태그보다 68 commit 앞선다.
- `devel` 최근 활동은 내부 리팩토링 (라운드 1~4: `parse_paragraph_list` 해체, `object_ops.rs` 9839줄 하위 모듈 분해, `layout_composed` 226→146) — main→devel diff 1561 파일 / +130K / -17K.
- 본 spec 이 흡수하는 fidelity 개선 (#1451 등) 은 이미 `main` HEAD (`10f5c51e`) 에 머지되어 포함됨.
- 우리 소비 심볼은 `devel` 에서도 시그니처 보존 (대조 완료) — 파일 위치만 이동 (`document_core/commands/`, `queries/` 하위 분해).

### 검증자 반박

- "devel 리팩토링이 크니 미리 채택해 다음 sync 를 줄이는 게 낫지 않나?" → 미릴리스 개발 브랜치라 미완 리팩토링의 회귀를 조기 노출하고 upstream 이 아직 검증 중인 상태에 결합된다. upstream 자체도 `main` merge 로만 GA 를 낸다.
- "devel 을 채택하면 fidelity 를 더 얻나?" → 본 spec 이 목표하는 fidelity 는 이미 `main` 에 있다. devel 추가분은 주로 내부 리팩토링이라 사용자 관찰 이득이 미미하고 계약은 불변.

### 최종 결정

A 채택 (`main`, pin `10f5c51e`). `.gitmodules` 의 `main` 추적 정책을 유지하고 `devel` 리팩토링은 상류 GA 시점에 흡수한다.

### 1차 소스

- `.gitmodules` (`branch = main`)
- 상류 브랜치 구조: <https://github.com/edwardkim/rhwp>

## 참조

- [roadmap/v0.8.1/hwpx-fidelity-sync.md](../../roadmap/v0.8.1/hwpx-fidelity-sync.md) — 본 리서치의 결정 요약
- 상류 이슈: [edwardkim/rhwp#1451](https://github.com/edwardkim/rhwp/issues/1451)
13 changes: 13 additions & 0 deletions docs/roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ rhwp-python 의 버전별 로드맵 + **활성 spec 인덱스 SSOT**. 모든 spe
| v0.6.0 (png-vlm-render) | Frozen | [v0.6.0/png-vlm-render.md](v0.6.0/png-vlm-render.md) | [design/v0.6.0/png-vlm-render-research.md](../design/v0.6.0/png-vlm-render-research.md) |
| v0.7.0 (hwpx-writeback-baseline) | Frozen | [v0.7.0/hwpx-writeback-baseline.md](v0.7.0/hwpx-writeback-baseline.md) | [design/v0.7.0/hwpx-writeback-baseline-research.md](../design/v0.7.0/hwpx-writeback-baseline-research.md) |
| v0.8.0 (hwpx-writeback-expansion) | Frozen | [v0.8.0/hwpx-writeback-expansion.md](v0.8.0/hwpx-writeback-expansion.md) | [design/v0.8.0/hwpx-writeback-expansion-research.md](../design/v0.8.0/hwpx-writeback-expansion-research.md) |
| v0.8.1 (hwpx-fidelity-sync) | Draft | [v0.8.1/hwpx-fidelity-sync.md](v0.8.1/hwpx-fidelity-sync.md) | [design/v0.8.1/hwpx-fidelity-sync-research.md](../design/v0.8.1/hwpx-fidelity-sync-research.md) |

## 미착수 작업 계획

Expand Down Expand Up @@ -73,6 +74,18 @@ SemVer 0.x.y 단계에서 minor 는 단조 증가. v1.0.0 은 API 안정 선언
- 완전한 레이아웃 보존 (폰트 embedding 미포함 상태의 재생성) — 뷰어 차이 허용
- 매크로·폼 필드·OLE 임베딩 — HWP 독자 확장 기능은 장기 과제

### IR 노출 확장 — 상류 파싱 정보의 미노출 갭 해소

2026-07-07 갭 조사 결과: 상류 `model` 은 완전한 문서 모델 (글자·문단 스타일, 문서 메타데이터, 하이퍼링크, 차트 데이터 등) 을 파싱하나 우리 IR 은 텍스트 + 기본 구조만 노출한다. 대부분 상류가 이미 파싱해 두므로 얇은 wire-through 로 메울 수 있다 (신규 파싱 구현 아님). v0.3.0 "IR 확장" 라인의 연장이며 위 writeback 라인과 독립 축. 버전 분할은 착수 시점 `/new-spec` 으로 확정.

우선순위 (RAG/문서이해 가치 × 노력):

- **티어 1 — 값싼 노출, 즉시 가치** (상류 파싱 완료, 현재 IR 이 버림): 문서 메타데이터 (제목·작성자·작성일, 현재 항상 빈값) · 글자 스타일 확장 (크기·색상·폰트, 현재 굵기/기울임/밑줄/취소선 4개만) · 문단 속성 (정렬·들여쓰기·줄간격, 현재 head_type/level 만) · 하이퍼링크 URL (현재 컨트롤 통째 미노출).
- **티어 2 — 중간 노력, 큰 기능**: 차트 구조화 데이터 추출 (`series{name, values}` — 읽기 전용, 현재 SVG 렌더만) · 페이지 단위 Markdown / 레이아웃 인식 텍스트 (RAG 청킹, 현재 전체 IR 순서 `extract_text` 만) · HWP5 binary writeback (`to_hwp`, 상류 준비 완료 — 위 writeback 라인과 합류).
- **티어 3 — 상황별**: 표 스타일 (제목행 반복·테두리·셀 정렬) · 그림 기하·효과·alt-text · 전체 텍스트 검색 · HTML 렌더.

양식개체 (체크박스/버튼) 는 위 비범위의 "폼 필드" 와 일치 — 장기 과제로 유지. 편집 commands (에디터 지향) 와 문서 생성 빌더는 "문서 이해·변환" 정체성 밖.

> 과거 GA 완료된 minor (v0.1.x ~ v0.3.0) 의 historical record 는 [v0.1.0/rhwp-python.md](v0.1.0/rhwp-python.md) / [v0.2.0/ir.md](v0.2.0/ir.md) / [v0.3.0/ir-expansion.md](v0.3.0/ir-expansion.md) / [v0.3.0/cli.md](v0.3.0/cli.md) 가 보유.

## 구현 / 검증 로그 (Frozen)
Expand Down
Loading
Loading