From 6e0aceb18cb3d95b7bf312f8e058d851e8d69fd3 Mon Sep 17 00:00:00 2001 From: danmeon Date: Tue, 7 Jul 2026 14:49:54 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20v0.8.1=20=E2=80=94=20=EC=83=81?= =?UTF-8?q?=EB=A5=98=20HWPX=20fidelity=20sync=20=ED=9D=A1=EC=88=98=20(?= =?UTF-8?q?=EB=8F=84=ED=98=95=20shapeComment=20=EC=9D=B4=EC=8A=88=201451)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 본 프로젝트가 보고한 상류 이슈 1451 (legacy 도형 shapeComment 미직렬화) 해결로 verify_hwpx_roundtrip 의 보존 boundary 가 도형 shapeComment 까지 실질 확대됨. 무손실화로 무효화된 negative 테스트 test_lossy 를 무손실 positive + RoundtripReport(ok=False) 계약 검증으로 재설계함. 코드 변경 0 — 테스트 1파일 + CHANGELOG/spec/버전 bump. --- CHANGELOG.md | 19 ++++ Cargo.toml | 2 +- .../v0.8.1/hwpx-fidelity-sync-research.md | 98 +++++++++++++++++++ docs/roadmap/README.md | 13 +++ docs/roadmap/v0.8.1/hwpx-fidelity-sync.md | 46 +++++++++ docs/traces/coverage.md | 5 +- docs/upstream/README.md | 2 +- .../issue-hwpx-shapecomment-drawing-shapes.md | 4 +- tests/test_hwpx_writeback.py | 37 ++++--- 9 files changed, 211 insertions(+), 15 deletions(-) create mode 100644 docs/design/v0.8.1/hwpx-fidelity-sync-research.md create mode 100644 docs/roadmap/v0.8.1/hwpx-fidelity-sync.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e07d541..3c178de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 를 흡수한다. diff --git a/Cargo.toml b/Cargo.toml index 137bc21..1a363f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 에 문서로 안내 diff --git a/docs/design/v0.8.1/hwpx-fidelity-sync-research.md b/docs/design/v0.8.1/hwpx-fidelity-sync-research.md new file mode 100644 index 0000000..fd9aef8 --- /dev/null +++ b/docs/design/v0.8.1/hwpx-fidelity-sync-research.md @@ -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개의 `다각형입니다.` 가 `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차 소스 + +- 상류 이슈: +- 상류 해결 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: +- 전례: `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`) +- 상류 브랜치 구조: + +## 참조 + +- [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) diff --git a/docs/roadmap/README.md b/docs/roadmap/README.md index 35a242f..f2c1bd6 100644 --- a/docs/roadmap/README.md +++ b/docs/roadmap/README.md @@ -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) | ## 미착수 작업 계획 @@ -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) diff --git a/docs/roadmap/v0.8.1/hwpx-fidelity-sync.md b/docs/roadmap/v0.8.1/hwpx-fidelity-sync.md new file mode 100644 index 0000000..d9cbd1b --- /dev/null +++ b/docs/roadmap/v0.8.1/hwpx-fidelity-sync.md @@ -0,0 +1,46 @@ +--- +status: Draft +description: "v0.8.1 — 상류 HWPX serializer fidelity 개선 흡수 (legacy 도형 shapeComment #1451 등) + 'verify_hwpx_roundtrip' 보존 boundary 실질 확대 + 무효화된 'test_lossy' 재설계" +target: v0.8.1 +last_updated: 2026-07-07 +--- + +# v0.8.1 — HWPX fidelity 상류 sync + +v0.8.0 이 상류 `diff_documents` 검증 범위로 확대한 `verify_hwpx_roundtrip` 의 보존 boundary 를, 그 사이 상류 HWPX serializer 가 도달한 fidelity 개선까지 흡수한다. 핵심 계기는 상류 [#1451](https://github.com/edwardkim/rhwp/issues/1451) — 본 프로젝트가 보고한 legacy 도형 (polygon / ellipse 등) `shapeComment` 미직렬화가 상류에서 해결되어, 회귀 fixture `table-vpos-01.hwpx` 가 무손실 round-trip 으로 전환됐다. + +verify 로직은 상류 `diff_documents` 위임이라 이 개선이 코드 변경 없이 반영된다. 본 릴리스의 작업은 흡수한 상류 커밋의 submodule pin 갱신 문서화 + 상류 개선으로 무효화된 negative 회귀 테스트 (`test_lossy_document_reports_human_readable_differences`) 재설계에 한정된다. 공개 API·IR schema (`"1.1"`) 불변, 추가 표면 없음. + +주요 결정의 근거·대안·실패 시나리오는 짝 페어: [hwpx-fidelity-sync-research.md](../../design/v0.8.1/hwpx-fidelity-sync-research.md). + +## 결정 사항 + +| 항목 | 값 | 근거 | +|---|---|---| +| 1 — 버전 등급 | v0.8.1 PATCH | 공개 API 시그니처·IR schema (`"1.1"`) 불변, 표면 신규 0, breaking 0. 상류 GA 흡수 + 회귀 테스트 polish 성격이라 v0.6.1 (상류 v0.7.11~v0.7.12 흡수 PATCH) 전례 동형. 자세한 MINOR 대비 비교는 ADR §2. | +| 2 — submodule pin | `7d9aae7f` → `10f5c51e` (상류 v0.7.17 + 68, `main` HEAD) | v0.8.0 GA 기록 (`7d9aae7f`) 이후 흡수된 238 commit 을 pin 으로 확정. `main` 추적 유지 — `devel` (959 commit 리팩토링) 은 미릴리스라 미채택. 자세한 브랜치 비교는 ADR §3. | +| 3 — 보존 boundary 확대 반영 방식 | 상류 위임, binding 코드 변경 0 | `verify_hwpx_roundtrip` 이 `diff_documents` 위임이라 상류가 비교 대상에 추가한 필드만 자동 반영된다. 본 구간 실질 확대는 legacy 도형 `shapeComment` (`IrDifference::ObjectComment`, 상류 #1451) 1건. 동반된 serializer 개선 (바탕쪽 MasterPage / 쪽 테두리 element / 묶음 좌표) 은 `roundtrip.rs` 에 해당 diff variant 가 없어 (BorderFill 은 count 만 비교, v0.8.0 커버) boundary 밖 — emit ≠ verify (v0.8.0 § 배경 계승). 회귀 가드·문서만 갱신. | +| 4 — `test_lossy` 재설계 | 손실 e2e 제거 → 무손실 positive + invariant + `RoundtripReport` unit 계약 | `table-vpos-01.hwpx` 가 #1451 해결로 무손실화되어 자연 발생 negative fixture 가 소멸. detection 은 상류 `diff_documents` 위임, binding 의 `ok=False` 리포트 분기는 unit-construct 로 계약 가드. 옵션 A/B/C 비교는 ADR §1. | +| 5 — 상류 #1451 이슈 문서 전환 | RESOLVED (in-place Frozen) | 본 프로젝트가 보고한 이슈가 상류 머지됨. `docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md` 를 CONVENTIONS § upstream 정책대로 전환 — 본문 첫 헤더 위 `> **RESOLVED** — 상류 commit '2d6d0cf9'+'c0f94fbb'` 인용 블록 + frontmatter `status: Frozen`, 기존 body 보존. | + +## 인수조건 + +- **AC-1** — `table-vpos-01.hwpx` 를 parse → `verify_hwpx_roundtrip()` 결과가 `report.ok is True` 이고 `report.differences == []` (상류 #1451 해결로 polygon `shapeComment` 무손실 round-trip). +- **AC-2** — 무손실 문서 `aift.hwpx` 의 `verify_hwpx_roundtrip()` 는 `report.ok is True` + `report.differences == []` (v0.8.0 positive 보장 회귀 보존). +- **AC-3** — invariant `report.ok == (len(report.differences) == 0)` 가 모든 fixture 에서 성립 (양방향). +- **AC-4** — 손실 리포트 계약: `RoundtripReport(ok=False, differences=["..."])` 로 구성한 리포트에서 `differences` 는 비어있지 않은 `list[str]` (각 항목 `strip()` 후 비지 않음) 이고 invariant `ok == (len(differences) == 0)` 가 `False` 방향에서도 성립한다. e2e 손실 유도는 상류 무손실 도달로 현 코퍼스에서 불가 — detection 은 상류 `diff_documents` 계약 위임, 본 AC 는 binding 표면의 리포트 계약을 unit 으로 가드 (ADR §1). +- **AC-5** — 상류 pin `10f5c51e` 에서 `pytest -m "not slow"` 회귀 0. IR baseline byte-equal (`aift.hwp` + `table-vpos-01.hwpx`) 유지, 공개 API·IR schema (`"1.1"`) 불변. + +## 영구 비목표 + +- **HWP5 binary writeback** (`Document.to_hwp_bytes` / `export_hwp`) — v0.9.0 예약 스코프. 본 PATCH 는 HWPX 검증 경로만 다룬다. +- **`devel` 브랜치 리팩토링 (959 commit) 선제 채택** — 미릴리스 개발 브랜치라 `main` 추적 정책상 다음 GA sync 대상. 우리 소비 심볼 시그니처는 조사 시점 `devel` 대조에서 보존됐으나, 미채택 브랜치라 실제 채택 시 재검증 대상. +- **상류 `diff_documents` 미비교 요소의 round-trip 보장** — 수식 script (description 만 비교) / 표 cell rowspan·colspan / BinData 실제 byte / 도형 raw byte 는 상류 비교 밖이라 boundary 밖 (v0.8.0 비목표 계승). +- **mutable IR 편집 API** — 사용자가 IR 을 편집해 새 문서를 생성하는 빌더 표면은 v1.0 API 안정 시점 검토 (v0.8.0 비목표 계승). + +## 참조 + +- 짝 페어 (ADR): [hwpx-fidelity-sync-research.md](../../design/v0.8.1/hwpx-fidelity-sync-research.md) +- 상류 이슈: [edwardkim/rhwp#1451](https://github.com/edwardkim/rhwp/issues/1451) — legacy 도형 `shapeComment` 직렬화 +- upstream 이슈 초안 (RESOLVED 전환 대상): [issue-hwpx-shapecomment-drawing-shapes.md](../../upstream/issue-hwpx-shapecomment-drawing-shapes.md) +- CONVENTIONS § upstream/ 해결 정책: [CONVENTIONS.md](../../CONVENTIONS.md) diff --git a/docs/traces/coverage.md b/docs/traces/coverage.md index 0bd4e8d..42c3a82 100644 --- a/docs/traces/coverage.md +++ b/docs/traces/coverage.md @@ -644,8 +644,11 @@ v0.4.0+ 신규 spec 의 인수조건 ↔ 테스트 매핑. 기존 v0.1.0 ~ v0.3. | v0.7.0/hwpx-writeback-baseline | AC-6 | `tests/test_hwpx_writeback.py::TestAdditiveNoSideEffects::test_writeback_does_not_mutate_existing_surfaces` | | v0.8.0/hwpx-writeback-expansion | AC-1 | `tests/test_hwpx_writeback.py::TestExpansionTableAndPicture::test_table_and_picture_roundtrip_equivalent` | | v0.8.0/hwpx-writeback-expansion | AC-2 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_preserved_document_is_ok_with_invariant` | -| v0.8.0/hwpx-writeback-expansion | AC-3 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_lossy_document_reports_human_readable_differences` | | v0.8.0/hwpx-writeback-expansion | AC-3 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_preserved_document_differences_are_empty_str_list` | | v0.8.0/hwpx-writeback-expansion | AC-4 | `tests/test_hwpx_writeback.py::TestVerifyNoSideEffects::test_verify_does_not_mutate_existing_surfaces` | | v0.8.0/hwpx-writeback-expansion | AC-5 | `tests/test_hwpx_writeback.py::TestVerifyErrorContract::test_serializable_document_passes_verify_serialization` | | v0.8.0/hwpx-writeback-expansion | AC-6 | `tests/test_hwpx_writeback.py::TestV070GuaranteeIntact::test_text_paragraph_guarantee_holds_under_verify` | +| v0.8.1/hwpx-fidelity-sync | AC-1 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_shapecomment_document_roundtrips_lossless` | +| v0.8.1/hwpx-fidelity-sync | AC-2 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_preserved_document_is_ok_with_invariant` | +| v0.8.1/hwpx-fidelity-sync | AC-3 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_shapecomment_document_roundtrips_lossless` | +| v0.8.1/hwpx-fidelity-sync | AC-4 | `tests/test_hwpx_writeback.py::TestVerifyReport::test_report_surfaces_differences_when_constructed_lossy` | diff --git a/docs/upstream/README.md b/docs/upstream/README.md index 2c88db4..1c7ca80 100644 --- a/docs/upstream/README.md +++ b/docs/upstream/README.md @@ -11,7 +11,7 @@ | [issue-find-control-text-positions.md](issue-find-control-text-positions.md) | Frozen | [edwardkim/rhwp#390](https://github.com/edwardkim/rhwp/issues/390) | 2026-04-28 ([PR #405](https://github.com/edwardkim/rhwp/pull/405)) | `Paragraph::control_text_positions(&self)` 옵션 A 채택. v0.3.1 spec 이 본 파일 참조 → 삭제 대신 in-place Frozen | | [issue-utf16-pos-to-char-idx.md](issue-utf16-pos-to-char-idx.md) | Frozen | [edwardkim/rhwp#484](https://github.com/edwardkim/rhwp/issues/484) | 2026-04-30 ([PR #494](https://github.com/edwardkim/rhwp/pull/494)) | #390 후속 같은 결. `Paragraph::utf16_pos_to_char_idx(&self)` 옵션 A 채택. v0.3.2 spec 이 본 파일 참조 → 삭제 대신 in-place Frozen | | [issue-macos-png-coretext-hang.md](issue-macos-png-coretext-hang.md) | Frozen | [edwardkim/rhwp#823](https://github.com/edwardkim/rhwp/issues/823) | 2026-06-04 (상류 v0.7.13) | headless macOS PNG 렌더 hang (CoreText IPC). v0.8.0 상류 sync 로 fix 흡수 + `ci.yml` macOS smoke 잡 복원 | -| [issue-hwpx-shapecomment-drawing-shapes.md](issue-hwpx-shapecomment-drawing-shapes.md) | Active | [edwardkim/rhwp#1451](https://github.com/edwardkim/rhwp/issues/1451) | — | `render_common_shape_xml` 이 ellipse/arc/polygon/curve/chart/ole 의 `hp:shapeComment` 미방출 (#1392 후속). 제안 패치 적용 시 round-trip diff 0 실측 | +| [issue-hwpx-shapecomment-drawing-shapes.md](issue-hwpx-shapecomment-drawing-shapes.md) | Frozen | [edwardkim/rhwp#1451](https://github.com/edwardkim/rhwp/issues/1451) | 2026-07-07 (상류 commit `2d6d0cf9`+`c0f94fbb`) | legacy 도형 (`render_common_shape_xml`) `hp:shapeComment` 미방출 (#1392 후속). 상류 해결 흡수 (pin `10f5c51e`) → `table-vpos-01.hwpx` round-trip 무손실. v0.8.1 spec 이 본 파일 참조 → 삭제 대신 in-place Frozen | ## Archive 정책 diff --git a/docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md b/docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md index 66230b5..57e33b9 100644 --- a/docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md +++ b/docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md @@ -1,9 +1,11 @@ --- -status: Active +status: Frozen description: "업스트림 제안 — HWPX serializer 의 legacy 도형 경로(ellipse/arc/polygon/curve/chart/ole)가 hp:shapeComment 를 미직렬화. #1392 는 pic/equation/container/rect 4경로만 구현, render_common_shape_xml 누락. 실문서 round-trip 으로 polygon 설명 소실 재현. 제안 패치 diff 0 실측. 상류 등록 [#1451](https://github.com/edwardkim/rhwp/issues/1451)." last_updated: 2026-06-21 --- +> **RESOLVED 2026-07-07** — 상류 [#1451](https://github.com/edwardkim/rhwp/issues/1451) 머지로 해결. legacy 도형 shapeComment 직렬화 commit `2d6d0cf9` (1단계) + `c0f94fbb` (Polygon 보존 가드 2단계). rhwp-python pin `10f5c51e` 에 흡수 — `table-vpos-01.hwpx` round-trip 무손실 (v0.8.1). 아래 본문은 제안 시점 historical record. + > 외부 binding (`rhwp-python`) 구현 중 업스트림에서 수정이 필요해 보이는 부분을 발견하여, Claude 로 조사 및 다차례 사실 검증을 거친 결과입니다. # HWPX serializer: hp:shapeComment 미직렬화 — legacy 도형 경로 (ellipse/arc/polygon/curve) diff --git a/tests/test_hwpx_writeback.py b/tests/test_hwpx_writeback.py index a89a2c4..a6870da 100644 --- a/tests/test_hwpx_writeback.py +++ b/tests/test_hwpx_writeback.py @@ -158,6 +158,7 @@ def test_table_and_picture_roundtrip_equivalent( class TestVerifyReport: @pytest.mark.spec("v0.8.0/hwpx-writeback-expansion#AC-2") + @pytest.mark.spec("v0.8.1/hwpx-fidelity-sync#AC-2") def test_preserved_document_is_ok_with_invariant(self, samples_dir: Path) -> None: doc = rhwp.parse(str(samples_dir / "hwpx" / "aift.hwpx")) report = doc.verify_hwpx_roundtrip() @@ -174,20 +175,34 @@ def test_preserved_document_differences_are_empty_str_list(self, samples_dir: Pa assert isinstance(report.differences, list) assert report.differences == [] - @pytest.mark.spec("v0.8.0/hwpx-writeback-expansion#AC-3") - def test_lossy_document_reports_human_readable_differences( - self, parsed_hwpx: rhwp.Document - ) -> None: - # ^ table-vpos-01.hwpx 는 도형 shapeComment 가 round-trip 에서 손실되는 자연 - # 발생 fixture (상류 serializer 미직렬화) — verify 의 손실 검출력 회귀 가드. - # 특정 개수·메시지는 박지 않는다 (상류가 손실을 고치면 이 fixture 갱신). + @pytest.mark.spec("v0.8.1/hwpx-fidelity-sync#AC-1") + @pytest.mark.spec("v0.8.1/hwpx-fidelity-sync#AC-3") + def test_shapecomment_document_roundtrips_lossless(self, parsed_hwpx: rhwp.Document) -> None: + # ^ table-vpos-01.hwpx 의 도형 shapeComment 는 상류 #1451 (legacy 도형 + # shapeComment 직렬화) 해결로 이제 round-trip 무손실이다. 손실이 재발하면 + # ok is False 로 red — 상류 fidelity 회귀 가드. report = parsed_hwpx.verify_hwpx_roundtrip() - assert report.ok is False - assert report.differences, "verify must surface the upstream shapeComment loss" - assert all(isinstance(d, str) and d.strip() for d in report.differences) - # 불변은 양방향 모두 성립 + assert report.ok is True + assert report.differences == [] assert report.ok == (len(report.differences) == 0) + @pytest.mark.spec("v0.8.1/hwpx-fidelity-sync#AC-4") + def test_report_surfaces_differences_when_constructed_lossy(self) -> None: + # ^ 현 fixture 코퍼스는 무손실 (상류 fidelity 성숙) 이라 손실을 자연 재현할 수 + # 없다. detection 은 상류 diff_documents 에 위임하고, 여기서는 binding 표면의 + # 리포트 계약 — differences 가 채워졌을 때의 형태 + False 방향 invariant — 를 + # RoundtripReport 를 직접 구성해 가드한다. + lossy = rhwp.RoundtripReport( + ok=False, + differences=[ + 'section[0] paragraph[33]/ctrl[0] shape comment: expected="다각형입니다." actual=""' + ], + ) + assert lossy.differences, "lossy report must surface non-empty differences" + assert all(isinstance(d, str) and d.strip() for d in lossy.differences) + # 불변은 False 방향에서도 성립 + assert lossy.ok == (len(lossy.differences) == 0) + class TestVerifyNoSideEffects: @pytest.mark.spec("v0.8.0/hwpx-writeback-expansion#AC-4")