fix(mcp): 세션 편집 직후 페이지 어휘 스테일 — 채움·치환 후 pageCount·텍스트·렌더·검색 주소가 편집 전 레이아웃(실측 3쪽 vs 10쪽) - #3704
Closed
kevin9327 wants to merge 3 commits into
Closed
fix(mcp): 세션 편집 직후 페이지 어휘 스테일 — 채움·치환 후 pageCount·텍스트·렌더·검색 주소가 편집 전 레이아웃(실측 3쪽 vs 10쪽)#3704kevin9327 wants to merge 3 commits into
kevin9327 wants to merge 3 commits into
Conversation
hwp_doc_fill_fields/hwp_doc_replace_text 는 코어 recompose 로 dirty 만 남기고 재페이지네이션하지 않아, 이후 hwp_doc_info(pageCount)·hwp_doc_text· hwp_doc_render_page·hwp_doc_search(page 주소)가 전부 편집 전 레이아웃을 서빙했다. 실측: 4,620자 채움 후 세션 pageCount 3(스테일) vs 저장본 신규 파싱 10쪽 — 7쪽 격차. hwp_doc_info 는 "편집 후 페이지 수 변화를 추적할 때 쓴다"고 약속하고, 같은 세션의 hwp_doc_set_cell 은 코어 경로가 paginate_if_needed 를 불러 이미 갱신한다 — 편집 3종 중 2종만 스테일인 비대칭이었다. - document_core: repaginate_if_needed 공개 표면 추가 (batch 모드 규약 유지, dirty 구역만 증분 재처리) - mcp_serve: fill(적용 1건 이상)·replace(계수 1 이상) 직후 도구 호출당 1회 호출 - 계약 테스트 2종: 채움·치환이 쪽수를 늘렸을 때 세션 pageCount 가 저장본 신규 파싱과 일치하고, 늘어난 쪽이 text/search 로 곧바로 보이는지 고정 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
편집 3종 비대칭 구조, BEFORE 스테일 pageCount(3 vs 10)·범위초과 거부, AFTER 일치·쪽 접근 성공, 계약 테스트 설계와 전제 확인 assert 의도, 무회귀 15종, 한계·후속을 기록한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
새 공개 표면이 `pub fn (&mut self)` 뮤테이터 후보로 분류돼 CI 의 classification_drift_is_blocked 가 실패했다. 이 함수는 dirty 구역을 다시 쪽으로 나눌 뿐(pagination·측정 캐시) 문서 IR 을 바꾸지 않으므로 SessionState 로 등재한다 — 같은 파일의 flush_deferred_pagination 과 같은 계열이다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8 tasks
kevin9327
added a commit
to kevin9327/rhwp
that referenced
this pull request
Aug 1, 2026
…im#3630 P3 완결) 에이전트는 편집 결과를 눈으로 못 본다. 표면이 "몇 쪽이 바뀌었나"를 지정해야 그 쪽만 hwp_doc_render_page 로 렌더하는 검증 루프가 상수 비용으로 닫힌다: - 코어 pub 질의 pages_covering_paragraphs: grep 페이지 인덱스와 같은 순회를 재사용하되 문단이 걸친 모든 쪽(분할 표 포함)을 담는다 — 누락은 거짓 통과, 상위집합은 렌더 한 번 더일 뿐. 하나라도 커버리지 밖이면 None(부분 목록 금지) - 진입 시 paginate_if_needed — 편집이 남긴 dirty 를 저장 직전 조판으로 소화 - 추적: fill=FieldLocation, replace=치환 전 grep 매치(문단 인덱스 불변), set-cell=호스트 문단, run 저널=step 합집합(set_checkbox n번째 □ 포함) - 봉투: edit 3종 --json + run 저널 changedPages:[n]|null, dry-run·무산출 null - capabilities outputFields 5곳 동기화 - 세션 도구는 edwardkim#3704(세션 재조판) 머지 후 후속 적층(이슈 명시) 검증: changed_pages_contract 5건 green, cli_json 22·run_plan 6·edit_verify 4· fill 7·replace 4·set-cell 5 무회귀, clippy 0, fmt clean. 실측 4종(evidence.txt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jangster77
self-requested a review
August 1, 2026 14:17
jangster77
pushed a commit
that referenced
this pull request
Aug 1, 2026
에이전트는 편집 결과를 눈으로 못 본다. 표면이 "몇 쪽이 바뀌었나"를 지정해야 그 쪽만 hwp_doc_render_page 로 렌더하는 검증 루프가 상수 비용으로 닫힌다: - 코어 pub 질의 pages_covering_paragraphs: grep 페이지 인덱스와 같은 순회를 재사용하되 문단이 걸친 모든 쪽(분할 표 포함)을 담는다 — 누락은 거짓 통과, 상위집합은 렌더 한 번 더일 뿐. 하나라도 커버리지 밖이면 None(부분 목록 금지) - 진입 시 paginate_if_needed — 편집이 남긴 dirty 를 저장 직전 조판으로 소화 - 추적: fill=FieldLocation, replace=치환 전 grep 매치(문단 인덱스 불변), set-cell=호스트 문단, run 저널=step 합집합(set_checkbox n번째 □ 포함) - 봉투: edit 3종 --json + run 저널 changedPages:[n]|null, dry-run·무산출 null - capabilities outputFields 5곳 동기화 - 세션 도구는 #3704(세션 재조판) 머지 후 후속 적층(이슈 명시) 검증: changed_pages_contract 5건 green, cli_json 22·run_plan 6·edit_verify 4· fill 7·replace 4·set-cell 5 무회귀, clippy 0, fmt clean. 실측 4종(evidence.txt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
postmelee
pushed a commit
to postmelee/rhwp
that referenced
this pull request
Aug 2, 2026
무상태 편집 3종은 edwardkim#3712 이후 "어느 쪽을 보라"를 봉투로 지정하지만 세션 편집 3종(hwp_doc_fill_fields/replace_text/set_cell)은 못 냈다. 정작 눈검증 루프를 약속한 것은 세션 쪽(hwp_doc_render_page)이라, 에이전트에게 남는 선택지는 전수 렌더(예산 폭발)나 무검증(거짓 통과)뿐이었다. 새 계산은 넣지 않는다. 코어 질의 pages_covering_paragraphs 를 그대로 쓰고, 변경 문단 근거도 무상태 판과 같은 출처를 쓴다 — fill 은 collect_all_fields 의 FieldLocation, replace 는 치환 전 grep 매치 주소(문자열 치환은 문단 인덱스를 밀지 않는다), set_cell 은 resolve_table_cell 의 호스트 문단이다. 두 경로가 같은 근거·같은 질의를 쓰므로 답이 갈라질 수 없고, 계약 테스트도 값을 적어 두는 대신 세션 봉투와 무상태 봉투를 맞대어 본다. 호출 시점이 계약의 절반이다. 세션은 편집 뒤에도 같은 인스턴스가 살아 있어서 재조판 전에 계산하면 편집 전 레이아웃을 보고한다(edwardkim#3704 가 조회 4종에서 고친 스테일). 질의가 진입에서 paginate_if_needed 를 부르므로 재조판 지점 뒤에 두는 것으로 규약이 지켜진다. 실측: 회사명에 5,000자를 채우면 3쪽→11쪽이 되고 changedPages 는 [0..8] — 3~8쪽은 편집 전에 존재하지 않던 쪽인데 그 자리에서 hwp_doc_render_page 로 렌더된다(182KB SVG). 변경이 없으면 빈 목록이다. null 은 "확정 불가, 전체를 보라"라서 치환 0건이나 notFound 뿐인 호출마다 전수 렌더를 유도하게 된다. 대상 문단이 하나라도 조판 커버리지 밖이면 그때만 전체 null 이다(부분 목록 금지). 세션 도구는 capabilities --mcp 의 tools[] 에 실리지 않아 outputFields 축이 없다. 그래서 산출을 자기서술하는 유일한 채널인 도구 description 에 규약을 적고, 신규 계약 테스트가 선언과 봉투의 드리프트를 잡는다. 검증: 신규 계약 6종 + 세션 무회귀 24 + 드리프트 가드 38 + 무상태 축 31 = 99 passed / 0 failed, clippy -D warnings 0, fmt clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
mcp-serve세션 편집(#3598hwp_doc_fill_fields/ #3601hwp_doc_replace_text) 직후, 같은 핸들의 조회 4종이 편집 전 페이지네이션을 서빙합니다. 코어 편집 경로가recompose_section(재조판 + dirty 마킹 + 캐시 무효화)까지만 하고 pagination 벡터를 갱신하지 않는데, 세션 조회는 전부 그 벡터를 읽기 때문입니다.실측(4,620자 채움,
samples/field-01.hwp):hwp_doc_info.pageCounthwp_doc_textpage=5isError: 페이지 범위 초과: 5 (0~2)hwp_doc_render_pagepage=7isError: 페이지 범위 초과: 7 (0~2)hwp_doc_searchmatches[].page오류가 나지 않는 것이 핵심입니다. 봉투는
isError:false에 그럴듯한 숫자를 담아 돌려주고 에이전트는 그 숫자로 다음 판단을 합니다.hwp_doc_info는 도구 설명에서 "편집 후 페이지 수 변화를 추적할 때 쓴다"고 명시하고,hwp_doc_render_page는 "편집 직후 눈검증(VLM) 루프가 세션 안에서 닫힌다"고 약속하는데 — 정확히 그 용도에서 값이 갱신되지 않습니다.상세 보고서:
mydocs/report/mcp_session_repaginate/README.md결함 구조 — 편집 3종의 비대칭
hwp_doc_set_celltext_editing.rs셀 편집 진입점이paginate_if_needed()호출hwp_doc_fill_fieldsfield_query.rs::set_field_value_by_name_at→recompose_section까지hwp_doc_replace_textsearch_query.rs::replace_all_native→recompose_section까지뿌리는 무상태 전제를 상태 유지 표면이 물려받은 것입니다.
grep모듈 헤더는 "from_bytes가 로드 시paginate()를 끝내므로 순수 조회다"라는 전제를 명시하는데, 무상태 CLI 에서는 참이지만 세션에서는 첫 편집 순간 깨집니다.edit fill-fields는 편집 직후 직렬화하고 프로세스가 끝나므로 pagination 을 다시 읽을 일이 없습니다 — 세션이 도입되며 생긴 새 수명에 맞는 갱신 지점이set_cell경로에만 있었습니다.실측 — BEFORE
방금 자기가 만들어 낸 쪽을 세션이 존재하지 않는다고 거부합니다. 에이전트 입장에서는 편집이 실패한 것처럼 보이지만 IR 에는 정상 반영돼 있어, 재시도하면 같은 값이 한 번 더 채워지는 이중 편집 위험까지 생깁니다.
수정
DocumentCore::repaginate_if_needed()공개 표면 추가 — 내부paginate_if_needed()위임. batch 모드 규약 유지, dirty 구역만 증분 재처리. 새 로직을 만들지 않고set_cell이 이미 쓰는 그 경로를 노출만 해서 세 편집이 같은 규약을 공유하게 했습니다.실측 — AFTER
세션
pageCount10 == 저장본 신규 파싱 10. 편집이 만든 5쪽 텍스트를 곧바로 읽고 7쪽을 SVG 로 렌더 — 약속된 VLM 눈검증 루프가 실제로 닫힙니다.회귀 가드 (신규 계약 테스트 2종)
session_fill_repaginates_page_vocabulary— 세션pageCount를 저장본 신규 파싱과 대조(보고를 믿지 않고 지상 진실 확보) + 늘어난 마지막 쪽이hwp_doc_text로 읽히는지. 전제 확인(truth > before) 을 별도 assert 로 둬서, 샘플이 바뀌어 채움이 더는 쪽수를 늘리지 않으면 시험이 무의미해진 사실 자체가 실패로 드러납니다.session_replace_repaginates_page_vocabulary— 대량 치환 후pageCount증가와 검색page주소가 편집 전 마지막 쪽을 넘어서는지.검증
cargo test --test mcp_session_edit_contract --test mcp_session_query_contract— 12/12 (신규 2종 포함)cargo test --test mcp_session_view_contract --test mcp_session_setcell_contract --test mcp_server_contract— 15/15 무회귀cargo clippy --profile release-test --bin rhwp— 경고 0 /cargo fmt --check— 통과범위 밖 (별도 PR 로 잇겠습니다)
edit_serialize는 IR 을 직렬화하므로 스테일 pagination 이 파일에 각인되지 않습니다. 이 결함은 세션이 보고하는 값의 문제입니다.&self라 시그니처 변경이 광범위합니다. 편집 지점에서 미는 이번 방식이 범위 대비 효과가 크다고 판단했습니다.hwp_doc_text의 잘못된page타입이 전체 덤프로 빠지는 문제, HWP3 세션 save 의 라이브 IR 변형 등)은 범위를 섞지 않기 위해 분리합니다.🤖 Generated with Claude Code