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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions