152 KiB
VERSION_HISTORY
0.3.10 (2026-10-29)
- 작성자: 개발 지시 — Oz(
Ariweb-Suitle_Dev-Order_v0.3.10.md, 2026-10-29) · 구현 — Claude Fable 5.1(브랜치feature/hal-shell-copy-v0.3.10, 기준 mainbed038f= 0.3.9). 커밋·push·main병합·릴리스 빌드·bearhal21.ariweb.netHAL 배포까지 수행(아래 "배포"). - 이전 버전: 0.3.9(2026-10-29). 끝 번호 1 증가(결함 대응 1항목). HAL 에만 적용, 다른 모듈 변경 없음(배포하지 않음).
요구(사용자 요구 — 약화 없이 반영)
텍스트 셸에서 브라우저를 열고 인증 후 프롬프트를 입력하라고 나오는데, 긴 브라우저 접속 주소를 복사할 수 없다. 이런 상황에 대한 대응책을 마련하라.
원인
- 0.3.9 단말 영역은 포커스 상태에서 모든 키를 셸로 보냈다 → Ctrl+C 가 복사 대신 ^C(중단)로 전달되어 복사가 되지 않았다.
- 긴 인증 주소가 단말 열 수(100)에서 끊겨 두 줄로 그려져 한 번에 고르기 어려웠고, 클릭·복사 단추가 없었다.
수정한 기존 기능
hal/ui/public/term.mjs:joinedLines(꽉 찬 줄을 다음 줄과 이어 붙임)·plainText·extractUrls(스크롤백·대체 화면 포함, 상자 문자(│┃║|) 제외, 꼬리 구두점 제거, 중복 제거)·copyDecision(선택 있으면 Ctrl+C 복사, Ctrl+Shift+C·Ctrl+Insert 항상 복사, Ctrl+V·Shift+Insert 붙여넣기, 그 밖은 전송).hal/ui/public/ai-render.mjs:shellModalHtml(셸 모달 본문을 순수 템플릿으로)·shellUrlsHtml(감지된 주소: 복사 단추, 새 탭 열기rel="noopener noreferrer"), 전체 출력 복사·선택 복사·텍스트로 보기(읽기 전용 textarea)·복사 안내.hal/ui/public/ai.js:copyText(Clipboard API → 숨은 textarea 폴백 → 안내), 키 처리에copyDecision, 폴링마다 주소 목록·텍스트 보기 갱신.ops.css: 단말user-select:text, 주소 목록 스타일.hal/ui.mjs: 셸 절에 복사 방법 안내.- 버전·문서: 0.3.10(package.json·README·버전 검사 테스트),
docs/HAL-AI.md§9, 개발 지시 v0.3.10.
고객 화면
- 변경 없음.
운영자 화면
- 텍스트 셸 모달에 "감지된 주소" 목록(복사·열기), "전체 출력 복사", "선택 복사", "텍스트로 보기". 단말 글을 드래그로 선택하면 Ctrl+C 가 복사가 되고(Ctrl+Shift+C 는 항상 복사), 셸에 중단 신호를 보내려면 선택을 풀고 Ctrl+C 또는 키 단추.
보안 결정
- 열기 링크는 새 탭 +
noopener noreferrer(HAL 창 접근 차단). 주소는 화면 표시·클립보드로만 가고 서버에 저장하지 않는다. 셸 입력·출력 저장 정책은 0.3.2 그대로(입력 미저장, 출력 메모리만).
테스트
- RED(구현 전): 새
test/hal-ai-0310.test.mjs0/4(extractUrls·copyDecision·shellUrlsHtml없음, 화면 안내 없음). - GREEN:
test/hal-ai-0310.test.mjs4/4 — 줄 끝에서 끊긴 주소 이어 붙이기(40열)·꽉 차지 않은 줄은 잇지 않음·꼬리 마침표 제거·상자 문자 제외·스크롤백·중복 제거·대체 화면·전체 글 / 복사 판정(선택 Ctrl+C=복사, 없으면 전송, Ctrl+Shift+C·Ctrl+Insert=복사, Ctrl+V·Shift+Insert=붙여넣기) / 셸 모달 순수 템플릿(주소 목록 이스케이프·복사·열기 noopener·전체 복사·텍스트 보기·안내) / 화면·버전·문서. - 기존:
hal-ai-0396/6 ·hal-ai-0326/6 ·hal-ai12/12 ·hal-ui7/7 ·webmaker-visual-editor5/5. 전체 Suitenode test/run.mjs: 29개 파일 실패 0, 199건 통과. 변경 파일 secret-like 스캔 0건. 러너 임시 파일 미커밋.
배포(bear hal21.ariweb.net, HAL 만)
- 절차와 read-back 은 배포 뒤 이 절에 후속 커밋으로 기록한다.
알려진 제한
- 주소가 두 줄 이상에 걸쳐 끊길 때 중간 줄이 꽉 차지 않으면(TUI 가 상자 안에서 임의로 줄을 나눈 경우) 이어 붙이지 못할 수 있다 — 그때는 "텍스트로 보기"에서 직접 고른다.
- Clipboard API 는 HTTPS·사용자 동작 안에서만 동작한다(hal21 은 HTTPS). 실패 시 폴백·안내.
미확정 정책값
- 없음.
0.3.9 (2026-10-29)
- 작성자: 개발 지시 — Oz(
Ariweb-Suitle_Dev-Order_v0.3.9.md, 2026-10-29) · 구현 — Claude Fable 5.1(브랜치feature/hal-ai-shell-v0.3.9, 기준 mainf861c7e= 0.3.2). 커밋·push·main병합·릴리스 빌드·bearhal21.ariweb.netHAL 배포까지 수행(아래 "배포"). - 이전 버전: 0.3.2(2026-10-29). 지시에 따라 버전은 0.3.9(끝 번호 7 증가 — 독립 개선 6항목 + 점검 수정). HAL 에만 적용, 다른 모듈 변경 없음(배포하지 않음).
요구(사용자 요구 — 약화 없이 반영)
- 로그인 터미널 개선 — claude 는 설치되어 있으면 바로 실행되고 신뢰 여부를 입력하면 연결되므로, 무조건
claude --login뒤 브라우저 인증을 하는 구조는 동작하지 않을 수 있다. 종합적으로 고려해 CLI 로그인 인증을 개선. 2. 텍스트 셸 개선 — 화면만 보여 주는 수준으로는 여러 입력 상황(예: Trust 선택)에 대응할 수 없고, 현재 입력창은 코드만 받는다. 범용적으로 쓸 수 있게. 로그인 안내 메시지는 나오는데 이를 수행할 텍스트 셸이 동작하지 않는다. 3. "열린 텍스트 셸이 2개입니다" 오류 — 화면에서는 모두 닫았는데 시스템은 다르게 인식. 원인을 찾아 수정. 4. LLM 연동 후 모델을 사용자가 선택. 5. 컨텍스트 입력 창에/order형태의 기본 명령어 셋,/help로 목록. 6. 미확인 버그·오작동 점검·수정.
점검 결과와 원인
- (1) bear 의 Claude Code 2.1.312 에서
claude login은 로그인 명령이 아니라 대화형 세션에 "login" 을 입력하는 것이다(비 TTY 로 실행하면 "Failed to authenticate: OAuth session expired" 만 출력). 실제 로그인은claude auth login(--claudeai(기본)·--console·--sso옵션, 화살표·Enter 로 고르는 TUI). bareclaude는 새 작업 폴더에서 "이 폴더의 파일을 신뢰합니까?" 를 묻는다(~/.claude.json의projects에 HOME 은 이미 신뢰됨). - (2) 0.3.2 셸은 출력에서 ANSI 를 걷어 내 줄로 붙이고 입력은 "글 + Enter" 만 보내 TUI(커서 이동·선택 목록)를 그릴 수도, 화살표·단일 키(y/n)를 보낼 수도 없었다.
- (3) 모달 "닫기"가 세션을 종료하지 않았다(폴링만 멈춤) → 대화형 CLI 가 입력을 기다리며 살아남아 한도 2를 채움. 또 세션 수를
status==='running'으로만 세어 프로세스가 끝났는데 상태가 남는 경우도 한도에 들었다. - (6) 점검에서 더 고친 것:
window.open기능 문자열에 잘못된noopener=no, 패널에서reauth_required연결을 고칠 길이 없음(단추 추가), 로그인 안내가claude login(틀린 명령)을 가리킴, 확인 실패 메시지가 서버 셸을 가리키며 HAL 화면의 셸을 안내하지 않음.
수정한 기존 기능
hal/ai/shell.mjs: 명령표 교체(claudeauth login·auth logout·auth status·login_console·interactive, codexinteractive추가), 작업 폴더 =cliHome()(HOME), 단말 크기cols·rows(40250 × 1080, 기본 100×30) →COLUMNS/LINES, 살아 있는 프로세스 기준 한도 3(끝났는데 running 인 세션 정리), 409 에 열린 세션 목록,killAll(operator), 공개 객체에args·cwd·cols·rows. 안내문(SHELL_GUIDE)을 TUI·신뢰 질문·종료 방법 중심으로 다시 씀.hal/ai/providers.mjs:CLI_TOOLS.claude.loginCommand = 'claude auth login', 로그인 안내가 HAL 텍스트 셸을 가리키도록,cliHomeexport.- 새
hal/ui/public/term.mjs(순수 단말 에뮬레이터): 글자·\r\n\b\t, CSI 커서 이동(A B C D E F G H f d)·지우기(J K X P @)·줄 삽입/삭제(L M)·스크롤(S T)·저장/복원(s u, ESC 7/8)·스크롤 영역(r)·모드(?25 커서, ?1049/?47 대체 화면)·SGR 무시·OSC 무시·불완전 시퀀스 보관, 줄 끝 자동 줄 바꿈, 스크롤백 1000줄,renderHtml(커서 표시, 이스케이프),keyToBytes(브라우저 키 → 바이트). - 새
hal/ai/commands.mjs: 명령 20개(local 4 + prompt 16),parseCommand·helpText·publicCommands.hal/ai/engine.mjs:send()가/로 시작하면 명령 해석(모르면 400, 인자 누락 400), local 은localCommand(help·tools·model(개별 방만, 목록 검증)·reset — 모델 호출 없음), prompt 는 user 메시지에command블록(원문 + 확장 지시)을 붙이고buildMessages가 모델에게 확장 지시를 준다. hal/ai/rooms.mjs:listRooms의connection.models·toolsVia·lastError.hal/ai/routes.mjs:POST /shell에cols·rows,POST /shell/kill-all,GET /ops/api/ai/commands.- 화면:
hal/ui.mjs셸 절(claude auth login · 대화형(신뢰 설정) · 상태 / codex login · 대화형 · 상태 / 모두 종료, 세션 목록), 패널 입력 안내에/help;ai-render.mjsmodelSelectHtml(연결 표·패널 상태 줄)·shellSessionsHtml·commandsHelpHtml, 상태 줄 "로그인·재인증" 단추;ai.js셸 모달을 단말 에뮬레이터로(포커스 키 전달·붙여넣기·키 단추·줄 입력·닫기=종료·세션 다시 열기), 세션 목록·모두 종료, 모델 선택 변경 → PATCH,/만 치면/help;ops.css단말 스타일. - 버전·문서: 0.3.9(package.json·README·버전 검사 테스트),
docs/HAL-AI.md§8, 개발 지시 v0.3.9.
고객 화면
- 변경 없음.
운영자 화면
- AI 연동 › 텍스트 셸: "claude auth login"(TUI 로그인), "claude 대화형(신뢰 설정)", 상태, codex login·대화형·상태, "열린 셸 모두 종료", 세션 목록(실행 중·끝남, 열기·종료). 셸 모달: 검은 단말 화면(클릭 후 키 직접 입력, 커서 표시), 키 단추, 줄 입력란, 안내문. 닫으면 세션이 끝난다.
- 연결 표·패널 상태 줄: 모델 선택 상자(확인된 목록). 패널 상태 줄의 "로그인·재인증" 단추(재인증 필요 연결).
- 지시 입력:
/help로 명령 목록,/status/servers/jobs/releases/incidents/order <지시>/deploy …/model <이름>/tools/reset등. 운영자 원문이 기록에 남고 모델은 확장 지시를 받는다.
보안 결정
- 텍스트 셸은 여전히 허용 명령표만 실행한다(대화형 세션 추가 포함, 임의 명령 없음). 대화형 CLI 는 운영자 본인의 서버 계정 CLI 이며 HAL 환경 변수(Vault 키)는 넘기지 않는다. 입력은 저장·감사하지 않는다(시작·종료만 감사).
- 슬래시 명령의 확장 지시는 모델에게 "어떤 도구로 확인·제안하라"를 말할 뿐 승인 규칙(쓰기·고위험 제안 → 운영자 재인증·3단계)을 바꾸지 않는다.
/model은 확인된 모델 목록 밖을 거부한다. - 단말 에뮬레이터는 출력을 이스케이프해 그린다(TUI 출력에 HTML 이 섞여도 실행되지 않음).
테스트
- RED(구현 전): 새
test/hal-ai-039.test.mjs0/6(명령표['login'], interactive 거부 400, term.mjs 없음,connection.models없음,/ops/api/ai/commands404, 화면 문구·버전). - GREEN:
test/hal-ai-039.test.mjs6/6 — 명령표(auth login·auth logout·interactive)·안내·claude auth login힌트 / auth login TUI 흉내(크기 120×40 전달·작업 폴더 HOME·화살표 바이트 그대로·코드 입력·종료 코드) / 대화형 신뢰 질문에y단일 키 / 옛 명령 문자열·옵션 거부 / 한도 3·끝난 세션 제외·409 세션 목록·DELETE 뒤 재시작·kill-all / 단말 에뮬레이터(CR 덮어쓰기·커서 이동·줄 지우기·SGR·OSC·커서 숨김·화면 지우기·스크롤백·대체 화면 복원·절대 이동·이스케이프·자동 줄 바꿈) / 모델 선택(목록·PATCH·/model·목록 밖 400·통합 방 400) / 슬래시 명령(/help모델 호출 없음·/status확장 지시·원문 기록·명령 블록·/servers web·/order·인자 누락·/deploy portal 0.3.9·/tools·/reset·모르는 명령·통합 방/help·일반 글) / 화면·버전·문서. 중간 결함 2건을 테스트가 잡았다: ① CSI 의 private 표식(?25l·?1049h)을 매개변수 뒤에서 찾아 시퀀스가 영원히 보류되던 해석기 오류 → 표식을 앞에서 해석 ② 최소 열 수 10 → 4(좁은 화면 검사). - 기존:
test/hal-ai.test.mjs12/12(로그인 힌트claude auth login으로 갱신),test/hal-ai-032.test.mjs6/6(가짜 CLI 를auth login형식으로),hal-ui7/7,webmaker-visual-editor5/5. 전체 Suitenode test/run.mjs: 28개 파일 실패 0, 195건 통과. 변경 파일 secret-like 스캔 0건. 러너 임시 파일 미커밋.
배포(bear hal21.ariweb.net, HAL 만)
- 2026-10-29 02:25 UTC, 코드 릴리스만(새 마이그레이션 없음; data/config/Vault 파일 무변경). HAL 만 배포, 다른 모듈 미변경·미배포.
- 저장소: 기능 커밋
fe4800f94a55ed1199e6ff5b301176e9d9819b23(브랜치feature/hal-ai-shell-v0.3.9→mainfast-forward f861c7e..fe4800f, read-backorigin/main = fe4800f). 릴리스 빌드(그 커밋, 전체 테스트 재실행 28파일 실패 0): manifest0.3.9 · mainCommit fe4800f… · testsPassed=true · dirty=false · provenanceClean=true,verify5 artifact ✔. hal artifact sha256ebf56b375635738feed9b8fe84640c6c96d4fcf4aafaa548343a819f91ffff2b. - 적용(
~/oz-deploy/bear-apply.sh 0.3.9 …, dist 5개 artifact 업로드): 해시 OK → 사전 백업/srv/ariweb/hal/backups/pre-v0.3.9-20261029T022542Z/→ stage 추출·검증 →/srv/ariweb/hal/releases/0.3.9-fe4800f→register-release --approve --by oz-dev-order-v0.3.9(approved, 5 artifact) →bootstrap-release→ current 전환 → 재시작. restart →/api/health200 500ms, active,version 0.3.9 · bootstrap complete · releaseVersion 0.3.9 · mainCommit fe4800f… · artifactSha256 ebf56b37…. 보존 sha256 전=후(config.jsonae5963d2…, vault.json0e2dc987…, vault.key345d92e7…), 백업 sums OK, 이전 릴리스 디렉터리 11개 보존. 운영 DB: releases 7행 approved, bootstrap 0.3.9, 서버 2대 ready(heartbeat 재개 02:26:00Z·02:26:09Z), journal 오류 0. 운영 AI 연결 2건:H9001(local-cli codex, gpt-6.1-sol, active) ·H9000(local-cli claude, claude-opus-5-5,reauth_required— claude 로그인 전). - read-back(배포 코드 harness
~/oz-deploy/deployed-render-0.3.9.mjs, 메모리 DB·임시 data dir): 슬래시 명령 20개(help status health servers server jobs job releases incidents audit dr emergency order deploy rollback isolate incident model tools reset), 셸 명령표 claudelogin=[auth,login]·login_console·logout=[auth,logout]·status=[auth,status]·version·interactive=[],/ops/ui/term.mjs200,/ops/ai에 "claude auth login·대화형·열린 셸 모두 종료", 패널 입력 안내에/help, 공급자 6종, MCP 설정 경로releases/0.3.9-fe4800f/hal/index.mjs mcp, 도구 33, CLI 탐색 claude 2.1.312(로그인 필요)·codex 0.162.1(로그인됨). 외부https://hal21.ariweb.net/api/health/ops/login/ops/ai/ops/ui/ai.js/ops/ui/term.mjs모두 200. - 운영자 조치: AI 연동 › 텍스트 셸 › "claude auth login"(화살표·Enter 로 방식 선택 → URL 열고 코드 붙여넣기) 뒤 H9000 의 "확인"; 처음 대화형을 쓰려면 "claude 대화형(신뢰 설정)"에서 신뢰 질문에 답한다. 채팅에서
/help. - 되돌리기(코드만):
ln -sfn /srv/ariweb/hal/releases/0.3.2-8a111bc /srv/ariweb/hal/current && sudo systemctl restart ariweb-hal.
알려진 제한
- 단말 에뮬레이터는 색·굵게를 그리지 않고 한글 등 2칸 글자를 1칸으로 센다(TUI 가 2칸으로 계산하면 조금 어긋남). 단말 크기 변경(SIGWINCH)은 없다(세션 시작 때 고정).
- 실제
claude auth loginTUI 흐름(브라우저 URL → 코드 붙여넣기)은 bear 에서 운영자가 로그인한 뒤에만 확인된다(테스트는 TUI 를 흉내 낸 가짜 CLI). - 슬래시 명령은 고정 집합이다(운영자가 추가하려면
hal/ai/commands.mjs).
미확정 정책값
- 셸 한도(3)·단말 기본 크기(100×30)·폴링 간격(300ms).
0.3.2 (2026-10-29)
- 작성자: 개발 지시 — Oz(
Ariweb-Suitle_Dev-Order_v0.3.2.md, 2026-10-29) · 구현 — Claude Fable 5.1(브랜치feature/hal-ai-convenience-v0.3.2, 기준 mainad96f0b= 0.3.1). 커밋·push·main병합·릴리스 빌드·bearhal21.ariweb.netHAL 배포까지 수행(아래 "배포"). - 이전 버전: 0.3.1(2026-10-29, CLI 자동 탐색). 0.3.2 는 끝 번호를 올리는 HAL 개선 릴리스: AI 연동 편의성(사전 안내·OAuth 자동 진행·CLI 텍스트 셸·공급자 프리셋 4종 + 기타) + 도구 기본 설정·인증/승인 요청. HAL 에만 적용, 다른 모듈 변경 없음(배포하지 않음).
요구(사용자 요구 — 약화 없이 반영)
- AI 연동 편의성: OAuth 연동 시 필요한 연동 화면을 자동으로 띄운다. CLI 로그인은 text shell 화면을 제공해 화면에서 필요한 작업을 하게 한다. 이 작업들이 자동으로 진행되고 사전에 필요한 작업 내용을 안내한다. API 연동은 Claude·ChatGPT·Gemini·OpenRouter 를 기본 서비스 업체로 선택할 수 있게 하고 그 밖의 API 서비스 업체 연동도 지원한다.
- LLM·도구 연결 기본 설정과 인증: HAL 운영·관리용 도구와 스킬을 LLM 연동 시 즉시 쓸 수 있게 미리 설정해 제공하고, 인증·승인이 필요한 도구는 필요한 인증/승인을 요청할 수 있게 한다. 결과를 소스에 반영하고
https://hal21.ariweb.net에 배포한다.
수정한 기존 기능
hal/ai/providers.mjs: 공급자 프리셋PROVIDERS(anthropic=Claude · openai=ChatGPT · gemini · openrouter · other · local-cli;kind로 HTTP 형식 분기,guide사전 안내,oauthPreset). Gemini 어댑터(generateContent,systemInstruction,functionDeclarations(대문자 type 스키마),functionCall/functionResponse,usageMetadata, 모델 목록models/접두어 제거, 인증x-goog-api-key/Bearer). OpenRouter 는 OpenAI 호환 +HTTP-Referer/X-Title.toolFormat(). MCP 설정 주입(mcpConfig(d)): claude--mcp-config <임시 파일> --strict-mcp-config --allowedTools mcp__hal(실행 뒤 파일 삭제), codex-c mcp_servers.hal.command/args/env.hal/ai/connections.mjs:other는 기본 주소 필수(base_url_required), OAuth 프리셋 채우기(gemini: Google authorize/token·scope·access_type=offline·prompt=consent; openrouter:mode=openrouter_pkce— state 를 callback_url 에 실어 보내고/api/v1/auth/keys로 키를 받아 Vault 에 Bearer 로 저장), 공개 객체providerKind·tools·toolsVia, 도구 정책tools_json(프리셋 검증,unknown_tool).hal/ai/engine.mjs: 연결별 도구 정책대로 선언·실행(꺼진 도구 호출은 실행 없이 오류 결과), 로컬 CLI 는mcp플래그,proposeExternal(외부 출처 승인 요청 → 통합 방 HAL 안내 + 제안 카드,source), 연결 없는 제안의 승인·거부 결과를 통합 방 안내로.hal/ai/mcp.mjs: 쓰기·고위험tools/call→ 승인 요청({approvalRequested, actionId, …}, isError 아님).hal/ai/tools.mjs:hal_action_status·hal_actions_pending(읽기),TOOL_PRESETS(all·read_write·read_only·none),toolDefs(format, only)(gemini 형식 포함).- 새 파일
hal/ai/shell.mjs: 텍스트 셸 세션(허용 명령표SHELL_COMMANDS, 사전 안내SHELL_GUIDE,script -qfcPTY, 운영자당 2개, 유휴 10분·최대 30분, 출력 256KB 메모리, 입력 미저장, 감사는 시작·종료만).hal/deps.mjs:d.shell. hal/ai/routes.mjs:POST /connections응답next(oauth → start 경로 / cli_check / test), 콜백 →/ops/ai/oauth/done?ok=…(완료 페이지: 팝업이면postMessage+ 닫기, 아니면/ops/ai?oauth=ok|fail),error=콜백도 완료 페이지로,GET /ops/api/ai/mcp-config,GET /ops/api/ai/tools에presets, 텍스트 셸GET/POST /ops/api/ai/shell,GET /:id?after=,POST /:id/input,DELETE /:id(시작은requireRecent),providers[].guide/kind/defaultBase/oauthPreset·callbackUrl.hal/schema.mjs: 마이그레이션hal_0007_ai_tools_policy(ai_connections.tools_json,ai_actions.source).- 화면(
hal/ui.mjs·ai-render.mjs·ai.js·ops.css): 공급자 선택 6종, 사전 준비 안내 상자(공급자·인증 방식별, 콜백 주소 표시), OAuth 저장 직후 팝업 자동 열기·완료 자동 확인, 로컬 CLI 저장 직후 탐색·로그인 확인 → 미로그인이면 텍스트 셸 자동 열기(출력 0.5초 갱신, Enter 전송, Ctrl+C, 종료), 셸 단추(claude/codex login·상태), 도구 정책 fieldset(프리셋 + 직접 선택 체크박스, 위험 등급별 모두 켜기/끄기), MCP 설정 절(Claude Code 명령·JSON, Codex TOML), 패널 상태 줄에 "승인 대기 N", 연결 표에 도구 프리셋·MCP 표시. - 문서·버전:
docs/HAL-AI.md§7, 개발 지시 v0.3.2, 0.3.2 표기(package.json·README·버전 검사 테스트).
고객 화면
- 변경 없음.
운영자 화면
- AI 연동: 공급자 고르면 사전 준비 안내가 바뀐다(키 발급 주소, OAuth 리디렉션 URI 등록 안내와 HAL 콜백 주소, CLI 로그인 절차). OAuth 연결은 저장하면 로그인 창이 자동으로 열리고 승인 뒤 스스로 닫히며 모델 확인까지 이어진다(팝업 차단 시 같은 창 이동). 로컬 CLI 연결은 저장하면 탐색·로그인 확인 → 미로그인이면 텍스트 셸(login)이 자동으로 열린다. 텍스트 셸 단추로 언제든 claude/codex login·상태를 실행할 수 있다. 도구 정책은 기본 "전부"이며 바꿀 수 있다. MCP 설정 절에서 외부 클라이언트용 설정을 복사한다.
- 채팅룸: 외부(MCP) 승인 요청이 통합 방에 HAL 안내 + 제안 카드로 올라오고 상태 줄에 "승인 대기 N" 이 보인다. 승인·거부 흐름은 0.3.0 과 같다(재인증, 고위험 3단계).
보안 결정
- 텍스트 셸은 임의 셸이 아니다: (tool, command 이름)만 받아 허용표의 인자 배열을 실행하고, 실행 파일은 탐색된 절대 경로. 시작은 재인증, 입력은 저장·감사하지 않음, 출력은 메모리만. HAL 환경 변수(Vault 키)는 넘기지 않는다.
- OAuth 완료 페이지는 외부 스크립트 없이 결과·연결 id 만 다루고
postMessage는 같은 출처로만 보낸다. OpenRouter PKCE 로 받은 키는 Vault 에만. Google OAuth 는 client secret 을 서버→토큰 끝점에만. - MCP 승인 요청은 인자를 서버가 다시 검증해 제안으로만 올린다(실행 없음). 승인 라우트·재인증·3단계 확인은 0.3.0 그대로. 도구 정책으로 꺼진 도구는 선언도 실행도 하지 않는다.
- Gemini 선언 스키마는
additionalProperties를 뺀 OpenAPI 부분집합으로 변환하되 HAL 쪽 검증(validateArgs)은 원래 선언으로 한다(모르는 인자 거부 유지).
테스트
- RED(구현 전): 새
test/hal-ai-032.test.mjs0/6(공급자 gemini 없음 400bad_provider,/ops/api/ai/shell404, 도구 정책 없음, MCP 상태 도구 없음, 화면 문구 없음, 버전 0.3.1). - GREEN:
test/hal-ai-032.test.mjs6/6 — 프리셋·안내·OpenRouter 기본 주소·기타 주소 필수·Gemini 모델 목록(x-goog-api-key)·generateContent 도구 왕복 / OAuth 생성next·Google 프리셋(access_type=offline)·완료 페이지(opener postMessage·close·대체 이동)·실패 콜백·OpenRouter PKCE(callback_url 에 state,/api/v1/auth/keys교환 → Vault, Bearer) / 텍스트 셸(재인증 403·임의 명령 400·scriptPTY 출력·입력 Enter·종료 코드·입력 미감사·목록·강제 종료·404) / 도구 정책(기본 all·프리셋 목록·read_only·custom 검증·선언 필터)·MCP 설정 API·claude--mcp-config파일 내용·삭제·--allowedTools mcp__hal·정책 none 이면 미주입·codex-c mcp_servers.hal.*/ MCP 승인 요청(제안·hal_action_status·hal_actions_pending·콘솔 제안 목록(source mcp)·통합 방 카드·재인증 403·승인 실행·결과 안내·고위험 3단계·거부) / 화면·버전·문서. 기존test/hal-ai.test.mjs12/12(0.3.0 의 "MCP 쓰기 도구는 isError" 기대를 "승인 요청(제안)"으로 갱신). - targeted:
hal-ui7/7 ·hal-reauth-banner5/5 ·hal16/16 ·webmaker-visual-editor5/5. 전체 Suitenode test/run.mjs: 27개 파일 실패 0, 189건 통과. 변경 파일 secret-like 스캔 0건. 러너 임시 파일 미커밋.
배포(bear hal21.ariweb.net, HAL 만)
- 2026-10-29 01:46 UTC, 코드 릴리스만(마이그레이션
hal_0007_ai_tools_policy는 열 추가만; data/config/Vault 파일 무변경). HAL 만 배포, 다른 모듈 미변경·미배포. - 저장소: 기능 커밋
8a111bcb8b79097c32ac309f948418d9c00e7d54(브랜치feature/hal-ai-convenience-v0.3.2→mainfast-forward ad96f0b..8a111bc, read-backorigin/main = 8a111bc). 릴리스 빌드(그 커밋, 전체 테스트 재실행 27파일 실패 0): manifest0.3.2 · mainCommit 8a111bc… · testsPassed=true · dirty=false · provenanceClean=true,verify5 artifact ✔. hal artifact sha2565575bc18ad2921abdc4021edd2116388be48467b3f56a9527f0a2a6f7d168753. - 적용(
~/oz-deploy/bear-apply.sh 0.3.2 …, dist 5개 artifact 업로드): 해시 OK → 사전 백업/srv/ariweb/hal/backups/pre-v0.3.2-20261029T014617Z/→ stage 추출·검증 →/srv/ariweb/hal/releases/0.3.2-8a111bc→register-release --approve --by oz-dev-order-v0.3.2(approved, 5 artifact) →bootstrap-release→ current 전환 → 재시작. restart →/api/health200 500ms, active,version 0.3.2 · bootstrap complete · releaseVersion 0.3.2 · mainCommit 8a111bc… · artifactSha256 5575bc18…. 보존 sha256 전=후(config.jsonae5963d2…, vault.json0e2dc987…, vault.key345d92e7…), 백업 sums OK, 이전 릴리스 디렉터리 10개 보존. 운영 DB:_migrations에hal_0007_ai_tools_policy, releases 6행 approved, bootstrap 0.3.2, 서버 2대 ready(heartbeat 재개 01:46:30Z·01:46:37Z), journal 오류 0. 운영 AI 연결 1건(H9001, local-cli, active — tools_json null → 기본 정책 all 로 해석). - read-back(배포 코드 harness
~/oz-deploy/deployed-render-0.3.2.mjs, 메모리 DB·임시 data dir, 운영 데이터 미접근): 공급자 6종(anthropic·openai·gemini·openrouter·other·local-cli), 콜백 주소https://hal21.ariweb.net/ops/ai/oauth/callback, MCP 설정 command/usr/bin/nodeargs[…/releases/0.3.2-8a111bc/hal/index.mjs, mcp], 텍스트 셸 허용 CLI claude·codex, 완료 페이지 200(postMessage 포함),/ops/ai에 "사전 준비·텍스트 셸·MCP 설정·도구 정책·OpenRouter" 모두 있음, 도구 33, CLI 탐색 claude 2.1.312(로그인 필요)·codex 0.162.1(로그인됨). 외부https://hal21.ariweb.net/api/health/ops/login/ops/ai/ops/ui/ai.js/ops/ai/oauth/done모두 200. - 운영자 조치: claude 를 쓰려면 AI 연동 화면의 "claude login" 텍스트 셸(또는 CLI 연결 저장 시 자동으로 열림)에서 URL 을 열고 코드를 붙여 넣으면 된다. OAuth 공급자(Gemini: Google OAuth 클라이언트의 리디렉션 URI 에 위 콜백 주소 등록, OpenRouter: 설정 없음)는 저장하면 로그인 창이 자동으로 열린다.
- 되돌리기(코드만):
ln -sfn /srv/ariweb/hal/releases/0.3.1-3775b7b /srv/ariweb/hal/current && sudo systemctl restart ariweb-hal(0.3.1 은tools_json·source열을 모르지만 건드리지 않는다).
알려진 제한
- Anthropic·OpenAI 는 공개 OAuth 클라이언트가 없어 OAuth 는 자체 중계가 있을 때만 쓴다(안내에 명시). Gemini OAuth 는 운영자가 Google Cloud 에서 OAuth 클라이언트를 만들어야 한다.
- 텍스트 셸은 로그인·로그아웃·상태·버전만 실행한다. 터미널 크기 변경(SIGWINCH)은 지원하지 않는다(100×30 고정).
- 로컬 CLI 의 MCP 도구는 Claude Code 2.1.312 / Codex 0.162.1 기준 인자(
--mcp-config,-c mcp_servers.*)로 붙인다 — CLI 가 바뀌면 인자가 달라질 수 있다. 실제 운영 CLI 로의 MCP 도구 호출은 운영자가 로그인한 뒤 확인한다. - 실제 브라우저에서 팝업·셸 모달 동작은 자동 테스트하지 않았다(순수 렌더러·API·완료 페이지 내용으로 검증).
미확정 정책값
- 텍스트 셸 유휴/최대 시간(10분/30분), 동시 세션 수(2), 출력 상한(256KB). OAuth state 만료(10분).
0.3.1 (2026-10-29)
- 작성자: 개발 지시 — Oz(
Ariweb-Suitle_Dev-Order_v0.3.1.md, 2026-10-29) · 구현 — Claude Fable 5.1(브랜치feature/hal-cli-discovery-v0.3.1, 기준 main0d3a0b8= 0.3.0). 커밋·push·main병합·릴리스 빌드·bearhal21.ariweb.netHAL 배포까지 수행(아래 "배포"). - 이전 버전: 0.3.0(2026-10-29, HAL AI 채팅룸). 0.3.1 은 끝 버전 번호만 올리는 결함 수정: 운영 서버에 이미 설치된 CLI 를 HAL 이 찾지 못하던 문제. HAL 에만 적용, 다른 모듈 변경 없음(배포하지 않음).
요구(사용자 요구 — 약화 없이 반영)
bear 서버(HAL 운영 서버)에 이미 CLI 가 설치되어 있는데 설치된 것을 확인하지 못하고 있다. 서버의 설치 상황을 점검해서 이미 설치된 것은 찾아서 연결 가능하게 수정하라.
점검 결과(bear, 2026-10-29, HAL 실행 계정 master)
- 설치됨: Claude Code CLI 2.1.312
~/.local/bin/claude→~/.local/share/claude/versions/2.1.312, OpenAI Codex CLI 0.162.1~/.local/bin/codex→~/.codex/packages/standalone/current/bin/codex. 로그인 셸 PATH 에는~/.local/bin이 있지만 HAL 서비스(systemd) PATH 는/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin이라 0.3.0 의cliBin()(지정 경로 또는 PATH 의claude)이 찾지 못했다. 이것이 원인. - 로그인: codex
login status= "Logged in using ChatGPT"(기본 모델gpt-6.1-sol,~/.codex/models_cache.json11개). claudeauth status=loggedIn:false, headless 실행 결과 "Failed to authenticate: OAuth session expired and could not be refreshed" → claude 는 서버에서claude login이 필요(HAL 이 대신할 수 없다). - 유닛 샌드박스(
ProtectSystem=strict,ReadWritePaths=/srv/ariweb/hal)와 같은 조건으로systemd-run실행:claude --version·claude -p(인증 오류 JSON 반환)·codex exec --json("OK", usage) 모두 실행됨 — 유닛 변경 불필요.
수정한 기존 기능
hal/ai/providers.mjs: CLI 자동 탐색(findCliBin·discoverClis·cliSearchDirs) — CLAUDE_BIN/CODEX_BIN → HAL_CLI_DIRS → HOME 의 알려진 설치 위치(~/.local/bin,~/.npm-global/bin,~/.claude/local,~/.codex/packages/standalone/current/bin,~/node_modules/.bin) →/usr/local/bin·/usr/bin→ PATH. 로그인 상태(cliLoginStatus: claudeauth statusJSON / codexlogin status), codex 기본 모델·모델 목록(~/.codex/config.toml·models_cache.json, 숨김 제외). Codex 지원(chatCodex:exec --json -s read-only --skip-git-repo-check --ephemeral -C <임시 폴더> -m <모델>, JSONL 해석). Claude 의is_error결과(인증 만료)는 모델 답으로 꾸미지 않고cli_not_logged_in으로. CLI 환경 변수는 HOME·PATH(탐색 위치 포함)·LANG 등 최소.hal/ai/connections.mjs:cli.tool(claude|codex) 검증, 모델 비우면 CLI 기본 모델, "확인" 때 탐색된 실행 파일·버전·로그인 상태를 연결에 기록,cli_not_logged_in→ 상태reauth_required. 확인 실패 응답은 5xx 대신 424 로(5xx 는 화면에 "일시적 오류"로 가려져 원인이 안 보였다).hal/ai/routes.mjs:GET /ops/api/ai/cli(탐색 결과·탐색 위치·지원 CLI).hal/ai/engine.mjs: CLI 로그인 만료도 연결 상태reauth_required.- 화면(
hal/ui.mjs·ai-render.mjs·ai.js): AI 연동 화면에 "서버에서 찾은 CLI" 카드(실행 파일·실제 경로·버전·로그인 배지와 안내·기본 모델·"이 CLI 로 연결 추가"(양식 미리 채움)·"다시 탐색"), 연결 추가 양식에 CLI 종류(claude/codex) 선택과 경로 생략 시 자동 탐색 안내, 연결 표에 CLI 종류·버전·경로·로그인 상태. - 문서:
docs/HAL-AI.md§2·§6, 개발 지시Ariweb-Suitle_Dev-Order_v0.3.1.md. 버전 표기 0.3.1(package.json·README·Suite 버전 검사 테스트).
고객 화면
- 변경 없음.
운영자 화면
- AI 연동(
/ops/ai): "서버에서 찾은 CLI" 카드. bear 에서는 Claude Code 2.1.312(로그인 필요 —claude login안내)와 Codex 0.162.1(로그인됨, 기본 모델 gpt-6.1-sol)이 보이고, "이 CLI 로 연결 추가"로 바로 연결할 수 있다. 연결 뒤 "확인"을 누르면 상태가active(codex) 또는reauth_required(claude, 로그인 전)로 표시된다. - 운영자 조치: claude 를 쓰려면 bear 에서
master계정으로claude login을 실행한 뒤 AI 연동 화면에서 "확인". codex 는 바로 연결 가능.
보안 결정
- HAL 은 CLI 의 세션 파일·자격(
~/.claude.json,~/.claude/.credentials.json,~/.codex/auth.json)을 읽지도 넘기지도 않는다. 점검 때도 키 이름·존재 여부만 확인했다. 로그인은 서버에서 사람이 한다. - CLI 실행은 지정 또는 탐색된 절대 경로의 실행 파일만, 인자는 고정 형식(프롬프트는 인자 하나). Codex 는 읽기 전용 샌드박스·임시 폴더·세션 비보존으로 돈다. HAL 환경 변수(Vault 키 등)는 CLI 에 넘기지 않는다.
- 연결 설정의
cli.tool은 claude|codex 만,cli.bin은 절대 경로만 받는다(상대 경로·다른 실행 파일 이름 거부).
테스트
- RED(구현 전):
test/hal-ai.test.mjs새 테스트 "0.3.1 CLI 자동 탐색…" →GET /ops/api/ai/cli404 로 실패(+ 버전 0.3.1 검사 2건 실패). 통과 9 · 실패 3. - GREEN:
test/hal-ai.test.mjs12/12(가짜HOME/.local/bin의 claude(로그인 만료)·codex(로그인됨) 스크립트,HAL_CLI_HOME·HAL_CLI_DIRS로 탐색 위치 고정 — 탐색 API 결과·탐색 위치, codex 연결(tool 만) → 확인 active·모델 목록 → 대화 "OK from codex"·usage·exec --json -s read-only --skip-git-repo-check --ephemeral -m gpt-6.1-sol인자, claude 연결 → 확인 424cli_not_logged_in→reauth_required+claude login안내, 모르는 tool 400, 빈 HOME →cli_unavailable+ 탐색 위치). 중간 결함 1건을 테스트가 잡았다: 확인 실패를 502 로 내면lib/http가 메시지를 "일시적 오류"로 가려 원인이 안 보임 → 424. - targeted:
test/hal-ui.test.mjs7/7 ·test/hal-reauth-banner.test.mjs5/5 ·test/hal.test.mjs16/16 ·test/webmaker-visual-editor.test.mjs5/5. - 전체 Suite
node test/run.mjs: 26개 파일 실패 0, 183건 통과. 러너의docs/verification/local-e2e-*임시 파일 미커밋. 변경 파일 secret-like 스캔 0건.
배포(bear hal21.ariweb.net, HAL 만)
- 2026-10-29 01:14 UTC, 코드 릴리스만(data/config/Vault/DB 파일 무변경, 새 마이그레이션 없음). HAL 만 배포, 다른 모듈은 바뀌지 않아 배포하지 않음.
- 저장소: 기능 커밋
3775b7bee429981dc0ee47054ec438096d741f52(브랜치feature/hal-cli-discovery-v0.3.1→mainfast-forward 0d3a0b8..3775b7b, read-backorigin/main = 3775b7b). 릴리스 빌드(그 커밋, 전체 테스트 재실행 26파일 실패 0): manifest0.3.1 · mainCommit 3775b7b… · testsPassed=true · dirty=false · provenanceClean=true,verify5 artifact ✔. hal artifact sha25629f7286522d3ad43ca40896950de57ded206025dc0b368d2b763848efd98db6e. - 적용(
~/oz-deploy/bear-apply.sh 0.3.1 …, dist 5개 artifact 업로드): 해시 OK → 사전 백업/srv/ariweb/hal/backups/pre-v0.3.1-20261029T011449Z/(hal.sqlite·-wal·-shm, config.json, vault.json, vault.key, SHA256SUMS) → stage 추출·검증(package.json 0.3.1, ai 파일 존재,node --check) →/srv/ariweb/hal/releases/0.3.1-3775b7b+ REVISION.json →register-release --approve --by oz-dev-order-v0.3.1(0.3.1 approved, 5 artifact) →bootstrap-release→ current 전환 →systemctl restart ariweb-hal. - 결과: restart →
/api/health200 500ms, active, 프로세스 cwdreleases/0.3.1-3775b7b; healthversion 0.3.1 · bootstrap complete · releaseVersion 0.3.1 · mainCommit 3775b7b… · artifactSha256 29f72865…. 보존 sha256 전=후(config.jsonae5963d2…, vault.json0e2dc987…, vault.key345d92e7…), 백업 sums OK, 이전 릴리스 디렉터리 9개 보존. 운영 DB: releases 5행(0.1.1·0.2.0·0.2.1·0.3.0·0.3.1 approved), bootstrap 0.3.1, 서버 2대 ready(heartbeat 재개 01:15:30Z·01:15:35Z), 재시작 창 Agent long-poll 502 2건 외 비-2xx 없음, journal 오류 0(배포 전 01:14:17Z 의…/test 5001건은 0.3.0 의 가려진 확인 실패 — 0.3.1 에서 424 로 원인 표시). - 배포 코드 탐색 read-back(
~/oz-deploy/deployed-render-0.3.1.mjs, 서비스와 같은 PATH/usr/local/sbin:…:/snap/bin·HOME=/home/master, 메모리 DB·임시 data dir, 운영 데이터·Vault 미접근):GET /ops/api/ai/cli→ claude/home/master/.local/bin/claude→…/.local/share/claude/versions/2.1.312,2.1.312 (Claude Code), loggedIn false(method none) / codex/home/master/.local/bin/codex→…/.codex/packages/standalone/releases/0.162.1-…/bin/codex,codex-cli 0.162.1, loggedIn true(chatgpt), 기본 모델gpt-6.1-sol, 모델 8개./ops/·/ops/ai200(패널·연결 추가 양식), 도구 31. 외부https://hal21.ariweb.net/api/health/ops/login/ops/ai/ops/ui/ai.js200. - 운영 DB 의 기존 AI 연결(읽기 전용 확인, 비밀 없음):
H9000(local-cli · login · claude-opus-5-5, 2026-10-29 00:52Z 생성) 상태error"CLI 실행 파일을 실행할 수 없습니다(claude): ENOENT" — 0.3.0 의 결함 그대로. 운영자 조치: AI 연동 화면에서 H9000 의 "확인"을 다시 누르면 0.3.1 이 claude 를 찾되 로그인 만료라reauth_required+ "claude login" 안내가 뜬다 → bear 에서master로claude login뒤 다시 "확인". 또는 "서버에서 찾은 CLI" 의 codex(로그인됨)로 연결 추가하면 바로 쓸 수 있다. - 되돌리기(코드만):
ln -sfn /srv/ariweb/hal/releases/0.3.0-ba3c5cb /srv/ariweb/hal/current && sudo systemctl restart ariweb-hal.
알려진 제한
- claude 로그인 만료는 HAL 이 풀 수 없다(서버에서
claude login). 로그인 뒤 "확인"을 눌러야 상태가 바뀐다(자동 재확인 없음). - 로컬 CLI 연결은 대화·분석 전용(HAL 도구 없음). 도구가 필요하면 API 키/OAuth 연결을 쓴다(Codex 의 ChatGPT 로그인은 API 키가 아니라 도구 호출 경로가 없다).
- gemini 등 다른 CLI 는 탐색하지 않는다(요청 시 CLI_TOOLS 에 추가).
미확정 정책값
- CLI 실행 시간 상한(기본 10분, 연결 설정
cli.timeoutMs), codex 샌드박스 모드(read-only 고정).
0.3.0 (2026-10-29)
- 작성자: 개발 지시 — Oz(
Ariweb-Suitle_Dev-Order_v0.3.0.md, 2026-10-29) · 구현 — Claude Fable 5.1(브랜치feature/hal-ai-chat-v0.3.0, 기준 origin/main6bfde82c5ef607704cb05628dc33d9b6ef68c50f= 0.2.1). 지시에 따라 커밋·push·main병합·릴리스 빌드·bearhal21.ariweb.netHAL 배포까지 수행한다(아래 "배포" 절에 read-back). - 이전 버전: 0.2.1(2026-10-29, HAL 고위험 재인증 안내 위치 수정 — 아래 절). 0.3.0 은 가운데 버전 번호를 올리는 운영자 기능 릴리스다: HAL 운영자 콘솔에 AI 채팅룸(통합 방 + 모델별 개별 방)과 AI 모델 연결(로그인·OAuth·API 키), HAL 제어·감시 스킬·도구·MCP 가 더해졌다. HAL 에만 적용.
- 구성: HAL 0.3.0. 고객 포털 · Node Agent · 도입 모듈 Webmaker 1.8.0 · AriMail 1.0.11 은 0.2.1 그대로(변경 없음 — 배포하지 않는다). 외부 npm 의존성 없음(공급자 HTTP 는 Node fetch, MCP 는 stdio JSON-RPC 직접 구현), Node 22.13+.
요구(사용자 요구 — 약화 없이 반영)
HAL 화면 오른쪽에 칸을 나눠 AI 채팅룸을 둔다. AI 모델은 로그인·OAuth·API 키로 연결하고 여러 모델은 탭으로 전환한다. AI 모델이 HAL 의 모든 기능을 제어·감시할 수 있도록 스킬·도구·MCP 를 준비하고, 상태·로그 정보의 의미를 AI 가 이해하도록 미리 준비한다. 각 모델에는 이름·업무·특성 같은 고유 정보를 연동 때 지정한다. 통합 채팅룸은 항상 첫 탭(고정)이며 모든 모델이 함께 참여한다 — 내 지시는 모두에게, 이름을 지정한 지시는 그 모델만, 모델→모델 전달의 대답은 자동으로 진행되되 그 대답에 대한 두 번째 반응은 사용자 지시로. 개별 채팅룸은 모델별 1:1 이며 탭 이름은 모델 이름이 기본값, 수정 가능. 결과를 소스에 반영하고 https://hal21.ariweb.net 에 배포한다. 전문은 Ariweb-Suitle_Dev-Order_v0.3.0.md §1.
재사용한 기존 기능(그대로)
- HAL 보안 경계 그대로 재사용: 운영자 미들웨어(네트워크 관문 → 운영자 세션 → CSRF),
requireRecent(최근 10분 재인증),threeStep(3단계 확인), 감사(lib/audit, redact), Vault(lib/vault), 세션(lib/session). AI 라우트는hal/app.mjs가 같은requireRecent·threeStep·wrap을 넘겨 준다. - AI 도구는 운영자 API 와 같은 core/jobs 함수를 같은 인자로 부른다(
core.listServers/getServer/publicServer/metrics/listReleases/approveRelease/updateServer/createServer/approveServer/revokeAgent,d.jobs.enqueue/list/get/childrenSummary/cancel/retry/resume/pause/revert,emergency.listSessions,dr.readiness/listSnapshots/snapshot,mailsvc.readback,overviewLocal,changelog,listAudit). - 재인증 창 계약
hal/ui/public/reauth.mjs(0.2.1): 패널의 승인 흐름도 같은callWithReauth·reauthPrompt를 써서 서버가reauth_required로 거부한 그 때만 고위험 안내가 붙는다. - 로컬 CLI 연결은 Webmaker
lib/claude.js와 같은 계열(claude -p --output-format json), 최소 환경 변수·프로세스 그룹 종료 방식.
수정한 기존 기능
hal/schema.mjs: 마이그레이션hal_0006_ai_chat—ai_connections(비밀값 없음, 참조·상태만) ·ai_rooms·ai_messages(방 안 seq 유일) ·ai_actions(제안) ·ai_oauth_states(state 해시). 기존 표 변경 없음.hal/deps.mjs:d.ai = createEngine(d).hal/app.mjs:registerAiRoutes(app, d, { requireRecent, threeStep, wrap }).hal/index.mjs: 새 명령mcp(stdout 은 JSON-RPC 전용, 로그 stderr).hal/ui.mjs: 메뉴AI 연동(/ops/ai), 모든 화면 공통 틀에 오른쪽 패널#ai-panel(탭·상태·기록·입력·너비 조절)과 머리 단추AI(열기/닫기),/ops/ai화면(연결 표·연결 추가 양식(API 키/OAuth/로그인)·고유 정보·도구 목록), 스크립트/ops/ui/ai.js.hal/ui/public/ops.css: 패널·탭·메시지·제안 카드·1100px 이하 반응형.- 새 파일:
hal/ai/{connections,providers,rooms,engine,tools,routes,mcp}.mjs,hal/ai/skill.md,hal/ui/public/ai.js,hal/ui/public/ai-render.mjs(순수 렌더러),docs/HAL-AI.md,test/hal-ai.test.mjs. - 버전 표기:
package.json0.3.0(HAL 런타임·scripts/release.mjs가 따름), README,test/webmaker-visual-editor.test.mjs의 Suite 버전 검사 0.3.0.
고객 화면
- 변경 없음(포털·Webmaker·AriMail 미변경).
운영자 화면
- AI 채팅룸 패널(모든 운영자 화면 오른쪽, 머리의
AI단추로 열고 닫음, 왼쪽 가장자리 끌어 너비 조절, 운영자별 localStorage 에 열림·너비·선택 탭 저장·복원, 1100px 이하에서는 본문 아래 전체 너비): 탭 줄(◎ 통합 고정 → 연결 순서대로 개별 탭, 비활성은 흐리게·점 표시,+→ AI 연동), 상태 줄(통합: 규칙 안내·응답 중 수 / 개별: 연결 상태·모델·공급자), 기록(운영자·모델·도구 결과(접힘)·HAL 안내, 전달 응답 표시, 실패 메시지는 오류 코드와 함께), 입력(Ctrl/Cmd+Enter 전송, "현재 화면 문맥 포함", "새 대화"). 탭을 두 번 누르면 이름 변경(통합 불가). - 제안 카드: 쓰기·고위험 도구 호출은 카드로 멈춘다 —
승인(재인증)/승인(재인증·3단계 확인)/거부. 승인은 서버가reauth_required로 거부할 때만 재인증 창을 열고(0.2.1 계약), 고위험은 3단계 확인 창(영향 범위 → 식별정보 재입력 → 최종 실행). 처리 뒤 카드에 "승인·실행됨/거부됨(실행 안 함)/실행 실패"와 처리자가 남는다. - AI 연동 화면(
/ops/ai): 연결 표(이름·역할·특성, 공급자·인증 방식·키 힌트·토큰 만료, 모델·사용 가능 모델 수, 상태·마지막 확인·오류, 조치: 확인 / OAuth 로그인 / 키 교체 / 수정 / 비활성화·활성화 / 삭제(이름 재입력)), 연결 추가 양식(공급자에 맞는 인증 방식만 보임, 비밀 입력란은 저장 뒤 비움), 도구 목록(위험 등급). OAuth 콜백 뒤?oauth=ok안내. - 운영자 조치 항목: 모델을 쓰려면 AI 연동 화면에서 연결을 추가하고 "확인"을 눌러 모델 목록을 받아 둔다. 설정값·보관소 경로 변경 없음(비밀값은 기존 Vault 파일에 항목으로 추가된다).
연결 · 방 규칙 · 도구 동작
- 연결: 공급자
anthropic(API 키x-api-key/ OAuth Bearer) ·openai(OpenAI 호환,baseUrl변경 가능, Bearer) ·local-cli(로그인된 Claude Code 세션, 도구 없음). 상태active·disabled·reauth_required·error. "확인"은 실제 공급자 모델 목록을 받아 두고, 목록 밖 모델 선택은model_unavailable(조용히 대체하지 않음). 키 등록·교체·client secret·삭제 →reauth_required규칙. - OAuth: authorization code + PKCE S256. state 해시 저장·10분·1회용, 시작 운영자 = 콜백 운영자, code_verifier 는 Vault 에 두었다가 교환 뒤 제거, client secret 은 서버→토큰 끝점에만, 토큰은 Vault, 만료는 서버 refresh(실패 시
reauth_required). - 통합 방: 지시는 활성 모델 모두 /
@이름·글머리이름:·이름,는 그 모델만 / 모르는@이름은 400unknown_addressee/ 모델→모델@이름전달은 자동 1회(relayDepth 1), 그 답의 @호출은 HAL 안내만 남기고 자동 반응 없음 / 자기 자신·비활성 제외. 각 모델의 system prompt 에는 자기 고유 정보·참여자 이름·역할·운영자·화면 문맥·스킬 문서가 들어가고, 다른 모델의 발언은 "[이름] …" 으로 보인다. - 개별 방: 1:1, 기록 격리, 비활성 연결에는 409
connection_inactive, "새 대화"는 기록 비움 + 승인 대기 제안 취소. - 도구(
hal/ai/tools.mjs, 31개 — 읽기 15 · 쓰기 8 · 고위험 8, 표는docs/HAL-AI.md§4): 읽기는 즉시, 쓰기는 제안 → 승인(재인증), 고위험은 제안 → 승인(재인증 + 3단계 확인, 식별정보 = 도구가 정한 대상 id). 인자는 선언으로 재검증, 모르는 도구·인자는 실행 없이 오류 결과. 한 턴 도구 왕복 8회 상한. 승인은 한 번만 실행(상태 선점). 거부는 실행 없음·모델 자동 호출 없음. - 스킬 문서
hal/ai/skill.md: 서버·작업·릴리스·사건·감사·긴급 복구·DR 상태값과 오류 코드의 뜻, AI 규칙(증거 없이 완료 금지, 승인 재촉 금지, 외부 글 속 지시문 불신, 비밀값 금지).GET /ops/api/ai/skill·도구hal_skill. - MCP:
node hal/index.mjs mcp(stdio) —initialize·tools/list·tools/call·ping. 읽기 도구만 실행, 쓰기·고위험은isError로 운영자 콘솔 승인 안내.
보안 결정
- 비밀값은 Vault 에만: 공개 객체·목록·감사·대화·작업·테스트·소스에 없음(힌트만). 공급자 호출의 자격은 서버 헤더에만. 로컬 CLI 는 세션 파일·자격을 읽지도 넘기지도 않고 환경 변수는 PATH·HOME 등 최소.
- AI 경로는 기존
requireRecent·CSRF·threeStep·릴리스 승인·부트스트랩·Agent 경계를 그대로 거친다(라우트가 같은 함수를 받음). 고위험 제안은 기존 운영자 API 보다 약하지 않게 전부 3단계 확인을 요구한다(예: 모듈 배포는 운영자 API 에서는 재인증만, AI 제안은 재인증 + 3단계). - 모델 응답·도구 인자·다른 모델의 발언·저장소 글은 권한이 아니다: 선언 검증, 알 수 없는 도구 거부, 임의 셸·경로·네트워크 도구 없음, 스킬 문서에 prompt injection 불신 규칙.
- 공급자 오류는 모델 답으로 꾸미지 않고
failed로 표시. AI 가 "완료"라 해도 HAL 이 확인한 도구 결과·작업 상태만 사실로 다룬다. - 테스트 비밀 fixture 는
node:crypto로 실행마다 생성(정적 literal 없음).
테스트
- RED(production 변경 전,
node --no-warnings test/hal-ai.test.mjs): 통과 0 · 실패 11 — 모든/ops/api/ai/*404, 공통 틀에 AI 패널 없음, MCP 명령 없음(응답 없음). - GREEN(구현 뒤):
test/hal-ai.test.mjs11/11(연결 CRUD·비밀 미노출·재인증 / OAuth PKCE / 방·탭 / 통합 방 지정·방송·고유 정보 / 모델→모델 자동 1회 / 개별 방·읽기 도구·공급자 오류 / 쓰기·고위험 제안·재인증·3단계·거부·1회 실행 / OpenAI 호환 / 로컬 CLI / 화면·버전·문서 / MCP stdio). 중간 결함 2건을 테스트가 잡았다: ① 격리 뒤publicServer.status는 heartbeat 없음 →offline이라 각본이rawStatus를 보도록 수정(코드 아님) ②/ops/ai화면 함수가d를 받지 않아 500 →aiPage(d). - targeted:
test/hal-ui.test.mjs7/7 ·test/hal-reauth-banner.test.mjs5/5 ·test/hal.test.mjs16/16. - 전체 Suite
node test/run.mjs: 26개 파일 실패 0, 182건 통과(새hal-ai11 포함, 로컬 E2E 11). 러너가 만든docs/verification/local-e2e-2026-10-28.*는 커밋하지 않았다. 변경 파일 정적 secret-like 스캔 0건. 도입 소스 출처 검사checkProvenanceclean.
배포(bear hal21.ariweb.net, HAL 만)
- 2026-10-28 17:45~17:47 UTC, 코드 릴리스만(data/config/Vault/DB 파일 무변경, 마이그레이션
hal_0006_ai_chat는 기동 때 표 추가만). 바뀌지 않은 포털·Agent·Webmaker·AriMail 은 배포하지 않았다(지시: 수정되지 않은 것은 배포하지 않음). bear 에는 HAL 만 둔다. - 저장소: 기능 커밋
ba3c5cb2f82374108f22f56badeab52cb07b308b(브랜치feature/hal-ai-chat-v0.3.0push →mainfast-forward 6bfde82..ba3c5cb, 원격 read-backorigin/main = ba3c5cb). 릴리스 빌드node scripts/release.mjs build(깨끗한 그 커밋, 전체 테스트 재실행 26파일 실패 0): manifestversion 0.3.0 · mainCommit ba3c5cb… · testsPassed=true · dirty=false · provenanceClean=true,verify5 artifact ✔. hal artifact sha256c1377c276dab3f7dae67cd8b884db77aee7f748fad5b09e00be509c18905430f(195,484 B; portalb442a7f9…agent892d1b19…webmaker2b39fb01…arimailef8037b3…). - 적용(
~/oz-deploy/bear-apply-0.3.0.sh→ 4단계부터 수동 이어 실행): dist 5개 artifact 를/srv/ariweb/hal/dist/0.3.0/에 올림(HAL artifact 만 올리면register-release가artifact_missing: portal로 거부 — 등록은 manifest 의 전 artifact 를 요구한다) → 사전 백업/srv/ariweb/hal/backups/pre-v0.3.0-20261028T174538Z/(hal.sqlite·-wal·-shm, config.json(+이전 사본), vault.json, vault.key, SHA256SUMS, 0700) → stage 추출·검증(package.json 0.3.0,hal/ai/engine.mjs·hal/ui/public/ai.js존재,node --check3개) →/srv/ariweb/hal/releases/0.3.0-ba3c5cb+ REVISION.json →register-release --manifest … --approve --by oz-dev-order-v0.3.0(릴리스 0.3.0 approved, artifact hal·portal·agent·webmaker·arimail) →bootstrap-release --version 0.3.0 --commit ba3c5cb… --sha256 c1377c27…→ln -sfncurrent 전환 →systemctl restart ariweb-hal. - 결과: restart →
/api/health200 831ms, PID 54774, active, NRestarts 0, 프로세스 cwd/srv/ariweb/hal/releases/0.3.0-ba3c5cb, journal 오류 0, 기동 로그 "Ariweb HAL v0.3.0 … 부트스트랩 완료". 보존 확인 sha256 전=후: config.jsonae5963d29ae899bc…, vault.json0e2dc9875f2779a0…, vault.key345d92e77c3fcd78…; 백업sha256sum -cOK. 이전 릴리스 디렉터리 8개 전부 보존(0.1.1×6, 0.2.0-0a56d46, 0.2.1-6bfde82). - read-back:
/api/health(로컬·https://hal21.ariweb.net)ok · version 0.3.0 · db · jobs · bootstrap complete · releaseVersion 0.3.0 · mainCommit ba3c5cb… · artifactSha256 c1377c27… · halRunningVersion 0.3.0 · firstAgent ag_01a17bd7…. 운영 DB(읽기 전용 node:sqlite):_migrations에hal_0006_ai_chat,ai_rooms=room_group(통합 방) 1행,ai_connections0, releases 4행(0.1.1·0.2.0·0.2.1·0.3.0 approved), bootstrap_staterelease_version=hal_running_version=0.3.0, 서버 2대 ready(heartbeat 재개 17:46:45Z hostinger, 17:47:00Z jitsi), 열린 작업 0. 재시작 창(17:46:13Z)의 Agent long-pollGET /agent/v1/jobs/next502 2건 외 Agent 요청 비-2xx 없음(이전 배포와 같은 양상). - 배포된 코드 서버 렌더 검증(운영자 자격 없음 →
~/oz-deploy/deployed-render-0.3.0.mjs가/srv/ariweb/hal/current의 app/deps 만 import, 메모리 DB·임시 data dir, 운영 데이터·Vault 미접근):/ops/200 +#ai-panel·/ops/ui/ai.js·메뉴 "AI 연동" 있음,/ops/ai200 + "연결 추가",/ops/api/ai/rooms= [room_group],/ops/api/ai/tools31개, 연결 0. 외부:https://hal21.ariweb.net/api/health/ops/login/ops/ai/ops//ops/ui/ai.js/ops/ui/ai-render.mjs/ops/ui/reauth.mjs모두 200. - 되돌리기(코드만):
ln -sfn /srv/ariweb/hal/releases/0.2.1-6bfde82 /srv/ariweb/hal/current && sudo systemctl restart ariweb-hal(0.2.1 코드는ai_*표를 모르지만 건드리지 않으므로 그대로 둬도 된다). 데이터까지: 위 백업 디렉터리. - blocker/unknown: 운영자 자격이 없어 실제 운영자 세션으로 패널을 열어 보지는 못했다(배포 코드 렌더 harness·회귀 테스트·외부 200 으로 판단). 실제 모델 연결은 Oz/운영자가 AI 연동 화면에서 키 또는 OAuth 를 등록해야 시작된다.
알려진 제한
- v0.1.4 §41.3·§41.4·§42 의 상세 계약 중 이번 범위 밖: 탭 간 변경 잠금(원자적 server-side lock·fencing·compare-and-swap), AI 의 격리 작업공간 코드 수정·커밋·push·릴리스 생성 도구, 제한된 합의 절차(최초 의견 1회 + 반론 1회 + 최종안). 이번 통합 방은 Oz 지시의 "방송·이름 지정·모델→모델 자동 1회" 규칙까지다.
- 응답은 스트리밍이 아니라 턴 완료 뒤 한 번에 보인다(패널은 응답 중 1초 간격으로 다시 읽음). 진행 중 턴은 HAL 재시작 때 사라진다(거짓 진행 표시 없음).
- 로컬 CLI 연결은 bear 에 CLI 가 없고 유닛이
ProtectSystem=strict라 운영 서버에서는cli_unavailable로 표시된다. 실제 모델 공급자 호출은 운영자가 키·OAuth 를 등록해야 시작된다(이 릴리스는 가짜 공급자 테스트까지 검증). - 실제 브라우저(DOM)에서 패널 동작은 자동 테스트하지 않았다(순수 렌더러·정적 계약·API 로 검증). 배포 뒤 운영자 로그인으로 패널 열기·연결 추가·확인·통합 방 지시 1회 눈 확인을 권한다.
미확정 정책값
- 토큰 왕복 상한(8), 지시 길이 상한(20000자), 도구 결과 전달 상한(24000자), 패널 기본 너비(400px) — 운영자 요청 시 설정값으로 뺄 수 있다. 공급자 비용 표시는 토큰 수만(가격표 없음).
0.2.1 (2026-10-29)
- 작성자: 개발 지시 — Neo(
Ariweb-Suitle_Dev-Order_v0.2.1.md, 오즈 승인) · 구현 — Claude Fable 5.1(격리 작업 트리feature/hal-risk-banner-v0.2.1, 기준 origin/main0a56d4612c86dddfcbe644e775980d3c5b413b2e). 커밋·push·병합·릴리스 빌드·운영 배포는 하지 않았다 — Neo 가 검토 뒤 수행한다. - 이전 버전: 0.2.0(2026-10-28, Webmaker 직접 편집 — 아래 절). 0.2.1 은 끝 버전 번호만 올리는 운영자 화면 수정이다: 사용자 기능·도입 모듈·보안 경계 변경 없음.
- 구성: HAL · 고객 포털 · Node Agent · 도입 모듈 Webmaker 1.8.0 · AriMail 1.0.11 모두 0.2.0 그대로(HAL 운영자 콘솔 화면 2곳 + 브라우저 스크립트만 수정). 외부 npm 의존성 없음, Node 22.13+.
요구(사용자 요구 — 약화 없이 반영)
HAL 운영자 콘솔의 모든 일반 화면 위에 재인증이 10분을 넘기면 늘 떠 있던 전역 배너 ! 고위험 작업(서버 삭제·되돌리기·인증 키 변경·복구)은 최근 10분 안의 재인증이 필요합니다 — 재인증 을 없앤다. 그 안내는 backend 가 고위험 요청을 실제로 reauth_required 로 거부한 바로 그 시점의 재인증 창 안에서만 보인다. 단순 화면 조회·로그인 직후·10분 경과·일반 이동 중에는 보이지 않는다. 계정 화면의 수동 재인증 단추·동작은 유지하되 고위험 안내를 붙이지 않는다. backend 의 requireRecent·CSRF·3단계 확인·고위험 분류·거부 동작은 그대로다.
수정한 기존 기능
hal/ui.mjsshell(): 재인증 만료(!session.recentlyAuthed)를 전역 배너로 그리던 한 줄을 제거(이력 주석으로 대체). 다른 전역 배너(기기 귀속 접근권 만료 안내·HAL 자체 건강 비정상·긴급 복구 세션 진행·부트스트랩 미완료·비밀번호 변경 필요)와.alerts영역은 그대로.hal/ui/public/ops.js:call()(API 호출 + 재인증 재시도)과reauth()창이 새 순수 helperhal/ui/public/reauth.mjs의callWithReauth·reauthPrompt를 쓴다. 서버가reauth_required로 거부했을 때 여는 재인증 창에만 고위험 안내(note, HTML 이스케이프)를 붙이고, 계정 화면의 수동 재인증(data-act="reauth")은reauth()를 인자 없이 불러 안내 없이 비밀번호만 묻는다. 재시도 규칙은 전과 같다(성공 시 딱 한 번, 취소·실패 시 원 요청 미실행).hal/ui/render.mjsaccountsView(공개 사본hal/ui/public/render.mjs는 심볼릭 링크): 계정 화면 "최근 재인증" 상태 배지 문구재인증 필요(고위험 작업)→만료(10분 지남). 조회 화면이 고위험 안내를 품지 않게 하는 중립 표기이며 상태 판정·단추는 같다.- 새 파일
hal/ui/public/reauth.mjs(순수, DOM·fetch 없음,/ops/ui/reauth.mjs로 정적 제공):REAUTH_REQUIRED·HIGH_RISK_NOTICE(옛 배너 문구 그대로 — 위치만 옮김)·reauthPrompt({required})·callWithReauth(run, reauth). 브라우저와 Node 테스트가 같은 파일을 쓴다. - 버전 표기:
package.json0.2.1(HAL 런타임hal/deps.mjs VERSION·scripts/release.mjs는 이 값을 따른다),README.md버전·개발 지시 목록,test/webmaker-visual-editor.test.mjs의 Suite 버전 검사 0.2.1.
고객 화면
- 변경 없음.
운영자 화면
- 일반 화면(운영 현황·고객·주문·상품·사이트·메일·도메인·서버·백업·이전·릴리스·작업·장애·감사·복구·계정)에서 고위험 재인증 전역 배너가 사라진다. 운영자는 고위험 작업(서버 삭제·격리·역할 변경, 모듈 되돌리기·배포·재조정, 작업 되돌리기, 운영자 추가·접근권 발급, MFA 해제, 설정 변경, 메일 가져오기 적용·되돌리기, 릴리스 승인, 긴급 복구 선언·승인·자격, DR 설정·복원)을 실제로 시도해 서버가 거부한 순간에만 "재인증(최근 10분)" 창 위쪽에서
고위험 작업(서버 삭제·되돌리기·인증 키 변경·복구)은 최근 10분 안의 재인증이 필요합니다를 본다. - 계정 화면: "재인증" 단추 유지(수동 재인증 창은 비밀번호만), "최근 재인증" 행은
유효(10분)/만료(10분 지남). 운영자 조치 항목 없음(설정값·보관소·DB 변경 없음). 배포 뒤 브라우저 캐시의 옛ops.js가 새reauth.mjs를 못 찾는 경우는 없다(옛 ops.js 는 helper 를 가져오지 않음).
보안 결정
- 서버 쪽 판단 코드는 한 줄도 바꾸지 않았다(
hal/app.mjsrequireRecent·CSRF·threeStep·고위험 경로 분류,lib/session.mjsrecentlyAuthed,POST /ops/api/reauth). 변경은 "거부됐을 때 브라우저가 어떻게 묻고 몇 번 다시 보내는가"와 안내 문구 위치뿐이다. - 재인증 창 취소·재인증 실패 → 원 요청을 다시 보내지 않고 원래 오류를 호출자에게 전파(고위험 작업이 몰래 실행되지 않음). 성공 → 같은 method·path·body 로 딱 한 번 재시도(
api직접 호출,call재귀 아님). 재시도도 거부되면 다시 묻지 않는다(무한 반복 금지).reauth_required외의 오류는 재인증을 묻지 않는다. - 안내 문구는 helper 단일 출처이며
R.esc로 이스케이프해 modal 에 넣는다. 비밀번호·토큰·키·개인정보는 파일·테스트·출력에 없다 — 새 테스트test/hal-reauth-banner.test.mjs의 운영자 비밀번호 fixture 는 정적 literal 없이node:cryptorandomBytes로 실행마다 생성하며(정답·오답 값이 서로 다름 보장) 출력에도 찍지 않는다.
테스트
- RED(production 변경 전,
node --no-warnings test/hal-reauth-banner.test.mjs): 통과 1 · 실패 4 — ①/ops/: 10분 경과 뒤에도 고위험 배너 없음 (실제 false)(배너가 아직 그려짐) ② ③ERR_MODULE_NOT_FOUND hal/ui/public/reauth.mjs(helper 없음, 문구·재시도 계약 테스트 2건) ④hal/ui.mjs 는 고위험 배너 문구를 더 이상 그리지 않음 (실제 false)(정적 계약). 통과 1건은 backend 경계 보호 테스트(변경 대상이 아니므로 처음부터 통과해야 한다). - GREEN(최소 구현 뒤):
test/hal-reauth-banner.test.mjs5/5; targetedtest/hal-ui.test.mjs7/7,test/hal.test.mjs16/16(기존 reauth_required → 재인증 → 통과 흐름 포함).node --check로ops.js·reauth.mjs구문 확인. - 전체 Suite
node test/run.mjs: 25개 파일 실패 0(새hal-reauth-banner5 포함, 로컬 E2E 11 통과; 러너가 만든docs/verification/local-e2e-2026-10-28.*임시 파일은 diff 에서 제거). 기존 테스트 변경은test/webmaker-visual-editor.test.mjs의 Suite 버전 검사 0.2.1 뿐. - 새 테스트가 못 박는 계약: 일반 화면 6곳 + 계정 화면에 로그인 직후·10분 경과 모두 전역 배너·배너 영역 재인증 링크·
고위험 작업(문구 없음, 계정 수동 재인증 단추 유지, 부트스트랩 배너 유지 / backend: 10분 경과 뒤 서버 삭제·격리 403reauth_required(detail.action=reauth)·거부된 요청 미실행·CSRF 없는 재인증 403csrf·틀린 비밀번호 거부·재인증 뒤recentlyAuthed·3단계 확인 없이는 여전히 거부 / helper: 수동 창 note 없음·거부 창 note = 옛 배너 문구·취소 시 run 1회·오류 전파·성공 시 run 2회·재시도 거부 시 재질문 없음·다른 오류는 재인증 없음·정상 응답 그대로 / 정적:hal/ui.mjs배너 코드 없음,ops.js가./reauth.mjs·callWithReauth·reauthPrompt를 쓰고reauth: () => reauth()(인자 없음)이며 문구를 직접 품지 않음.
알려진 제한
- 실제 브라우저(DOM)에서 재인증 modal 이 열리는 모습은 자동 테스트하지 않았다(저장소에 브라우저 러너 없음, 큰 dependency 추가 금지). 흐름·문구는 순수 helper 로, 결선은 정적 계약으로 검사했다. 배포 뒤 운영자가 10분 경과 상태에서 고위험 단추(예: 서버 격리) 하나를 눌러 안내가 붙은 재인증 창이 뜨고 취소 시 실행되지 않음을 한 번 눈으로 확인하는 것을 권한다.
hal/ui/render.mjsrelayError({code:'reauth_required'})가 포털 중계 응답에 "재인증 필요" 상태 패널을 그리는 동작은 그대로다(이것도 서버가 거부한 그 시점의 표시이며 고위험 문구는 없다).docs/verification/에 0.2.1 전용 검증 문서는 만들지 않았다(지시 범위: HAL UI·테스트·버전/이력·개발 지시). RED/GREEN 증거는 이 절과 인도 보고에 있다.
미확정 정책값
- 0.2.0 의 미확정 정책값 그대로. 재인증 유효 시간(10분)·고위험 작업 분류는 바꾸지 않았고 이번 범위 밖이다.
0.2.0 (2026-10-28)
- 작성자: 개발 지시 — Neo(
Ariweb-Suitle_Dev-Order_v0.2.0.md) · 구현 — Claude Fable 5.1(격리 작업 트리feature/visual-editor-v0.2.0, 기준 main4b3714e37c543fcfa8862dbc68c76f62a9b0bf97). 커밋·push·병합·배포·운영 서버 접근은 하지 않았다 — Neo 가 검토 뒤 수행한다. - 이전 버전: 0.1.1(2026-10-26, 첫 구현 릴리스 — 아래 절) · 0.1.4 개발 지시(2026-10-27, HAL AI 대시보드·다중 AI 공동 작업창 지시 문서만 추가,
4b3714e). 0.2.0 은 가운데 버전 번호를 올리는 기능 릴리스다: 도입 모듈 Webmaker 에 사용자 기능(직접 편집)이 더해졌고 Webmaker 자체도 1.7.3 → 1.8.0 으로 올랐다. 0.1.1 의 모든 기준·0.1.4 의 지시는 그대로 유효하다(이번 범위 밖의 0.1.4 항목은 구현하지 않았다). - 구성: HAL · 고객 포털 · Node Agent 는 0.1.1 그대로. 도입 모듈 Webmaker 1.8.0(직접 편집 포함) · AriMail 1.0.11(변경 없음). 외부 npm 의존성 없음, Node 22.13+.
요구(사용자 요구 — 약화 없이 반영)
Webmaker 가 초안을 만들어 보이는 페이지가 생긴 뒤 직접 시각 편집 모드를 더한다(Wix·아임웹 방식 끌어다 놓기). 변경은 Suite 의 modules/webmaker 와 기존 Webmaker 저장소 양쪽에 반영한다. 필수 동작·보안 규칙은 Ariweb-Suitle_Dev-Order_v0.2.0.md 에 계약으로 적었다.
재사용한 기존 기능(그대로 도입)
- Webmaker 1.8.0 의 직접 편집(시각 편집기) 전부 — 미리보기 도구 줄 "직접 편집" 전환(사이트·HTML 페이지가 골라져 있을 때만), 캔버스 글 바로 고치기(두 번 클릭/Enter), 요소 선택·도구 막대(↑↓·복제·삭제·끌기 손잡이), 기존 블록 끌어 옮기기, 구성 요소 팔레트 7종(섹션/컨테이너·제목·문단·버튼(링크)·이미지·구분선·여백) 끌어다 놓기 + "추가" 버튼, 속성 창(상위 선택·이동·복제·삭제·글·링크 주소·이미지 주소/대체 글·정렬·글자색·배경색·안쪽/바깥 여백), 저장 상태(변경 없음/저장되지 않음/저장 중/저장됨/저장 실패 — aria-live), 저장(Ctrl/Cmd+S), 실행 취소/다시 실행, 버리고 다시 불러오기, 편집 끝내기, 페이지·사이트·화면 전환·새로고침·소스 보기 전환·창 닫기 전 저장하지 않은 변경 보호. 기존 소스 편집기·모바일 미리보기·버전/복원·배포·가져오기·다국어·모듈·SEO·Site Brief 는 그대로.
- 재사용한 함수(Webmaker 기존 코드를 그대로 호출):
resolveTextFile(경로 검증·링크 거부),listFiles,gitRecord/lib/gitstore.recordChange(버전 기록 — 새 종류visual-edit),publicView,broadcast,jobOf(AI 작업·배포 중 판정),readBody,sessionUser·fromTenantPage·auth.csrfOk(출처·CSRF),siteUrl,isSecureReq,MAX_TEXT_BYTES(5MB 상한),store.save(Suiterev증가). 화면:discardOk·renderPreview·renderGit·loadFrame·setEditorOpen·logLine·pushMsg·api(CSRF 헤더)·T.
수정한 기존 기능
- Webmaker(
modules/webmaker, 파일 머리·줄 주석v1.8.0):server.js—require('./lib/visual-editor'),veRuntime()·requestOrigin(),GET /files응답에hash, 새 경로GET/PUT /api/sites/<id>/visual/<페이지>(아래 "직접 편집·끌어다 놓기·저장·버전·보안").public/app.js—discardOk(직접 편집 변경도 묻기),VER_KIND에visual-edit(기존manual-edit는 "소스 편집" 으로 표기),renderPreview(직접 편집 중이면 미리보기 덮어쓰지 않고VisualEditor.onSiteUpdated),btnToggleSource(한 번에 하나),beforeunload,resetToNew,btnReload·fileSelect.onchange,openSite끝(사이트 바뀌면 편집 종료).public/index.html—#btnVisual,#veBar,.ve-stage(#vePalette·#frameWrap·#veInspector), 스크립트 2개.public/style.css—.ve-*(900px 이하 세로 배치·접기).public/admin.js— 버전 종류 표기.package.json1.8.0. - Suite 최소 연결 변경(
[Ariweb Suite 변경], 0.1.1 의 8개 파일 변경은 그대로 유지):modules/webmaker/server.js의 직접 편집PUT에도 저장본rev충돌 검사(revConflict, §9.3)를 소스 편집기·기본 사항 저장과 같이 적용 — 화면이 보낸rev가 다르면 409 와 양쪽 요약(conflict.currentRev등). 화면(visual-editor-ui.js)은GET /visual의rev를 기억해 저장 때 보내고 응답의rev로 갱신한다(upstream Webmaker 는rev를 돌려주되 무시한다). 그 밖의 Suite 전용 변경(SSO·내부 API·배너·lib/ariweb.js·store.rev)은 바뀌지 않았다 —diff로 upstream 과의 차이가 0.1.1 의 Suite 블록 + 1.8.0 공통 변경만임을 확인했다. - Suite 도구:
scripts/import-sources.mjs에--allow-dirty(참조 저장소를 나란히 개발 중일 때 고정 커밋 위의 작업 트리 변경·추적되지 않은 새 파일을 가져오고upstream{version·branch·headCommit·workingTreeDirty·note}를 기록) 와--only때 다른 소스 기록 유지.hal/modules/index.mjs·agent/jobs/module.mjs의 Webmaker 버전 주석,portal/webmaker/REUSE.md갱신. Agent·포털 코드 동작은 바뀌지 않았다(릴리스 artifact 는package.json0.2.0 으로webmaker-0.2.0.tar.gz등으로 만들어진다).scripts/release.mjs와hal/core.mjs는 바뀌었다 — 도입 소스 출처 fail-closed(아래 ⑪)와 릴리스 승인 세 검사 필수(아래 종결 검토 ③). - 오류 수정: Webmaker 직접 편집 저장에서 JSON 파싱 실패가 500 으로 나오던 것을 400 한국어 안내로(TDD 중 발견, 직접 편집 경로에만 적용).
- 검토 수정(Neo 독립 검토) — ① 출시 차단: 직접 편집 PUT 이 본문을 읽기(await) 전에 파일 hash 를 잡아 두어 두 저장이 나란히 들어오면 둘째가 첫째를 조용히 덮어쓸 수 있었다 → 본문을 다 읽은 뒤 사이트 기록(
store.load)·파일을 다시 읽어 hash(Suite 는rev도)를 비교하고 검증·쓰기까지 await 없이 이어 간다(둘째는 409 stale, 첫 저장 보존). 결정적 회귀 테스트(chunked PUT 두 개의 본문을 순서대로 완료). ② 안전 강화: 직접 편집 저장을lib/visual-editor.writeTextAtomic(같은 폴더 임시 파일 0644 → rename, 실패 때 정리·오류 전달, 파일 시스템 주입 가능)로 바꿔 쓰기 실패·중단 때 마지막 정상 페이지가 잘리지 않는다(500 + "기존 페이지는 그대로 있습니다", 버전 미생성). 소스 편집기의 기존 쓰기는 바꾸지 않았다.
고객 화면
- 포털 사이트 상세 › 제작 탭 › "제작 화면 열기"(SSO) → Webmaker 빌더 오른쪽 미리보기 도구 줄의 직접 편집. 포털 자체 화면은 바뀌지 않았다. 직접 편집은 AI Wallet(캐시)을 쓰지 않으므로 포털 AI Wallet 카드에 영향이 없고, 직접 편집 저장본(버전
visual-edit)은 포털 배포 화면의 저장본 선택·Source 변경 이력에 그대로 보인다(내부 APIprojects/<id>의versionList). - 포털 상단 배너(그룹·운영 사이트)는 직접 편집 중에도 그대로 보인다.
운영자 화면
- HAL 운영자 콘솔 변경 없음. Webmaker 관리자 콘솔의 버전·변경 종류 표기에 "직접 편집"(
visual-edit)이 추가되고 기존manual-edit는 "소스 편집" 으로 보인다. 운영자 조치 항목 없음(설정값·보관소 변경 없음).
직접 편집 · 끌어다 놓기 · 저장 · 버전 · 보안 동작
- 캔버스:
GET /api/sites/<id>/visual/<페이지>가 저장된 HTML 을 편집 캔버스 문서로 바꿔 준다(lib/visual-editor.prepareCanvas). 페이지<script>(외부 src 포함)는type="text/x-wm-ve-disabled", 인라인 이벤트 속성은data-wm-ve-on-*,javascript:링크·iframe/frame/embed src·object data·meta http-equiv·base href 는data-wm-ve-*보존 속성으로 바뀌어 페이지 스크립트가 실행되지 않는다(지우지 않고 보존).<meta http-equiv="Content-Security-Policy">가 nonce 있는 편집 런타임(인라인visual-editor-core.js+wm-ve-canvas.js)만 허용하고,<base href="<사이트 주소>/<폴더>/">로 상대 경로 자원이 저장 페이지와 같게 풀린다. 화면은 이를 기존 미리보기 iframe(sandbox, allow-same-origin 없음 → 출처 null, 쿠키·저장소·빌더 API 접근 불가)의srcdoc에 넣는다. 응답에 32자리 무작위 세션·파일 sha256(hash)·rev. - 규약: 봉투
{wmve:1, session, type, payload}. 부모는 iframecontentWindow+ 출처'null'+ 세션 + 허용 종류(ready·selected·changed·error·body·hotkey)만, 캔버스는window.parent+ 부모 출처 + 세션 + 허용 종류(init·select·move·duplicate·delete·insert·setProps·applyHtml·setMode·requestBody)만 처리한다(WMVisualCore.validateMessage). HTML 속성·메시지 내용은 권한으로 쓰지 않는다. 캔버스는 서버와 통신하지 않는다. - 저장:
PUT /api/sites/<id>/visual/<페이지>{body, baseHash, rev}— 원본 doctype·<head>·<body …>·꼬리는 글자 그대로 두고 본문만 바꿔 끼운다. 보존 속성 복원, 편집기 표식 제거. 거절(한국어): 로그인 401 · 다른 사용자 404 · 생성 사이트 출처/CSRF 없음 401 · HTML 아님 400 · 경로 탈출·링크 400 · AI 작업·배포 중 409 ·rev불일치 409(+ 양쪽 요약, Suite) ·baseHash불일치 409(+ currentHash) · baseHash 없음/형식·본문 비문자열·JSON 파싱 실패 400 · 5MB 초과 413 · 원본에 없던 스크립트(추가·변경)·인라인 이벤트 속성·javascript:/vbscript:/data:text 주소·iframe/object/embed/meta/link/base·스타일 표현식·본문 안 html/head/body 400. 저장 뒤manualEdits{kind:'visual'}, 버전visual-edit("직접 편집: <경로>"), Suiterev+1, 미리보기·소스 편집기용hash·files·git·versions갱신. 캐시 미차감·AI 명령·수정 횟수 미계수. - 끌어다 놓기: 팔레트 → 캔버스는 포인터 끌기 다리가 주 경로(부모가 팔레트 항목의 포인터를 추적해 iframe 안쪽일 때만 상대 좌표를 검증된 메시지
paletteDragAt/paletteDropAt/paletteDragCancel로 보내고 캔버스가 id·좌표·모드를 다시 검사해 스스로 놓기 표시·삽입 — Chromium 이 HTML5 끌기를 출처 'null' sandbox iframe 으로 넘기지 않기 때문), HTML5 끌어다 놓기는 포인터 이벤트가 없는 환경의 대체 경로(text/wm-ve-component+text/plain이름 공간 값만 받고 구성 요소 HTML 은isSafeComponentHtml검사). 기존 블록은 포인터 기반 끌어 옮기기(같은 문서 안, 놓을 자리 표시). 버튼(↑↓·추가)·키보드(Alt+↑/↓·Delete·Ctrl+D·Enter·Esc·Ctrl+S·Ctrl+Z/Ctrl+Shift+Z) 대안. - 반응형·접근성: 팔레트·속성 창은 캔버스 옆, 900px 이하에서 아래로 내려가며 접힌다(캔버스를 가리지 않음). 전환 버튼
aria-pressed, 도구 줄role=toolbar, 상태aria-live=polite, 팔레트·속성 창role=region+aria-label, 접기aria-expanded, 캔버스 도구 버튼aria-label·포커스 표시.
도입 소스(출처)
-
원본 저장소
https://source.ibit.kr/OZ-Group/WebHosting_AI_agent, 고정 커밋4b8c4d0de0f69025001a9e0a28a58a94e1a1f088(Webmaker 1.8.0 —feature/visual-editor-v1.8.0·main 원격 ref 검증됨; 0.1.1 의 불변 기준 커밋3b10859884ef2fd0f2af11934ab6c16370fd64b8(1.7.3) 위의 변경). 개발 중에는 커밋 전 작업 트리(참조 저장소 worktree/home/master/work/WebHoting_AI_Agent-visual-editor-v180)에서node scripts/import-sources.mjs --only webmaker --allow-dirty로 가져왔고, 커밋 뒤scripts/import-sources.mjs의pin을4b8c4d0으로 올렸다. 깨끗한 도입 기록(IMPORTED_SOURCES.json/.md, 그 pin 으로--allow-dirty없이 다시 만든 것)의 webmaker 항목:commit=upstream.headCommit=4b8c4d0,upstream{version:'1.8.0', branch:'feature/visual-editor-v1.8.0', workingTreeDirty:false}, 파일 184개(0.1.1 의 176 + 신규 8: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,test/visual-editor-app.test.js,docs/verification/visual-editor-1.8.0-tdd.md— 모두 원본과 동일), 수정 8개(0.1.1 과 같은lib/auth.js lib/config.js lib/store.js lib/vault.js public/app.js public/index.html public/style.css server.js— Suite 블록 때문). 이 작업 트리에 있는IMPORTED_SOURCES.json/.md는 아직 개발 중 기록(기준3b10859,workingTreeDirty:true)이라 Neo 가 올린 pin 으로 다시 만든다(이 작업 트리의 참조 체크아웃은 그 커밋에 있지 않아 여기서는 재생성하지 않았다). -
원본 경로 → 대상 경로는
IMPORTED_SOURCES.md표(modules/webmaker/<같은 경로>). 라이선스 검토: OZ-Group 내부 전용(private, 별도 LICENSE 없음) — 같은 조직의 Suite 재사용 허용, 외부 배포 불가(0.1.1 과 같음). AriMail 은 변경 없음(a07f69f). -
실제 브라우저 시각 검증 수정(Neo 의 브라우저 게이트) — 직접 편집을 켜도 옛 작업 칸(
.left)이 남아 1280×633 에서 캔버스 iframe 이 115px 로 줄고 390×844 에서 캔버스가 y≈2228 로 밀리는 출시 차단 결함.body.ve-on의 CSS 배치 규칙이 없던 것이 원인(JS 전환은 정상, 가짜 DOM 실행 테스트로 확인).modules/webmaker/public/style.css에body.ve-on .left { display:none }·body.ve-on .app { grid-template-columns: var(--rail-w) minmax(0,1fr) }(1280px 캔버스 568px)·1180px 이하 패널 190px·900px 이하 레일+캔버스 두 행 규칙을 추가(독립 Webmaker 와 동일). 끝내면 원래 세 칸 배치 복원. 회귀: CSS 계약 테스트 2건 + JS 전환 동작 테스트 1건(test/visual-editor.test.js). -
실제 브라우저 시각 검증 수정 2(배치 수정 뒤 게이트) — 직접 편집을 켜고 요소를 고르기 전인데 어두운 선택 도구 줄(
.wm-ve-tools)이 이미 페이지 위에 보이는 출시 차단 결함(DOM 은hidden, 속성 창은 "선택한 요소 없음"). 캔버스 런타임의 작성자 규칙.wm-ve-tools{display:flex}가 UA 의[hidden]{display:none}을 덮어쓴 것이 원인.modules/webmaker/public/wm-ve-canvas.js런타임 CSS 에.wm-ve-tools[hidden],.wm-ve-drop[hidden]{display:none !important}한 줄 추가(독립 Webmaker 와 byte-identical).renderOverlay()가hidden=false로 바꿀 때만 보이고 선택 해제·미리보기·applyHtml뒤 다시 숨겨진다. 회귀: 런타임 CSS 를 파싱해hidden요소의 최종 display 를 cascade 모형으로 계산하는 계약 테스트 1건(test/visual-editor.test.js, RED actual 'flex' → GREEN). -
실제 브라우저 게이트 3(Playwright 자동 게이트) — 실제 Chrome 에서 frameLocator 로 캔버스 iframe 을 직접 조작해 선택·글 편집·실행 취소/다시 실행·Alt+↑/↓ 이동은 통과했지만, 팔레트 → 캔버스 실제 HTML5 끌어다 놓기가 들어가지 않는 출시 차단 결함. 부모는 전용 MIME
text/wm-ve-component만 넣는데 캔버스는sandbox="allow-scripts"(allow-same-origin 없음) 출처 'null' 문서라 Chromium 이 다른 출처의 전용 MIME 끌기 자료를 넘겨주지 않는 것이 원인. 전용 MIME 은 유지하고 표준text/plain에wm-ve-component:<id>를 함께 넣으며, 캔버스는core.dragComponentId한곳에서 전용 MIME 우선 → 정확히 일치하는 text/plain + 알려진 id 만 받는다(임의 글·모르는 id 는 삽입·표식 없음).modules/webmaker/public/visual-editor-core.js·visual-editor-ui.js·wm-ve-canvas.js(독립 Webmaker 와 byte-identical). sandbox·CSP·메시지 검증·저장 검증·"추가" 버튼 대안 불변. 회귀: 순수 규칙·팔레트 dragstart·캔버스 동작(가짜 DOM 위 실제 런타임 부트) 3건(test/visual-editor.test.js, RED → GREEN). -
실제 브라우저 게이트 4(살균 DnD 진단) — 포인터 끌기 다리 — MIME 보완 뒤에도 끌어다 놓기가 실패해 부모·캔버스 양쪽에 살균 리스너(종류·플래그만)를 붙여 확인: 부모 dragstart 2건(두 종류 모두 유효)인데 캔버스 dragenter·dragover·drop 0건. Chromium 은 HTML5 끌기 자체를 부모 → 출처 'null' sandbox iframe 으로 넘기지 않으므로(제품 결함) sandbox 를 유지한 채 포인터 다리를 주 경로로 넣었다: 부모 팔레트 항목이 포인터(마우스·펜)를 추적해("추가" 버튼·터치·보조 버튼 제외, 포인터 캡처, 4px 문턱) iframe 안쪽일 때만 상대 좌표를 검증된 메시지
paletteDragAt/paletteDropAt(밖·Escape·pointercancel·끝내기는paletteDragCancel)로 보내고, 캔버스가 메시지 규약 뒤 id(core.isComponentId)·좌표(core.canvasPoint)·편집 모드를 다시 검사해 스스로 놓기 표시·삽입을 한다(잘못된 입력은 삽입·표식 없음). HTML5 DnD 는 대체 경로로 남김.modules/webmaker/public/visual-editor-core.js·visual-editor-ui.js·wm-ve-canvas.js(독립 Webmaker 와 byte-identical) +style.css2줄(.ve-comp { user-select:none; touch-action:pan-y }·끌기 중 class). 터치 스크롤·"추가" 버튼·키보드·CSP·저장 검증 불변. 회귀: 규약·좌표 순수 규칙 / 부모 포인터 순서 동작 / 캔버스 수신 동작 3건(test/visual-editor.test.js, RED → GREEN). -
독립 검토(읽기 전용) 차단 항목 12건 수정 — 보안 7·규격/회귀 5. 공유 파일은 참조 저장소와 byte-identical 로 반영하고 Suite 블록 파일(
server.js·app.js·lib/store.js)은 패치했다(상세·RED 증거:docs/verification/webmaker-visual-editor-0.2.0.md, upstreammodules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md). ① 캔버스 연결 규약 — 캔버스가MessageChannel포트를 첫 'ready' 창 메시지에 동봉하고 그 뒤 모든 명령·이벤트는 포트로만(core.validatePortMessage); 부모는 첫 ready 한 번만 연결·연결 뒤 창 메시지 전면 무시·끝내기 때 포트 닫음; 캔버스는 창 메시지를 듣지 않음(iframe 안 문서가 바뀌어도 세션·명령을 이어받을 수 없다). 심층 방어: a/area/제출 클릭·submit 차단(두 모드),<base target="_blank">, CSP form-action none, meta refresh 비활성화. ② 주소 스킴 정규화 — 서버·화면·캔버스가 한 구현(core.urlScheme)으로 이름·숫자 엔티티(두 겹)·제어·폭 없는 문자를 걷어내고, 스킴 자리의 모르는 엔티티는 거절. ③prepareCanvas가 구조 스캐너로 진짜 head/html/body 를 찾아 CSP·런타임을 넣고 없으면 진짜<head>생성. ④ 저장 검증을 완성 문서 전체 ↔ 원본 전체 비교로(머리 요소를 본문에 복사 불가). ⑤ 사이트별 변경 잠금withSiteLock— 소스/직접 편집 PUT·버전 복원은 본문을 읽은 뒤 잠금 안에서 실행 중·배포 중·기록(rev)·hash 재검사 → 쓰기; AI/기획/가져오기/복사/다국어/배포 시작은claimJob; 소스 편집기 PUT 에 선택적baseHash(화면이 보냄, 409 때 다시 불러오기 질문). ⑥ 직접 편집 저장 트랜잭션 — 기록/Git 실패 때 페이지·사이트 기록(rev 포함,store.save(prev,{restore:true})는 rev 를 올리지 않음)을 되돌리고 커밋을 되감아 500(gitRecordstrict; 종결 검토 ② 에서 원격 푸시 순서·검증된 되돌리기로 재설계). ⑦ Suite 전용: 직접 편집 저장은 유한한 숫자rev필수(없음·문자열·null → 400revRequired, 오래됨·다른 파일 저장으로 오른 rev → 409). ⑧ 전환 보호 —openSite는 전환 전에 묻고 취소하면 현재 사이트 유지, 홈 탭도 묻고,onSiteUpdated/renderPreview는 HTML 페이지가 없어져도 변경이 있으면 묻고 캔버스를 남겨 둔다. ⑨ 복제가 자손 id 도 제거. ⑩ 블록 끌기 — 손잡이 포인터 캡처, pointercancel·창 blur·Escape 가 상태를 모두 정리. ⑪ Suite 전용: 새lib/provenance.mjs—scripts/release.mjs build는 IMPORTED_SOURCES 의 출처가 깨끗하지 않으면(workingTreeDirty=true·불완전·commit≠headCommit) artifact 를 만들지 않고 종료 1, manifest.json 에checks.provenanceClean/provenanceProblems·imported[].upstream;register --approve는provenanceClean !== true면 승인하지 않고,hal/core.approveRelease는provenanceClean === false를release_checks_failed로 거절(종결 검토 ③ 에서 세 검사 모두 명시적 통과 필수로 강화). 개발 중 기록(workingTreeDirty: true)으로는 build 가 멈췄다 — 참조 저장소 1.8.0 이4b8c4d0으로 커밋·검증되어 pin 을 올렸고, 그 pin 으로 다시 만든 깨끗한 기록(workingTreeDirty:false, 184 파일·수정 8)으로 build·approve 가 된다. ⑫ 문서 수치를 최종값으로 맞추고 브라우저 게이트 상태를 기록. 도입 기록(IMPORTED_SOURCES.json, 생성 manifest 기준): webmaker 184개 파일(수정 8: lib/auth.js·lib/config.js·lib/store.js·lib/vault.js·public/app.js·public/index.html·public/style.css·server.js), 고정 커밋3b10859, upstream 1.8.0feature/visual-editor-v1.8.0workingTreeDirty=true. upstream 대비 Suite 블록 차이:server.js115줄(107 → 115),app.js4줄,style.css5줄,index.html2줄,lib/store.js4줄. -
브라우저 게이트 상태 — Neo 의 Playwright 게이트(네이티브 포인터 순서)는 이 검토 전 에 37/37 통과. 수정 뒤 Neo 의 첫 재실행은
edit-text에서 멈췄는데 원인은 게이트 멱등성 결함(같은 저장 폴더·같은 고정 제목 → 변경 없음 = clean)이었고 제품 결함이 아니다(로컬 결정적 재현). 같은 조사에서 가운데 클릭(auxclick) 틈을 찾아 캔버스 런타임에 차단을 더했다. 고친 게이트(실행마다 다른 제목, 연결 규약·이동 활성화 6종·위조 창 메시지 단언 추가)는 로컬 Chrome 156 에서 61/61(연속 2회), Neo 호스트의 실제 Chrome 에서도 61/61 통과(2026-10-28) — 수정 뒤 브라우저 재실행은 완료되었고 남은 재실행은 없다(종결 검토 3건은 브라우저 동작을 바꾸지 않는다). -
독립 검토 2차(종결 검토) 차단 항목 3건 수정 + 문서 수정 — 참조 저장소와 같은 코드(공유 파일 byte-identical,
server.js는 Suite 블록 보존 패치; 상세·RED/GREEN:docs/verification/webmaker-visual-editor-0.2.0.md, upstreammodules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md, 변경 이력 §9). ① SVG SMIL 저장형 XSS 우회(보안) —validateVisualSave가<animate attributeName="href" values="javascript:…">·<set …>를 받았다(정적 주소만 검사, 위험 요소 목록에 SMIL 없음; Chromium 에서 실행 확인).lib/visual-editor.js가 SMIL 요소 전부(animate·set·animateMotion·animateTransform·animateColor·discard·mpath, 대소문자·svg:접두어 포함)를 위험 요소 묶음으로 세어 원본 수까지만 허용(삭제 가능, 추가·변경 거절), 캔버스에서는wm-ve-x-<이름>비활성 이름으로 바꿨다가 복원 때 표준 이름으로 되돌린다;core.isSafeComponentHtml도 거절. ② Git 저장 트랜잭션 재설계 —gitstore.recordChangeStrict: 스냅샷 → 로컬 커밋·변경 브랜치 → 사이트 기록 저장(rev +1) → 원격이 있으면 main + 변경 브랜치를git push --atomic한 번으로(마지막 단계, 그 뒤 상태 변경 없음); 어느 단계가 실패해도reset --mixed(커밋 되감기 + 인덱스 비움, 첫 커밋 전이면 브랜치 삭제 + 빈 인덱스)·변경 브랜치 삭제/복원·페이지 원본·store.save(prev,{restore:true})(rev 보존) 뒤 검증(HEAD·인덱스·브랜치·작업 트리·페이지 바이트·기록) —rolledBack:true는 검증 통과 때만, 아니면rolledBack:false+rollbackProblems. 강제 푸시 되돌리기 없음, 원격 없는 저장소는 기존처럼 로컬 커밋. 푸시 전 에 저장되는 기록은 원격 반영을 주장하지 않는다(pushed:false, remote:'pending', 응답도 같음) — 별도 조정reconcilePushes(저장 뒤·기동 때·다음 기록 때, 잠금 안)가ls-remote로 원격 ref 를 확인한 뒤에만pushed:true, remote:'verified'로 바꾸고, 원격에 없으면(기록 저장과 푸시 사이에 프로세스가 죽은 경우) 로컬 변경 브랜치 + main 을 원자적으로 올린 뒤 확인한다. 조정의 기록 저장은{restore:true}라 rev 를 올리지 않는다(Suite 동시 편집 충돌 규칙과 무관한 메타데이터). ③ Suite 전용 — 릴리스 승인 fail-open:hal/core.releaseApprovalProblem한 규칙으로testsPassed === true·dirty === false·provenanceClean === true를 모두 요구(없음·null·문자열 거절) —approveRelease·registerRelease(approve:true)·운영자 APIPOST /ops/api/releases/:v/approve·scripts/release.mjs register --approve(DB 열기 전) 모두 적용,--skip-tests빌드는 등록만 가능. 기존 테스트 픽스처(hal-cli·hal·e2e 가짜 릴리스)는 깨끗한 checks 를 넣도록 고쳤다(옛 관용 정책 없음). ④ 문서 — "재실행 필요/미실행"·"자동화하지 않음"·옛 수치(103/110·182 파일·HTML5 주 경로·release.mjs 변경 없음)를 현재 증거로 고쳤다. 도입 기록 재생성: webmaker 184 파일·수정 8·workingTreeDirty=true(의도 유지 — 참조 저장소 커밋 뒤 재도입). -
독립 검토 3차(최종 검토) 차단 항목 4건 수정 — ① Suite 전용 · 승인된 릴리스 불변:
hal/core.registerRelease가 승인된 버전을 다른 내용(artifact·checks·커밋·빌드 시각·메모)으로 재등록하면release_immutable로 거절하고 아무것도 바꾸지 않는다(같은 내용 재등록은 멱등, approve 요청이어도 그대로); 승인되지 않은 버전의 내용 변경 재등록은 registered 로 두고 승인을 물려받지 않으며 approve 는 세 검사 규칙(releaseApprovalProblem)을 지나야 한다.hal/index.mjs register-release는 거부를 종료 1 로 알린다(DB 불변). ② 사이트 변경 잠금 전면 감사(공유 코드, Suite 블록 보존):/api/sites/<id>/…변경 요청은 본문을 잠금 밖에서 메모 읽기 → 잠금 안에서 기록 재적재 → 짧은 경로는 처리기 전체를 잠금 안에서, 긴 작업 경로는 재적재한 기록으로(claimJob 까지 await 없음); 재진입 잠금(AsyncLocalStorage)·detached();changeLanguages는 claimJob 먼저. Suite 의 rev 충돌 검사는 재적재된 최신 기록으로 이루어진다. ③ 트랜잭션 완결성·모호한 푸시 판정:recordChangeStrict의 준비 단계(initSite·스냅샷·원격 설정)도 트랜잭션 안(실패하면 onRollback·새로 만든 저장소 삭제·스냅샷 전 실패는 ok:false), 푸시 오류는 원격 ref 를 다시 읽어 성공/원격 불변(되돌림 + 원격 재검증)/모호(pending 유지, 200 +remoteState:'ambiguous', rolledBack 주장 없음)로 판정. ④ 문서 합계 통일. ⑤ 배경 작업 저장 합치기(저장소 불변식, Suite 블록 포함):modules/webmaker/lib/store.js가 읽은 객체마다 읽었을 때 상태(__base, 열거 안 됨)와 저장 표식saveId를 기억하고, 디스크 기록이 그 사이 다른 객체로 저장되었으면 이 객체가 바꾼 최상위 항목만 디스크 최신 기록 위에 합쳐 저장한다 — 배경 AI 작업의 종료 저장이 작업 중의 기본 사항·모델 등급 변경을 지우지 않는다(RED: 제목이 "VE site" 로 되돌아감 → GREEN). Suite 의rev는 합칠 때 디스크 값을 기준으로 올리고, 원격 반영 조정은keepRev(합치되 rev 불변)로 저장한다(restore는 스냅샷 그대로). upstream 대비lib/store.js차이는 5줄(rev·keepRev). ⑥ 같은 잎 동시 변경 fail-closed: 합치기를 잎 단위 3자 병합으로 바꾸고(같은 최상위 항목의 다른 잎은 합침), 같은 잎을 둘 다 다르게 바꾼 경우만site_save_conflict(409, fields)로 거절해 디스크의 먼저 쓴 값을 보존한다. 배경 작업의 모든 저장(실패·중단 기록 포함)은saveJob으로 가서 충돌 때store.update로 최신 기록을 다시 읽어 작업이 가진 항목만 다시 적용하고 객체를 맞춘다(store.adopt); 다시 푸시·배경 조정은 원격 ref 결과를store.update로 적용. RED: 작업 중 다시 푸시가 쓴verifiedAt이 작업 종료 저장에 지워지고 배경 재확인이 다른 시각을 씀 → GREEN. 상세·RED/GREEN:docs/verification/webmaker-visual-editor-0.2.0.md, upstreammodules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md(변경 이력 §10). -
독립 검토 4차 차단 항목 4건 수정 — ① Suite 전용 · artifact 설치 불변·fail-closed:
hal/core.installArtifacts— 원본 바이트의 sha256·크기를 manifest 와 대조(불일치artifact_hash_mismatch, 아무것도 쓰지 않음), 승인된 버전은 메타데이터·설치 파일 해시가 같을 때만 멱등(복사 없음)·다르면release_immutable로 파일·DB 불변 거절, 새·미승인 설치는 임시 파일 → 해시 재확인 → rename.hal/index.mjs register-release·scripts/release.mjs register모두 설치 → 등록 순서로 이 함수를 쓰고 거부는 종료 1. ② 읽기 요청 부작용 제거(공유 코드): GET/HEAD/OPTIONS 는x-ui-lang으로 저장하지 않는다(saveId·rev 불변) — 언어는 변경 요청의 잠금 안 재적재에서만 저장. ③ 폐기 잠금·표식 규약(공유 코드, Suite 의 포털 영향 확인 블록 보존): 잠금 안 재검사 + 폐기 표식 원자적 claim → 잠금 밖 배포 서버 정리 → 다시 잠금·같은 표식 확인 뒤 삭제; 표식 중 모든 변경 요청·작업 시작 409; 실패는 표식 해제 +store.update로deleteFailed. 포털 연결 보상(잔여 위험 해소): 연결 해제(ariweb.unlink)를 정리 전 에 하던 것을 공유destroySite의beforeIrreversible훅으로 옮겨 배포 서버 정리 성공·사이트 폴더/기록을 임시 이름으로 옮긴 뒤(되돌릴 수 없는 기록 삭제 직전)에만 하고 readback 으로 확인한다; 정리가 실패하면 연결·파일·기록이 모두 그대로, 연결 해제가 실패하면 옮긴 것을 되돌리고 중단, 기록 삭제가 실패하면compensate(ariweb.link로 되살리기 + readback,linkedAt보존) 뒤 실패로 알린다(되돌리지 못하면recoveryRequired+ 감사site.destroy.failed). 뒷정리(폴더 등) 실패는 성공 응답의warnings와 감사site.destroy.cleanup으로 운영자에게 보인다. ④ 파서 차이(저장형 XSS)(공유 코드): 스캐너가 svg/math 안에서는 title·textarea·style·script 를 원문으로 읽지 않아<svg><title><script>가 거절·비활성화된다. 상세·RED/GREEN:docs/verification/webmaker-visual-editor-0.2.0.md, upstreammodules/webmaker/docs/verification/visual-editor-1.8.0-tdd.md(변경 이력 §11).
테스트
- 독립 검토 4차 4건 수정 뒤(현재 증거): 참조 저장소
npm test115/115(단위 39 + 서비스 23 + 화면 전환 1 + 원본 52),modules/webmakernpm test125/125(Suite 전용test/ariweb.test.js10 — 폐기 보상 3 추가), Suitenode test/run.mjs24개 파일 실패 0(test/release-provenance.test.mjs11건), 실제 Chrome 게이트(Neo 호스트) 61/61 그대로. RED: Suite artifact 불변 2 실패(승인된 바이트가 B 로 덮임), 참조 단위 38/39(svg title script 통과)·서비스 21/23(읽기가 기록 파일을 바꿈, 폐기 중 저장 미거절), Suite 모듈 폐기 보상 7/10(정리 실패 뒤 연결이 풀림·연결 해제 실패인데 정리를 먼저 하지 않음·되살리기 없음). - 최종 검토(3차) 4건 + 잎 단위 저장소 합치기(fail-closed) 수정 뒤: 참조 저장소
npm test112/112(단위 38 + 서비스 21 + 화면 전환 1 + 원본 52),modules/webmakernpm test119/119, Suitenode test/run.mjs24개 파일 실패 0(test/release-provenance.test.mjs9건), 실제 Chrome 게이트(Neo 호스트) 61/61. RED: Suite 승인 불변 core·CLI 2 실패(재등록이 승인자를 바꿈), 참조 서비스 15/18(잠금 경쟁·모호한 푸시·준비 단계 실패). - 종결 검토 3건 + 원격 반영 보수 기록 수정 뒤: 참조 저장소
npm test107/107(단위 38 + 서비스 16 + 화면 전환 1 + 원본 52),modules/webmakernpm test114/114, Suitenode test/run.mjs24개 파일 실패 0(test/release-provenance.test.mjs7건 — 승인 규칙 3건 추가). RED: 참조 저장소 단위 37/38(SMIL 1 실패)·서비스 11/14(Git 실패 되돌리기 검증·원격 트랜잭션 실패), 그 뒤 서비스 13/16(푸시 순간의 기록이pushed:true를 주장 — 보수 기록 수정 전), Suite provenance 4/7(core 승인·운영자 API·release.mjs 3 실패). 실제 브라우저 게이트(Neo 호스트 실제 Chrome) 61/61. - 독립 검토(1차) 차단 항목 수정 뒤: 참조 저장소
npm test103/103(단위 37 + 서비스 13 + 화면 전환 1 + 원본 52),modules/webmakernpm test110/110, Suitenode test/run.mjs24개 파일 실패 0(새test/release-provenance.test.mjs4건 포함,test/webmaker-visual-editor.test.mjs5건은 rev 필수·교차 파일 409·되돌리기 rev 보존 검사를 더함). RED: 참조 저장소 단위 24/37(13 실패)·서비스 9/13(4 실패)·app 0/1, Suite provenance 0/4, Suite rev(rev 줄을 뺀 임시 서버 복사본) 4/5. - 포인터 끌기 다리 뒤: 참조 저장소
npm test87/87(단위 26 + 서비스 9 포함),modules/webmakernpm test94/94, Suitenode test/run.mjs23개 파일 실패 0. - 끌어다 놓기 수정 뒤: 참조 저장소
npm test84/84(단위 23 + 서비스 9 포함),modules/webmakernpm test91/91, Suitenode test/run.mjs23개 파일 실패 0. - 도구 줄 수정 뒤: 참조 저장소
npm test81/81(단위 20 + 서비스 9 포함),modules/webmakernpm test88/88, Suitenode test/run.mjs23개 파일 실패 0. - 배치 수정 뒤: 참조 저장소
npm test80/80(단위 19 + 서비스 9 포함),modules/webmakernpm test87/87, Suitenode test/run.mjs23개 파일 실패 0. - 검토 수정 뒤: 참조 저장소
npm test77/77(단위 16 + 서비스 9 포함),modules/webmakernpm test84/84, Suitenode test/run.mjs23개 파일 실패 0. 아래는 첫 인도 때 수치. - Webmaker 참조 저장소(1.8.0):
test/visual-editor.test.js15건(문서 나누기·캔버스 변환·복원·저장 검증 허용/거절·hash·공용 핵심),test/visual-editor-server.test.js7건(권한·출처·CSRF / 허용 페이지 / 캔버스 계약 / 저장·버전·캐시 미차감 / 오래된 저장 409·잘못된 본문 400 / AI 작업 중 409 — 가짜 claude 실행 파일로 작업을 멈춘 두 번째 서버 / 화면 접근성 조작·런타임 서빙). RED(모듈 없음·경로 404·버튼 없음) → GREEN 기록:docs/verification/visual-editor-1.8.0-tdd.md. 전체npm test74/74(원본 52 + 22). - Suite:
test/webmaker-visual-editor.test.mjs5건(도입 파일·버전·IMPORTED_SOURCES upstream / 순수 모듈 / 공용 핵심 / 화면 접근성 + Suite 배너 보존 / 실제 서버로 rev 충돌 409 → 맞는 rev 저장 200·버전 visual-edit·rev 증가). RED(0/5) → GREEN(5/5):docs/verification/webmaker-visual-editor-0.2.0.md.modules/webmaker전체npm test81/81(59 + 22). Suite 전체node test/run.mjs23개 파일 실패 0(로컬 E2E 포함,docs/verification/local-e2e-2026-10-28.md/.json생성). 모든 서버 테스트는CLAUDE_BIN을 즉시 실패하는 가짜 실행 파일로 두고 실행했다(개발 PC 의 진짜 claude 가 사이트 생성 작업으로 실행되지 않게 — 첫 RED 실행에서 한 번 실행되어 즉시 종료시켰고, 테스트를 고쳤다).
알려진 제한
- 실제 브라우저 검증은 Neo 의 게이트(시각 게이트 1280×633·390×844 → Playwright 자동 게이트, 실제 Chrome)로 다섯 차례 수행되어 배치·도구 줄·끌어다 놓기 결함을 찾아 고쳤고, 독립 검토 수정 뒤 재실행(게이트 멱등성 수정 뒤)은 Neo 호스트에서 61/61 통과 — 캔버스 안 실제 포인터 조작(contenteditable 입력·실행 취소/다시 실행·이동·팔레트 포인터 끌어다 놓기·저장·연결 규약·이동 활성화·위조 메시지)은 이 게이트로 자동 검증했다. Node 에서는 문법 검사·
eval부재·정적 구조·CSS 배치 계약·가짜 DOM 위 실제 런타임/app.js 실행을 확인. 자동화하지 않은 것: 포털 SSO 로 들어간 실제 화면·운영 서버 배포·Chrome 외 브라우저 — 사람 확인 목록은 WebmakerDev_Order_v1.8.0.md끝. - 본문은 브라우저 직렬화(innerHTML)로 정규화된다(속성 큰따옴표·
&→&·태그 소문자·void 요소/>제거) — 의미는 같고 머리·doctype·꼬리는 글자 그대로.</body>뒤 스크립트는 브라우저가 본문으로 옮기므로 저장 때 꼬리의 같은 스크립트를 제거해 중복을 막는다. - 캔버스는 페이지 스크립트를 실행하지 않으므로 스크립트가 만드는 동적 내용(슬라이더·모듈 위젯 목록)은 빈 틀로 보인다. 구성 요소는 7종의 첫 릴리스, 블록 이동은 같은 문서 안. 다국어 사이트는 페이지마다 따로 편집(자동 재번역 없음 — 기존 다국어 카드 흐름 사용).
- 참조 저장소 1.8.0 은
4b8c4d0de0f69025001a9e0a28a58a94e1a1f088으로 커밋·검증되어scripts/import-sources.mjs의 webmakerpin을 그 커밋으로 올렸다. 이 작업 트리의IMPORTED_SOURCES.json/.md는 아직 개발 중에 만든 기록(기준3b10859,workingTreeDirty:true)이며, 그 pin 으로node scripts/import-sources.mjs --only webmaker(--allow-dirty없이)를 돌려 다시 만들면commit = upstream.headCommit = 4b8c4d0,workingTreeDirty:false, 184 파일·수정 8 의 깨끗한 기록이 되어 build·approve 가 가능하다(여기서는 참조 체크아웃이 그 커밋에 있지 않아 재생성하지 않았다). 0.1.4 지시(HAL AI 대시보드 등)는 이번 범위 밖으로 구현되지 않았다. 운영 서버 배포·검증·점검은 하지 않았다(출시 선언 없음).
미확정 정책값
- 0.1.1 의 미확정 정책값(
alias_counting·large_attachment·ai_wallet_pricing, 후속 상품정책 값)은 그대로다. 직접 편집은 AI Wallet 을 쓰지 않으므로 과금 정책에 새 항목이 없다. 운영자가 뒤에 정할 수 있는 값: 직접 편집 본문 상한(현재 소스 편집기와 같은 5MB 고정), 실행 취소 기록 수(화면 200개 고정), 구성 요소 목록 확장.
0.1.1 (2026-10-26)
- 작성자: 개발 지시 — Hera(
Ariweb-Suitle_Dev-Order_v0.1.1.md, 통합 검토: Hera v0.1.0·Neo v0.1.0·1차 개발계획서) · 구현 — Claude Fable 5.1(Ariweb Suite 통합 개발, 커밋 Co-Authored-By 표기) - 이전 버전: 0.1.0 — 개발 지시 문서(
Ariweb-Suitle_Dev-Order.md)만 저장소에 있었고 코드는 없었다. 0.1.1 은 Ariweb Suite 의 첫 구현 릴리스다. - 정식 저장소: https://source.ibit.kr/OZ-Group/AriWeb-Suite (브랜치
release/0.1.1→main병합) - 구성: HAL(제어 평면·운영자 콘솔·공개 상태) · 고객 포털 · Node Agent · 도입 모듈 Webmaker 1.7.3 · AriMail 1.0.11. 외부 npm 의존성 없음, Node 22.13+.
경고 구간 변경
- 사용량 경고 구간을 70% · 80% · 90% · 95% 네 단계로, 100% 에서 자원별 제한으로 통일했다(v0.1.0 지시의 구간 체계를 대체). 값은 코드에 고정하지 않고 상품정책 버전(
product_versions.warn_levels_json, 기본[70,80,90,95])에서 읽는다. - 단계별 동작(§13.4): 70% Portal 사전 안내 → 80% Portal + 외부 이메일 → 90% 예상 소진 시각과 증설·정리 방법을 강조한 긴급 경고 → 95% 곧 제한될 기능과 즉시 조치를 적은 최종 경고 → 100% 자원별 제한과 유지 기능·해제 방법 표시. 같은 원인의 반복은 하나의 사건(incident key
<그룹>:<자원>:<대상>:<구간>)으로 묶인다. - 100% 제한의 자원별 의미: 웹 아웃바운드 → 사이트 대신 제한 안내, 다음 KST 월 또는 추가 용량 뒤 복구 · 웹 디스크 → 조회·삭제·내보내기 유지, 새 업로드·DB 증가·새 배포 제한 · 메일 저장 공간 → 조회·삭제 유지, 새 수신 임시 거부(발신 서버 재시도) · 월간 메일 통수 → 새 발신 차단 + 새 수신 임시 거부 · 메일함 수 → 새 메일함 생성 차단. 고객 데이터는 자동 삭제하지 않는다.
- AriMail 쪽에도 같은 구간을 적용해
/api/usage가warnLevel·limitedReasons·monthReset을 돌려준다.
재사용한 기존 기능(그대로 도입)
기능별 상세 표는 portal/webmaker/REUSE.md(Webmaker, 원본 revision 4b8c4d0 — 1.8.0; 0.1.1 기준 3b10859)와 portal/mail/REUSE.md(AriMail, 원본 revision a07f69f)에 있다. 두 모듈의 원본 회귀 테스트는 도입 뒤에도 모두 통과한다(Webmaker 59/59 — 원본 52 + 추가 7, AriMail 9개 묶음 모두 통과).
- Webmaker(§0.1):
AI에게 맡기기·내가 고르기제작, Site Brief, Template 과 기존 Module, Page 생성·편집·미리보기·저장, AI 생성·수정과 Wallet/Cache 차감, 저장본 선택, Domain·DNS·TLS 관리(등록 업체·DNS 공급자·와일드카드 인증서), Gitea Source 연결과 변경 이력, 배포 결과 확인과 이전 Version 복구, AriMail Server 연결과 Mail 사용량 표시, 사용자·관리자 화면의 설정·상태·통계. 포털 사이트 상세 › 제작 탭의 "제작 화면 열기"(SSO) 로 진입한다. - AriMail(§0.3): Multi-domain Mail Service, Webmail(수신함·발신함·작성·회신·전달·검색), SMTP 발신·수신, IMAP·POP3, Resend·Haraka·External SMTP 발송 방식, Mail Domain·Account 관리, Mailbox 용량, 자동 응답·전달, 일반 첨부·링크 방식 대용량 첨부, POP3·IMAP 설정, DNS 설정·도메인 확인, 사용량 API, Webmaker 연동, 초기 설정·관리자·진단. 포털 메일 화면의 "웹메일 열기"(SSO 티켓) 와 외부 프로그램 설정 안내로 진입한다.
- 공통 라이브러리에 옮겨 적은 기존 방식: Webmaker 의 원자적 JSON 저장·세션 세대·고정 관리자 비밀번호 폐지·scrypt 해시(→
lib/db·lib/session·lib/crypto), AriMail 의 의존성 없는 SMTP 클라이언트와node:sqlite운영(→portal/mailer.mjs,lib/db), Webmakerdeploy/remote-update.sh의 갱신 순서(데이터 백업 → 교체 → 재시작 → 유닛 점검)와 AriMaildeploy/deploy.sh의 배포 순서(DB 백업 → 릴리스 복사 → 마이그레이션 → 재시작 → health → Haraka)를 HAL 모듈 설명자(hal/modules/index.mjs)와 Agent 모듈 설치기(agent/jobs/module.mjs)에 그대로 반영.
수정한 기존 기능(최소 연결 변경·오류 수정·정책 개선)
- Webmaker(§0.2,
modules/webmaker, 파일 머리[Ariweb Suite 변경])- 최소 연결 변경:
GET /api/auth/sso?ticket=(포털 서명 티켓으로 별도 로그인 없이 진입, 계정aw-<그룹8>-<사용자8>자동 준비) · 내부 API/api/internal/ariweb/*(프로젝트 목록·연결·소스·저장본 export·credits·health, 서비스 서명) · 상단 배너에 현재 사용자 그룹·사이트 표시 · 설정 그룹ariweb.*(포털 주소, 공유 비밀은 보관소) ·lib/ariweb.js(티켓 검증·연결 등록부data/ariweb-links.json). - 정책 개선: 프로젝트와 운영 사이트를 구분(연결 등록부, "프로젝트" 표기), 연결된 프로젝트의 발행/재배포는 포털 배포 화면으로 안내(409 + 배포 URL), 프로젝트 삭제 전 포털 영향 조회(
/internal/webmaker/project-impact)와 확인 필수 — 운영 사이트·배포본·도메인은 자동 삭제하지 않음. - 오류 수정/개선: 여러 사용자의 동시 편집을 조용히 덮어쓰지 않도록 사이트 저장본
rev를 두고 불일치 시 409 와 양쪽 요약을 돌려준다. - 포털 쪽 정책: Draft 사이트는 운영 사이트 수를 쓰지 않고 최초 공개 배포 때
예약됨→ 대표 도메인 확인 뒤사용 중; AI Wallet(Webmaker credits)과 호스팅·메일 사용량을 분리 표시; 외부 자가 배포는 비용·책임·상태를 구분.
- 최소 연결 변경:
- AriMail(§0.4,
modules/arimail, 15개 파일, 회귀 테스트test/ariweb.test.mjs8건 추가)- 오류 수정:
test/usage.test.mjs의 KST·UTC 월 경계 — 상대 시각(daysAgo) 을 KST 정오 기준으로 고정하고limits.statsNow/monthlyCount(env, at)로 고정 시각 검사(2026-10-31T14:59:59Z ↔ 15:00:00Z 경계 테스트) · Haraka/External SMTP 발신 때RESEND_API_KEY를 잘못 요구하던server/smtp.mjs451 오류. - 정책 개선: 도메인 등록부에
ariweb {kind: site_bound|standalone, siteId, groupId, mailServiceId}와operating(active|migrating_out|migrating_in|frozen)· 도메인별 보장량(LIMIT_ACCOUNTS/TOTAL_BYTES/MAILBOX_BYTES/MONTHLY_MESSAGES를 테넌트 config 로) · 월간 통수 = 송신+수신, 최종 수신자 한 명당 한 통 · 저장 공간에 원문·일반 첨부·Inline·대용량 첨부 포함 · 100% 동작(저장 공간 → 수신 임시 거부·조회/삭제 유지, 월간 통수 → 발신 차단mail_monthly_limit+ 수신 임시 거부, 다음 KST 월 초기화) · 발송 큐(outbound_queue표, 재시작 복구)와 안전 속도(도메인·서버별 초당 5건, 제출 초당 10건 이상은 제한·경고·악용 확인abuse_flags, 운영자/api/admin/rates) · 이전 중 두 서버 동시 처리 방지(operating≠ active 면 수신 임시 거부·발신 차단) · SSOGET /api/auth/sso?ticket=. - 포털 쪽 정책: Site 대표 Mail 과 독립 MailService 구분, 사이트당 대표 Mail Domain 하나, 독립 MailService 는 운영 사이트 수 미소비, Site-bound → 독립 전환, 고객용 Mail Backup·복원 미제공 명시, 해지 후 30일 읽기·Export 와 삭제 예정일.
- 오류 수정:
- 미사용: Webmaker 자체의 배포 대상(deploy targets)·되돌리기 기능은 Ariweb 연결 프로젝트에 쓰지 않는다(관리형·외부 배포는 HAL 작업으로 통일). AriMail 별칭·전달 전용 주소 생성은 AriMail 에 기능이 없고 정책이 미확정이라 이번 버전에서 제공하지 않는다(무제한 제공 금지 원칙).
고객 화면
- 공통 틀(§2): 상단 그룹 전환·알림·도움말·사용자 메뉴, 왼쪽 메뉴(대시보드·사이트·메일·도메인·백업·상품 및 결제·사용량·사용자 그룹 설정), 제목 아래 현재 위치·주요 작업 버튼, 상단 주의 항목. 모든 화면이
불러오는 중·비어 있음·정보 지연·부분 실패·전체 실패·권한 없음상태를 갖고, 상태는 색과 함께 글자·기호로 표시하며 360px 에서 표가 카드로 접힌다. 파괴적 작업은 대상·영향 재표시 + 입력 확인. - 작업 상태 어휘(§2.2) 14종을
lib/status로 통일하고, 서비스 상태·작업 상태·외부 건강 상태를 별도 필드로 둔다. 작업 블록은 현재/완료/다음 단계, 예상 완료 또는 산정 불가 사유, 마지막 갱신, 자동 재시도 횟수·다음 시각, 재시도·취소·되돌리기·문의 번호를 보인다. - 회원가입·로그인·복구(§3): 이메일 확인 전 유료 구매·운영 서비스 생성 불가, 중복 이메일은 존재 여부 비노출, 약관·개인정보·마케팅 동의 분리, 5회 실패 → 15분 잠금(해제 시각 표시), 기기별 세션 목록과 개별/전체 종료, 외부 이메일 재설정 링크(만료·사용 완료 표시), TOTP 다중 인증과 1회 표시 복구 코드.
- 사용자 그룹(§4): 그룹 전환 즉시 반영·다른 그룹 정보 비노출, 구성원·초대(7일, 재발송·취소·만료)·역할·제거·정지(본인 세션과 미실행 작업만, 영향 범위 사전 표시)·탈퇴·소유권 이전,
user-NN별칭, 사이트별 권한(조회·콘텐츠 편집·배포·도메인/메일·결제/해지·전체 관리), 직접 주소 접근도 거부와 필요 역할 안내. - 대시보드(§5): 운영/구매 사이트 수(예약됨·사용 중 구분), 초안·제작 중, 정상·경고·장애, 관리형·외부, 도메인 조치, 메일 서비스·메일함, 백업 사이트, 진행 작업, 이번 달 예상 비용·결제 대기 금액, 중요도순 주의 항목(조치 화면 링크), 사용자/자동 작업을 구분한 최근 활동.
- 알림 센터·감사 기록(§24): 분류·필수 알림·사건 묶음·조치 링크·읽음 처리, 누가·언제·어디서·무엇을·자동 여부·결과 조회, 권한·소유권·배포·해지·복원·이전·결제 승인은 삭제 불가 기록.
운영자 화면
- HAL 운영자 콘솔 신설(
hal.ariweb.net/ops/, §25): 허용 네트워크(사설망/VPN) 또는 기기 귀속 접근권 뒤의 별도 로그인(MFA), 고객 세션 차단, 접근권 만료·재인증 필요·승인 대기·거부 이유 배너, 메뉴 14개(운영 현황·고객과 사용자 그룹·주문과 결제 승인·상품과 정책·사이트와 배포·메일 서비스·도메인과 인증서·서버와 용량·백업과 복원·서버 이전·배포 릴리스·장애와 알림·감사 기록·플랫폼 복구) + 운영자 계정, 고위험 작업(모듈 되돌리기·서버 삭제 등) 3단계 확인, 1회 표시 값(등록 토큰·긴급 자격·접근권·복구 코드). - 운영 현황(§26.1): HAL·Portal·Webmaker·웹호스팅·AriMail·Source·백업·관측 상태, 등록/비정상 서버, 진행 중 배포·복원·이전, 처리 대기 승인, 용량 경고, 장애·유지보수 — 오래된 상태는
정보 지연과 마지막 확인 시각. - 고객 조회(§26.2): 검색·그룹 상세(구성원·권리·구독·사이트·메일·도메인·백업·주문·사용량·정지·보존·실패 작업), 대신 변경은 허용 항목만 전후 값·사유·승인자를 기록하고 고객 감사 기록에도 남김, 고객 지원용 일시 접근 기록(대상·이유·만료·승인).
- 주문 승인·상품 관리(§27), 서버 목록·등록 7단계·유지보수·격리·90일 지표(§28), 작업 화면의 대상별 결과(§29), 도메인·메일(원문 미노출, 접근 사유)·백업/복원·이전·Source 운영(§30), 릴리스·긴급 복구·재해복구 화면(§32~§34).
- HAL 백엔드(§25·§28·§29): 운영자 계정은 고객 계정과 분리(별도 로그인 화면·세션 종류
operator), 허용 네트워크(사설망/VPN CIDR) 또는 사용자·기기 귀속 단기 접근권 없이는 로그인 화면도 열리지 않음, 고위험 작업은 최근 재인증 + 3단계 확인(영향 범위 → 대상 식별정보 재확인 → 최종 실행과 되돌리기 가능 여부), 접근권 만료·재인증 필요·승인 대기·거부 이유 표시. - 포털 운영자 API(
/internal/ops/*, 서비스 키 서명): 주문 승인(대상·금액·권리 변화·현재 사용량, 반복 승인 안전), 상품 버전 관리(판매된 조건은 새 버전), 사용량·제한, 사이트·배포·도메인·Source·메일(원문 미노출, 진단 사유 기록)·백업/복원 승인(고객·그룹·사이트·도메인·백업 시각 최종 확인)·이전·소유권 이전·생명주기 운영.
사이트 생성과 배포
- 사이트 목록·필터·생성(§7): 이름·용도·Webmaker 프로젝트(새로/기존)·템플릿·언어·시간대·배포 방식 선택, 초안은 사이트 수 미소비, 생성 뒤 다음 단계 안내.
- 사이트 상세(§8) 10개 탭. 관리형 운영 사이트는 실제 수집값(
site.usage작업 →usage_samples)만 표시하고, 미배포 사이트는 준비 단계를, 외부 사이트는 Agent 연결·마지막 배포 결과·외부 확인·고객 책임 범위와제공되지 않음을 표시한다. - 배포 방식(§10): 관리형(설명·모니터링 범위·한도·백업·이전 가능성·예상 시간·승인 비용, 정적 + 승인된 동적 실행 형태 사전 점검) / 외부(지원 대상 = External Pull Agent 를 설치할 수 있는 Linux 서버, 준비 정보, 건별 비용, 미제공 기능, 장애 분리, 버전이 기록되는 동의
external-deploy-2026-10-0.1.1, 외부 메일 배포 미지원). - 배포(§11): 사전 확인(저장본·사이트 수·배포 위치·용량·도메인·필수 설정·작업 충돌, 통과/차단 구분), 10단계 진행 표시, 화면을 닫아도 계속, 완료 상태
배포 검증 완료 / 도메인 설정 필요 / 공개 운영 중, 최초 공개 배포 때 사이트 수예약됨→ 대표 도메인 확인 뒤사용 중, 실패 시 단계·안전한 오류·문의 번호·기존 버전 유지·재시도·되돌리기, 중간 결과지금 정리하고 반환. 배포 방식 변경은 새 위치 준비 → 별도 배포 → 검증 → 도메인 전환 → 이전 위치 정리. - 외부 배포: Agent 설치 안내·일회용 연결·연결 확인·배포·재배포·제거·마지막 외부 확인·실패 잔여물 정리.
호스팅
- Node Agent 가 관리 서버에서 사이트를
/var/www/ariweb-sites/<siteId>/releases/<n>+current로 배치하고 nginx vhost(와일드카드 인증서 재사용 또는 사이트별 certbot, 유지보수 페이지)를 템플릿으로 생성, 내부·외부 확인, 사용량 수집(디스크·DB·업로드, 일/월/누적 인바운드·아웃바운드, 24시간·7일·30일 추이, 오류율·p95). - 서버 등록(§28.2)은 역할·위치 → 일회용 등록 정보(10분, 1회, 재표시 없음) → Agent 설치 → 신원·기능·용량 확인 → 운영자 승인 → 배치 가능. 유지보수(신규 배치 중지 → 영향 확인 → 이전 → 시작/종료 → 재개). 총 보장량·실제 용량을 넘는 배치 금지.
메일
- 메일 서비스 종류·목록·생성 흐름(§15), 메일함 목록·생성·설정(§16, 초기 비밀번호 1회 표시 또는 초대 링크, 정지 메일함도 수·공간 소비 표시, POP3·IMAP 서비스 스위치와 적용 후 접속 확인, 웹메일 SSO 진입, 외부 프로그램 설정값·비밀번호 비표시), 사용량·제한(§17, 월간 송신+수신 합계·최종 수신자 기준·누적·할당·다음 KST 초기화·마지막 계측, 발송 제한 사유·해제 시각·문의, 대용량 첨부 정책 사전 표시), 고객용 메일 백업·복원 미제공 명시(§17.4), 메일 도메인 보안(§18, 정상·권장 개선·오류, 복사 가능한 값).
- 활성화는 결제·승인 권리가 있을 때만, 내부 준비만으로 완료하지 않고 실제 외부 송신·수신·회신 확인 작업을 거친다(운영자가 검증용 수신 주소를 설정하지 않으면
사용자 확인 필요로 정직하게 멈춤).
도메인과 인증서
- 도메인 목록·연결 8단계(§12: 입력 → 웹/메일/둘 다 → 소유 확인(TXT) → DNS 값 안내(복사) → 반영 확인(미반영과 잘못된 값 구분) → 인증서 발급 → 외부 접속 확인 → 대표 지정), 한 사이트 여러 도메인도 사이트 수 하나, 대표 도메인 변경 시 이전 주소 이동 처리 선택, 인증서 발급·갱신·만료 임박·실패 표시, 유효하지 않은 인증서를 정상 HTTPS 로 표시하지 않음, 같은 도메인의 다른 고객 연결 차단.
- 등록 업체·DNS 자동화는 Webmaker 의 기존 도메인 탭(SSO) 에서 수행하고 포털은 상태·확인을 담당한다. 도메인 등록 기능이 없는 환경(검증 Catalog)에서는 외부 등록기관 안내만 표시한다.
상거래
- 상품 목록(§6.1)은 운영자가 활성화한 최신 상품 버전을 표시(가격·한도를 화면에 고정하지 않음, 미확정 정책은
미확정 정책안내). 주문 8단계(§6.2), 0.1.1 기본 결제는 수동 결제 확인(입금 안내·주문 번호·금액·통화·세금·기한 → 운영자 입금 확인 → 승인/거절 → 승인 뒤에만 권리 활성화), 새로 고침·연타 중복 방지(멱등 키), 거절·만료 사유와 재주문, 자동 결제수단 비표시, 갱신 전 청구 예정 안내와갱신 결제 대기, 미납 제한 시점은 정책에서 읽어 고객·운영자 동일 표시, 미납 즉시 삭제 없음. - 결제 화면(§6.3), 상품 축소·해지(§6.4, 범위 안이면 즉시, 아니면 줄여야 할 항목을 구체적으로 표시하고 보류 → 정리 후 재검증), AI Wallet 분리 표시·자동 초과 사용 기본 꺼짐·초과 허용 시 항목별 상한·기간·철회 결과 확인(§6.5).
- 출시 검증 Catalog(
catalog/release-0.1.1.json): basic·team 두 기본 상품(사이트 수·웹 디스크·월간 웹 아웃바운드·메일 저장 공간·월간 메일 통수·메일함), 옵션(독립 메일·Source·백업·디스크/아웃바운드/메일함 추가), 가격·통화·세금·수동 승인·권리·한도·백업 일정·보존·복원 비용·생명주기 결과를 모두 채움. 출시 시험용이며 운영자가 콘솔에서 새 버전으로 관리한다.
백업과 복원
- 관리형 사이트 단위 유료 옵션(§19): 백업 가능한 관리형 사이트에서만 구매, 외부 사이트 미제공, 사이트당 하나, 구매 화면에 저장 한도·사용량·일정·Daily/Weekly/Monthly 보존 개수·첫 백업 예정·원격 사본 검증 방식·추가 저장 옵션(활성 상품정책에서 읽음). 포함: 현재 배포본·DB·업로드·복원에 필요한 설정. 제외: 메일 원문·소스 저장소·Webmaker 이력·외부 배포 데이터·플랫폼 내부 사본(화면에 명시).
- 백업은 운영 서버와 다른 저장 서버에 두고 생성 후 읽기 검증, 반복 실패 알림과 다음 재시도, 저장 공간 부족 시 최소 보존 세트를 지우지 않고 다음 백업 중지·안내, 중지된 사이트의 정기 백업 중지와 재개 방식 표시, 옵션 만료 후 7일 보관 뒤 삭제.
- 복원(§20): 요청 정보(백업 시각·포함 데이터·영향·예상 중단·비용·되돌리기 범위) → 비용·승인·작업 시간 → 9단계(보호 시점·격리 검증·복원·기능 확인·외부 확인·사용자 확인 또는 자동 완료·정리) → 실패 시 유지/되돌림, 보호 시점 유지 기간·마지막 되돌리기 시각·완료 방식 사전 표시.
이전
- 서버 이전(§21): 정적 자동(조건 충족 시)·동적/메일 승인형·고객 요청 유료 구분, 사전 안내(대상·이유·위치·시간·영향·변경 제한·비용·행동), 10단계, 시작 전과 전환 직전 재점검(조건 변화 시 자동 중지·알림), 두 서버 동시 운영 금지, 실패·일시 중지·취소·되돌리는 중·되돌림 완료 구분, 경계 표시(취소 가능 마지막 단계, 쓰기 제한 시각, 목표 중단 시간, 유일 운영 시점, 되돌리기 기간, 이전 위치 보존·삭제, DNS·인증서 대기 처리), 플랫폼 사유 이전 무과금.
- 소유권 이전(§22): 대상/제외 구분(AI Wallet·그룹 미배정 용량·청구 이력 제외, 독립 메일은 명시 선택), 요청 → 수락(받는 쪽 권리 부족 시 보류) → 운영자 심사 → 원자적 완료(일부만 이전된 상태 비노출), 진행 작업 중에는 시작 불가, 만료·취소 시점, 분쟁 동결, 청구 책임 종료·시작 시각.
- 운영 사이트 수 반환(§14) 8단계 점검표와 대표 메일의 독립 전환(주소·메일함·별칭·전달·비밀번호·DNS 유지 항목별 확인, 전환 전후 송수신 확인, 누락 시 반환 거부).
모니터링
- 공통 상태 어휘와
정보 확인 지연, HAL 응답 불가 시 포털은 신규 증가를 막고 기존 조회·삭제·내보내기를 유지(§13.4·§26.2), Agent heartbeat 30초·90초 침묵 시 오프라인, 서버 지표(CPU·메모리·디스크·inode·I/O·네트워크·시계 차이) 90일 보관, 사건(incident) 묶음, 공개 상태 페이지/status(개인정보·내부 위치 비표시). - 운영자 장애 화면(§31.2): 같은 원인 경고를 하나의 사건으로 묶고 고객 영향·최근 배포·담당자·고객 공지·실제 사용자 경로 확인 뒤 종료. 고객 화면(§31.1)은 자신의 서비스에 대한 정상·성능 저하·일부 장애·중단과 영향 기능·시각·대응 상태만 보이며 내부 서버 이름·다른 고객 정보는 노출하지 않는다. 공개 상태 페이지(§31.3)는 로그인 없이 주요 서비스 상태와 계획된 유지보수를 보인다.
HAL/Node Agent 운영
- HAL(§32): 릴리스 등록부(버전·main 커밋·빌드 시각·artifact sha256·검사 결과·승인), 부트스트랩은 최초 설치에서만(
deploy/bootstrap-hal.sh: 승인 릴리스·고정 artifact 선택, 보관소·인증서·접근 경로 준비, HAL 설치, 첫 Agent 등록 토큰) 이후 수동 설치 잠금, 정상 배포 순서(HAL 먼저 → HAL 자체 건강·실행 버전 확인 → Agent 연결 확인 → 모듈 배포 → 모듈별 건강·실제 버전 → 고객 경로 확인 → 완료/되돌리기), HAL 확인 실패 시 나머지 배포 금지, 대상별 성공·실패·대기 결과(일부 실패를 전체 성공으로 표시하지 않음), 목표 버전과 실제 버전 차이·조정(reconcile). - Node Agent: 일회용 토큰 등록 → 키·비밀 1회 발급, HMAC 서명 요청, long-poll 작업 수신, 단계 저장과 재시작 후 재개,
sudo -n범위 최소화(systemctl·nginx·certbot), sudo 불가 시사용자 확인 필요로 보고, External Pull Agent 모드(사이트 배포·제거·확인만, 한 사이트에 귀속). - 긴급 복구(§33): 사건 번호·사유·대상·허용 작업·짧은 유효 시간, 재인증 + 서로 다른 내용의 3단계 승인(한 명도 가능, 단계별 시각 기록), 모든 작업 기록, 자격 1회 표시·정상화 후 만료, 허용 범위(HAL·HAL 저장 정보·비밀정보·인증서 기반·artifact 저장소·최초 Agent 연결) 밖 작업 거부, HAL 정상화 뒤 실제/원하는 상태 비교·조정.
- 플랫폼 재해복구(§34): HAL 운영 정보·진행 작업·로그인·비밀정보·인증서 기반·배치·서버 등록 정보의 복구 사본·외부 보관·검증·훈련 기록, RTO/RPO/주기/보존 설정과 측정값 비교, 복구 시작 별도 승인과 기존 제어 위치 중지 확인, 하나의 HAL 만 운영(임대, 같은 호스트의 죽은 프로세스는 이어받음), 훈련 성공 전 정식 운영 준비 미표시.
실제 환경 배포에서 발견한 결함 수정(2026-10-26, ariweb.net 호스트 첫 배포)
- Agent 실행 계정 모드(
agent/jobs/module.mjs,deploy/module-as-user.sh,deploy/sudoers/ariweb-agent): 도입 모듈 AriMail 은 서버에서 다른 계정(arihakdang)으로 운영되어/srv/arihakdang-mail을 Agent(master)가 쓸 수 없었다. 유닛의User=가 Agent 계정과 다르면 풀기·REVISION·current 전환·정리·DB 백업·마이그레이션을sudo -n -u <계정> bash deploy/module-as-user.sh로 그 계정이 수행한다(sudoers 는 이 스크립트만 root 가 아닌 계정으로 허용, 경로는 모듈 디렉터리 안으로 제한). artifact 는<srv>/agent/work에 받는다. - AriMail 마이그레이션 DB 경로:
server/db-migrate.mjs는DB_FILE환경이 없으면 릴리스 디렉터리 안에 빈 DB 를 만든다. 유닛의EnvironmentFile(/etc/<유닛>/gateway.env)을 실행 계정으로 읽어 실행하도록 고쳤다(직접 모드도 같은 파일을 읽는다). - 도입 모듈 실제 버전: Webmaker(
package.json1.7.3)·AriMail(1.0.11)의 상류 버전이 HAL 의 목표 버전(0.1.1)과 달라version_mismatch로 배포가 실패했을 것이다. 풀 때 적는REVISION.json의 Suite 릴리스 버전을 실제 버전으로 읽고 상류 버전은upstreamVersion으로 함께 보고한다. - 기존 유닛 보존(
hal/modules/index.mjskeepExistingUnit): Webmaker 의 운영 유닛은ProtectSystem=strict·ReadWritePaths등 강화 지시문을 갖는데 템플릿이 Environment 줄만 보존하고 나머지를 지웠을 것이다. 유닛이 이미<appDir>/current를 가리키면 다시 쓰지 않는다. - 포털 nginx 접근 경로(
deploy/bootstrap-hal.sh):deploy/nginx/ariweb-portal.conf.tmpl이 어디서도 적용되지 않아my.ariweb.net이 열리지 않았다. 부트스트랩 4단계(접근 경로 준비)에서 HAL 과 함께 설치한다. - 부트스트랩 인증서 확인(
deploy/bootstrap-hal.sh):/etc/letsencrypt/live는 root 만 읽을 수 있어 일반 계정의[ -f ]가 항상 거짓이었다(실제 부트스트랩이 4단계에서 중단).sudo -n test -f로 확인한다. - 포털 artifact 에 Catalog 누락(
scripts/release.mjs): 상품정책 시드 파일catalog/release-0.1.1.json이 portal artifact 에 들어가지 않아 운영자 시드(/internal/ops/commerce/catalog/seed)가 실서버에서 실패했을 것이다. portal 경로에catalog를 넣었다. - artifact 디렉터리 모드(
scripts/release.mjs): 개발 PC 의 umask(077)가 artifact 에 0700 디렉터리로 들어가 서버에서 다른 실행 계정이 Agent 의deploy/module-as-user.sh와 풀린 릴리스를 읽지 못했다.tar --mode=u=rwX,go=rX --owner=0 --group=0로 정규화한다.deploy/agent-install.sh도 호출자의 umask 와 무관하게 코드 트리를a+rX로 맞춘다. - 실행 계정 모드 보완(
agent/jobs/module.mjs): 내려받은 artifact(0600)를 실행 계정이 읽도록 0644 로 바꾸고, 도우미 스크립트는 sudoers 규칙과 같은<srv>/agent/current/deploy/module-as-user.sh경로로 부른다(ESM 의import.meta.url은releases/<ver>실제 경로로 풀린다). - 같은 버전 재설치(
agent/jobs/module.mjs,module-as-user.sh rename): 실행 중인 current 디렉터리를 지우고 다시 풀던 것을, 임시 디렉터리에 풀고 기존 디렉터리를<ver>.prev-<시각>으로 보존(되돌리기 대상)한 뒤 제자리에 두도록 바꿨다. DB 백업 파일 이름은 같은 초에 겹치지 않게 한다. - Webmaker 데이터 백업(
agent/jobs/module.mjs): 운영 데이터 디렉터리의 읽을 수 없는 파일(root 가 남긴 마이그레이션 기록)이 tar 를 실패시켜 배포가 막혔다.--ignore-failed-read로 경고만 남기고(결과unreadable목록) 진행한다. ariweb.net 호스트의 해당 파일(/data/webmaker/migrations)은 소유권을 master 로 바로잡았다. - 부트스트랩 릴리스 기록(
hal/core.mjs setBootstrap): HAL 기동 전 CLIbootstrap-release가 쓴 UPDATE 가 행(id=1)이 없어 무효였다(건강 응답의releaseVersion이 null). 행을 먼저 만든다(test/hal.test.mjs회귀 테스트). - AriMail 마이그레이션의 EnvironmentFile 읽기(
deploy/module-as-user.sh migrate): systemd 문법(MAIL_FROM_NAME=Ari Hak dang처럼 따옴표 없는 공백 값)을 bash 로 소스하다 실패했다. 줄 단위로 읽어 따옴표를 벗기고 export 한다. - 사후 절차 실패 시 되돌리기(
agent/jobs/module.mjs): current 전환 뒤 마이그레이션이 실패하면 current 가 새 릴리스를 가리킨 채 남았다(다음 재시작 때 마이그레이션 없는 새 코드가 뜬다). 실패하면 이전 릴리스로 되돌리고 재시작한다. - AriMail 전체 설정 API 주소(
portal/mail/arimail.mjs주석·운영 안내): AriMail 은 설정 API(/api/setup/*)를 기본 도메인 호스트(req.headers.host, 예:mail.arihakdang.com)에서만 응답하고 다른 테넌트 호스트로는 404 를 준다. Node fetch 는 Host 헤더를 바꿀 수 없으므로(시도했다가 되돌림) 포털의 설정 주소(setup_url)는 기본 도메인 이름이어야 하고, 그 이름이 설정 포트(loopback 전용 8791)로 풀려야 한다 — ariweb.net 호스트에서는/etc/hosts에127.0.0.1 mail.arihakdang.com을 두었다(알려진 제한에 기록). - Agent heartbeat 의 모듈 버전(
agent/index.mjs):current/package.json버전(상류 1.7.3·1.0.11)을 보고해 HAL 이 배포 직후 "목표와 다름(drift)" 으로 표시했다.REVISION.json의 Suite 버전을 먼저 읽는다. - 새 유닛 enable(
agent/jobs/module.mjs): 템플릿으로 만든 유닛(포털)을 enable 하지 않아 재부팅 뒤 뜨지 않았을 것이다. 유닛을 쓸 때systemctl enable한다. - 서버 한 대 운영의 역할 대체(
hal/core.mjs pickServer,portal/backup/service.mjs pickStoreServer): 첫 서버는 역할이control하나뿐이라 사이트 배포(web)·백업 저장(backup) 대상 서버를 고를 수 없었다. 요청 역할의 서버가 없으면 control 서버를 web·backup 으로도 쓴다(전용 서버가 생기면 우선). 같은 서버에 백업을 두는 것은 여전히 운영자 override 가 필요하다. - 초안 사이트의 백업 중지 사유(
portal/backup/service.mjs): 미배포(초안) 사이트의 옵션을 정기 실행이site_cancelled(해지) 로 중지해 표시가 틀렸다.site_not_deployed(사이트 미배포) 로 구분한다. - 회귀 테스트:
test/agent.test.mjs에 실행 계정 모드(도우미 스크립트 실제 실행·경로 제한 거부)·REVISION 버전·기존 유닛 보존 검사를 추가했다.
HAL 전용 호스트 부트스트랩(2026-10-27, bear hal21.ariweb.net)
deploy/bootstrap-hal.sh --portal-host none: 포털을 다른 호스트에서 운영하는 HAL 전용 호스트에는my.ariweb.netnginx 접근 경로를 만들지 않는다(이전에는 항상 만들어 포털이 없는 호스트에도 죽은 vhost 가 남았다). 기본값(my.ariweb.net)의 동작은 그대로다.- bear(Ubuntu 24.04, nginx 1.24, Node 24.22, certbot nginx 플러그인 운영)에 HAL 만 최초 부트스트랩: 인증서는 호스트별 certbot(webroot
/var/www/html, ECDSA, 갱신 훅systemctl reload nginx) —--cert /etc/letsencrypt/live/hal21.ariweb.net --acme /var/www/html. 기존 Webmaker·Gitea·Matrix·기본 서버는 건드리지 않았다. 절차·검증·되돌리기는docs/RUNBOOK-0.1.1.md§7. 배포 artifact: mainab75b7d, hal sha256f574d875…(deploy/ 변경으로 ariweb.net 호스트의e7741899…와 다름). 알려진 사소한 결함: 부트스트랩 5단계의 HTTPS 자체 확인이 nginx reload 직후 실행되어 거짓 경고가 날 수 있다(결과에 영향 없음). - HAL 기본 주소 안내(
hal/app.mjs):https://hal21.ariweb.net/접속이 JSON 404를 반환하던 동작을 운영자 콘솔 표준 경로/ops/로 이동하도록 수정했다./ops의 기존 사설망/VPN·기기 접근권 관문과 별도 운영자 로그인은 그대로 적용된다. - 기존 사이트 인벤토리(
agent/webmaker-inventory.mjs, HAL 서버 상세): Node Agent가 사이트 디렉터리와 Webmaker 메타데이터·배포 이력을 대조해 모든 디렉터리의 ID·제목·계정 식별자·배포 상태(deployed/unpublished/draft/unknown)·서비스 상태·도메인·크기·파일 수를 HAL에 연결한다. 메타데이터가 없는 디렉터리도missing으로 남기며 총 수·수집 수·잘림 여부·스캔 오류를 함께 기록한다. 2 MiB heartbeat 제한과 1,000항목 제한을 넘는 인벤토리는 512 KiB 이하 인증 배치로 전송하고 마지막 배치에서만 원자 반영한다. 페이지 본문·프롬프트·연락처·인증정보는 전송하지 않는다.
운영 Webmaker ↔ HAL 연동 점검 도구(2026-10-27, webmaker.ariweb.net)
scripts/webmaker-inventory.mjs(+test/webmaker-inventory.test.mjs5건): 운영 Webmaker 데이터(JSON)·포털 DB·HAL DB 를 읽기 전용으로 안정 식별자(프로젝트 32-hex id, 포털s_/g_/u_, HALsrv_)로 대조해 연결 등록부 일치·충돌·중복·누락, SSO 회원(aw-<g8>-<u8>) 신원, 도메인, Giteasites/<id>/mainref, HAL 모듈 등록·버전·건강을 판정한다. 비밀로 보이는 키는 출력 전에 거부(assertNoSecrets). 충돌·누락이 있으면 종료 코드 2.- 2026-10-27 hostinger 실데이터 결과: 프로젝트 10(포털 연결 일치 3·Webmaker 고유 7), 회원 12(SSO 3 일치·고유 9), 도메인 2, Gitea refs 13, HAL
module_targets.webmaker0.1.1 health ok — 충돌 0·누락 0, 적용 변경 없음(교체도 불필요). 사전 스냅샷·복원 검증·사후 readback 은docs/verification/webmaker-hal-link-2026-10-27.md, 절차는 RUNBOOK §8. - HAL21 제어 평면 전환 완료: bear HAL을 main
12958ab로 갱신한 뒤 hostinger Node Agent를hal21.ariweb.net의 기존 서버srv_01a17bd4dbcdb55877f2d9fcab에 재등록·승인했다. Webmaker unit·데이터는 변경하거나 재시작하지 않았다. 명시적DATA_ROOT·DATA_DIR·SITES_ROOT로 수집한 HAL 인벤토리는 프로젝트 10/10, 절단 없음, 오류 0이며module_targets.webmaker는 0.1.1/health ok다. 사전 체크섬 32,608건 불일치 0, Git 7개 fsck 성공, Gitea ref 13개 동일. 결과와 롤백은docs/verification/webmaker-hal21-link-2026-10-27.md.
기존 메일 서비스(AriMail 인스턴스) ↔ HAL 관리형 MailService/Node 가져오기(2026-10-27, mail.arihakdang.com · webmail.ibit.kr → bear hal21.ariweb.net)
hal/mailsvc.mjs(+ 마이그레이션hal_0003_mail_services,test/hal-mailimport.test.mjs7건): HAL 관리형 메일 서비스 등록부(mail_services, 도메인 단위 논리 리소스, 불변 idmls_…, 출처 권한arimail:<hostname>:<unit>, 도메인별 active owner 정확히 하나 — 부분 유일 인덱스)와 가져오기 단위(mail_imports). 읽기 전용 discovery 문서만 입력으로 받고 비밀값이 어디에든 있으면 거부(비밀은secretRefs의 file:/dir:/vault:/env: 참조만 저장). dry-run 이 기본, 충돌(collision: 다른 서버/인스턴스가 이미 active)·중복·누락이 하나라도 있으면 아무것도 적용하지 않음, 적용은 한 트랜잭션 + 감사(immutable), 같은 출처·서버의 재적용은 update(멱등), 적용 단위 되돌리기(생성 행 삭제·갱신 행 복원). 소유 조직은 증거로 확인되기 전까지unassigned_legacy_managed. Agent heartbeat 의 arimail 모듈 상태가 그 서버의 행에last_sync·health로 비친다(core.heartbeat). Data Plane(AriMail 송수신·계정·메일·DNS/MX·TLS·provider)은 바꾸지 않는다 — HAL 이 멈춰도 메일은 그대로다.scripts/mail-discover.mjs(+test/mail-discover.test.mjs2건): 원본 호스트에서 단독 실행하는 읽기 전용 discovery — 유닛 상태(systemctl show)·current/REVISION·package.json·AriMail 도메인 등록부(data/domains.json)·건강 API·haraka-status·게이트웨이/테넌트 DB 집계(계정·메일 수·크기·무결성, 주소·내용 미열람)·유닛 PID 소유 리스너·nginx vhost·TLS 주체/만료·공개 DNS(1.1.1.1/8.8.8.8, 호스트 /etc/hosts 오염 방지)·certbot 훅. env/config 는 키 이름과 경로·권한만.- HAL CLI(
hal/index.mjs):mail-import --discovery <json> (--server <id> | --create-server --name …) [--apply],mail-import-rollback --import <id>,mail-services(readback JSON),server-token --server <id>(운영자 콘솔이 닿지 않는 부트스트랩·연동 단계의 시스템 신원 토큰 발급, 감사cli:<by>). 운영자 API:GET /ops/api/mail-services(+readback: 총수·active·도메인 중복·서버별·비밀 누출 검사),GET /ops/api/mail-services/:id(감사 포함),GET /ops/api/mail-services/imports,POST /ops/api/mail-services/import {discovery, serverId, apply}(적용은 재인증),POST /ops/api/mail-services/imports/:id/rollback(재인증). 운영자 화면/ops/mail: HAL 관리형 메일 서비스 표(도메인·공개 주소·서버/인스턴스·리비전·계정/메일함/메일·건강/동기화·TLS/DNS·비밀 참조 수)를 먼저 그리고 포털 메일 서비스는 그 아래#portal-mail에 중계로 채운다(ops.js). - 2026-10-27 실제 적용: bear HAL 을 커밋
79b45ca릴리스로 교체(재시작 2.6초, 메일 서비스 프로브 무중단) → 서버 2(pending) · MailService 7(hostinger 인스턴스 6 도메인 + jitsi 1, 모두unassigned_legacy_managed, duplicates 0, secretLeaks 0) · jitsi Node Agent 등록(heartbeat →last_sync_source=agent, 승인은 운영자 결정) · hostinger 는 기존 HAL(hal.ariweb.net) 제어 평면과의 이중 제어를 피해 Agent 미설치(관측 전용). 원본 메일 서비스 변경 0. 결과·백업 sha256·readback 은docs/verification/mail-hal-link-2026-10-27.md, 절차·되돌리기는docs/RUNBOOK-0.1.1.md§9.
포털·hostinger Node Agent 제어 평면 이전 — hal.ariweb.net → hal21.ariweb.net (2026-10-27, Hera)
- HAL CLI(시스템 신원, 감사 actor
cli:<by>·reason) 보완:config-set(운영 값만 data/config.json 에 기록, 비밀 키 거부, 서비스 재시작 뒤 반영) ·module-deploy(운영자 APIPOST /ops/api/modules/:name/deploy와 같은 멱등 키 규칙으로module.deploy작업을 큐에 넣고, 실행은 serve 프로세스의 작업 엔진이 맡는다) ·job --id(작업·자식 readback). 운영자 콘솔 자격 증명이 없는 연동 단계용. 테스트test/hal-cli.test.mjs3건(별도 프로세스 실행·멱등·감사·서비스 측 엔진이 집어 실행). - 결과(docs/verification/portal-hal21-link-2026-10-27.md): bear HAL21 을 main
dfb708e릴리스(artifact 4e419a72…)로 교체·등록·승인,portalUrl=https://my.ariweb.net;link-portal값을 파이프로만 포털 vaultservice:hal에 넣고(구service:portal제거) 포털halUrl=https://hal21.ariweb.net; HAL21 작업j_01a17cb5716c669450412967f7로 portal 0.1.1 재배포(이전 릴리스.prev-20261027093350보존, health ok). 포털→HAL21 인증 읽기(health·servers·jobs 계약) 실측, 포털 DB 집계·Webmaker 인벤토리·AriMail 전후 동일, 외부 URL/TLS 전부 정상. 구 HAL 은 server offline 상태로 잔존(중지는 사람 결정). blocker: hostinger 서버 행 rolemail→pickServer(web)null(운영자가 control 로 변경 필요).
운영자 콘솔 부트스트랩 배너 결함 수정 — 완료 뒤에도 "부트스트랩 미완료" 경고(2026-10-27, bear hal21.ariweb.net, Hera)
- 증상: bear HAL21
/api/health는bootstrap.complete=true · halRunningVersion=0.1.1 · firstAgent 있음 · manualInstallLocked=true인데, 운영자 콘솔 모든 화면 상단에! 부트스트랩 미완료(HAL 실행 버전 확인 전, 첫 Agent 미등록)…경고가 계속 보이고, 배포 릴리스 화면도HAL 실행 버전: 확인 전 · 부트스트랩 미완료로 그려졌다. - 원인:
hal/ui.mjs의 공통 틀(shell) 배너가core.selfHealth(d).bootstrap의 실제 계약(camelCasecomplete·halRunningVersion·firstAgent)이 아니라 존재하지 않는 snake_case 열 이름(completed_at·hal_running_version·first_agent_id)을 읽어 항상undefined→ 늘 "미완료·확인 전·미등록"으로 렌더링. 같은 파일의/ops/releases라우트는releasesView가 bootstrap 행(snake_casehal_running_version·manual_install_locked)을 기대하는데 camelCaseselfHealth().bootstrap을 넘겨 같은 증상. 서버 렌더(HTML) 결함이며ops.js/ops.css는 관여하지 않는다(브라우저 캐시 무관). - 수정(
hal/ui.mjs만, 2곳): 배너는health.bootstrap.complete/halRunningVersion/firstAgent를 쓰고,/ops/releases는running: core.bootstrapState(d)를 넘긴다. 옛 snake_case fallback 은 두지 않았다(단일 계약). - 검증(strict TDD):
test/hal-ui.test.mjs에 회귀 1건 추가 — selfHealth.bootstrap 에 snake_case 필드가 없음을 고정하고, (1) 초기확인 전·미등록(2)claimLive뒤0.1.1·미등록(3) 첫 Agent 등록·승인(완료) 뒤 운영 현황·서버·릴리스 화면에 경고 없음 + 릴리스 화면HAL 실행 버전: 0.1.1 · 수동 설치 잠김(4) Agent 만 승인된 새 HAL 은확인 전·등록됨. RED: 수정 전 (2)에서확인 전으로 실패(완료 상태에서도 경고·릴리스 화면확인 전잔존을 별도 스크립트로 기록). GREEN: 파일 7건·전체 22파일 150건 통과·실패 0. 배포 결과는docs/verification/hal21-ops-banner-fix-2026-10-27.md. - 배포 결과(bear hal21, 2026-10-27 14:36Z): 커밋
648247f릴리스(hal artifacted957a0a…)를/srv/ariweb/hal/releases/0.1.1-648247f22415에 stage → 추출된hal/ui.mjssha·node --check·snake_case 참조 0 확인 → register-release(approved, by hera-ops-banner-fix)·bootstrap-release → current 전환·재시작(health 까지 1.08초, NRestarts 0). data/config(opsAllowCidrs 5항목)/vault/DB sha·크기 전후 동일, 사설 백업/srv/ariweb/backups/hal-pre-banner-fix-20261027T143608Z. readback:/api/healthbootstrap complete·mainCommit648247f, Agent 2대 heartbeat 재개, 배포된 코드만 import 한 in-memory 서버 렌더에서 완료 상태 배너 없음·릴리스 화면HAL 실행 버전: 0.1.1 · 수동 설치 잠김; 포털·Webmaker·메일 회귀 이상 없음. 되돌리기: current 를0.1.1-dfb708e933e5로 되돌리고 재시작.
도입 소스
- 가져오기:
node scripts/import-sources.mjs가 고정 커밋(Webmaker3b10859884ef2fd0f2af11934ab6c16370fd64b8, AriMaila07f69f1ffb6667de6e66b3d1ec43c1c66fab924)을 검사하고 실행에 필요 없는 문서·보고서·샘플을 제외한 전체를modules/webmaker(176 파일)·modules/arimail(88 파일)로 복사, 파일별 원본 경로·대상 경로·수정 여부·원본 해시·라이선스 검토를IMPORTED_SOURCES.md/.json에 기록한다. - 라이선스 검토: 두 저장소 모두
private: true, 별도 LICENSE 없음 — OZ-Group 내부 전용 소스로 같은 조직의 Ariweb Suite 에서 재사용 가능, 외부 배포 불가. AriMailharaka/는 npm Haraka(MIT) 를 설치 시점에 내려받는 래퍼만 포함. - 수정된 파일(원본 대비): Webmaker 8개(
lib/auth.js lib/config.js lib/store.js lib/vault.js public/app.js public/index.html public/style.css server.js+ 신규lib/ariweb.js,test/ariweb.test.js), AriMail 15개(deploy/deploy.sh haraka/plugins/arimail.js package.json server/api.mjs server/config.mjs server/domains.mjs server/limits.mjs server/main.mjs server/providers/index.mjs server/smtp.mjs server/usage.mjs test/gateway.test.mjs test/smtp.test.mjs test/usage.test.mjs web/setup.js+ 신규server/sendqueue.mjs,migrations/0009_ariweb_queue.sql,test/ariweb.test.mjs). 이유는 위 "수정한 기존 기능" 과 각 파일의[Ariweb Suite 변경]주석. - 참조 저장소는 직접 배포하지 않는다. HAL 이
modules/*에서 만든 artifact(webmaker-0.1.1.tar.gz,arimail-0.1.1.tar.gz)만 배포한다.
알려진 제한
- 로컬 E2E 검증(
docs/verification/local-e2e-2026-10-26.md, 실제 HAL·포털·Node Agent 4대 + 가짜 Webmaker·AriMail·메일): 34개 완료 조건 중 19개 로컬 확인, 14개 부분 확인, 1개(외부 메일 왕복) 로컬 확인 불가, 결함 0. 부분 확인 항목은 실제 DNS·공인 서버·실 AriMail 왕복·시간 경과(30일 보존)가 필요한 부분이며, 실제 환경 확인 결과는docs/verification/에 별도 기록한다. - ariweb.net 호스트의
/etc/hosts에127.0.0.1 hal.ariweb.net my.ariweb.net webmail.ariweb.net mail.arihakdang.com이 있다: 앞의 둘은 공개 DNS 생성 전 같은 서버 안의 Agent·포털이 HAL 에 닿기 위한 임시 항목이고,mail.arihakdang.com은 AriMail 전체 설정 포트(8791, loopback 전용)에 기본 도메인 이름으로 접속하기 위한 항목이다(cloud-init 이/etc/hosts를 관리하므로 재부팅 뒤 확인 필요). - 실제 환경 검증(
docs/verification/real-env-0.1.1.md, ariweb.net 호스트, 2026-10-26): 34개 중 실제 확인 7(가입·확인 메일, 그룹·초대, 주문·승인·권리, 초안 사이트, 독립 메일 서비스, HAL 우선 배포·HAL 작업 배포, HAL 재시작 유지), 부분 확인 7(Webmaker 진입, 관리형 배포 요청, 사이트 화면, 대표 메일·메일함, 백업 옵션, 모듈 갱신, 감사 일치), 미수행 20(공개 DNS·실 도메인·수신 주소·다중 서버·시간 경과 필요), 결함 0. 출시 완료는 선언하지 않는다. - DNS:
ariweb.net은 hosting.co.kr 네임서버(웹 패널)라hal.ariweb.net·my.ariweb.netA 레코드는 운영자가 패널에서 만들어야 한다. 레코드가 생기기 전에는 로컬--resolve로만 확인된다. - 실제 외부 메일 왕복 검증은 운영자가 검증용 수신 주소(자동 응답 메일함)를 설정해야 완료된다.
- AI Wallet 은 Webmaker credits 를 읽어 표시만 하며, 초과 사용 과금 라인 생성은 하지 않는다(표시·제한 해제만).
- 메일 서비스 서버 이전은 AriMail
operating상태 전환 도우미(setMigrating) 가 포털에 없어설정 필요로 안내된다. - 별칭·전달 전용 주소 생성, 대량 .eml 내보내기(IMAP/POP3·원문 다운로드 안내로 대체), Gitea 브랜치 자동 삭제(상태만), 외부 메일 배포, 자동 결제수단은 이번 버전에서 제공하지 않는다.
- 실제 환경(ariweb.net 호스트)은 서버 한 대(역할 control)로 운영된다. 관리형 사이트 배포·백업 저장은 control 서버를 대체 사용하고, 백업을 같은 서버에 두려면 운영자 override 가 필요하다. 서버 간 이전·외부 Agent·다른 호스트 재해복구는 서버가 늘어난 뒤 실환경에서 확인한다.
- Webmaker 의 페이지 제작(AI 생성·저장)은 사람이 제작 화면에서 수행해야 하므로 실환경 검증에서 저장본 없는 초안 사이트까지만 자동 확인했다(관리형 배포·백업 실행·복원·외부 확인은 저장본이 생긴 뒤 수행).
- Agent 의 사용량 수집은 Agent 가 배포한 사이트의 nginx
awstats로그만 집계한다(Webmaker 자체 호스팅 사이트는 Webmaker 통계 유지).
미확정 정책값
- 활성 상품정책에
{status: 'deferred'}로 표시되어 화면에미확정 정책으로 안내되는 값:alias_counting(별칭·전달 전용 주소 계산 방식 — 확정 전 무제한 제공 금지, 메일함 수에 포함),large_attachment(대용량 첨부 허용 크기·다운로드 횟수·보관 기한 — 시스템 안전 상한 우선),ai_wallet_pricing(AI 크레딧 단가 — Webmaker 관리자 설정을 따름). - 운영자가 후속 상품정책에서 정할 값: 도메인 등록·갱신 가격(등록 업체 연동 시), 외부 배포 건별 비용의 최종 금액, 이전 목표 중단 시간·되돌리기 기간·이전 위치 보존 기간·고객 요청 이전 비용의 운영 값, 복원 비용·중단 시간, 보존 기간 재정의(기본 30/30/30/7일), 초과 사용 단가. 검증 Catalog 의 수치는 모두 출시 시험용이다.