Files
AriWeb-Suite/docs/verification/webmaker-visual-editor-0.2.0.md

41 KiB
Raw Permalink Blame History

Webmaker 1.8.0 직접 편집(시각 편집기) 도입 — Ariweb Suite 0.2.0 TDD RED→GREEN 기록

  • 작업 트리: /home/master/work/Ariweb-suite-visual-editor-v020, 브랜치 feature/visual-editor-v0.2.0, 기준 origin/main 4b3714e37c543fcfa8862dbc68c76f62a9b0bf97
  • 참조 저장소 작업 트리: /home/master/work/WebHoting_AI_Agent-visual-editor-v180(feature/visual-editor-v1.8.0, 기준 3b10859) — 그쪽 RED→GREEN 은 modules/webmaker/../docs/verification/visual-editor-1.8.0-tdd.md(참조 저장소 문서)에 있다.
  • 날짜: 2026-10-28 · Node v24.22.1 · 커밋·push·병합·배포·운영 접근 없음(Neo 검토 전 인도). 출력은 실제 실행 결과를 그대로 옮겼다(스택 줄만 줄임).
  • 서버를 띄우는 테스트는 모두 CLAUDE_BIN 을 즉시 실패하는 가짜 실행 파일로 두고 실행했다(사이트 생성이 AI 작업을 시작하므로 개발 PC 의 진짜 claude 가 실행되지 않게). 첫 Suite 테스트 실행에서는 그 설정이 빠져 진짜 claude 프로세스가 하나 시작되어 즉시 종료시켰고, 테스트를 고쳤다.

RED — node --no-warnings test/webmaker-visual-editor.test.mjs (도입 전: 모듈·화면·경로 없음)

  ✘ 도입 모듈에 직접 편집 순수 모듈·런타임·화면 파일이 있고 Webmaker 1.8.0 · Suite 0.2.0 이다
    Error: lib/visual-editor.js (실제 false)
  ✘ lib/visual-editor: 캔버스 변환은 스크립트를 실행하지 않게 하고, 저장 검증은 새 스크립트·핸들러를 거절한다
    Error: Cannot find module '/home/master/work/Ariweb-suite-visual-editor-v020/modules/webmaker/lib/visual-editor.js'
  ✘ visual-editor-core: 실행 취소/다시 실행·저장 상태·메시지 검증·구성 요소가 Suite 체크아웃에서도 같다
    Error: Cannot find module '/home/master/work/Ariweb-suite-visual-editor-v020/modules/webmaker/public/visual-editor-core.js'
  ✘ 화면(index.html)에 직접 편집 전환·저장·실행 취소·다시 실행·버리기·팔레트·속성 조작이 접근성 속성과 함께 있고 Suite 배너는 남아 있다
    Error: 직접 편집 전환 버튼 (실제 false)
  ✘ server.js [Ariweb Suite 변경]: 직접 편집 저장도 동시 편집 rev 충돌(409)을 돌려주고, 맞는 rev 면 저장된다 (실제 서버)
    Error: 기대 200 실제 404
✘ 통과 0 · 실패 5

도입 작업

  1. 참조 저장소에서 1.8.0 을 TDD 로 구현(위 참조 문서). 새 파일 6개는 cp 로 그대로 가져옴(lib/visual-editor.js, public/visual-editor-core.js, public/wm-ve-canvas.js, public/visual-editor-ui.js, test/visual-editor.test.js, test/visual-editor-server.test.js), public/admin.js 는 Suite 변경이 없어 upstream 그대로.
  2. Suite 블록([Ariweb Suite 변경])이 있는 server.js·public/app.js·public/index.html·public/style.css 는 같은 변경을 손으로 적용(덮어쓰지 않음) + server.js 직접 편집 PUT 에 revConflict 추가. diff 로 upstream 과의 차이를 확인: server.js 107줄(모두 0.1.1 Suite 블록 + rev 검사 1줄), app.js 4줄, index.html 2줄, style.css 5줄(배너) — 1.8.0 공통 변경은 양쪽 동일.
  3. 중간 실행 1 — 4/5: rev 충돌 테스트가 "AI 작업 실행 중" 409 를 받음(진짜 claude 가 생성 작업으로 실행 중) → 테스트에 가짜 CLAUDE_BIN·작업 종료 대기 추가.
  4. WEBMAKER_SRC=<참조 작업 트리> node scripts/import-sources.mjs --only webmaker --allow-dirty 로 IMPORTED_SOURCES.json/.md 재생성(처음 182 파일 → 최종 184 파일: 0.1.1 의 176 + 신규 8, 수정 8 = 0.1.1 과 같은 Suite 블록 파일, upstream 1.8.0·workingTreeDirty).

GREEN — node --no-warnings test/webmaker-visual-editor.test.mjs

  ✔ 도입 모듈에 직접 편집 순수 모듈·런타임·화면 파일이 있고 Webmaker 1.8.0 · Suite 0.2.0 이다 (1ms)
  ✔ lib/visual-editor: 캔버스 변환은 스크립트를 실행하지 않게 하고, 저장 검증은 새 스크립트·핸들러를 거절한다 (13ms)
  ✔ visual-editor-core: 실행 취소/다시 실행·저장 상태·메시지 검증·구성 요소가 Suite 체크아웃에서도 같다 (2ms)
  ✔ 화면(index.html)에 직접 편집 전환·저장·실행 취소·다시 실행·버리기·팔레트·속성 조작이 접근성 속성과 함께 있고 Suite 배너는 남아 있다 (3ms)
  ✔ server.js [Ariweb Suite 변경]: 직접 편집 저장도 동시 편집 rev 충돌(409)을 돌려주고, 맞는 rev 면 저장된다 (실제 서버) (1323ms)
✔ 통과 5 · 실패 0

전체 묶음

  • modules/webmaker: CLAUDE_BIN=<가짜 exit 1> npm test → ℹ tests 81 · pass 81 · fail 0 (0.1.1 의 59 + 새 22)
  • Suite: CLAUDE_BIN=<가짜 exit 1> node test/run.mjs → ✔ 테스트 파일 23개 중 실패 0개(agent 9·auth 7·backup 8·commerce 11·dashboard 4·domains 3·e2e 11·groups 5·hal-cli 3·hal-mailimport 7·hal-ui 7·hal 16·lib 10·lifecycle 6·mail-discover 2·mail 9·relocation 6·sites 11·transfer 4·usage 4·webmaker-adapter 2·webmaker-inventory 5·webmaker-visual-editor 5). 로컬 E2E 하네스가 docs/verification/local-e2e-2026-10-28.md/.json 을 새로 썼다(결함 0).

요구 ↔ Suite 테스트 대응

요구 테스트
도입 파일·버전(1.8.0/0.2.0)·IMPORTED_SOURCES 불변 기준 커밋·upstream 1.8.0 도입 모듈 파일·버전
스크립트 비실행 변환·저장 검증(새 스크립트 거절·머리 보존) lib/visual-editor
실행 취소/다시 실행·저장 상태·메시지 규약(세션 불일치 거절)·구성 요소 안전 visual-editor-core
필수 접근성 조작 + Suite 배너·SSO 변경 보존 화면(index.html)
Suite 전용 rev 충돌 409 → 맞는 rev 저장 200·버전 visual-edit·rev 증가(실제 서버) server.js [Ariweb Suite 변경]
권한·CSRF·허용 페이지·오래된 저장·AI 작업 중·캐시 미차감 modules/webmaker/test/visual-editor-server.test.js(81개 묶음 안)

검토 수정(Neo 독립 검토, 2026-10-28) — 동시 저장 경쟁(출시 차단) · 원자적 저장(안전 강화)

참조 저장소에서 RED→GREEN 으로 고친 뒤(참조 문서 "검토 수정" 절: 경쟁 테스트 actual 200/expected 409, 원자성 테스트 actual 200/expected 500, helper 없음 TypeError → 모두 GREEN) Suite 에 반영했다.

  • 그대로 가져옴(cp, diff 동일): modules/webmaker/lib/visual-editor.js(writeTextAtomic 추가), test/visual-editor.test.js(16건), test/visual-editor-server.test.js(9건 — 경쟁·원자성 추가).
  • 손으로 반영: modules/webmaker/server.js 직접 편집 PUT — 본문을 읽은 뒤 store.load 로 사이트 기록과 파일을 다시 읽고(Suite 의 revConflict 도 다시 읽은 최신 rev 로 비교), hash 검사·검증·writeTextAtomic 까지 await 없이 이어 간다. 쓰기 실패 500 한국어 안내. upstream 과의 차이는 여전히 Suite 블록만(107줄).
  • Suite 전용 추가 테스트는 두지 않았다: 공유 테스트 파일(modules/webmaker/test/visual-editor-server.test.js)이 Suite 모듈 서버로도 실행되어 경쟁·원자성을 검증하고, test/webmaker-visual-editor.test.mjs 의 rev 충돌 테스트가 다시 읽은 rev 경로를 그대로 지나기 때문이다.

GREEN(Suite 체크아웃)

cd modules/webmaker && node --test test/visual-editor-server.test.js test/visual-editor.test.js
ℹ tests 25
ℹ pass 25
ℹ fail 0
node --no-warnings test/webmaker-visual-editor.test.mjs
  ✔ server.js [Ariweb Suite 변경]: 직접 편집 저장도 동시 편집 rev 충돌(409)을 돌려주고, 맞는 rev 면 저장된다 (실제 서버) (1213ms)
✔ 통과 5 · 실패 0

전체 묶음(수정 뒤, 모두 CLAUDE_BIN=<가짜 exit 1>):

  • 참조 저장소 npm test → ℹ tests 77 · pass 77 · fail 0
  • Suite modules/webmaker npm test → ℹ tests 84 · pass 84 · fail 0
  • Suite node test/run.mjs → ✔ 테스트 파일 23개 중 실패 0개(webmaker-visual-editor 5/5 포함, 로컬 E2E 가 local-e2e-2026-10-28.md/.json 을 다시 씀, 결함 0)

실제 브라우저 시각 검증 수정(Neo 의 브라우저 게이트, 2026-10-28) — 직접 편집 중 배치(출시 차단)

  • 발견: 독립 Webmaker 코드의 실제 브라우저 검증(1280×633·390×844)에서 직접 편집을 켜도 옛 작업 칸(.left 460px)이 남아 캔버스 iframe 이 114.6px, 390px 에서는 캔버스가 y≈2228 로 밀림. 원인은 body.ve-on 의 CSS 배치 규칙 부재(JS 전환은 정상).
  • 참조 저장소에서 RED(test/visual-editor.test.js +3: CSS 계약 2건 실패 직접 편집 중 옛 작업 칸 숨김·좁은 화면 body.ve-on .app 규칙, JS 전환 동작 1건은 통과 — 진단 확인) → public/style.css 수정 → GREEN 19/19. Suite 에 동일하게 반영: modules/webmaker/public/style.css 같은 블록을 손으로 추가(upstream 과의 차이는 여전히 배너 5줄), 테스트 파일은 cp(diff 동일). Suite 모듈에서 node --test test/visual-editor.test.js → ℹ tests 19 · pass 19 · fail 0.
  • 전체 묶음(배치 수정 뒤, 모두 CLAUDE_BIN=<가짜 exit 1>): 참조 저장소 npm test → ℹ tests 80 · pass 80 · fail 0 · Suite modules/webmaker npm test → ℹ tests 87 · pass 87 · fail 0 · Suite node test/run.mjs → ✔ 테스트 파일 23개 중 실패 0개(webmaker-visual-editor 5/5 포함). 하네스가 다시 쓴 local-e2e-2026-10-28.* 보고서 파일은 이번 작업의 산출물이 아니라 보존하지 않았다.
  • 문법·차이 확인: node --check public/visual-editor-ui.js(두 저장소) 통과, style.css upstream 대비 차이 5줄(포털 배너)만, test/visual-editor.test.js 두 저장소 동일.

실제 브라우저 시각 검증 수정 2(배치 수정 뒤 게이트, 2026-10-28) — 선택 전 도구 줄 노출(출시 차단)

  • 발견: 직접 편집을 켜고 아무 요소도 고르지 않았는데 어두운 선택 도구 줄 .wm-ve-tools 가 페이지 위에 이미 그려짐(DOM 은 tools.hidden = true, 속성 창은 "선택한 요소 없음"). 원인은 캔버스 런타임(wm-ve-canvas.js)의 작성자 규칙 .wm-ve-tools{…display:flex…} 가 UA 규칙 [hidden]{display:none} 을 덮어쓰는 것.
  • 참조 저장소에서 RED(test/visual-editor.test.js +1: 런타임 소스의 CSS 블록을 파싱해 hidden 인 .wm-ve-tools·.wm-ve-drop 의 최종 display 를 cascade 모형으로 계산 — AssertionError: hidden 인 .wm-ve-tools 의 최종 display, actual 'flex' / expected 'none', 20건 중 19 통과 1 실패) → public/wm-ve-canvas.js 런타임 CSS 에 한 줄 추가 .wm-ve-tools[hidden],.wm-ve-drop[hidden]{display:none !important} → GREEN 20/20. Suite 에는 modules/webmaker/public/wm-ve-canvas.js·test/visual-editor.test.js 를 cp(두 파일 모두 upstream 과 byte-identical, cmp 확인). Suite 모듈에서 node --test test/visual-editor.test.js → ℹ tests 20 · pass 20 · fail 0. 다른 동작 변경 없음(JS 의 hidden 전환·보일 때 flex 배치 그대로, 테스트가 함께 확인).
  • 전체 묶음(도구 줄 수정 뒤, 모두 CLAUDE_BIN=<가짜 exit 1>): 참조 저장소 npm test → ℹ tests 81 · pass 81 · fail 0 · Suite modules/webmaker npm test → ℹ tests 88 · pass 88 · fail 0 · Suite node test/run.mjs → ✔ 테스트 파일 23개 중 실패 0개(webmaker-visual-editor 5/5 포함). 하네스가 다시 쓴 local-e2e-2026-10-28.* 보고서 파일은 보존하지 않았다.
  • 문법·차이 확인: node --check public/wm-ve-canvas.js(두 저장소) 통과, wm-ve-canvas.js·test/visual-editor.test.js 두 저장소 동일, style.css upstream 대비 차이는 여전히 배너 5줄.

실제 브라우저 게이트 3(Playwright 자동 게이트, 2026-10-28) — 팔레트 → 캔버스 끌어다 놓기 실패(출시 차단)

  • 발견: 실제 Chrome 에서 frameLocator 로 캔버스 iframe 을 직접 조작한 게이트가 선택·contenteditable 글 편집·실행 취소/다시 실행·Alt+↑/↓ 이동·팔레트 표시(19/19)를 통과시킨 뒤, 부모 팔레트 "문단" 의 실제 HTML5 끌어다 놓기에서 실패(끌어다 놓기 뒤 요소 수 증가 없음). 원인: 부모가 전용 MIME text/wm-ve-component 만 넣는데 캔버스는 sandbox="allow-scripts"(allow-same-origin 없음) 출처 'null' 문서라 Chromium 이 다른 출처의 전용 MIME 끌기 자료를 넘겨주지 않음. 제품 결함.
  • 참조 저장소에서 RED(test/visual-editor.test.js +3 — 순수 추출 규칙 core.dragComponentId 미존재, 팔레트 dragstart 에 text/plain 없음, 동작 테스트: 가짜 DOM 위에서 실제 wm-ve-canvas.js 를 부트하고 전용 MIME 이 없는 DataTransfer 로 dragover/drop → dragover preventDefault actual false; 23건 중 20 통과 3 실패) → 수정 → GREEN 23/23. 수정: public/visual-editor-core.js 에 COMPONENT_MIME·componentDragText·isComponentDrag·dragComponentId(전용 MIME 우선, 없으면 wm-ve-component:<id> 정확 일치 + 알려진 id 만), public/visual-editor-ui.js dragstart 가 text/plain 도 넣음, public/wm-ve-canvas.js dragover/drop 이 종류로 인식하고 drop 때 검증된 id 만 삽입(아니면 삽입·표식 없음, 기본 drop 차단). sandbox·CSP·메시지 검증·저장 검증·"추가" 버튼 대안 불변.
  • Suite 에는 modules/webmaker/public/visual-editor-core.js·visual-editor-ui.js·wm-ve-canvas.js·test/visual-editor.test.js 를 cp(네 파일 모두 upstream 과 byte-identical, cmp 확인). Suite 모듈에서 node --test test/visual-editor.test.js → ℹ tests 23 · pass 23 · fail 0.
  • 전체 묶음(끌어다 놓기 수정 뒤, 모두 CLAUDE_BIN=<가짜 exit 1>): 참조 저장소 npm test → ℹ tests 84 · pass 84 · fail 0 · Suite modules/webmaker npm test → ℹ tests 91 · pass 91 · fail 0 · Suite node test/run.mjs → ✔ 테스트 파일 23개 중 실패 0개(webmaker-visual-editor 5/5 포함). 하네스가 다시 쓴 local-e2e-2026-10-28.* 보고서 파일은 보존하지 않았다.
  • 문법·차이 확인: node --check 세 런타임 파일(두 저장소) 통과, 공유 파일 두 저장소 동일, style.css upstream 대비 차이는 여전히 배너 5줄, server.js·app.js·index.html 의 Suite 블록 차이 불변.

실제 브라우저 게이트 4(살균 DnD 진단, 2026-10-28) — HTML5 끌기가 sandbox iframe 으로 넘어가지 않음 → 포인터 끌기 다리(출시 차단)

  • 발견: MIME 보완 뒤 같은 게이트 재실행도 끌어다 놓기 단계에서 실패. 부모·캔버스 양쪽 살균 리스너(종류·플래그만) 진단: 부모 dragstart 2건(text/plain·전용 MIME 모두 유효), 캔버스 dragenter·dragover·drop 0건. Chromium 은 HTML5 끌기 자체를 부모 → 출처 'null' sandbox iframe 으로 넘기지 않는다. 제품 결함.
  • 참조 저장소에서 RED(test/visual-editor.test.js +3 — 규약·좌표 순수 규칙 paletteDragAt 는 부모→캔버스 종류, 부모 포인터 순서 동작 pointerdown/move/up 처리기, 캔버스 수신 동작 놓기 표시 보임; 26건 중 23 통과 3 실패) → 수정 → GREEN 26/26. 수정(sandbox 그대로, allow-same-origin 없음): public/visual-editor-core.js 에 toCanvas 종류 paletteDragAt·paletteDragCancel·paletteDropAt + isComponentId·POINTER_DRAG_THRESHOLD(4px)·pointInFrame(테두리·스크롤바 보정 상대 좌표)·canvasPoint(유한 숫자·뷰포트 안); public/visual-editor-ui.js 팔레트 항목 포인터 다리(pointerdown — "추가" 버튼·터치·보조 버튼 제외 → 포인터 캡처 → 문턱 → iframe 안에서만 상대 좌표 메시지, 밖·Escape·pointercancel·끝내기 → 취소, 포인터 끌기 중 네이티브 dragstart 차단, HTML5 DnD 는 대체 경로); public/wm-ve-canvas.js 가 메시지 규약 뒤 id·좌표·모드를 다시 검사해 스스로 dropTargetAt → 놓기 표시 / insertComponent(잘못된 입력은 삽입·표식 없이 표시만 숨김); public/style.css 에 .ve-comp { user-select:none; touch-action:pan-y }·끌기 중 class 2줄.
  • Suite 에는 modules/webmaker/public/visual-editor-core.js·visual-editor-ui.js·wm-ve-canvas.js·test/visual-editor.test.js 를 cp(네 파일 모두 upstream 과 byte-identical, cmp 확인), modules/webmaker/public/style.css 에는 같은 2줄을 손으로 추가(upstream 대비 차이 여전히 배너 5줄). Suite 모듈에서 node --test test/visual-editor.test.js → ℹ tests 26 · pass 26 · fail 0.
  • 전체 묶음(포인터 다리 뒤, 모두 CLAUDE_BIN=<가짜 exit 1>): 참조 저장소 npm test → ℹ tests 87 · pass 87 · fail 0 · Suite modules/webmaker npm test → ℹ tests 94 · pass 94 · fail 0 · Suite node test/run.mjs → ✔ 테스트 파일 23개 중 실패 0개(webmaker-visual-editor 5/5 포함). 하네스가 다시 쓴 local-e2e-2026-10-28.* 보고서 파일은 보존하지 않았다.
  • 문법·차이 확인: node --check 세 런타임 파일(두 저장소) 통과, 공유 파일 두 저장소 동일, server.js·app.js·index.html 의 Suite 블록 차이 불변.

독립 검토(읽기 전용) 차단 항목 12건 수정(2026-10-28) — Suite 쪽 기록

참조 저장소 쪽 상세(항목 16·810 의 결함·수정·RED 증거 표)는 modules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md(upstream 과 동일). 여기에는 Suite 반영과 Suite 전용 항목(7·11)을 적는다.

  • 공유 파일 반영: lib/visual-editor.js·lib/gitstore.js·public/visual-editor-core.js·visual-editor-ui.js·wm-ve-canvas.js·test/visual-editor.test.js·test/visual-editor-server.test.js·새 test/visual-editor-app.test.js 를 cp(모두 upstream 과 byte-identical, cmp). Suite 블록이 있는 server.js·public/app.js·lib/store.js 는 참조 저장소의 수정 전/후 diff 를 patch 로 적용하고(server.js 의 소스/직접 편집 PUT 두 hunk 는 Suite rev 줄 때문에 손으로 반영) Suite 블록을 보존했다. upstream 대비 차이: server.js 115줄(107 → 115: 항목 7 의 rev 필수 2줄·잠금 안으로 옮긴 revConflict 줄·주석), app.js 4줄·style.css 5줄·index.html 2줄 불변, lib/store.js 4줄(rev 증가 + restore 때 rev 보존).

  • 항목 7(Suite 전용) — 직접 편집 저장은 유한한 숫자 rev 필수: PUT /api/sites/<id>/visual/<페이지> 는 typeof rev === 'number' && Number.isFinite(rev) 가 아니면 400 { revRequired: true }(없음·문자열 "3"·null·"NaN"·""). 잠금 안에서 다시 읽은 사이트 기록의 rev 와 다르면(다른 파일 저장으로 오른 rev 포함) 409 conflict{ yourRev, currentRev }. 소스 편집기 PUT 의 옛 화면 호환 규칙(rev 없으면 통과)은 이 경로에 적용하지 않는다(fail-closed). 공유 서버 테스트는 GET 이 준 rev 를 모든 PUT 에 함께 보낸다(단독 Webmaker 는 0 을 주고 무시) — 처음 Suite 에서 npm test 를 돌리자 rev 없는 PUT 8건이 400 으로 떨어져 이를 드러냈고, 뒤처진 저장의 409 는 Suite 에서 hash(stale) 가 아니라 rev(conflict) 로 먼저 잡히므로 테스트가 둘 중 하나를 받는다(expectStale).

  • 항목 6 의 Suite 부분 — 되돌리기 저장은 rev 를 올리지 않는다: lib/store.js save(site, { restore: true }) 는 rev 를 그대로 둔다. 실패한 직접 편집 저장(사이트 기록 폴더 읽기 전용 → store 실패)은 500 뒤 페이지·rev 가 저장 전 값 그대로이고 같은 rev·baseHash 로 재시도하면 200.

  • 항목 11(Suite 전용) — 도입 소스 출처 fail-closed: 새 lib/provenance.mjs checkProvenance(manifest): 소스마다 40자리 commit·비어 있지 않은 파일 목록(from·to·origHash), upstream 이 있으면 version·branch·headCommit(= commit)·workingTreeDirty === false 를 요구하고, 모르는 형태·빈 목록은 불완전으로 거절. upstream 칸이 없는 0.1.x 기록(AriMail)은 깨끗한 고정 커밋만 받던 스크립트가 만든 것이라 legacy 로 받아들인다. scripts/release.mjs build 는 검사를 테스트보다 먼저 하고 깨끗하지 않으면 artifact 를 만들지 않고 종료 1(이유 출력), manifest.json 에 checks.provenanceClean·provenanceProblems·imported[].upstream/legacy 를 남긴다. register --approve 는 checks.provenanceClean !== true(false·옛 빌드의 없음)면 DB 를 열기 전에 종료 1. hal/core.approveRelease 는 checks.provenanceClean === false 면 release_checks_failed. 테스트용 ARIWEB_IMPORTED_SOURCES 로 manifest 경로를 바꿀 수 있다. 개발 중 기록(workingTreeDirty: true, --allow-dirty)으로는 build 가 멈췄다(의도). 참조 저장소 1.8.0 이 4b8c4d0de0f69025001a9e0a28a58a94e1a1f088 으로 커밋·검증된 뒤 scripts/import-sources.mjs 의 pin 을 그 커밋으로 올렸고, 그 pin 으로 --allow-dirty 없이 다시 만든 기록(commit = headCommit = 4b8c4d0, workingTreeDirty:false, 184 파일·수정 8)이면 build·approve 가 된다 — 이 작업 트리의 IMPORTED_SOURCES 파일은 아직 개발 중 기록이라 Neo 가 다시 만든다.

  • 항목 12 — 이 문서·VERSION_HISTORY·upstream 문서의 수치를 최종값으로, 가져온 파일 수는 생성된 manifest(IMPORTED_SOURCES.json)에서 읽어 적었다(VERSION_HISTORY).

  • 수정 뒤 브라우저 게이트 재실행 조사(2026-10-28): Neo 의 재실행이 edit-text 에서 "저장 상태 dirty" 시간 초과. 원인은 게이트 멱등성 결함(같은 저장 폴더에 이전 실행의 같은 제목이 이미 있어 변경 없음 = clean, 제품 규칙은 옳음)이며 로컬에서 결정적으로 재현·수정(실행마다 다른 제목 + 사전 단언). 같은 로컬 실행에서 실제 틈 하나(가운데 클릭 auxclick 이 sandbox 에서도 새 페이지를 만듦)를 찾아 wm-ve-canvas.js 에 auxclick 차단을 더했다(단위 RED→GREEN, upstream 과 byte-identical 로 반영). 로컬 Chrome 156 게이트 61/61(연속 2회), 이어서 Neo 호스트의 실제 Chrome 재실행 61/61 통과(2026-10-28). 상세: modules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md.

독립 검토 2차(종결 검토) 차단 항목 3건 + 문서 수정(2026-10-28) — Suite 쪽 기록

참조 저장소 쪽 상세(항목 1·2 의 결함·수정·RED 증거)는 modules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md(upstream 과 동일). 여기에는 Suite 반영과 Suite 전용 항목 3 을 적는다.

  • 공유 파일 반영: lib/visual-editor.js·lib/gitstore.js·public/visual-editor-core.js·test/visual-editor.test.js·test/visual-editor-server.test.js·docs/verification/visual-editor-1.8.0-tdd.md·README.md 를 cp(모두 upstream 과 byte-identical, cmp). server.js 는 참조 저장소의 수정 전/후 diff 를 patch 로 적용하되 직접 편집 PUT 의 트랜잭션 hunk 는 Suite rev 주석 때문에 손으로 반영(되돌리기의 store.save(prev,{restore:true}) 줄에 [Ariweb Suite 변경] 주석 — rev 보존). upstream 대비 차이: server.js 115줄(변함없음), app.js 4줄·style.css 5줄·index.html 2줄·lib/store.js 4줄 불변. 도입 기록 재생성: 184 파일·수정 8·workingTreeDirty=true(의도).
  • 항목 1 — SVG SMIL 저장형 XSS(공유 코드): Suite 는 같은 lib/visual-editor.js 를 쓰므로 그대로 적용된다 — 새 SMIL 요소(animate·set·animateMotion·animateTransform·animateColor·discard·mpath, 대소문자·svg: 접두어 포함)는 400, 원본에 있던 것은 그 수까지만(삭제 허용), 캔버스는 wm-ve-x- 비활성 이름. Suite 모듈 단위 테스트 38/38.
  • 항목 2 — Git 저장 트랜잭션(공유 코드 + Suite rev): gitstore.recordChangeStrict 가 로컬 커밋 → 사이트 기록(Suite 는 여기서 rev +1 한 번) → 원격 push --atomic(main + 변경 브랜치) 순서로 끝내고, 실패하면 reset --mixed·브랜치 삭제/복원·페이지 원본·store.save(prev,{restore:true})(rev 보존) 뒤 검증한 결과로만 rolledBack 을 준다. 1차 수정 경로가 하던 두 번의 store.save(manualEdits 저장 + gitRecord 안 저장)가 한 번으로 줄어 rev 는 성공한 저장마다 정확히 1 오른다(기존 test/webmaker-visual-editor.test.mjs 의 currentRev >= … 검사 그대로 통과). 푸시 전에 저장되는 기록은 pushed:false, remote:'pending' 이고(원격 반영 미주장 — 훅이 푸시 순간에 복사한 기록으로 확인), 트랜잭션 밖 reconcileSite(잠금 안, 저장 뒤·기동 때)가 ls-remote 로 확인한 뒤에만 verified 로 바꾸며 그 저장은 {restore:true} 라 rev 를 올리지 않는다(재기동 조정 테스트가 saved.rev === site.rev 를 확인). 공유 서비스 테스트(bare 원격 + 훅 실패 주입, 첫 커밋 전 되감기, 기동 조정, 모호한 푸시 판정, 잠금 경쟁)가 Suite 모듈에서도 그대로 돈다(18/18).
  • 항목 3(Suite 전용) — 릴리스 승인 fail-open: hal/core.approveRelease 가 checks.testsPassed === false·provenanceClean === false 만 거절해 checks 가 없거나(옛 빌드·수동 등록) testsPassed: null(--skip-tests)·dirty 미기록인 릴리스를 승인했고, registerRelease(approve:true)·운영자 API POST /ops/api/releases/:version/approve 도 같은 함수를 지나 같은 구멍이 있었다. 새 hal/core.releaseApprovalProblem(checks) 한 규칙: testsPassed === true·dirty === false·provenanceClean === true 가 모두 있어야 하며(없음·null·문자열 "true"·1·"false" 모두 거절) 이유를 release_checks_failed 메시지로 돌려준다. scripts/release.mjs register --approve 도 같은 함수로 DB 를 열기 전에 종료 1(이유 출력). --skip-tests 빌드(testsPassed null)는 등록만 되고 승인은 불가. 기존 테스트 픽스처(hal-cli 2곳·hal 1곳 registerRelease, e2e buildFakeRelease manifest)는 깨끗한 checks 를 넣도록 고쳤다 — 현재 요구사항에 관용 정책이 없어 옛 정책은 남기지 않았다.
  • 문서 수정 — 이 문서·VERSION_HISTORY·upstream 문서의 "재실행 필요/미실행"·"자동화하지 않음"·옛 수치(103/110·182 파일·HTML5 주 경로·release.mjs 변경 없음)를 현재 증거(Neo 호스트 실제 Chrome 61/61, 106/113/24, 184 파일·수정 8, 포인터 다리 주 경로 + HTML5 대체, release.mjs·hal/core 변경됨)로 고쳤다.

독립 검토 3차(최종 검토) 차단 항목 4건(2026-10-28) — Suite 쪽 기록

  • 항목 1(Suite 전용) — 승인된 릴리스 불변: hal/core.registerRelease 는 기존 행이 approved 면 같은 내용(main 커밋·빌드 시각·artifact·checks 를 키 순서 무관하게 비교, 메모는 준 경우)의 재등록만 멱등하게 돌려주고(approve:true 여도 승인자·시각 그대로), 다르면 release_immutable 로 거절하며 UPDATE 를 실행하지 않는다. 기존 행이 승인되지 않았으면 내용을 갱신하되 status='registered', approved_by=NULL, approved_at=NULL 로 두고, approve 는 releaseApprovalProblem 규칙을 지나야 한다. hal/index.mjs register-release 는 거부를 릴리스 등록 거부 (release_immutable): … 로 출력하고 종료 1(DB 불변). 운영자 API 의 승인 경로(POST /ops/api/releases/:v/approve)는 같은 규칙(앞 절 테스트). RED → GREEN: test/release-provenance.test.mjs +2 — core(8가지 변경 × approve true/false 모두 거절·readback 불변, 미승인 버전의 승인 미승계) · CLI(같은 manifest 재등록 멱등, 불량 checks·다른 artifact 재등록 종료 1 + DB readback 불변).
  • 항목 2·3(공유 코드): modules/webmaker/server.js 에 라우터 수준 잠금·재적재(본문 메모 읽기 → 잠금 안 재적재 → 짧은 경로 전체 잠금 / 긴 경로 재적재)와 재진입 잠금, changeLanguages 순서, 직접 편집 PUT 의 remoteState:'ambiguous' 응답을 upstream 과 같은 블록으로 반영(Suite 블록 보존). lib/gitstore.js 는 byte-identical. Suite 의 직접 편집 rev 충돌 검사는 잠금 안에서 재적재된 최신 기록으로 이루어진다.
  • 항목 4: 이 문서·VERSION_HISTORY 의 합계(107/114 등)를 현재 증거로 바꿨다.
  • 항목 5(공유 코드 + Suite 블록) — 배경 작업 저장 합치기: modules/webmaker/lib/store.js 의 load 가 읽은 상태를 __base(열거 안 됨)로 기억하고 save 가 디스크 saveId 변화를 보고 이 객체가 바꾼 최상위 항목만 합친다. Suite 블록: 합칠 때 rev 는 디스크 값을 기준으로 올리고, keepRev(원격 반영 조정)는 합치되 rev 를 올리지 않는다(restore 는 스냅샷 그대로, rev 불변). upstream 대비 차이 5줄. 되돌리기 검증(server.js)은 updatedAt·saveId 만 제외하고 기록을 비교한다. 공유 서비스 테스트 +1(작업 중 PATCH 제목·PUT 등급 → 중단 → 유지)이 Suite 모듈에서도 그대로 돈다(19/19).
  • 항목 6(공유 코드 + Suite 블록) — 같은 잎 동시 변경 fail-closed: store.js 합치기를 잎 단위 3자 병합으로(일반 객체는 안으로, 배열·기본값은 잎), 같은 잎을 둘 다 다르게 바꾼 경우만 SaveConflict(code site_save_conflict, status 409, fields)로 거절·디스크 보존. store.update(id, mutate, opts)·store.adopt(site, fresh) 추가(Suite: update 도 keepRev 를 받아 조정 저장은 rev 불변). server.js: 배경 작업 저장 전부 saveJob(site, reapply)(충돌이면 최신 기록 위에 작업 항목만 재적용 + adopt), gitRecord 충돌 때 버전만 추가·옛 실패분 재시도는 잠금 안 조정으로, /git/retry·reconcileSite 는 applyPushState 를 store.update 로 적용. 그 밖의 화면 경로의 충돌은 409 로 드러난다(조용한 손실 없음). RED → GREEN: 공유 서비스 테스트 +2(저장소 규칙 6가지 / 실제 서버 — git 래퍼로 commit 2초 지연 → 작업 종료의 버전 기록 창에 다시 푸시 → 다시 푸시의 verifiedAt 그대로·작업의 새 버전도 기록); 중간 RED 에서 충돌이 다시 푸시 경로에 409 로 드러난 것을 확인한 뒤 그 경로를 update 로 옮겼다. Suite 모듈 21/21.

독립 검토 4차 차단 항목 4건(2026-10-28) — Suite 쪽 기록

  • 항목 1(Suite 전용) — artifact 설치 불변·fail-closed: hal/core.installArtifacts(d, {version, mainCommit, builtAt, checks, artifacts:{mod:{file, sha256, size, src}}}) — ① 원본 바이트 sha256·크기를 선언값과 대조(불일치 → artifact_hash_mismatch, 아무것도 쓰지 않음) ② 승인된 버전이면 releaseImmutableProblem(메타데이터) + 설치된 파일 해시 모두 같을 때만 멱등(복사 없음), 아니면 release_immutable(기존 파일·DB 불변) ③ 새·미승인 설치는 .<file>.tmp-… 에 쓰고 해시를 다시 확인한 뒤 rename. hal/index.mjs register-release·scripts/release.mjs register 는 설치 → registerRelease 순서로 바뀌었고 거부는 종료 1(release.mjs 는 DB 를 닫고 종료). RED → GREEN: test/release-provenance.test.mjs +2 — HAL CLI(A 승인 → B 유효 해시·B 바이트에 A 해시 거짓 선언 × approve 유무 → 종료 1, 대상 바이트/해시/DB 가 A, 임시 파일 없음, A 재등록 멱등, 새 버전 해시 불일치 미설치) · release.mjs(같은 4 경우 + 멱등).

  • 항목 2·3·4(공유 코드): modules/webmaker/server.js — 읽기 요청 부작용 제거(언어 저장은 잠금 안 재적재에서만), 폐기 규약(destroySite(siteId, by, {ownerId, confirm}): 잠금 안 재검사·표식 claim → 바깥 정리 → 같은 표식 확인 뒤 삭제; 라우터 재적재·claimJob 의 폐기 중 409; 실패는 store.update 로 deleteFailed) — 사용자 경로는 Suite 의 포털 영향 확인 블록(ariweb.impactFromPortal·acknowledgeImpact·unlink)을 그대로 두고 마지막 호출만 바꿨다(upstream 대비 118줄 차이 — 기존 Suite 블록 + 폐기 전 검사 2줄). lib/visual-editor.js 스캐너의 외래 콘텐츠 깊이는 byte-identical.

  • 잔여 위험 해소 — 폐기 때 포털 연결 보상(Suite 블록 + 공유 훅): 포털 연결 등록부(lib/ariweb.js 의 ariweb-links.json, 로컬 파일)의 해제(unlink)는 되돌릴 수 있고(link 로 되살림, linkedAt 보존 인자 추가), 포털 자체에는 영향 조회(impactFromPortal, 읽기)만 한다. 공유 destroySite 의 최종 잠금 단계를 "임시 이름으로 옮김(rename) → beforeIrreversible → 기록 unlink(되돌릴 수 없는 한 단계) → 최선의 뒷정리" 로 바꾸고, Suite 사용자 경로는 beforeIrreversible 에서 ariweb.unlink + readback(남아 있으면 502), compensate 에서 ariweb.link(원래 연결) + readback 을 한다. 순서상 정리 실패·연결 해제 실패에는 되돌릴 수 없는 변경이 하나도 없고, 기록 삭제 실패에는 되살리기가 있으며 되살리지 못하면 recoveryRequired(응답·deleteFailed·감사 site.destroy.failed)로 운영자에게 보인다. 뒷정리 실패는 성공 응답의 warnings + 감사 site.destroy.cleanup(남은 .deleting-… 폴더). RED → GREEN: modules/webmaker/test/ariweb.test.js +3(가짜 ssh·가짜 배포 대상·가짜 포털) — ① 정리 실패(SSH_FAIL): 연결·파일·기록 그대로 + deleteFailed, 정리가 되면 폐기 성공·연결 없음 ② 정리 성공 뒤 연결 등록부 쓰기 실패(DATA_ROOT 읽기 전용): ssh 호출 있음, 연결·파일·기록 그대로(임시 폴더 없음), 쓸 수 있게 되면 성공 ③ 되살리기 readback + 뒷정리 실패(지울 수 없는 하위 폴더): 200 + warnings, 연결·기록·원래 폴더 없음, 남은 폴더 1개 확인 뒤 정리.

RED — Suite(4차 검토)

  • node test/release-provenance.test.mjs(수정 전 CLI): ✘ 통과 9 · 실패 2 — B 유효 해시 + approve: 대상 바이트 A 그대로: 기대 "artifact-A-bytes" 실제 "artifact-B-bytes-different", release.mjs 기대 "rel-A" 실제 "rel-B-other"(승인된 artifact 바이트가 덮임).
  • 공유 테스트(참조 저장소, 수정 전): 단위 38/39(거절되어야 함: …<svg><title><script>alert(1)</script></title></svg>), 서비스 21/23(읽기 요청은 기록 파일을 바꾸지 않는다, 폐기 중 저장이 "폐기" 로 거절되지 않음).
  • Suite 모듈 test/ariweb.test.js(수정 전 순서 — unlink 먼저): ℹ tests 10 · pass 7 · fail 3 — 포털 연결이 풀리지 않았다(연결 해제는 정리 성공 뒤에만): null, 정리는 먼저 수행됐다(연결 해제 실패가 정리 전에 남), 연결이 되살아났다(readback): null.

GREEN — Suite(4차 검토 뒤, 현재 증거)

node test/release-provenance.test.mjs                     ✔ 통과 11 · 실패 0
modules/webmaker: CLAUDE_BIN=<가짜 exit 1> npm test       ℹ tests 125 · pass 125 · fail 0   (단위 39 · 서비스 23 · 화면 전환 1 · 원본 59 + ariweb 폐기 보상 3)
CLAUDE_BIN=<가짜 exit 1> node test/run.mjs                ✔ 테스트 파일 24개 중 실패 0개   (생성된 local-e2e-2026-10-28.{md,json} 은 삭제)

참조 저장소: npm test 115/115(단위 39 + 서비스 23 + 화면 전환 1 + 원본 52). 실제 Chrome 게이트(Neo 호스트) 61/61 그대로(게이트가 보는 화면 동작 불변).

RED — Suite(최종 검토)

  • node test/release-provenance.test.mjs(수정 전 hal/core.mjs): ✘ 통과 7 · 실패 2 — Error: 승인자·시각도 그대로: 기대 {"status":"approved","by":"op",…} 실제 {"status":"approved","by":"op2",…}(승인된 행이 재등록으로 바뀜), CLI 재등록 뒤 approved_by 가 "test" → "test2".
  • 공유 서비스 테스트(수정 전 코드, 참조 저장소): ℹ tests 18 · pass 15 · fail 3 — 잠금 경쟁(설정 저장이 직접 편집 버전을 덮어쓰지 않는다: ["1:create"]), 모호한 푸시(원격이 받았으면 성공: {…"rolledBack":true,"stage":"push"}), 준비 단계 실패(stage·rollback 없음, 페이지 미복원). 항목 5(수정 전 store.js): ℹ tests 19 · pass 18 · fail 1 — 작업의 종료 저장이 화면의 제목 변경을 덮어쓰지 않는다: {"title":"VE site","status":"idle"}.

GREEN — Suite(최종 검토 뒤)

node test/release-provenance.test.mjs                     ✔ 통과 9 · 실패 0
modules/webmaker: CLAUDE_BIN=<가짜 exit 1> npm test       ℹ tests 119 · pass 119 · fail 0   (단위 38 · 서비스 21 · 화면 전환 1 · 원본 59)
CLAUDE_BIN=<가짜 exit 1> node test/run.mjs                ✔ 테스트 파일 24개 중 실패 0개   (생성된 local-e2e-2026-10-28.{md,json} 은 삭제)

참조 저장소(그때): npm test 112/112(단위 38 + 서비스 21 + 화면 전환 1 + 원본 52). 현재 증거는 위 "4차 검토 뒤" 절.

RED — Suite(종결 검토)

  • node test/release-provenance.test.mjs(수정 전 hal/core.mjs·scripts/release.mjs): ✘ 통과 4 · 실패 3 — Error: checks 없음 (approveRelease)(승인됨), Error: 7.7.0: 거절 200 {"ok":true,"release":{…}}(운영자 API 가 checks 없는 릴리스를 승인), Error: tests-null: HAL 릴리스 등록: 0.2.0 (approved) … 기대 1 실제 0(register --approve 가 testsPassed null 을 승인).
  • 공유 서비스 테스트(수정 전 코드, 참조 저장소에서 실행): ℹ tests 14 · pass 11 · fail 3 — Git 실패 되돌리기 rolledBack:false(옛 boolean 은 상태를 검증하지 않음), 원격 트랜잭션(옛 코드는 pre-receive 거부를 200 + rec.error 로 삼킴). 단위: SMIL 거절되어야 함: …<animate attributeName="href" values="javascript:alert(1)"….

GREEN — Suite(종결 검토 뒤)

node test/release-provenance.test.mjs                     ✔ 통과 7 · 실패 0   (checks 13가지 불량 × approveRelease/registerRelease 거절, 운영자 API 거절/승인, register --approve 4가지 거절 + 깨끗한 manifest 승인)
modules/webmaker: CLAUDE_BIN=<가짜 exit 1> npm test       ℹ tests 114 · pass 114 · fail 0   (단위 38 · 서비스 16 · 화면 전환 1 · 원본 59)
CLAUDE_BIN=<가짜 exit 1> node test/run.mjs                ✔ 테스트 파일 24개 중 실패 0개   (생성된 local-e2e-2026-10-28.{md,json} 은 삭제)

참조 저장소(그때): npm test 107/107(단위 38 + 서비스 16 + 화면 전환 1 + 원본 52). 현재 증거는 위 "최종 검토 뒤" 절.

RED — Suite(1차 검토)

  • node --no-warnings test/release-provenance.test.mjs(구현 전): ✘ 통과 0 · 실패 4(lib/provenance.mjs 없음 → 네 테스트 모두 실패).
  • node --no-warnings test/webmaker-visual-editor.test.mjs 를 rev 필수 줄과 { restore: true } 를 뺀 임시 서버 복사본 으로 실행: ✘ 통과 4 · 실패 1 — Error: rev 없음: {"path":"index.html",…,"rev":11,…}(rev 없는 저장이 200 으로 파일을 바꿈). 실행 뒤 복사본은 수정본으로 되돌렸다(cmp 확인).
  • Suite modules/webmaker npm test(공유 테스트가 rev 를 보내기 전): ℹ tests 110 · pass 102 · fail 8 — 모두 {"error":"저장본 개정 번호(rev)가 없거나 올바르지 않습니다…","revRequired":true}(항목 7 이 동작함을 보임) → 공유 테스트에 rev 추가 → pass 108 · fail 2(409 가 stale 대신 conflict) → expectStale → 110/110.

GREEN — Suite(1차 검토 뒤)

node --no-warnings test/release-provenance.test.mjs      ✔ 통과 4 · 실패 0
node --no-warnings test/webmaker-visual-editor.test.mjs  ✔ 통과 5 · 실패 0   (rev 없음/문자열/null/NaN/빈 → 400, 교차 파일 rev → 409, store 실패 → 500·rev 보존·재시도 200)
modules/webmaker: CLAUDE_BIN=<가짜 exit 1> npm test       ℹ tests 110 · pass 110 · fail 0
CLAUDE_BIN=<가짜 exit 1> node test/run.mjs                ✔ 테스트 파일 24개 중 실패 0개   (release-provenance 포함; 생성된 local-e2e-2026-10-28.{md,json} 은 삭제)

참조 저장소: npm test 103/103(단위 37 + 서비스 13 + 화면 전환 1 + 원본 52).

브라우저 검증 상태 · 자동화하지 않은 것

  • 실제 브라우저 검증(Neo 의 게이트, 실제 Chrome)은 다섯 차례 수행되었다: 배치 결함 → 선택 전 도구 줄 노출 → 끌어다 놓기 실패 → 살균 진단으로 "HTML5 끌기가 sandbox iframe 에 도달하지 않음" 확정(3·4차는 선택·글 편집·실행 취소/다시 실행·키보드 이동 통과) → 포인터 다리 뒤 네이티브 포인터 순서 게이트 37/37. 독립 검토 수정 뒤 재실행은 게이트 멱등성 결함을 고친 뒤 Neo 호스트에서 61/61 통과(연결 규약·이동 활성화 6종·위조 창 메시지 단언 포함) — 캔버스 안 실제 포인터 조작(글 입력·실행 취소/다시 실행·이동·팔레트 끌어다 놓기·저장·저장 파일·390px)은 이 게이트로 자동 검증했다. Suite 쪽 화면은 독립 Webmaker 와 같은 style.css·visual-editor-ui.js·wm-ve-canvas.js 를 쓰므로 같은 수정이 적용된다(style.css 포털 배너 5줄만 추가). 종결 검토 3건은 브라우저 동작을 바꾸지 않아 추가 재실행은 필요 없다.
  • 자동화하지 않은 것: 포털 SSO 로 들어간 실제 화면 확인, 운영 서버 배포, Chrome 외 브라우저. 사람 확인 목록은 Webmaker Dev_Order_v1.8.0.md 끝.