Skip to content

fix(reverse_sync): verify diff 발생 문서 7건 (split/ko-proofread-20260221-administrator-manual-general) #992

@jk-kim0

Description

@jk-kim0

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 발생 문서 목록

  1. src/content/ko/administrator-manual/general/company-management/channels.mdx<br/> 앞 trailing space
  2. src/content/ko/administrator-manual/general/system/api-token.mdx<br/> 앞 trailing space
  3. src/content/ko/administrator-manual/general/system/integrations/integrating-with-slack-dm.mdx<br/> 앞 trailing space
  4. src/content/ko/administrator-manual/general/system/maintenance.mdx — 테이블 separator 길이
  5. src/content/ko/administrator-manual/general/user-management.mdx — 파일 끝 빈 줄 추가
  6. src/content/ko/administrator-manual/general/user-management/authentication/setting-up-multi-factor-authentication.mdx<br/> 앞 trailing space
  7. 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) 마커입니다.

파이프라인 추적:

  1. Confluence XHTML 구조:

    <li>
      <p>...저장합니다.</p>
      <ac:image ...>...</ac:image>
      <p> </p>
    </li>

    XHTML에 <br> 요소는 존재하지 않습니다.

  2. XHTML 패치 단계 (patch_builder.py): _has_inline_boundary_change()가 bold 경계 변경만 감지하므로, <br/> 앞 공백 변경에 대해 inline fixup이 트리거되지 않습니다. 텍스트 <p> 안에 <br />를 삽입하는 로직이 없어, 패치된 XHTML은 여전히 <p>...저장합니다.</p> (공백 없이 마침표로 끝남)입니다.

  3. 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/>가 그대로 남습니다.

파이프라인 추적:

  1. 원본 XHTML (page-id: 544375969)이 <p /><p />(빈 <p> 2개)로 끝남

  2. MDX 블록 diff: empty-14 (두 번째 trailing 빈 줄)를 change_type: 'deleted'로 올바르게 감지

  3. 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으로 반환되어 아무 것도 삭제되지 않음

  4. 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.yamlmdx_blocks: [] 정보를 활용해야 합니다.

관련 코드:

  • bin/reverse_sync/patch_builder.py:67-68_NON_CONTENTempty 포함 → 매핑 제외
  • 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에 변경이 반영되지 않습니다.

파이프라인 추적:

  1. 원본 Confluence XHTML (page-id: 953221256):

    <li><p> Workflow Approval Rule...</p></li>

    <p> 안에 leading space가 존재합니다.

  2. Forward 변환: convert_liprefix = " * " (공백 1개) + SingleLineParser<p> 내용 Workflow... (leading space 보존) → " * Workflow..." (공백 2개). 이것이 원본 MDX에 * 가 있는 이유입니다.

  3. Improved MDX: 교정자가 * Workflow* Workflow로 수정 (leading space 제거)

  4. 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 변경 없음
  5. 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-1119collapse_ws()가 marker ws 차이를 소멸
  • bin/reverse_sync/patch_builder.py:1190-1194preserve_visible_ws 플래그가 <ac:link>만 검사
  • bin/converter/core.py:898-899prefix = " * " (항상 공백 1개)
  • bin/converter/context.py:436-442navigable_string_as_markdown이 leading space 보존
  • bin/reverse_sync/roundtrip_verifier.py:43-45_normalize_consecutive_spaces_in_text() (마스킹)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions