Description
reverse_sync verify --branch=split/ko-proofread-20260221-administrator-manual-general 실행 시 45건 모두 PASS이지만, 7건의 문서에서 verify diff가 발생합니다. improved MDX → XHTML patch → forward 변환(round-trip) 과정에서 원본과 미세한 차이가 생기는 케이스입니다.
Diff 유형별 분류
| 패턴 |
파일 수 |
설명 |
<br/> 앞 trailing space 제거 |
4 |
<br/> 앞 공백이 round-trip에서 제거됨 |
| 테이블 separator 길이 차이 |
1 |
--- 길이가 round-trip에서 달라짐 |
| 빈 줄 추가 |
1 |
파일 끝에 빈 줄이 추가됨 |
들여쓰기 공백 차이 (* vs * ) |
1 |
리스트 마커 뒤 공백 수가 달라짐 |
Diff 발생 문서 목록
src/content/ko/administrator-manual/general/company-management/channels.mdx — <br/> 앞 trailing space
src/content/ko/administrator-manual/general/system/api-token.mdx — <br/> 앞 trailing space
src/content/ko/administrator-manual/general/system/integrations/integrating-with-slack-dm.mdx — <br/> 앞 trailing space
src/content/ko/administrator-manual/general/system/maintenance.mdx — 테이블 separator 길이
src/content/ko/administrator-manual/general/user-management.mdx — 파일 끝 빈 줄 추가
src/content/ko/administrator-manual/general/user-management/authentication/setting-up-multi-factor-authentication.mdx — <br/> 앞 trailing space
src/content/ko/administrator-manual/general/user-management/profile-editor/custom-attribute.mdx — 리스트 들여쓰기 공백
개별 검증 명령
BRANCH=split/ko-proofread-20260221-administrator-manual-general
for f in \
src/content/ko/administrator-manual/general/company-management/channels.mdx \
src/content/ko/administrator-manual/general/system/api-token.mdx \
src/content/ko/administrator-manual/general/system/integrations/integrating-with-slack-dm.mdx \
src/content/ko/administrator-manual/general/system/maintenance.mdx \
src/content/ko/administrator-manual/general/user-management.mdx \
src/content/ko/administrator-manual/general/user-management/authentication/setting-up-multi-factor-authentication.mdx \
src/content/ko/administrator-manual/general/user-management/profile-editor/custom-attribute.mdx \
; do
python bin/reverse_sync_cli.py verify "$BRANCH:$f"
done
Related tickets & links
- Branch:
split/ko-proofread-20260221-administrator-manual-general
근본 원인 분석
roundtrip_verifier.py의 normalize 함수들이 파이프라인의 실제 버그를 마스킹하고 있습니다. _normalize_table_cell_padding만이 정당한 normalization이며(테이블 포맷팅은 round-trip에서 본질적으로 lossy), 나머지 3가지 diff는 파이프라인이 whitespace 변경을 XHTML에 올바르게 반영하지 못하는 버그입니다.
1. <br/> 앞 trailing space 제거 (4건)
증상: improved MDX의 저장합니다. <br/>가 verify MDX에서 저장합니다.<br/>로 변경
근본 원인: <br/>는 XHTML에 실제 존재하는 요소가 아니라, forward converter가 <li> 안의 연속 <p> 사이에 삽입하는 합성(synthetic) 마커입니다.
파이프라인 추적:
-
Confluence XHTML 구조:
<li>
<p>...저장합니다.</p>
<ac:image ...>...</ac:image>
<p> </p>
</li>
XHTML에 <br> 요소는 존재하지 않습니다.
-
XHTML 패치 단계 (patch_builder.py): _has_inline_boundary_change()가 bold 경계 변경만 감지하므로, <br/> 앞 공백 변경에 대해 inline fixup이 트리거되지 않습니다. 텍스트 <p> 안에 <br />를 삽입하는 로직이 없어, 패치된 XHTML은 여전히 <p>...저장합니다.</p> (공백 없이 마침표로 끝남)입니다.
-
Forward 변환 단계 (core.py:916,933): convert_li가 연속 <p> 사이에 synthetic <br/>를 삽입하고 ''.join(li_itself)로 결합하므로, 저장합니다.<br/> (공백 없음)이 생성됩니다.
마스킹 normalizer: roundtrip_verifier.py:49-55의 _normalize_br_space()가 양쪽에서 <br/> 앞 공백을 제거하여 차이를 숨깁니다.
수정 방향: reverse_sync 패치 단계에서 improved MDX의 . <br/>를 감지하면, XHTML <p> 안에 <br />를 삽입하고 후속 빈 <p>를 제거해야 합니다. 즉, <br/> 앞 공백이 XHTML 구조 변경으로 표현되어야 합니다.
관련 코드:
bin/converter/core.py:916 — synthetic <br/> 삽입
bin/converter/core.py:933 — ''.join(li_itself) (공백 없이 결합)
bin/reverse_sync/patch_builder.py:131-144 — _has_inline_boundary_change() (bold만 감지)
bin/reverse_sync/roundtrip_verifier.py:49-55 — _normalize_br_space() (마스킹)
2. 테이블 separator 길이 차이 (1건)
증상: 테이블 --- separator와 셀 패딩 길이가 달라짐
근본 원인: Forward converter가 CJK display-width 기반(_display_width(): CJK 문자 폭 2)으로 컬럼 폭을 계산하지만, 원본 MDX는 단순 문자 수(character count) 기준으로 작성되었습니다. 이는 테이블 포맷팅이 round-trip에서 본질적으로 lossy한 특성이며, _normalize_table_cell_padding이 이를 정당하게 처리합니다.
이 케이스는 정상 동작입니다 — normalization이 필요한 유일한 경우입니다.
관련 코드:
bin/converter/core.py:119-128 — _display_width() (CJK 폭 2 계산)
bin/converter/core.py:1234 — "-" * col_widths[i] (separator 생성)
bin/reverse_sync/roundtrip_verifier.py:166-195 — _normalize_table_cell_padding() (정당한 normalization)
3. 파일 끝 빈 줄 추가 (1건)
증상: improved MDX에 없는 trailing 빈 줄이 verify MDX에 추가됨
근본 원인: 삭제된 empty MDX 블록에 대한 XHTML delete patch가 생성되지 않아, 원본 XHTML의 trailing <p/>가 그대로 남습니다.
파이프라인 추적:
-
원본 XHTML (page-id: 544375969)이 <p /><p />(빈 <p> 2개)로 끝남
-
MDX 블록 diff: empty-14 (두 번째 trailing 빈 줄)를 change_type: 'deleted'로 올바르게 감지
-
Delete patch 생성 실패 (patch_builder.py:1423): _build_delete_patch()가 find_mapping_by_sidecar()를 호출하지만, _build_mdx_to_sidecar_from_v3() (line 67-68)에서 empty 블록이 _NON_CONTENT 타입으로 제외되어 매핑을 찾지 못합니다. 결과: delete patch가 None으로 반환되어 아무 것도 삭제되지 않음
-
Forward 변환: 패치되지 않은 XHTML의 trailing <p/><p/>가 각각 \n을 생성 → 빈 줄 추가
마스킹 normalizer: roundtrip_verifier.py:58-65의 _normalize_trailing_blank_lines()가 trailing \n을 하나로 통일하여 차이를 숨깁니다.
수정 방향: _build_delete_patch()가 empty 블록 삭제 시 대응하는 trailing <p/>를 XHTML에서 제거할 수 있도록, sidecar 매핑에서 empty 블록을 제외하지 않거나, mapping.yaml의 mdx_blocks: [] 정보를 활용해야 합니다.
관련 코드:
bin/reverse_sync/patch_builder.py:67-68 — _NON_CONTENT에 empty 포함 → 매핑 제외
bin/reverse_sync/patch_builder.py:1423 — _build_delete_patch() → None 반환
bin/converter/core.py:853 — 빈 <p/>마다 '\n' 무조건 append
bin/reverse_sync/roundtrip_verifier.py:58-65 — _normalize_trailing_blank_lines() (마스킹)
4. 리스트 마커 뒤 공백 차이 (1건)
증상: improved MDX의 * Workflow(공백 1개)가 verify MDX에서 * Workflow(공백 2개)로 변경
근본 원인: text-level 패치에서 collapse_ws()가 마커 공백 차이를 소멸시켜 XHTML에 변경이 반영되지 않습니다.
파이프라인 추적:
-
원본 Confluence XHTML (page-id: 953221256):
<li><p> Workflow Approval Rule...</p></li>
<p> 안에 leading space가 존재합니다.
-
Forward 변환: convert_li가 prefix = " * " (공백 1개) + SingleLineParser가 <p> 내용 Workflow... (leading space 보존) → " * Workflow..." (공백 2개). 이것이 원본 MDX에 * 가 있는 이유입니다.
-
Improved MDX: 교정자가 * Workflow → * Workflow로 수정 (leading space 제거)
-
XHTML 패치 실패 (patch_builder.py:1179-1204):
_contains_preserved_anchor_markup() = True (<ac:image> 존재) → clean list 교체 차단
_old_plain = collapse_ws(_old_plain_raw) → "Workflow..." (leading space 소멸)
_new_plain = collapse_ws(_new_plain_raw) → "Workflow..." (leading space 소멸)
_contains_preserved_link_markup() = False (<ac:link> 없음) → raw plain 대신 collapsed plain 사용
transfer_old_plain == transfer_new_plain → 차이가 없으므로 XHTML 변경 없음
-
Forward 변환: 변경되지 않은 XHTML의 <p> Workflow...가 다시 * Workflow를 생성
마스킹 normalizer: roundtrip_verifier.py:43-45의 _normalize_consecutive_spaces_in_text()가 연속 공백을 1개로 축소하여 차이를 숨깁니다.
수정 방향: text-level 패치 경로에서 collapse_ws()가 마커 whitespace 차이를 보존하도록 수정하거나, <ac:image>만 있는 경우(regeneration 가능) clean list 교체를 허용해야 합니다. <ac:link> (regeneration 불가)와 <ac:image> (regeneration 가능)를 구분하는 것이 핵심입니다.
관련 코드:
bin/reverse_sync/patch_builder.py:1118-1119 — collapse_ws()가 marker ws 차이를 소멸
bin/reverse_sync/patch_builder.py:1190-1194 — preserve_visible_ws 플래그가 <ac:link>만 검사
bin/converter/core.py:898-899 — prefix = " * " (항상 공백 1개)
bin/converter/context.py:436-442 — navigable_string_as_markdown이 leading space 보존
bin/reverse_sync/roundtrip_verifier.py:43-45 — _normalize_consecutive_spaces_in_text() (마스킹)
Description
reverse_sync verify --branch=split/ko-proofread-20260221-administrator-manual-general실행 시 45건 모두 PASS이지만, 7건의 문서에서 verify diff가 발생합니다. improved MDX → XHTML patch → forward 변환(round-trip) 과정에서 원본과 미세한 차이가 생기는 케이스입니다.Diff 유형별 분류
<br/>앞 trailing space 제거<br/>앞 공백이 round-trip에서 제거됨---길이가 round-trip에서 달라짐*vs*)Diff 발생 문서 목록
src/content/ko/administrator-manual/general/company-management/channels.mdx—<br/>앞 trailing spacesrc/content/ko/administrator-manual/general/system/api-token.mdx—<br/>앞 trailing spacesrc/content/ko/administrator-manual/general/system/integrations/integrating-with-slack-dm.mdx—<br/>앞 trailing spacesrc/content/ko/administrator-manual/general/system/maintenance.mdx— 테이블 separator 길이src/content/ko/administrator-manual/general/user-management.mdx— 파일 끝 빈 줄 추가src/content/ko/administrator-manual/general/user-management/authentication/setting-up-multi-factor-authentication.mdx—<br/>앞 trailing spacesrc/content/ko/administrator-manual/general/user-management/profile-editor/custom-attribute.mdx— 리스트 들여쓰기 공백개별 검증 명령
Related tickets & links
split/ko-proofread-20260221-administrator-manual-general근본 원인 분석
roundtrip_verifier.py의 normalize 함수들이 파이프라인의 실제 버그를 마스킹하고 있습니다._normalize_table_cell_padding만이 정당한 normalization이며(테이블 포맷팅은 round-trip에서 본질적으로 lossy), 나머지 3가지 diff는 파이프라인이 whitespace 변경을 XHTML에 올바르게 반영하지 못하는 버그입니다.1.
<br/>앞 trailing space 제거 (4건)증상: improved MDX의
저장합니다. <br/>가 verify MDX에서저장합니다.<br/>로 변경근본 원인:
<br/>는 XHTML에 실제 존재하는 요소가 아니라, forward converter가<li>안의 연속<p>사이에 삽입하는 합성(synthetic) 마커입니다.파이프라인 추적:
Confluence XHTML 구조:
XHTML에
<br>요소는 존재하지 않습니다.XHTML 패치 단계 (
patch_builder.py):_has_inline_boundary_change()가 bold 경계 변경만 감지하므로,<br/>앞 공백 변경에 대해 inline fixup이 트리거되지 않습니다. 텍스트<p>안에<br />를 삽입하는 로직이 없어, 패치된 XHTML은 여전히<p>...저장합니다.</p>(공백 없이 마침표로 끝남)입니다.Forward 변환 단계 (
core.py:916,933):convert_li가 연속<p>사이에 synthetic<br/>를 삽입하고''.join(li_itself)로 결합하므로,저장합니다.<br/>(공백 없음)이 생성됩니다.마스킹 normalizer:
roundtrip_verifier.py:49-55의_normalize_br_space()가 양쪽에서<br/>앞 공백을 제거하여 차이를 숨깁니다.수정 방향: reverse_sync 패치 단계에서 improved MDX의
. <br/>를 감지하면, XHTML<p>안에<br />를 삽입하고 후속 빈<p>를 제거해야 합니다. 즉,<br/>앞 공백이 XHTML 구조 변경으로 표현되어야 합니다.관련 코드:
bin/converter/core.py:916— synthetic<br/>삽입bin/converter/core.py:933—''.join(li_itself)(공백 없이 결합)bin/reverse_sync/patch_builder.py:131-144—_has_inline_boundary_change()(bold만 감지)bin/reverse_sync/roundtrip_verifier.py:49-55—_normalize_br_space()(마스킹)2. 테이블 separator 길이 차이 (1건)
증상: 테이블
---separator와 셀 패딩 길이가 달라짐근본 원인: Forward converter가 CJK display-width 기반(
_display_width(): CJK 문자 폭 2)으로 컬럼 폭을 계산하지만, 원본 MDX는 단순 문자 수(character count) 기준으로 작성되었습니다. 이는 테이블 포맷팅이 round-trip에서 본질적으로 lossy한 특성이며,_normalize_table_cell_padding이 이를 정당하게 처리합니다.이 케이스는 정상 동작입니다 — normalization이 필요한 유일한 경우입니다.
관련 코드:
bin/converter/core.py:119-128—_display_width()(CJK 폭 2 계산)bin/converter/core.py:1234—"-" * col_widths[i](separator 생성)bin/reverse_sync/roundtrip_verifier.py:166-195—_normalize_table_cell_padding()(정당한 normalization)3. 파일 끝 빈 줄 추가 (1건)
증상: improved MDX에 없는 trailing 빈 줄이 verify MDX에 추가됨
근본 원인: 삭제된
emptyMDX 블록에 대한 XHTML delete patch가 생성되지 않아, 원본 XHTML의 trailing<p/>가 그대로 남습니다.파이프라인 추적:
원본 XHTML (
page-id: 544375969)이<p /><p />(빈<p>2개)로 끝남MDX 블록 diff:
empty-14(두 번째 trailing 빈 줄)를change_type: 'deleted'로 올바르게 감지Delete patch 생성 실패 (
patch_builder.py:1423):_build_delete_patch()가find_mapping_by_sidecar()를 호출하지만,_build_mdx_to_sidecar_from_v3()(line 67-68)에서empty블록이_NON_CONTENT타입으로 제외되어 매핑을 찾지 못합니다. 결과: delete patch가None으로 반환되어 아무 것도 삭제되지 않음Forward 변환: 패치되지 않은 XHTML의 trailing
<p/><p/>가 각각\n을 생성 → 빈 줄 추가마스킹 normalizer:
roundtrip_verifier.py:58-65의_normalize_trailing_blank_lines()가 trailing\n을 하나로 통일하여 차이를 숨깁니다.수정 방향:
_build_delete_patch()가empty블록 삭제 시 대응하는 trailing<p/>를 XHTML에서 제거할 수 있도록, sidecar 매핑에서empty블록을 제외하지 않거나,mapping.yaml의mdx_blocks: []정보를 활용해야 합니다.관련 코드:
bin/reverse_sync/patch_builder.py:67-68—_NON_CONTENT에empty포함 → 매핑 제외bin/reverse_sync/patch_builder.py:1423—_build_delete_patch()→None반환bin/converter/core.py:853— 빈<p/>마다'\n'무조건 appendbin/reverse_sync/roundtrip_verifier.py:58-65—_normalize_trailing_blank_lines()(마스킹)4. 리스트 마커 뒤 공백 차이 (1건)
증상: improved MDX의
* Workflow(공백 1개)가 verify MDX에서* Workflow(공백 2개)로 변경근본 원인: text-level 패치에서
collapse_ws()가 마커 공백 차이를 소멸시켜 XHTML에 변경이 반영되지 않습니다.파이프라인 추적:
원본 Confluence XHTML (
page-id: 953221256):<p>안에 leading space가 존재합니다.Forward 변환:
convert_li가prefix = " * "(공백 1개) +SingleLineParser가<p>내용Workflow...(leading space 보존) →" * Workflow..."(공백 2개). 이것이 원본 MDX에*가 있는 이유입니다.Improved MDX: 교정자가
* Workflow→* Workflow로 수정 (leading space 제거)XHTML 패치 실패 (
patch_builder.py:1179-1204):_contains_preserved_anchor_markup()=True(<ac:image>존재) → clean list 교체 차단_old_plain = collapse_ws(_old_plain_raw)→"Workflow..."(leading space 소멸)_new_plain = collapse_ws(_new_plain_raw)→"Workflow..."(leading space 소멸)_contains_preserved_link_markup()=False(<ac:link>없음) → raw plain 대신 collapsed plain 사용transfer_old_plain == transfer_new_plain→ 차이가 없으므로 XHTML 변경 없음Forward 변환: 변경되지 않은 XHTML의
<p> Workflow...가 다시* Workflow를 생성마스킹 normalizer:
roundtrip_verifier.py:43-45의_normalize_consecutive_spaces_in_text()가 연속 공백을 1개로 축소하여 차이를 숨깁니다.수정 방향: text-level 패치 경로에서
collapse_ws()가 마커 whitespace 차이를 보존하도록 수정하거나,<ac:image>만 있는 경우(regeneration 가능) clean list 교체를 허용해야 합니다.<ac:link>(regeneration 불가)와<ac:image>(regeneration 가능)를 구분하는 것이 핵심입니다.관련 코드:
bin/reverse_sync/patch_builder.py:1118-1119—collapse_ws()가 marker ws 차이를 소멸bin/reverse_sync/patch_builder.py:1190-1194—preserve_visible_ws플래그가<ac:link>만 검사bin/converter/core.py:898-899—prefix = " * "(항상 공백 1개)bin/converter/context.py:436-442—navigable_string_as_markdown이 leading space 보존bin/reverse_sync/roundtrip_verifier.py:43-45—_normalize_consecutive_spaces_in_text()(마스킹)