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")