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

187 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 쪽 기록
참조 저장소 쪽 상세(항목 1~6·8~10 의 결함·수정·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` 끝.