AX 노트 · 사고 기록

편집 기능을 만들고도 꺼서 출시했다: HWP 저장 검증 게이트가 잡은 누름틀 결함

저장 검증 게이트가 누름틀 채우기 저장을 2/2로 막아, 편집을 빌드 플래그로 끈 채 1.1.0을 내고 9일 뒤 누름틀을 제외한 범위로 다시 열었습니다.

작성 SugarMount 검증과 출시 읽는 시간 10분 최종 업데이트

PARAGRAPHS control record tag 71 changed. 한글뷰어 슈가독의 편집 기능으로 HWP 문서의 누름틀(빈칸 입력 칸)을 채우고 저장하면, 저장 직전 검증 게이트가 매번 이 이유로 저장을 막았습니다. 2026년 10월 9일 Galaxy S20 실기기 E2E에서 누름틀 문서 2개가 모두 그랬습니다. 레코드를 대조해 보니 엔진이 사용자가 손대지 않은 문단까지 다시 쓰고 있었습니다. 그날 편집은 빌드 플래그로 끈 채 1.1.0을 내부 트랙에 올렸고, 9일 뒤 1.3.0에서 누름틀을 제외한 범위로 다시 열었습니다.

근거는 SugarMount 앱 저장소(sugar-utils)의 기기 E2E 보고서(2026-10-09), 1.1.0·1.3.0 출시 기록, 설계 문서의 저장 검증 절, git 기록 d9cd29b·43046e0·1b7c328, 작업 세션 기록입니다. 기준일은 2026년 10월 23일입니다.

목차 (8)
  1. 누름틀을 채우면 rhwp가 고치지 않은 문단도 다시 썼습니다
  2. 처음 계획은 엔진의 검증 함수였고, 그 함수는 쪽 수만 봤습니다
  3. 코퍼스 편집 2,899회에 누름틀은 한 번도 없었습니다
  4. 게이트를 넓히지 않고 편집 범위를 줄였습니다
  5. 이 방식이 맞지 않는 경우
  6. 파일을 쓰는 기능에 붙일 저장 검증 체크리스트
  7. 다음에 확인할 것
  8. 자주 묻는 질문

누름틀을 채우면 rhwp가 고치지 않은 문단도 다시 썼습니다

이 앱의 편집은 오픈소스 HWP 엔진 rhwp 0.8.6을 WebView 안에서 돌려 글자를 바꾸고, 결과는 새 파일로만 저장합니다. 저장 전에는 Kotlin 검사기가 내보낸 바이트를 HWP 레코드 단위로 원본과 비교합니다. 달라진 문단의 집합이 사용자가 고친 문단의 집합과 정확히 같아야 합니다.

PC에서 같은 편집을 재현해 레코드를 맞대 보니 차이는 세 가지였습니다. 채운 필드의 컨트롤 머리 레코드(CTRL_HEADER, 71번)에서 속성 바이트가 0x01에서 0x80으로 바뀌었고, 그 문단의 69번 레코드 하나가 사라졌습니다. 세 번째로, 사용자가 손대지 않은 다른 문단들에서도 필드 끝 문자의 id가 다시 쓰였습니다(clk+0x0109 → %clk+0). 게이트는 이 차이를 보고 저장을 막았습니다. (기기 E2E 입력은 adb로 넣은 영문 마커뿐이라 한글 입력은 확인하지 못했습니다.)

비교 그림: 게이트는 고친 문단만 바뀌어야 한다고 보는데, 누름틀을 채운 파일은 고치지 않은 문단의 필드 끝 id까지 바뀌어 누름틀 문서 2개 모두 저장이 막혔습니다.
엔진이 고치지 않은 문단까지 다시 쓴 순간을 게이트가 막았습니다.

화면에는 채운 글자가 제대로 보였습니다. 게이트가 없었다면 다른 문단의 필드 정보까지 달라진 파일이 저장됐을 겁니다.

처음 계획은 엔진의 검증 함수였고, 그 함수는 쪽 수만 봤습니다

제품 요구 문서의 R1.1 줄은 "exportHwpVerify 통과 시만 저장"이었습니다. 편집 스파이크가 이 가정이 틀렸음을 보였습니다. 이 함수는 쪽 수만 비교해서, HWP3 문서의 내용이 빠지거나 배포용 문서의 보호가 풀려도 "recovered":true를 돌려줬습니다. 손실 보고(contentLoss)는 모든 사례에서 0건이었습니다.

그래서 검사기를 따로 만들었습니다. 문단 판정의 뼈대는 아래와 같습니다(저장소 코드를 줄여 옮김).

// HwpExportVerifier (섹션마다)
문단 키 = (섹션, 문단 번호[, 표 컨트롤·칸·칸 안 문단 경로])
원본과 결과의 문단 수가 다르면                     → GATE_PARAGRAPHS
for 같은 위치의 문단 a(원본), b(결과):
    키나 깊이가 다르면                              → 실패
    컨트롤 레코드가 허용 목록 밖에서 다르면         → "control record tag N changed"
    문단 레코드가 모두 같으면                       → 다음 문단
    a.키 ∈ 고친 문단(ops)                           → changed에 넣고 고친 문단 검사
    편집한 섹션의 재직렬화 허용 목록 안             → 다음 문단
    그 밖                                           → "unedited paragraph changed"
고친 문단 - changed 가 비어 있지 않으면            → 실패

// EditWiringTest (1.1.0): release는 편집 꺼짐
if (!BuildConfig.DEBUG) assertEquals(false, BuildConfig.FLAG_EDIT)

허용 목록은 아무것도 고치지 않은 왕복 저장으로 정했습니다. 한 문서는 고치지 않아도 LIST_HEADER(72번) 레코드 43개가 달랐습니다.

우리 쪽 가정도 하나 틀렸습니다. 처음 넣은 크기 하한(원본의 0.5배)입니다. 무편집 저장 15건이 원본의 0.05~0.48배였는데 레코드와 그림이 모두 같았습니다. 한컴 파일에 있던 빈 섹터가 빠졌을 뿐이라 하한은 지웠습니다. 검사 비용은 작았습니다. 77쪽 문서 하나를 고쳐 저장하는 데 S20에서 3.6초(새로 띄운 프로세스 4.9초), S21에서 5.1초가 걸렸고 기준은 10초였습니다.

코퍼스 편집 2,899회에 누름틀은 한 번도 없었습니다

PC 코퍼스 결과는 좋아 보였습니다. HWP5 문서 164개 코퍼스에서 편집할 수 있는 문서마다 무작위 편집을 20회씩 돌려, 2,899회 중 2,662회가 통과했습니다. 통과했는데 허용 목록 밖 차이가 숨은 사례는 0건이었습니다. 코퍼스 생성기(edit_corpus.mjs)는 에이전트 작업 세션에서 R1.1 구현과 함께 들어온 스크립트였고, 생성기가 만든 편집은 본문 글자 묶음(run)과 표 칸 글자뿐이었습니다. E2E 보고서는 이렇게 적었습니다. "편집 코퍼스(무작위 run 편집)에는 필드 채우기가 없어 놓쳤다."

같은 날 편집 스파이크를 맡은 에이전트는 권장 범위에 "본문 누름틀 채우기"를 넣었습니다. setFieldValue API가 있고, PC에서 채운 값이 저장 뒤에도 남았다는 근거였습니다. 그 권고가 기기 E2E에서 2/2 실패로 뒤집혔습니다.

같은 구멍은 한 번 더 나왔습니다. 1.3.0 출시 전 E2E에서 컨트롤이 없는 빈 문단에 글자를 넣으면 저장이 늘 GATE_CONTROLS로 막혔고, 한 문서의 빈 문단 7곳이 모두 그랬습니다. 빈 문단에는 글자 묶음이 없으니 무작위 run 편집이 그 자리를 고를 수 없었습니다. 두 번 모두 원인은 같았습니다. 생성기는 편집 위치를 엔진이 그려 준 글자 묶음에서만 골랐고, UI가 실제로 내주는 편집 종류는 보지 않았습니다.

게이트를 넓히지 않고 편집 범위를 줄였습니다

게이트가 막은 기능을 게이트를 넓혀서 내지 마십시오. 범위를 줄이거나, 플래그로 끈 채 내십시오. F1(누름틀 결함)을 두고 검토한 길은 셋이었습니다.

누름틀을 막아도 편집을 바로 열 수는 없었습니다. 무료 사용자에게 메뉴를 보일지, 편집을 넣은 Pro의 가격, 결과 파일이 한컴오피스에서 열리는지가 미정이었고, TalkBack으로 고칠 곳을 찾는 기능(edit_target_a11y)도 아직 없었습니다. 설계 문서의 견적이 작은 항목 묶음(R1.1a) 약 3일, 편집(R1.1b) 약 19일이라, 편집을 기다리며 작은 항목(보낸 앱 표시, 다른 앱 창 숨기기, 저장 공간 부족 안내)까지 묶어 두는 안은 버렸습니다.

남은 길은 app-config.properties의 flagEdit=false였습니다. 꺼지면 메뉴의 '고치기', 편집 모드, Pro 안내의 편집 줄이 모두 사라집니다. EditWiringTest가 release 값을 false로 고정했고, 새 가드 12행은 가드를 지운 사본에서 모두 실패했습니다(방법: 테스트 3,066개가 초록인데 DB를 못 여는 코드가 숨어 있었다: 가드 지워 보기). 1.1.0은 release 단위 테스트 446건을 통과해 10월 9일 내부 트랙에 올랐고(커밋 43046e0), S20·S21의 축소된 release 빌드 메뉴에 '고치기'는 없었습니다.

단계 그림: 10월 9일 누름틀 저장 2/2 실패, 같은 날 편집을 끈 1.1.0, 10월 18일 1.2.0 production, 1.3.0 E2E에서 빈 문단 결함, 1.3.0에서 편집 활성화(단위 테스트 1,254건 실패 0), 출시 뒤 한컴 다시 저장 2개 통과.
편집은 9일 동안 꺼진 채 두 번 출시됐고, 범위를 줄인 1.3.0에서 켜졌습니다.

10월 18일에 운영자가 남은 결정 세 개를 골랐습니다. 무료 사용자에게도 'Pro' 표지를 단 '고치기'를 보이고, 가격은 그대로 두고, 한컴 호환은 에이전트가 S21에서 확인하기로 했습니다. 그날 저녁 1b7c328(39개 파일, +924/-72)이 flagEdit=true로 고쳤습니다. 전체 단위 테스트 1,254건이 실패 0이었고, 게이트가 실패하면 원본에서 다시 열어 한 번 더 검사하는 재시도도 이때 들어갔습니다.

사람 손이 꼭 필요했던 곳은 둘이었습니다. 한컴독스 다시 저장에는 계정 로그인이 필요한데, 에이전트는 비밀번호를 입력하지 않는 규칙이라 운영자가 S21에 로그인하고 파일 접근 권한을 켠 뒤에야 두 파일을 확인했습니다. TalkBack 두 번 탭은 adb로 넣은 탭이 TalkBack을 거치지 않아 자동화가 안 됐고, 운영자가 손으로 확인했습니다.

이 방식이 맞지 않는 경우

결과 파일을 엔진과 따로 읽을 파서가 없으면 레코드 비교는 못 합니다. 이 앱은 뷰어용 컨테이너 파서 위에 레코드 비교기를 새로 얹었습니다.

엔진이 아무것도 고치지 않아도 다른 파일을 쓰면, 게이트가 막는 범위는 결함 하나에서 기능 전체로 커집니다. HWPX는 무편집 저장 34개 중 통과가 0개라(그림 참조와 일부 속성을 버림) 편집 범위에서 통째로 제외했습니다. 코퍼스의 HWP5 편집 중 8.2%가 막혔고 12개 문서에 몰렸습니다(엔진이 문단 수를 줄이거나 컨트롤 순서를 바꾼 경우). 그중 실제 손상이 아닌 것이 얼마인지는 따로 가리지 않았습니다. 1.3.0에 넣은 재시도 1회는 이런 차이를 풀지 못합니다. 되돌리기·스냅숏이 남긴 상태 때문에 생긴 실패를 원본에서 다시 열어 회복하려는 장치이고, 재시도로 풀린 건수는 아직 세지 않았습니다.

구조가 같다고 다른 앱에서 잘 열린다는 보장은 없습니다. 출시 전에는 저장 기능이 없는 한컴오피스 Viewer에서 결과 파일 7개가 열리는 것까지만 봤고, 한컴독스 다시 저장은 production에 올린 뒤 2개만 확인했습니다. release 빌드로는 "플래그 꺼짐 + Pro 사용자"를 기기에서 볼 수 없어 테스트가 대신 맡았습니다. 꺼진 편집 코드와 문자열도 APK에 그대로 실립니다.

파일을 쓰는 기능에 붙일 저장 검증 체크리스트

  1. 엔진의 검증 함수에 손실이 나는 입력(구버전 형식, 보호 문서)을 넣어 실패를 보고하는지 봅니다.
  2. 무편집 왕복 기준선을 코퍼스 전체로 먼저 재고, 거기서 나온 차이만 허용합니다. 크기 비율 같은 기준도 이때 검증합니다.
  3. 판정은 "달라진 단위 집합 = 사용자가 한 편집 집합"으로 정합니다. "편집 수 이하"가 아닙니다.
  4. 코퍼스 생성기는 UI가 내주는 편집 종류 목록에서 만들고, 종류마다 최소 1건이 들어 있는지 테스트로 확인합니다.
  5. 기기 E2E는 편집 종류마다 실제 양식 문서로 돌리고, 가장 큰 문서의 저장 시간을 측정합니다.
  6. 게이트가 막으면 PC에서 레코드 차이로 재현하고, 게이트 대신 그 편집의 진입을 막습니다.
  7. 막은 채 낼 기능은 빌드 플래그로 끄고, release 값과 "진입 경로 없음"을 테스트로 고정한 뒤 가드를 지워 실패를 봅니다.
  8. 다시 열 때는 플래그 한 줄과 고정 테스트를 한 번에 함께 고칩니다.

다음에 확인할 것

reason=field로 막힌 건수를 아직 세어 보지 않았습니다. 누름틀 채우기를 다시 열지는 이 숫자를 본 뒤 정하고, 연다면 필드 끝 id 정규화와 코퍼스 보강이 먼저입니다. 한컴 다시 저장을 해 보지 않은 결과 파일 5개와 PC 한글도 남아 있습니다. 엔진 측정은 안드로이드 한글 뷰어 만들기: rhwp + WebView를 문서 222개로 실측한 기록에 있습니다.

자주 묻는 질문

HWP 파일을 앱에서 고쳐 저장하면 파일이 손상되나요?

한글뷰어 슈가독은 저장 전에 결과를 원본과 레코드 단위로 비교해, 고친 문단 밖에서 차이가 나면 저장하지 않습니다. 저장은 늘 새 파일입니다. 고친 파일을 한컴독스에서 다시 저장하는 것은 2026-10-18에 파일 2개로 확인했습니다.

한글뷰어 슈가독에서 누름틀(빈칸)을 채울 수 있나요?

2026-10-23 기준으로는 채울 수 없습니다. 1.3.0에서 고칠 수 있는 것은 HWP 5 문서의 본문 글자와 표 칸 글자이고, 누름틀을 누르면 아직 채울 수 없다는 안내가 나옵니다. HWPX 문서도 아직 고칠 수 없습니다.

기능 플래그로 끈 기능이 출시 빌드에서 정말 안 보이는지 어떻게 확인하나요?

release 값을 테스트로 고정하고 메뉴와 진입 경로가 없다는 테스트를 둔 뒤, 그 가드를 지웠을 때 테스트가 실패하는지 봅니다. 이 앱은 축소된 release 빌드를 S20·S21에 깔아 메뉴도 직접 봤습니다.

이 글은 SugarMount가 직접 측정한 값으로 쓰고, 초안 작성에 AI 코딩 에이전트를 썼습니다.

검증과 출시 글 전체 보기 · AX 노트 전체 보기