From d1eefaa15f9188c2395b385168b965a319650f6f Mon Sep 17 00:00:00 2001 From: zzabisagent-lab Date: Mon, 26 Oct 2026 14:35:04 +0000 Subject: [PATCH] =?UTF-8?q?=EC=8B=A4=EC=A0=9C=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=C2=B7=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?(0.1.1):=20ariweb.net=20=ED=98=B8=EC=8A=A4=ED=8A=B8=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=99=84=EB=A3=8C,=2034=EA=B0=9C=20=ED=9D=90?= =?UTF-8?q?=EB=A6=84=20=EC=8B=A4=ED=99=98=EA=B2=BD=20=ED=8C=90=EC=A0=95(?= =?UTF-8?q?=EC=8B=A4=EC=A0=9C=207=C2=B7=EB=B6=80=EB=B6=84=207=C2=B7?= =?UTF-8?q?=EB=AF=B8=EC=88=98=ED=96=89=2020),=20RUNBOOK=20=EC=9A=B4?= =?UTF-8?q?=EC=98=81=20=EC=82=AC=EC=8B=A4,=20=EB=A1=9C=EC=BB=AC=20E2E=20?= =?UTF-8?q?=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/verification/real-env-0.1.1.md/.json: 배포 경과·결함 수정 요약, 흐름별 단계·증거, 재검증(systemd/nginx/HAL·포털 건강/배포 영수증/모듈 버전) 기록 - docs/RUNBOOK-0.1.1.md: 서버 한 대 운영, 메일 서버 등록 값, 발신 메일, 같은 릴리스 재등록 절차 - VERSION_HISTORY.md 알려진 제한에 실환경 검증 결과 기입. 출시 완료는 선언하지 않음 Co-Authored-By: Claude Fable 5.1 --- VERSION_HISTORY.md | 1 + docs/RUNBOOK-0.1.1.md | 7 + docs/verification/local-e2e-2026-10-26.json | 214 ++-- docs/verification/local-e2e-2026-10-26.md | 144 +-- docs/verification/real-env-0.1.1.json | 1082 +++++++++++++++++++ docs/verification/real-env-0.1.1.md | 817 +++++++++++++- 6 files changed, 2075 insertions(+), 190 deletions(-) create mode 100644 docs/verification/real-env-0.1.1.json diff --git a/VERSION_HISTORY.md b/VERSION_HISTORY.md index 3da4e0e..5feb96d 100644 --- a/VERSION_HISTORY.md +++ b/VERSION_HISTORY.md @@ -122,6 +122,7 @@ ### 알려진 제한 - 로컬 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.net` A 레코드는 운영자가 패널에서 만들어야 한다. 레코드가 생기기 전에는 로컬 `--resolve` 로만 확인된다. - 실제 외부 메일 왕복 검증은 운영자가 검증용 수신 주소(자동 응답 메일함)를 설정해야 완료된다. - AI Wallet 은 Webmaker credits 를 읽어 표시만 하며, 초과 사용 과금 라인 생성은 하지 않는다(표시·제한 해제만). diff --git a/docs/RUNBOOK-0.1.1.md b/docs/RUNBOOK-0.1.1.md index 0cc8c89..261189b 100644 --- a/docs/RUNBOOK-0.1.1.md +++ b/docs/RUNBOOK-0.1.1.md @@ -41,6 +41,13 @@ curl -fsSL https://hal.ariweb.net/agent/v1/install.sh | bash -s -- --hal https:/ - arimail: 기존 `/srv/arihakdang-mail` 유지(실행 계정 `arihakdang` — Agent 가 `deploy/module-as-user.sh` 로 그 계정이 파일 작업을 하게 한다), 마이그레이션 `0009_ariweb_queue.sql` 적용(유닛의 `EnvironmentFile` 의 `DB_FILE`). 포털 › 운영자 › 메일 서버 등록(`POST /internal/ops/mail/servers`)에 setup-token(`/data/setup-token`)·usage-key(기본 도메인 설정의 `USAGE_API_KEY`)를 넣고, 고객 도메인의 `ARIWEB_SSO_SECRET` 은 포털이 서비스 생성 때 스스로 넣는다. - 도입 모듈의 실제 버전은 `REVISION.json` 의 Suite 버전(0.1.1)이고 상류 버전(1.7.3·1.0.11)은 `upstreamVersion` 으로 함께 보인다. +## 4-1. 실제 배포(2026-10-26)에서 확인한 운영 사실 +- 서버가 한 대(역할 control)면 사이트 배포·백업 저장은 control 서버가 대체한다(`pickServer`). 백업을 같은 서버에 두려면 운영자 override(`/internal/ops/backup/options/:id/override-same-server`)가 필요하다. +- 포털 메일 서버 등록(`POST /ops/api/portal/mail/servers`): `baseUrl https://webmail.ariweb.net`, `internalUrl http://127.0.0.1:8790`, `setupUrl https://mail.arihakdang.com:8791`(AriMail 전체 설정 API 는 기본 도메인 호스트에서만 응답하므로 기본 도메인 이름을 쓰고, 설정 포트가 loopback 전용이라 서버 `/etc/hosts` 에 `127.0.0.1 mail.arihakdang.com` 을 둔다), 비밀은 `setup-token`(`/data/setup-token`)·`usage-key`(없으면 `POST /api/setup/usage-key {action:'generate'}` 로 발급, 기본 도메인 Host 로). +- 발신 메일: 포털 보관소 `mailer:resend-key`(Webmaker 보관소의 Resend 키 재사용), 설정 `mailer: resend`, `mailFrom no-reply@ariweb.net`. 검증 때는 `resendBase` 를 로컬 포획 서버로 돌려 링크를 꺼냈고, 끝난 뒤 되돌렸다. +- 수정 artifact 를 같은 릴리스(0.1.1)로 다시 내려면 `node hal/index.mjs register-release --manifest --approve` 로 재등록(기존 버전은 artifact·커밋이 갱신된다)한 뒤 운영자 콘솔에서 `agent` → `hal` → 바뀐 모듈 순으로 배포한다. `bootstrap-release --commit` 으로 건강 응답의 main 커밋도 맞춘다. +- 검증 데이터(`*@check.ariweb.net` 계정, `check-*/solo-*.ariweb.net` 도메인·메일 서비스)는 운영자 취소로 정리했고 보존 기간 뒤 삭제된다. + ## 5. 검증(Release 0.1.1 완료 조건 34개) `docs/verification/` 의 로컬 E2E 결과와 실제 환경 확인 기록을 함께 둔다. 각 흐름은 버튼 성공이 아니라 최종 화면·외부 접속·실제 메일 왕복·실제 사용량·실제 배포 버전으로 판정한다. 충족하지 못한 흐름은 완료로 표시하지 않는다. diff --git a/docs/verification/local-e2e-2026-10-26.json b/docs/verification/local-e2e-2026-10-26.json index 51c96ca..89d80aa 100644 --- a/docs/verification/local-e2e-2026-10-26.json +++ b/docs/verification/local-e2e-2026-10-26.json @@ -1,14 +1,14 @@ { - "generatedAt": "2026-10-26T14:22:13.542Z", - "startedAt": "2026-10-26T14:19:54.258Z", + "generatedAt": "2026-10-26T14:29:49.315Z", + "startedAt": "2026-10-26T14:27:30.390Z", "env": { "agents": 4, "ports": { - "hal": 42095, - "portal": 46233, - "wm": 34303, - "am": 41929, - "mail": 36337 + "hal": 40961, + "portal": 39713, + "wm": 37209, + "am": 46099, + "mail": 45135 }, "mailServerRegistered": true, "node": "v24.22.1" @@ -99,12 +99,12 @@ { "name": "팀 그룹 만들기 → 현재 그룹 전환", "ok": true, - "detail": "g_01a17895158846031051e25505" + "detail": "g_01a1789c0ba4cc346b9a816d13" }, { "name": "이메일 초대(7일) → 초대 메일 링크", "ok": true, - "detail": "akfdWdWvppAkoWrVUJnYkIdG2WcBGy8L" + "detail": "92k9hZNmEVZ5s11c5YD_AKFXC9VLtCw2" }, { "name": "초대 수락(1회용) → 별칭 user-02 · 역할 member", @@ -162,7 +162,7 @@ "detail": { "count": 2, "team": { - "id": "pv_01a17895142780400bdd2c40c1", + "id": "pv_01a1789c0a26a92faf63434d5a", "price": 49000, "sites": 3 }, @@ -179,7 +179,7 @@ "orderNo": "AW-20261026-0001", "status": "awaiting_payment", "total": 63800, - "dueAt": "2026-11-02T14:19:58.848Z", + "dueAt": "2026-11-02T14:27:35.064Z", "hasInstructions": true } }, @@ -221,7 +221,7 @@ "detail": { "sites": 3, "subs": 1, - "nextBilling": "2026-11-26T14:19:58.916Z" + "nextBilling": "2026-11-26T14:27:35.130Z" } } ], @@ -233,7 +233,7 @@ "bill": { "sites": 3, "subs": 1, - "nextBilling": "2026-11-26T14:19:58.916Z" + "nextBilling": "2026-11-26T14:27:35.130Z" } } }, @@ -248,7 +248,7 @@ "name": "사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내", "ok": true, "detail": { - "id": "s_01a1789516843da274b3515e56", + "id": "s_01a1789c0c9a313add86e28440", "lifecycle": "draft", "projectId": "00000000000000000000000000000001", "nextSteps": [ @@ -283,7 +283,7 @@ "name": "제작 화면 열기: 포털 SSO 티켓 → Webmaker /api/auth/sso 302(1회용)", "ok": true, "detail": { - "portalRedirect": "http://127.0.0.1:34303/api/auth/sso?ticket=…", + "portalRedirect": "http://127.0.0.1:37209/api/auth/sso?ticket=…", "webmaker": 302, "reuse": 401 } @@ -299,7 +299,7 @@ ], "evidence": { "sso": { - "portalRedirect": "http://127.0.0.1:34303/api/auth/sso?ticket=…", + "portalRedirect": "http://127.0.0.1:37209/api/auth/sso?ticket=…", "webmaker": 302, "reuse": 401 }, @@ -320,7 +320,7 @@ "name": "배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나", "ok": true, "detail": { - "jobId": "j_01a1789516d0014fae08329a93", + "jobId": "j_01a1789c0ce4dcffc59011e15d", "steps": 10, "again": false, "siteCount": "reserved", @@ -351,7 +351,7 @@ "ok": true, "detail": { "lifecycle": "needs_domain", - "version": "20261026142000-rev-c1-000000", + "version": "20261026142736-rev-c1-000000", "verify": "needs_domain", "health": "ok", "agentHasSite": true @@ -360,7 +360,7 @@ ], "evidence": { "d": { - "jobId": "j_01a1789516d0014fae08329a93", + "jobId": "j_01a1789c0ce4dcffc59011e15d", "steps": 10, "again": false, "siteCount": "reserved", @@ -383,7 +383,7 @@ }, "after": { "lifecycle": "needs_domain", - "version": "20261026142000-rev-c1-000000", + "version": "20261026142736-rev-c1-000000", "verify": "needs_domain", "health": "ok", "agentHasSite": true @@ -401,7 +401,7 @@ "name": "도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT)", "ok": true, "detail": { - "domainId": "d_01a1789526a4d3b7920a84ba47", + "domainId": "d_01a1789c1cb665247a22f3d74a", "txtName": "_ariweb-verify.aw-e2e.localhost", "txtValueShape": "ariweb-verify=…", "copyable": true @@ -422,7 +422,7 @@ ], "evidence": { "dm": { - "domainId": "d_01a1789526a4d3b7920a84ba47", + "domainId": "d_01a1789c1cb665247a22f3d74a", "txtName": "_ariweb-verify.aw-e2e.localhost", "txtValueShape": "ariweb-verify=…", "copyable": true @@ -500,7 +500,7 @@ "name": "사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시", "ok": true, "detail": { - "id": "msv_01a17895af3560200993a9a392", + "id": "msv_01a1789ca5510f0181ed01486e", "status": "needs_setup", "tenant": true, "ariwebKind": "site_bound", @@ -517,7 +517,7 @@ "name": "메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음", "ok": true, "detail": { - "id": "mb_01a17895af6fdd3d34bd384db5", + "id": "mb_01a1789ca58c25f8651b6813b4", "address": "kim@aw-e2e.localhost", "passwordShownOnce": "[redacted]", "mustChange": true, @@ -584,7 +584,7 @@ ], "evidence": { "svc": { - "id": "msv_01a17895af3560200993a9a392", + "id": "msv_01a1789ca5510f0181ed01486e", "status": "needs_setup", "tenant": true, "ariwebKind": "site_bound", @@ -592,7 +592,7 @@ "siteCountAllocated": 0 }, "mb": { - "id": "mb_01a17895af6fdd3d34bd384db5", + "id": "mb_01a1789ca58c25f8651b6813b4", "address": "kim@aw-e2e.localhost", "passwordShownOnce": "[redacted]", "mustChange": true, @@ -644,7 +644,7 @@ "name": "메일 전용 도메인 등록(solo-e2e.localhost, purpose mail)", "ok": true, "detail": { - "id": "d_01a17895c5ce20bce855525e0c", + "id": "d_01a1789cbfd52a398c63de3b05", "jobCancelled": null } }, @@ -652,7 +652,7 @@ "name": "독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비)", "ok": true, "detail": { - "id": "msv_01a17895c5d6f2d5021f63bddd", + "id": "msv_01a1789cbfddf212586ba4b18d", "kind": "standalone", "mailServicesAllocated": 1, "sitesAllocated": 0 @@ -661,7 +661,7 @@ ], "evidence": { "r": { - "id": "msv_01a17895c5d6f2d5021f63bddd", + "id": "msv_01a1789cbfddf212586ba4b18d", "kind": "standalone", "mailServicesAllocated": 1, "sitesAllocated": 0 @@ -701,8 +701,8 @@ "ok": true, "detail": { "status": "active", - "firstDue": "2026-10-27T14:20:44.016Z", - "nextRun": "2026-10-26T14:20:44.016Z" + "firstDue": "2026-10-27T14:28:21.235Z", + "nextRun": "2026-10-26T14:28:21.235Z" } }, { @@ -712,7 +712,7 @@ "timedOut": false, "optionStatus": "active", "lastJob": null, - "siteServer": "srv_01a178950dff5cea2276c1a427", + "siteServer": "srv_01a1789c03fc9ee146184fa07c", "backup": { "status": "done", "tier": "monthly", @@ -733,14 +733,14 @@ }, "opt": { "status": "active", - "firstDue": "2026-10-27T14:20:44.016Z", - "nextRun": "2026-10-26T14:20:44.016Z" + "firstDue": "2026-10-27T14:28:21.235Z", + "nextRun": "2026-10-26T14:28:21.235Z" }, "run1": { "timedOut": false, "optionStatus": "active", "lastJob": null, - "siteServer": "srv_01a178950dff5cea2276c1a427", + "siteServer": "srv_01a1789c03fc9ee146184fa07c", "backup": { "status": "done", "tier": "monthly", @@ -763,19 +763,19 @@ "ok": true, "detail": { "backup": { - "id": "bk_01a17895dfaa66220d595c048d", - "createdAt": "2026-10-26T14:20:44.023Z", + "id": "bk_01a1789cd5b1cc3ce60f2f4349", + "createdAt": "2026-10-26T14:28:21.242Z", "scope": { "files": true, "db": true, "uploads": true, "config": true, - "storeServerId": "srv_01a178950eacfe3242171975e8", + "storeServerId": "srv_01a1789c04ba2129a08801860c", "verifyMethod": "sha256 + 격리 환경 읽기 확인" }, "sizeBytes": 1896, "verifyState": "verified", - "expiresAt": "2027-01-27T14:20:50.475Z", + "expiresAt": "2027-01-27T14:28:26.674Z", "tier": "monthly" }, "included": [ @@ -809,7 +809,7 @@ "name": "복원 요청 → 운영자 승인 대기 · 중복 요청 없음", "ok": true, "detail": { - "id": "rs_01a17895e40cc31b5b957071f0", + "id": "rs_01a1789cd82e8d48adf5b09f5a", "status": "awaiting_approval", "dup": false, "finalConfirmation": { @@ -838,19 +838,19 @@ "evidence": { "info": { "backup": { - "id": "bk_01a17895dfaa66220d595c048d", - "createdAt": "2026-10-26T14:20:44.023Z", + "id": "bk_01a1789cd5b1cc3ce60f2f4349", + "createdAt": "2026-10-26T14:28:21.242Z", "scope": { "files": true, "db": true, "uploads": true, "config": true, - "storeServerId": "srv_01a178950eacfe3242171975e8", + "storeServerId": "srv_01a1789c04ba2129a08801860c", "verifyMethod": "sha256 + 격리 환경 읽기 확인" }, "sizeBytes": 1896, "verifyState": "verified", - "expiresAt": "2027-01-27T14:20:50.475Z", + "expiresAt": "2027-01-27T14:28:26.674Z", "tier": "monthly" }, "included": [ @@ -880,7 +880,7 @@ "deferred": [] }, "rq": { - "id": "rs_01a17895e40cc31b5b957071f0", + "id": "rs_01a1789cd82e8d48adf5b09f5a", "status": "awaiting_approval", "dup": false, "finalConfirmation": { @@ -970,8 +970,8 @@ "name": "외부 배포 사이트 생성(초안) + 저장본", "ok": true, "detail": { - "id": "s_01a17896cf836296e6cf8802ab", - "group_id": "g_01a17895158846031051e25505", + "id": "s_01a1789dc3ada89ee43472fe26", + "group_id": "g_01a1789c0ba4cc346b9a816d13", "name": "E2E 외부 사이트", "purpose": "e2e", "webmaker_project_id": "00000000000000000000000000000004", @@ -999,9 +999,9 @@ "retention_until": null, "delete_after": null, "deleted_at": null, - "created_by": "u_01a1789514e318307a9166fe78", - "created_at": "2026-10-26T14:21:51.876Z", - "updated_at": "2026-10-26T14:21:51.880Z", + "created_by": "u_01a1789c0afae66fc53b01be8a", + "created_at": "2026-10-26T14:29:27.597Z", + "updated_at": "2026-10-26T14:29:27.603Z", "runtime": "static", "consent_version": null, "pending_cleanup_json": null, @@ -1042,8 +1042,8 @@ "ok": true, "detail": { "token": "[redacted]", - "install": "curl -fsSL http://127.0.0.1:42095/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:42095 --token … --external --site s_01a17896cf836296e6cf8802ab", - "expiresAt": "2026-10-26T14:31:51.915Z", + "install": "curl -fsSL http://127.0.0.1:40961/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:40961 --token … --external --site s_01a1789dc3ada89ee43472fe26", + "expiresAt": "2026-10-26T14:39:27.642Z", "status": "pending" } }, @@ -1126,8 +1126,8 @@ "agreed": false }, "tok": { - "install": "curl -fsSL http://127.0.0.1:42095/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:42095 --token … --external --site s_01a17896cf836296e6cf8802ab", - "expiresAt": "2026-10-26T14:31:51.915Z" + "install": "curl -fsSL http://127.0.0.1:40961/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:40961 --token … --external --site s_01a1789dc3ada89ee43472fe26", + "expiresAt": "2026-10-26T14:39:27.642Z" }, "conn": { "status": "connected", @@ -1311,7 +1311,7 @@ "approve": 200, "sites": 1, "mailboxes": 5, - "product": "pv_01a178951427477eb7bc895885" + "product": "pv_01a1789c0a2573b629e03f3013" } } ], @@ -1347,7 +1347,7 @@ "approve": 200, "sites": 1, "mailboxes": 5, - "product": "pv_01a178951427477eb7bc895885" + "product": "pv_01a1789c0a2573b629e03f3013" } } }, @@ -1430,9 +1430,9 @@ "name": "요청(보내는 그룹, 초안 사이트) → 받는 사용자 알림", "ok": true, "detail": { - "id": "tr_01a17896f2625044dea12cf4e9", + "id": "tr_01a1789de6a46d148f37fda029", "status": "requested", - "expiresAt": "2026-11-02T14:22:00.802Z", + "expiresAt": "2026-11-02T14:29:36.548Z", "bobNotified": true } }, @@ -1460,7 +1460,7 @@ "bad": "final_confirmation_mismatch", "ok": 200, "status": "completed", - "cutover": "2026-10-26T14:22:00.850Z" + "cutover": "2026-10-26T14:29:36.598Z" } }, { @@ -1489,9 +1489,9 @@ "excluded": 3 }, "rq": { - "id": "tr_01a17896f2625044dea12cf4e9", + "id": "tr_01a1789de6a46d148f37fda029", "status": "requested", - "expiresAt": "2026-11-02T14:22:00.802Z", + "expiresAt": "2026-11-02T14:29:36.548Z", "bobNotified": true }, "acc": { @@ -1503,7 +1503,7 @@ "bad": "final_confirmation_mismatch", "ok": 200, "status": "completed", - "cutover": "2026-10-26T14:22:00.850Z" + "cutover": "2026-10-26T14:29:36.598Z" }, "after": { "aliceSees": 404, @@ -1781,7 +1781,7 @@ "detail": { "status": "done", "canRetry": false, - "jobId": "j_01a1789516d0014fae08329a93" + "jobId": "j_01a1789c0ce4dcffc59011e15d" } }, { @@ -1806,7 +1806,7 @@ "dep": { "status": "done", "canRetry": false, - "jobId": "j_01a1789516d0014fae08329a93" + "jobId": "j_01a1789c0ce4dcffc59011e15d" }, "rt": { "status": 400, @@ -1832,7 +1832,7 @@ "detail": { "title": "주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다", "category": "billing", - "link": "/orders/o_01a178951600a2dd5f8363a606" + "link": "/orders/o_01a1789c0c18393dba2213c118" } }, { @@ -1848,7 +1848,7 @@ "ok": true, "detail": { "status": "approved", - "approvedAt": "2026-10-26T14:19:58.916Z" + "approvedAt": "2026-10-26T14:27:35.130Z" } } ], @@ -1856,7 +1856,7 @@ "n": { "title": "주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다", "category": "billing", - "link": "/orders/o_01a178951600a2dd5f8363a606" + "link": "/orders/o_01a1789c0c18393dba2213c118" }, "a": { "action": "order.approve", @@ -1864,7 +1864,7 @@ }, "same": { "status": "approved", - "approvedAt": "2026-10-26T14:19:58.916Z" + "approvedAt": "2026-10-26T14:27:35.130Z" } } }, @@ -1907,9 +1907,9 @@ "rpo_minutes": 30, "snapshot_interval_minutes": 60, "retention_count": 3, - "external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026141954/dr-external", - "updated_at": "2026-10-26T14:22:10.592Z", - "updated_by": "op_01a178950d042b39ebdc74f543" + "external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026142730/dr-external", + "updated_at": "2026-10-26T14:29:46.345Z", + "updated_by": "op_01a1789c02fd72d51f469e4d25" } }, { @@ -1969,9 +1969,9 @@ "rpo_minutes": 30, "snapshot_interval_minutes": 60, "retention_count": 3, - "external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026141954/dr-external", - "updated_at": "2026-10-26T14:22:10.592Z", - "updated_by": "op_01a178950d042b39ebdc74f543" + "external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026142730/dr-external", + "updated_at": "2026-10-26T14:29:46.345Z", + "updated_by": "op_01a1789c02fd72d51f469e4d25" }, "snap": { "verify": "ok", @@ -2005,7 +2005,7 @@ "ok": true, "detail": { "mailJob": { - "id": "j_01a17895c3aa79c0b0eb5bac38", + "id": "j_01a1789cb9c0e6f752db2a47b6", "status": "awaiting_external" }, "halJobs": 18 @@ -2017,7 +2017,7 @@ "detail": { "sessionKept": "[redacted]", "mailJob": { - "id": "j_01a17895c3aa79c0b0eb5bac38", + "id": "j_01a1789cb9c0e6f752db2a47b6", "status": "awaiting_external" }, "same": true @@ -2036,7 +2036,7 @@ "evidence": { "before": { "mailJob": { - "id": "j_01a17895c3aa79c0b0eb5bac38", + "id": "j_01a1789cb9c0e6f752db2a47b6", "status": "awaiting_external" }, "halJobs": 18 @@ -2044,7 +2044,7 @@ "pr": { "sessionKept": "[redacted]", "mailJob": { - "id": "j_01a17895c3aa79c0b0eb5bac38", + "id": "j_01a1789cb9c0e6f752db2a47b6", "status": "awaiting_external" }, "same": true @@ -2075,8 +2075,8 @@ "ok": true, "detail": [ { - "id": "s_01a17895a33370c86d2578bdfa", - "group_id": "g_01a1789515b8d48acf24ef196d", + "id": "s_01a1789c994d3878d056f3d2fc", + "group_id": "g_01a1789c0bd144ad600dd3bc46", "name": "동시 A", "purpose": "e2e", "webmaker_project_id": "00000000000000000000000000000002", @@ -2104,9 +2104,9 @@ "retention_until": null, "delete_after": null, "deleted_at": null, - "created_by": "u_01a1789515b89dff9e86481ccb", - "created_at": "2026-10-26T14:20:34.995Z", - "updated_at": "2026-10-26T14:20:35.004Z", + "created_by": "u_01a1789c0bd1e6e269d0df3589", + "created_at": "2026-10-26T14:28:11.213Z", + "updated_at": "2026-10-26T14:28:11.225Z", "runtime": "static", "consent_version": null, "pending_cleanup_json": null, @@ -2125,8 +2125,8 @@ "nextJob": null }, { - "id": "s_01a17895a3378265a5de54a98d", - "group_id": "g_01a1789515b8d48acf24ef196d", + "id": "s_01a1789c9951c330ce1ad58f13", + "group_id": "g_01a1789c0bd144ad600dd3bc46", "name": "동시 B", "purpose": "e2e", "webmaker_project_id": "00000000000000000000000000000003", @@ -2154,9 +2154,9 @@ "retention_until": null, "delete_after": null, "deleted_at": null, - "created_by": "u_01a1789515b89dff9e86481ccb", - "created_at": "2026-10-26T14:20:34.999Z", - "updated_at": "2026-10-26T14:20:35.006Z", + "created_by": "u_01a1789c0bd1e6e269d0df3589", + "created_at": "2026-10-26T14:28:11.217Z", + "updated_at": "2026-10-26T14:28:11.228Z", "runtime": "static", "consent_version": null, "pending_cleanup_json": null, @@ -2214,8 +2214,8 @@ "name": "외부 배포 사이트 생성(초안) + 저장본", "ok": true, "detail": { - "id": "s_01a17896cf836296e6cf8802ab", - "group_id": "g_01a17895158846031051e25505", + "id": "s_01a1789dc3ada89ee43472fe26", + "group_id": "g_01a1789c0ba4cc346b9a816d13", "name": "E2E 외부 사이트", "purpose": "e2e", "webmaker_project_id": "00000000000000000000000000000004", @@ -2243,9 +2243,9 @@ "retention_until": null, "delete_after": null, "deleted_at": null, - "created_by": "u_01a1789514e318307a9166fe78", - "created_at": "2026-10-26T14:21:51.876Z", - "updated_at": "2026-10-26T14:21:51.880Z", + "created_by": "u_01a1789c0afae66fc53b01be8a", + "created_at": "2026-10-26T14:29:27.597Z", + "updated_at": "2026-10-26T14:29:27.603Z", "runtime": "static", "consent_version": null, "pending_cleanup_json": null, @@ -2286,8 +2286,8 @@ "ok": true, "detail": { "token": "[redacted]", - "install": "curl -fsSL http://127.0.0.1:42095/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:42095 --token … --external --site s_01a17896cf836296e6cf8802ab", - "expiresAt": "2026-10-26T14:31:51.915Z", + "install": "curl -fsSL http://127.0.0.1:40961/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:40961 --token … --external --site s_01a1789dc3ada89ee43472fe26", + "expiresAt": "2026-10-26T14:39:27.642Z", "status": "pending" } }, @@ -2483,8 +2483,8 @@ "overall": "partial", "children": [ "srv_missing_e2e:failed:서버가 없습니다", - "srv_01a178950eacfe3242171975e8:done:", - "srv_01a178950dff5cea2276c1a427:done:" + "srv_01a1789c04ba2129a08801860c:done:", + "srv_01a1789c03fc9ee146184fa07c:done:" ], "timedOut": false } @@ -2522,8 +2522,8 @@ "overall": "partial", "children": [ "srv_missing_e2e:failed:서버가 없습니다", - "srv_01a178950eacfe3242171975e8:done:", - "srv_01a178950dff5cea2276c1a427:done:" + "srv_01a1789c04ba2129a08801860c:done:", + "srv_01a1789c03fc9ee146184fa07c:done:" ], "timedOut": false }, @@ -2562,7 +2562,7 @@ "name": "critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회)", "ok": true, "detail": { - "id": "em_01a1789719b470be87b0b500f7", + "id": "em_01a1789e0e13d2c9660482e88c", "noCred": "approvals_insufficient", "wrongStep": "wrong_step", "badIds": "identifiers_mismatch", @@ -2570,7 +2570,7 @@ "approved": "approved", "approvals": 3, "credential": "[redacted]", - "expiresAt": "2026-10-26T14:52:10.897Z" + "expiresAt": "2026-10-26T14:59:46.671Z" } }, { @@ -2591,14 +2591,14 @@ "evidence": { "healthy": "hal_healthy", "sess": { - "id": "em_01a1789719b470be87b0b500f7", + "id": "em_01a1789e0e13d2c9660482e88c", "noCred": "approvals_insufficient", "wrongStep": "wrong_step", "badIds": "identifiers_mismatch", "badText": "final_confirm_mismatch", "approved": "approved", "approvals": 3, - "expiresAt": "2026-10-26T14:52:10.897Z" + "expiresAt": "2026-10-26T14:59:46.671Z" }, "perf": { "forbidden": "action_forbidden", @@ -2650,8 +2650,8 @@ "ok": true, "detail": { "status": "readonly", - "deleteAfter": "2026-11-25T14:22:00.732Z", - "readonlyUntil": "2026-11-25T14:22:00.732Z" + "deleteAfter": "2026-11-25T14:29:36.479Z", + "readonlyUntil": "2026-11-25T14:29:36.479Z" } }, { @@ -2677,8 +2677,8 @@ }, "un": { "status": "readonly", - "deleteAfter": "2026-11-25T14:22:00.732Z", - "readonlyUntil": "2026-11-25T14:22:00.732Z" + "deleteAfter": "2026-11-25T14:29:36.479Z", + "readonlyUntil": "2026-11-25T14:29:36.479Z" }, "sweep": { "sweep": 200, diff --git a/docs/verification/local-e2e-2026-10-26.md b/docs/verification/local-e2e-2026-10-26.md index 97726a8..12eeb85 100644 --- a/docs/verification/local-e2e-2026-10-26.md +++ b/docs/verification/local-e2e-2026-10-26.md @@ -1,6 +1,6 @@ # 로컬 종단 간 검증 기록 — 2026-10-26 -생성: 2026-10-26T14:22:13.542Z · `node --no-warnings test/e2e.test.mjs` (test/e2e/) +생성: 2026-10-26T14:29:49.315Z · `node --no-warnings test/e2e.test.mjs` (test/e2e/) 실행 환경: 실제 HAL·고객 포털·Node Agent(명령 stub 모드) 프로세스 4대 + 가짜 Webmaker/AriMail/발신 메일 서버(127.0.0.1). 실 DNS·실 메일 왕복·실 서버는 포함하지 않는다. @@ -88,20 +88,20 @@ ### 2. 개인 사용자 그룹 생성과 다른 사용자 초대 -- ✔ 팀 그룹 만들기 → 현재 그룹 전환 — g_01a17895158846031051e25505 -- ✔ 이메일 초대(7일) → 초대 메일 링크 — akfdWdWvppAkoWrVUJnYkIdG2WcBGy8L +- ✔ 팀 그룹 만들기 → 현재 그룹 전환 — g_01a1789c0ba4cc346b9a816d13 +- ✔ 이메일 초대(7일) → 초대 메일 링크 — 92k9hZNmEVZ5s11c5YD_AKFXC9VLtCw2 - ✔ 초대 수락(1회용) → 별칭 user-02 · 역할 member — {"alias":"user-02","role":"member","reuse":"invite_invalid"} - ✔ 권한 없는 구성원의 직접 API 접근 → 403 + 필요한 역할 — {"requiredRole":["owner","admin"],"yourRole":"member"} - ✔ 그룹 전환 격리: 개인 그룹으로 바꾸면 팀 데이터가 보이지 않음 — {"team":{"live":0,"purchased":0,"inUse":0,"reserved":0,"unallocated":0},"personal":{"live":0,"purchased":0,"inUse":0,"reserved":0,"unallocated":0}} ### 3. 상품 주문, 결제 확인, 권리 활성화 -- ✔ 상품 목록(공개 버전만·가격·한도·미확정 정책 표시) — {"count":2,"team":{"id":"pv_01a17895142780400bdd2c40c1","price":49000,"sites":3},"deferred":3,"paymentMethods":["manual_transfer"]} -- ✔ 주문 제출(수동 결제) → 결제 대기·주문 번호·입금 안내 · 같은 키 재제출은 중복 없음 — {"orderNo":"AW-20261026-0001","status":"awaiting_payment","total":63800,"dueAt":"2026-11-02T14:19:58.848Z","hasInstructions":true} +- ✔ 상품 목록(공개 버전만·가격·한도·미확정 정책 표시) — {"count":2,"team":{"id":"pv_01a1789c0a26a92faf63434d5a","price":49000,"sites":3},"deferred":3,"paymentMethods":["manual_transfer"]} +- ✔ 주문 제출(수동 결제) → 결제 대기·주문 번호·입금 안내 · 같은 키 재제출은 중복 없음 — {"orderNo":"AW-20261026-0001","status":"awaiting_payment","total":63800,"dueAt":"2026-11-02T14:27:35.064Z","hasInstructions":true} - ✔ 입금 확인 전 승인 거부(409 payment_not_confirmed) — payment_not_confirmed - ✔ 운영자 입금 확인 → 승인 대기 목록에 권리 변화·현재 사용량 표시 — {"status":"awaiting_approval","delta":["sites","web_disk","web_out","mail_storage","mail_msgs","mailboxes","mail_services","mail_storage","mail_msgs","mailboxes"]} - ✔ 운영자 승인 → 권리 활성화(재승인은 중복 없음) — {"granted":10,"already":true} -- ✔ 고객 결제 화면: 활성 상품·권리·다음 청구일 — {"sites":3,"subs":1,"nextBilling":"2026-11-26T14:19:58.916Z"} +- ✔ 고객 결제 화면: 활성 상품·권리·다음 청구일 — {"sites":3,"subs":1,"nextBilling":"2026-11-26T14:27:35.130Z"} ```json { @@ -112,25 +112,25 @@ "bill": { "sites": 3, "subs": 1, - "nextBilling": "2026-11-26T14:19:58.916Z" + "nextBilling": "2026-11-26T14:27:35.130Z" } } ``` ### 4. 초안 사이트 생성 -- ✔ 사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내 — {"id":"s_01a1789516843da274b3515e56","lifecycle":"draft","projectId":"00000000000000000000000000000001","nextSteps":["build","import","domain","mode","perms"]} +- ✔ 사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내 — {"id":"s_01a1789c0c9a313add86e28440","lifecycle":"draft","projectId":"00000000000000000000000000000001","nextSteps":["build","import","domain","mode","perms"]} - ✔ 사이트 목록 필터(초안)·사용량 요약에 예약 0 — {"drafts":1,"sitesReserved":0,"sitesAllocated":0} ### 5. Webmaker에서 페이지 제작 -- ✔ 제작 화면 열기: 포털 SSO 티켓 → Webmaker /api/auth/sso 302(1회용) — {"portalRedirect":"http://127.0.0.1:34303/api/auth/sso?ticket=…","webmaker":302,"reuse":401} +- ✔ 제작 화면 열기: 포털 SSO 티켓 → Webmaker /api/auth/sso 302(1회용) — {"portalRedirect":"http://127.0.0.1:37209/api/auth/sso?ticket=…","webmaker":302,"reuse":401} - ✔ Webmaker 에서 페이지 저장(저장본 2개) → 포털 저장본 목록·배포 전 확인 통과 — {"versions":1,"precheckOk":true} ```json { "sso": { - "portalRedirect": "http://127.0.0.1:34303/api/auth/sso?ticket=…", + "portalRedirect": "http://127.0.0.1:37209/api/auth/sso?ticket=…", "webmaker": 302, "reuse": 401 }, @@ -143,14 +143,14 @@ ### 6. 관리형 배포 -- ✔ 배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나 — {"jobId":"j_01a1789516d0014fae08329a93","steps":10,"again":false,"siteCount":"reserved","server":null} +- ✔ 배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나 — {"jobId":"j_01a1789c0ce4dcffc59011e15d","steps":10,"again":false,"siteCount":"reserved","server":null} - ✔ HAL·Agent 를 거쳐 작업 완료 대기(최대 40초) — {"status":"done","steps":["accept:done","prepare_version:done","prepare_target:done","apply:done","data_check:done","start:done","internal_check:done","external_check:done","domain_https:done","done:done"]} -- ✔ 배포 뒤 사이트 상태·버전·건강 — {"lifecycle":"needs_domain","version":"20261026142000-rev-c1-000000","verify":"needs_domain","health":"ok","agentHasSite":true} +- ✔ 배포 뒤 사이트 상태·버전·건강 — {"lifecycle":"needs_domain","version":"20261026142736-rev-c1-000000","verify":"needs_domain","health":"ok","agentHasSite":true} ```json { "d": { - "jobId": "j_01a1789516d0014fae08329a93", + "jobId": "j_01a1789c0ce4dcffc59011e15d", "steps": 10, "again": false, "siteCount": "reserved", @@ -173,7 +173,7 @@ }, "after": { "lifecycle": "needs_domain", - "version": "20261026142000-rev-c1-000000", + "version": "20261026142736-rev-c1-000000", "verify": "needs_domain", "health": "ok", "agentHasSite": true @@ -183,14 +183,14 @@ ### 7. 도메인 연결과 HTTPS 발급 -- ✔ 도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT) — {"domainId":"d_01a1789526a4d3b7920a84ba47","txtName":"_ariweb-verify.aw-e2e.localhost","txtValueShape":"ariweb-verify=…","copyable":true} +- ✔ 도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT) — {"domainId":"d_01a1789c1cb665247a22f3d74a","txtName":"_ariweb-verify.aw-e2e.localhost","txtValueShape":"ariweb-verify=…","copyable":true} - ✔ 도메인 목록·상세: 소유 확인·DNS·인증서 상태 표시 — {} - ✔ (정리) 도메인 연결 작업 취소 — 포털이 외부 대기 중에도 5초마다 HAL domain.probe 를 새로 만들어 큐를 채우므로(결함 항목) 뒤 흐름을 위해 멈춘다 — {"queuedProbes":0} ```json { "dm": { - "domainId": "d_01a1789526a4d3b7920a84ba47", + "domainId": "d_01a1789c1cb665247a22f3d74a", "txtName": "_ariweb-verify.aw-e2e.localhost", "txtValueShape": "ariweb-verify=…", "copyable": true @@ -233,9 +233,9 @@ ### 10. 사이트 대표 메일과 메일함 생성 -- ✔ 사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시 — {"id":"msv_01a17895af3560200993a9a392","status":"needs_setup","tenant":true,"ariwebKind":"site_bound","limitAccounts":6,"siteCountAllocated":0} +- ✔ 사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시 — {"id":"msv_01a1789ca5510f0181ed01486e","status":"needs_setup","tenant":true,"ariwebKind":"site_bound","limitAccounts":6,"siteCountAllocated":0} - ✔ 같은 사이트에 두 번째 대표 메일 거부 — mail_domain_in_use -- ✔ 메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음 — {"id":"mb_01a17895af6fdd3d34bd384db5","address":"kim@aw-e2e.localhost","passwordShownOnce":"[redacted]","mustChange":true,"imapHost":"127.0.0.1","leakedOnReload":false,"inArimail":true} +- ✔ 메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음 — {"id":"mb_01a1789ca58c25f8651b6813b4","address":"kim@aw-e2e.localhost","passwordShownOnce":"[redacted]","mustChange":true,"imapHost":"127.0.0.1","leakedOnReload":false,"inArimail":true} - ✔ 초대 방식 메일함(링크 만료·재발송·사용 완료 상태) — {"inviteState":"pending","accepted":200,"reuse":"invite_used"} - ✔ DNS 확인: 미반영/잘못된 값 구분 표시(로컬 DNS 에는 레코드 없음 → missing) — {"ok":false,"missing":3,"items":["mx:missing","spf:missing","dkim:missing","dmarc:recommended"]} - ✔ 활성화 작업 → DNS 외부 대기(awaiting_external) · 멱등 — {"status":"awaiting_external","waitReason":"DNS 레코드가 아직 반영되지 않았습니다(누락 3, 잘못된 값 0). 도메인 DNS 에 안내된 값을 넣은 뒤 '이어서 진행' 을 누르세요","sameJob":true,"steps":["entitlement:done","dns:waiting","prepare:pending","verify:pending","activate:pending"]} @@ -244,7 +244,7 @@ ```json { "svc": { - "id": "msv_01a17895af3560200993a9a392", + "id": "msv_01a1789ca5510f0181ed01486e", "status": "needs_setup", "tenant": true, "ariwebKind": "site_bound", @@ -252,7 +252,7 @@ "siteCountAllocated": 0 }, "mb": { - "id": "mb_01a17895af6fdd3d34bd384db5", + "id": "mb_01a1789ca58c25f8651b6813b4", "address": "kim@aw-e2e.localhost", "passwordShownOnce": "[redacted]", "mustChange": true, @@ -287,13 +287,13 @@ ### 12. 독립 메일 서비스 생성 -- ✔ 메일 전용 도메인 등록(solo-e2e.localhost, purpose mail) — {"id":"d_01a17895c5ce20bce855525e0c","jobCancelled":null} -- ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a17895c5d6f2d5021f63bddd","kind":"standalone","mailServicesAllocated":1,"sitesAllocated":0} +- ✔ 메일 전용 도메인 등록(solo-e2e.localhost, purpose mail) — {"id":"d_01a1789cbfd52a398c63de3b05","jobCancelled":null} +- ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a1789cbfddf212586ba4b18d","kind":"standalone","mailServicesAllocated":1,"sitesAllocated":0} ```json { "r": { - "id": "msv_01a17895c5d6f2d5021f63bddd", + "id": "msv_01a1789cbfddf212586ba4b18d", "kind": "standalone", "mailServicesAllocated": 1, "sitesAllocated": 0 @@ -307,8 +307,8 @@ - ✔ 백업 옵션 주문(대상 사이트)·입금 확인·승인 → backup 권리 — {"granted":7,"orderStatus":"approved"} - ✔ 백업 화면: 구매 가능 여부·저장 한도·일정·보존·첫 백업 기한·검증 방식(상품정책에서) — {"eligible":true,"reasons":[],"deferred":[],"policy":null,"status":200} -- ✔ 옵션 활성화(확인 필수) → 첫 백업 예정 시각 — {"status":"active","firstDue":"2026-10-27T14:20:44.016Z","nextRun":"2026-10-26T14:20:44.016Z"} -- ✔ 지금 백업 실행 → HAL backup.create(저장 서버 ≠ 운영 서버) → Agent → 검증 — {"timedOut":false,"optionStatus":"active","lastJob":null,"siteServer":"srv_01a178950dff5cea2276c1a427","backup":{"status":"done","tier":"monthly","location":null},"job":null,"optionFailures":0} +- ✔ 옵션 활성화(확인 필수) → 첫 백업 예정 시각 — {"status":"active","firstDue":"2026-10-27T14:28:21.235Z","nextRun":"2026-10-26T14:28:21.235Z"} +- ✔ 지금 백업 실행 → HAL backup.create(저장 서버 ≠ 운영 서버) → Agent → 검증 — {"timedOut":false,"optionStatus":"active","lastJob":null,"siteServer":"srv_01a1789c03fc9ee146184fa07c","backup":{"status":"done","tier":"monthly","location":null},"job":null,"optionFailures":0} ```json { @@ -321,14 +321,14 @@ }, "opt": { "status": "active", - "firstDue": "2026-10-27T14:20:44.016Z", - "nextRun": "2026-10-26T14:20:44.016Z" + "firstDue": "2026-10-27T14:28:21.235Z", + "nextRun": "2026-10-26T14:28:21.235Z" }, "run1": { "timedOut": false, "optionStatus": "active", "lastJob": null, - "siteServer": "srv_01a178950dff5cea2276c1a427", + "siteServer": "srv_01a1789c03fc9ee146184fa07c", "backup": { "status": "done", "tier": "monthly", @@ -342,8 +342,8 @@ ### 14. 백업 복원과 외부 사이트 확인 -- ✔ 복원 안내(포함 데이터·영향·보호 시점 유지·되돌릴 수 있는 마지막 시각·완료 방식) — {"backup":{"id":"bk_01a17895dfaa66220d595c048d","createdAt":"2026-10-26T14:20:44.023Z","scope":{"files":true,"db":true,"uploads":true,"config":true,"storeServerId":"srv_01a178950eacfe3242171975e8","verifyMethod":"sha256 + 격리 환경 읽기 확인"},"sizeBytes":1896,"verifyState":"verified","expiresAt":"2027-01-2 -- ✔ 복원 요청 → 운영자 승인 대기 · 중복 요청 없음 — {"id":"rs_01a17895e40cc31b5b957071f0","status":"awaiting_approval","dup":false,"finalConfirmation":{"group":"E2E 팀","site":"E2E 사이트","domain":null}} +- ✔ 복원 안내(포함 데이터·영향·보호 시점 유지·되돌릴 수 있는 마지막 시각·완료 방식) — {"backup":{"id":"bk_01a1789cd5b1cc3ce60f2f4349","createdAt":"2026-10-26T14:28:21.242Z","scope":{"files":true,"db":true,"uploads":true,"config":true,"storeServerId":"srv_01a1789c04ba2129a08801860c","verifyMethod":"sha256 + 격리 환경 읽기 확인"},"sizeBytes":1896,"verifyState":"verified","expiresAt":"2027-01-2 +- ✔ 복원 요청 → 운영자 승인 대기 · 중복 요청 없음 — {"id":"rs_01a1789cd82e8d48adf5b09f5a","status":"awaiting_approval","dup":false,"finalConfirmation":{"group":"E2E 팀","site":"E2E 사이트","domain":null}} - ✔ 운영자 최종 확인(그룹·사이트·백업 시각) 불일치 거부 → 일치 승인(멱등) — {"bad":"final_confirmation_mismatch","ok":400} - ✔ 9단계 작업 진행 → 사용자 확인 필요(보호 시점·되돌리기 가능) — {"steps":[]} @@ -351,19 +351,19 @@ { "info": { "backup": { - "id": "bk_01a17895dfaa66220d595c048d", - "createdAt": "2026-10-26T14:20:44.023Z", + "id": "bk_01a1789cd5b1cc3ce60f2f4349", + "createdAt": "2026-10-26T14:28:21.242Z", "scope": { "files": true, "db": true, "uploads": true, "config": true, - "storeServerId": "srv_01a178950eacfe3242171975e8", + "storeServerId": "srv_01a1789c04ba2129a08801860c", "verifyMethod": "sha256 + 격리 환경 읽기 확인" }, "sizeBytes": 1896, "verifyState": "verified", - "expiresAt": "2027-01-27T14:20:50.475Z", + "expiresAt": "2027-01-27T14:28:26.674Z", "tier": "monthly" }, "included": [ @@ -393,7 +393,7 @@ "deferred": [] }, "rq": { - "id": "rs_01a17895e40cc31b5b957071f0", + "id": "rs_01a1789cd82e8d48adf5b09f5a", "status": "awaiting_approval", "dup": false, "finalConfirmation": { @@ -445,10 +445,10 @@ ### 16. 외부 배포 사이트 생성과 배포 -- ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a17896cf836296e6cf8802ab","group_id":"g_01a17895158846031051e25505","name":"E2E 외부 사이트","purpose":"e2e","webmaker_project_id":"00000000000000000000000000000004","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"external","primary_domain":null,"site_cou +- ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a1789dc3ada89ee43472fe26","group_id":"g_01a1789c0ba4cc346b9a816d13","name":"E2E 외부 사이트","purpose":"e2e","webmaker_project_id":"00000000000000000000000000000004","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"external","primary_domain":null,"site_cou - ✔ 배포 방식 화면: 외부 대상·고객 책임·미제공 기능(메일 미지원)·건별 비용·동의 버전 — {"notProvided":"[redacted]","consentVersion":"external-deploy-2026-10-0.1.1","agreed":false} - ✔ 동의 전 Agent 발급 거부(consent_required) → 동의 기록(버전) — {"before":"consent_required","consent":200} -- ✔ 외부 Agent 일회용 토큰·설치 명령(토큰은 저장하지 않음) — {"token":"[redacted]","install":"curl -fsSL http://127.0.0.1:42095/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:42095 --token … --external --site s_01a17896cf836296e6cf8802ab","expiresAt":"2026-10-26T14:31:51.915Z","status":"pending"} +- ✔ 외부 Agent 일회용 토큰·설치 명령(토큰은 저장하지 않음) — {"token":"[redacted]","install":"curl -fsSL http://127.0.0.1:40961/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:40961 --token … --external --site s_01a1789dc3ada89ee43472fe26","expiresAt":"2026-10-26T14:39:27.642Z","status":"pending"} - ✔ 고객 서버 역할의 외부 Agent 프로세스 등록·실행(HAL 등록, 사이트 귀속) — {"agentId":"[redacted]"} - ✔ 포털 연결 확인(외부 Agent 상태 connected) — {"status":"connected","stale":false,"halAgentId":"[redacted]"} - ✔ 배포 전 확인(외부: 관리형 용량 확인 없음) — {"ok":true,"blocks":[]} @@ -465,8 +465,8 @@ "agreed": false }, "tok": { - "install": "curl -fsSL http://127.0.0.1:42095/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:42095 --token … --external --site s_01a17896cf836296e6cf8802ab", - "expiresAt": "2026-10-26T14:31:51.915Z" + "install": "curl -fsSL http://127.0.0.1:40961/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:40961 --token … --external --site s_01a1789dc3ada89ee43472fe26", + "expiresAt": "2026-10-26T14:39:27.642Z" }, "conn": { "status": "connected", @@ -554,7 +554,7 @@ - ✔ 축소 미리보기(팀→베이직): 줄여야 할 항목(메일함 6→5) 구체 표시 — {"shortages":["mailboxes:1"]} - ✔ 축소 요청 → 보류(사용 중 권리 즉시 회수 없음) — {"fits":false,"hold":"holding","sitesStillPurchased":3} - ✔ 정리: 메일함 배정 6→5 축소 → 재검증 적합 → 축소 주문 생성 — {"allocate":200,"fits":true,"shortages":[],"order":"awaiting_payment"} -- ✔ 축소 주문 입금 확인·승인 → 권리 교체(베이직) — {"approve":200,"sites":1,"mailboxes":5,"product":"pv_01a178951427477eb7bc895885"} +- ✔ 축소 주문 입금 확인·승인 → 권리 교체(베이직) — {"approve":200,"sites":1,"mailboxes":5,"product":"pv_01a1789c0a2573b629e03f3013"} ```json { @@ -589,7 +589,7 @@ "approve": 200, "sites": 1, "mailboxes": 5, - "product": "pv_01a178951427477eb7bc895885" + "product": "pv_01a1789c0a2573b629e03f3013" } } ``` @@ -623,10 +623,10 @@ ### 20. 사이트 소유권 이전 - ✔ 이전 범위 미리보기(사이트·도메인·대표 메일·배정·진행 작업·미납 / 제외 항목) — {"sites":1,"domains":1,"siteMail":1,"needed":{"backup":1,"web_disk":1073741824,"mail_msgs":500,"mail_storage":2147483648,"mailboxes":6},"excluded":3} -- ✔ 요청(보내는 그룹, 초안 사이트) → 받는 사용자 알림 — {"id":"tr_01a17896f2625044dea12cf4e9","status":"requested","expiresAt":"2026-11-02T14:22:00.802Z","bobNotified":true} +- ✔ 요청(보내는 그룹, 초안 사이트) → 받는 사용자 알림 — {"id":"tr_01a1789de6a46d148f37fda029","status":"requested","expiresAt":"2026-11-02T14:29:36.548Z","bobNotified":true} - ✔ (정리) 받는 쪽 권리에 맞게 범위 조정: 백업 옵션 종료·메일함 배정 6→5(받는 그룹 베이직 5개) — {"endBackup":200,"alloc":200} - ✔ 받는 그룹 수락 → 권리 부족이면 보류(필요한 상품 안내) — {"status":200,"tstatus":"reviewing","hold":null} -- ✔ 운영자 심사(소유 증빙·도메인 확인) 미입력 거부 → 최종 확인 일치 승인 → 원자적 완료·청구 전환 시각 — {"bad":"final_confirmation_mismatch","ok":200,"status":"completed","cutover":"2026-10-26T14:22:00.850Z"} +- ✔ 운영자 심사(소유 증빙·도메인 확인) 미입력 거부 → 최종 확인 일치 승인 → 원자적 완료·청구 전환 시각 — {"bad":"final_confirmation_mismatch","ok":200,"status":"completed","cutover":"2026-10-26T14:29:36.598Z"} - ✔ 완료 뒤: 보내는 쪽은 접근 불가(404), 받는 쪽 대시보드·사용량 일치, 불변 감사 — {"aliceSees":404,"bobSees":200,"bobUsageSites":0,"audit":true} ```json @@ -645,9 +645,9 @@ "excluded": 3 }, "rq": { - "id": "tr_01a17896f2625044dea12cf4e9", + "id": "tr_01a1789de6a46d148f37fda029", "status": "requested", - "expiresAt": "2026-11-02T14:22:00.802Z", + "expiresAt": "2026-11-02T14:29:36.548Z", "bobNotified": true }, "acc": { @@ -659,7 +659,7 @@ "bad": "final_confirmation_mismatch", "ok": 200, "status": "completed", - "cutover": "2026-10-26T14:22:00.850Z" + "cutover": "2026-10-26T14:29:36.598Z" }, "after": { "aliceSees": 404, @@ -798,7 +798,7 @@ ### 23. 실패 작업의 재시도와 되돌리기 -- ✔ 배포 이력: 실패/성공 기록 · 재시도 가능 여부 · 문의용 작업 번호 — {"status":"done","canRetry":false,"jobId":"j_01a1789516d0014fae08329a93"} +- ✔ 배포 이력: 실패/성공 기록 · 재시도 가능 여부 · 문의용 작업 번호 — {"status":"done","canRetry":false,"jobId":"j_01a1789c0ce4dcffc59011e15d"} - ✔ 재시도 요청(POST /api/sites/:id/jobs/:jobId/retry) — {"status":400,"error":"job_not_retryable"} - ✔ 되돌리기 요청(POST /api/sites/:id/rollback) — 이전 버전 없으면 거부 — {"status":409,"error":"no_previous","message":"되돌릴 이전 버전이 없습니다"} @@ -807,7 +807,7 @@ "dep": { "status": "done", "canRetry": false, - "jobId": "j_01a1789516d0014fae08329a93" + "jobId": "j_01a1789c0ce4dcffc59011e15d" }, "rt": { "status": 400, @@ -823,16 +823,16 @@ ### 24. 운영자 승인, 감사 기록, 고객 알림의 일치 -- ✔ 고객 알림 센터에 승인 알림 — {"title":"주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다","category":"billing","link":"/orders/o_01a178951600a2dd5f8363a606"} +- ✔ 고객 알림 센터에 승인 알림 — {"title":"주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다","category":"billing","link":"/orders/o_01a1789c0c18393dba2213c118"} - ✔ 감사 기록: order.approve 는 삭제 불가(immutable) — {"action":"order.approve","immutable":true} -- ✔ 운영자 화면과 고객 주문 화면의 상태 일치 — {"status":"approved","approvedAt":"2026-10-26T14:19:58.916Z"} +- ✔ 운영자 화면과 고객 주문 화면의 상태 일치 — {"status":"approved","approvedAt":"2026-10-26T14:27:35.130Z"} ```json { "n": { "title": "주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다", "category": "billing", - "link": "/orders/o_01a178951600a2dd5f8363a606" + "link": "/orders/o_01a1789c0c18393dba2213c118" }, "a": { "action": "order.approve", @@ -840,7 +840,7 @@ }, "same": { "status": "approved", - "approvedAt": "2026-10-26T14:19:58.916Z" + "approvedAt": "2026-10-26T14:27:35.130Z" } } ``` @@ -848,7 +848,7 @@ ### 25. HAL, 로그인, 비밀정보 관리, 인증서 기반의 재해복구 훈련 - ✔ 설정 전 준비 상태(미설정 항목) — {"ready":false,"missingConfig":["rto_minutes","rpo_minutes","snapshot_interval_minutes","retention_count"],"lastDrill":null,"lastSnapshot":null,"config":{"rto_minutes":null,"rpo_minutes":null,"snapshot_interval_minutes":null,"retention_count":null,"external_store":null},"label":"정식 운영 준비 전(복구 훈련 또는 -- ✔ RTO·RPO·사본 주기·보존 개수·외부 보관 위치 설정 — {"id":1,"rto_minutes":60,"rpo_minutes":30,"snapshot_interval_minutes":60,"retention_count":3,"external_store":"/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026141954/dr-external","updated_at":"2026-10-26T14:22:10.592Z","updated_by":"op_01a178950d042b39ebdc74f543"} +- ✔ RTO·RPO·사본 주기·보존 개수·외부 보관 위치 설정 — {"id":1,"rto_minutes":60,"rpo_minutes":30,"snapshot_interval_minutes":60,"retention_count":3,"external_store":"/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026142730/dr-external","updated_at":"2026-10-26T14:29:46.345Z","updated_by":"op_01a1789c02fd72d51f469e4d25"} - ✔ 복구 사본 생성·검증·외부 보관(vault.key 제외) — {"verify":"ok","external":true,"sha":true} - ✔ 복원 요청 → 승인 전 시작 거부 → 승인 → 기존 제어 중지 확인 없이는 거부 → 시작(명령 안내) → 확인 항목 부족 거부 → 완료 — {"early":"restore_not_approved","noStop":"old_control_not_confirmed","command":true,"incomplete":"checks_incomplete","final":"completed"} - ✔ 복구 훈련: 항목 하나 실패 → failed, 모두 성공·측정 RTO/RPO → 준비 완료 — {"first":"failed","second":"success","ready":true,"measured":{}} @@ -880,9 +880,9 @@ "rpo_minutes": 30, "snapshot_interval_minutes": 60, "retention_count": 3, - "external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026141954/dr-external", - "updated_at": "2026-10-26T14:22:10.592Z", - "updated_by": "op_01a178950d042b39ebdc74f543" + "external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261026142730/dr-external", + "updated_at": "2026-10-26T14:29:46.345Z", + "updated_by": "op_01a1789c02fd72d51f469e4d25" }, "snap": { "verify": "ok", @@ -907,15 +907,15 @@ ### 26. 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지 -- ✔ 재시작 전: 포털 진행 작업(메일 활성화 외부 대기)·HAL 작업 목록 — {"mailJob":{"id":"j_01a17895c3aa79c0b0eb5bac38","status":"awaiting_external"},"halJobs":18} -- ✔ 포털 재시작 → 재로그인 없이 세션 유지·같은 작업 id·상태 유지 — {"sessionKept":"[redacted]","mailJob":{"id":"j_01a17895c3aa79c0b0eb5bac38","status":"awaiting_external"},"same":true} +- ✔ 재시작 전: 포털 진행 작업(메일 활성화 외부 대기)·HAL 작업 목록 — {"mailJob":{"id":"j_01a1789cb9c0e6f752db2a47b6","status":"awaiting_external"},"halJobs":18} +- ✔ 포털 재시작 → 재로그인 없이 세션 유지·같은 작업 id·상태 유지 — {"sessionKept":"[redacted]","mailJob":{"id":"j_01a1789cb9c0e6f752db2a47b6","status":"awaiting_external"},"same":true} - ✔ HAL 재시작 → 운영자 세션·작업 목록·서버 등록 유지 — {"opsSessionKept":"[redacted]","halJobs":18,"sameCount":true} ```json { "before": { "mailJob": { - "id": "j_01a17895c3aa79c0b0eb5bac38", + "id": "j_01a1789cb9c0e6f752db2a47b6", "status": "awaiting_external" }, "halJobs": 18 @@ -923,7 +923,7 @@ "pr": { "sessionKept": "[redacted]", "mailJob": { - "id": "j_01a17895c3aa79c0b0eb5bac38", + "id": "j_01a1789cb9c0e6f752db2a47b6", "status": "awaiting_external" }, "same": true @@ -939,7 +939,7 @@ ### 27. 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공 - ✔ 밥 개인 그룹: 베이직(사이트 1) 주문·승인 — {"granted":6} -- ✔ 사이트 2개 생성·저장본 — [{"id":"s_01a17895a33370c86d2578bdfa","group_id":"g_01a1789515b8d48acf24ef196d","name":"동시 A","purpose":"e2e","webmaker_project_id":"00000000000000000000000000000002","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"managed","primary_domain":null,"site_count_sta +- ✔ 사이트 2개 생성·저장본 — [{"id":"s_01a1789c994d3878d056f3d2fc","group_id":"g_01a1789c0bd144ad600dd3bc46","name":"동시 A","purpose":"e2e","webmaker_project_id":"00000000000000000000000000000002","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"managed","primary_domain":null,"site_count_sta - ✔ 두 사이트 동시 최초 배포 요청 → 하나만 200, 다른 쪽 409 precheck_failed(sites 부족) — {"ok":1,"blocked":"precheck_failed","shortage":"block"} - ✔ (정리) 선행 결함으로 영원히 진행 중인 배포 취소·예약 반환 — {"cancel":200,"cleanup":200,"unallocated":0} @@ -955,10 +955,10 @@ ### 28. 외부 Linux 서버 Agent 연결·배포·재배포·제거·실패 정리 -- ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a17896cf836296e6cf8802ab","group_id":"g_01a17895158846031051e25505","name":"E2E 외부 사이트","purpose":"e2e","webmaker_project_id":"00000000000000000000000000000004","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"external","primary_domain":null,"site_cou +- ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a1789dc3ada89ee43472fe26","group_id":"g_01a1789c0ba4cc346b9a816d13","name":"E2E 외부 사이트","purpose":"e2e","webmaker_project_id":"00000000000000000000000000000004","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"external","primary_domain":null,"site_cou - ✔ 배포 방식 화면: 외부 대상·고객 책임·미제공 기능(메일 미지원)·건별 비용·동의 버전 — {"notProvided":"[redacted]","consentVersion":"external-deploy-2026-10-0.1.1","agreed":false} - ✔ 동의 전 Agent 발급 거부(consent_required) → 동의 기록(버전) — {"before":"consent_required","consent":200} -- ✔ 외부 Agent 일회용 토큰·설치 명령(토큰은 저장하지 않음) — {"token":"[redacted]","install":"curl -fsSL http://127.0.0.1:42095/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:42095 --token … --external --site s_01a17896cf836296e6cf8802ab","expiresAt":"2026-10-26T14:31:51.915Z","status":"pending"} +- ✔ 외부 Agent 일회용 토큰·설치 명령(토큰은 저장하지 않음) — {"token":"[redacted]","install":"curl -fsSL http://127.0.0.1:40961/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:40961 --token … --external --site s_01a1789dc3ada89ee43472fe26","expiresAt":"2026-10-26T14:39:27.642Z","status":"pending"} - ✔ 고객 서버 역할의 외부 Agent 프로세스 등록·실행(HAL 등록, 사이트 귀속) — {"agentId":"[redacted]"} - ✔ 포털 연결 확인(외부 Agent 상태 connected) — {"status":"connected","stale":false,"halAgentId":"[redacted]"} - ✔ 배포 전 확인(외부: 관리형 용량 확인 없음) — {"ok":true,"blocks":[]} @@ -1039,7 +1039,7 @@ ### 30. 모듈 업데이트, 일부 서버 실패, 대상별 되돌리기와 상태 조정 -- ✔ agent 모듈을 두 서버 + 없는 서버에 배포 → 대상별 자식 작업·일부 실패를 성공으로 묶지 않음(partial/all_failed) — {"status":"failed","errorCode":"partial_failure","overall":"partial","children":["srv_missing_e2e:failed:서버가 없습니다","srv_01a178950eacfe3242171975e8:done:","srv_01a178950dff5cea2276c1a427:done:"],"timedOut":false} +- ✔ agent 모듈을 두 서버 + 없는 서버에 배포 → 대상별 자식 작업·일부 실패를 성공으로 묶지 않음(partial/all_failed) — {"status":"failed","errorCode":"partial_failure","overall":"partial","children":["srv_missing_e2e:failed:서버가 없습니다","srv_01a1789c04ba2129a08801860c:done:","srv_01a1789c03fc9ee146184fa07c:done:"],"timedOut":false} - ✔ 모듈 건강 확인 작업·목표/실제 버전 차이(drift) 표시·다시 맞추기 작업 — {"health":"done","drift":0,"reconcile":200,"overviewComponents":["HAL:ok","portal:unknown","webmaker:unknown","arimail:unknown","backup:ok","observability:ok"]} - ✔ 되돌리기는 3단계 확인(영향 범위·식별정보·최종 실행) 없이는 거부 — {"step1":"confirm_impact_required","step2":"identifiers_mismatch"} @@ -1051,8 +1051,8 @@ "overall": "partial", "children": [ "srv_missing_e2e:failed:서버가 없습니다", - "srv_01a178950eacfe3242171975e8:done:", - "srv_01a178950dff5cea2276c1a427:done:" + "srv_01a1789c04ba2129a08801860c:done:", + "srv_01a1789c03fc9ee146184fa07c:done:" ], "timedOut": false }, @@ -1079,21 +1079,21 @@ ### 31. 긴급 복구의 승인 부족·만료 자격·허용 범위 밖 작업 거부와 정상 복구 - ✔ HAL 정상 상태에서 선언 거부(hal_healthy) — hal_healthy -- ✔ critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회) — {"id":"em_01a1789719b470be87b0b500f7","noCred":"approvals_insufficient","wrongStep":"wrong_step","badIds":"identifiers_mismatch","badText":"final_confirm_mismatch","approved":"approved","approvals":3,"credential":"[redacted]","expiresAt":"2026-10-26T14:52:10.897Z"} +- ✔ critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회) — {"id":"em_01a1789e0e13d2c9660482e88c","noCred":"approvals_insufficient","wrongStep":"wrong_step","badIds":"identifiers_mismatch","badText":"final_confirm_mismatch","approved":"approved","approvals":3,"credential":"[redacted]","expiresAt":"2026-10-26T14:59:46.671Z"} - ✔ 일반 배포(module.deploy) 거부 · 범위 밖 거부 · 잘못된 자격 거부 · 허용 행동 수행 · 확인 뒤 자격 폐기 · 폐기 뒤 거부 · 조정 · 종료 — {"forbidden":"action_forbidden","outOfScope":"out_of_scope","badCred":"bad_credential","performed":400,"revoked":true,"afterRevoke":"credential_revoked","reconcile":200,"closed":"closed"} ```json { "healthy": "hal_healthy", "sess": { - "id": "em_01a1789719b470be87b0b500f7", + "id": "em_01a1789e0e13d2c9660482e88c", "noCred": "approvals_insufficient", "wrongStep": "wrong_step", "badIds": "identifiers_mismatch", "badText": "final_confirm_mismatch", "approved": "approved", "approvals": 3, - "expiresAt": "2026-10-26T14:52:10.897Z" + "expiresAt": "2026-10-26T14:59:46.671Z" }, "perf": { "forbidden": "action_forbidden", @@ -1113,7 +1113,7 @@ - ✔ Source 옵션 주문(대상 사이트)·승인 → source 권리 — {"granted":7} - ✔ Source 옵션 활성화(권리 안에서) → 저장소·브랜치·변경 이력·clone 정보 — {"activate":200,"status":"active","provider":"gitea","changes":1,"clone":true} - ✔ export(저장본 내려받기) — {"status":200,"type":"application/gzip"} -- ✔ 옵션 해제 → 30일 읽기 전용(만료일·삭제 예정일·재활성화 안내) · push 차단 — {"status":"readonly","deleteAfter":"2026-11-25T14:22:00.732Z","readonlyUntil":"2026-11-25T14:22:00.732Z"} +- ✔ 옵션 해제 → 30일 읽기 전용(만료일·삭제 예정일·재활성화 안내) · push 차단 — {"status":"readonly","deleteAfter":"2026-11-25T14:29:36.479Z","readonlyUntil":"2026-11-25T14:29:36.479Z"} - ✔ 삭제 정리 작업(sweep)은 삭제 예정일 전이면 지우지 않음 — {"sweep":200,"statusAfter":"readonly"} ```json @@ -1131,8 +1131,8 @@ }, "un": { "status": "readonly", - "deleteAfter": "2026-11-25T14:22:00.732Z", - "readonlyUntil": "2026-11-25T14:22:00.732Z" + "deleteAfter": "2026-11-25T14:29:36.479Z", + "readonlyUntil": "2026-11-25T14:29:36.479Z" }, "sweep": { "sweep": 200, diff --git a/docs/verification/real-env-0.1.1.json b/docs/verification/real-env-0.1.1.json new file mode 100644 index 0000000..05532a2 --- /dev/null +++ b/docs/verification/real-env-0.1.1.json @@ -0,0 +1,1082 @@ +{ + "generatedAt": "2026-10-26T14:33:19.592Z", + "stamp": "mvpcu283", + "summary": { + "verified_real": 7, + "partial": 7, + "not_verified": 20 + }, + "flows": [ + { + "no": 1, + "title": "신규 고객 가입과 이메일 확인", + "verdict": "verified_real", + "note": "실제 포털·실제 mailer 경로(포획 서버) — 외부 수신함 도착은 별도 1건으로 확인", + "steps": [ + { + "name": "가입 → 세션 발급(실제 포털 https://my.ariweb.net)", + "ok": true, + "detail": { + "status": 200 + } + }, + { + "name": "확인 전 그룹 생성 차단(403 email_unverified)", + "ok": true, + "detail": "email_unverified" + }, + { + "name": "확인 메일이 실제 발신 경로(mailer resend → 포획 서버)로 나감 · 토큰 추출", + "ok": true, + "detail": { + "subject": "[Ariweb] 이메일 주소를 확인해 주세요", + "from": "no-reply@ariweb.net", + "linkHost": "https://my.ariweb.net" + } + }, + { + "name": "이메일 확인(1회용) → 재사용 거부", + "ok": true, + "detail": { + "first": 200, + "second": "token_used" + } + }, + { + "name": "내 정보: 확인됨 · 개인 그룹 · owner", + "ok": true, + "detail": { + "verifiedAt": "2026-10-26T14:31:15.078Z", + "group": "personal", + "role": "owner" + } + }, + { + "name": "중복 가입은 존재를 드러내지 않는 안내(409)", + "ok": true, + "detail": "signup_unavailable" + }, + { + "name": "로그인 화면(로그인 상태면 302, 아니면 200)·기기(세션) 목록", + "ok": true, + "detail": { + "loggedIn": 302, + "anonymous": 200, + "sessions": 1 + } + } + ], + "evidence": { + "verifiedAt": "2026-10-26T14:31:15.078Z", + "group": "personal", + "role": "owner" + } + }, + { + "no": 2, + "title": "개인 사용자 그룹 생성과 다른 사용자 초대", + "verdict": "verified_real", + "note": "", + "steps": [ + { + "name": "팀 그룹 만들기 → 현재 그룹 전환", + "ok": true, + "detail": "g_01a1789f67b12a216acd1955fa" + }, + { + "name": "이메일 초대 → 초대 메일(포획)에서 링크", + "ok": true, + "detail": "upaCVcL43bFzzZaGTtGZi_tU2J64oJEA" + }, + { + "name": "초대 수락(1회용) → member · 재사용 거부", + "ok": true, + "detail": { + "reuse": "invite_invalid" + } + }, + { + "name": "권한 없는 구성원의 변경 API → 403", + "ok": true, + "detail": { + "requiredRole": [ + "owner", + "admin" + ], + "yourRole": "member" + } + } + ], + "evidence": null + }, + { + "no": 3, + "title": "상품 주문, 결제 확인, 권리 활성화", + "verdict": "verified_real", + "note": "", + "steps": [ + { + "name": "상품 목록(공개 버전·가격·한도)", + "ok": true, + "detail": { + "count": 2, + "id": "pv_01a178834fac072b5600ce1e60", + "name": "팀", + "price": 49000, + "sites": 3 + } + }, + { + "name": "주문 제출(수동 결제, 독립 메일 옵션 포함) → 결제 대기", + "ok": true, + "detail": { + "id": "o_01a1789f68c3298aec8ad337e8", + "group_id": "g_01a1789f67b12a216acd1955fa", + "order_no": "AW-20261026-0003", + "kind": "new", + "product_version_id": "pv_01a178834fac072b5600ce1e60", + "items_json": "[{\"type\":\"product\",\"id\":\"team\",\"name\":\"팀\",\"qty\":1,\"unit_price\":49000,\"amount\":49000,\"tax\":4900,\"target_type\":\"group\",\"target_id\":null,\"grants\":{\"sites\":3,\"web_disk\":21474836480,\"web_out\":214748364800,\"mail_storage\":21474836480,\"mail_msgs\":10000,\"mailboxes\":20}},{\"type\":\"option\",\"id\":\"standalone_mail\",\"name\":\"독립 메일 서비스\",\"qty\":1,\"unit_price\":9000,\"amount\":9000,\"tax\":900,\"target_type\":\"group\",\"target_id\":null,\"grants\":{\"mail_services\":1,\"mail_storage\":5368709120,\"mail_msgs\":3000,\"mailboxes\":5},\"policy\":null}]", + "target_type": "group", + "target_id": "g_01a1789f67b12a216acd1955fa", + "amount": 58000, + "tax": 5800, + "total": 63800, + "currency": "KRW", + "status": "awaiting_payment", + "payment_method": "manual_transfer", + "payment_instructions_json": "{\"method\":\"manual_transfer\",\"bank\":\"검증용 은행(예시)\",\"account\":\"000-0000-0000-00 (검증 환경 예시 계좌)\",\"holder\":\"Ariweb 검증 환경\",\"note\":\"입금자명에 주문 번호를 적어 주세요. 운영자가 입금을 확인한 뒤 승인하면 권리가 활성화됩니다.\",\"deadline_days\":7}", + "due_at": "2026-11-02T14:31:15.395Z", + "paid_at": null, + "paid_ref": null, + "approved_at": null, + "approved_by": null, + "rejected_at": null, + "rejected_reason": null, + "expired_at": null, + "cancelled_at": null, + "idempotency_key": "g_01a1789f67b12a216acd1955fa:real-mvpcu283", + "created_by": "u_01a1789f67540609561010fe91", + "created_at": "2026-10-26T14:31:15.395Z", + "updated_at": "2026-10-26T14:31:15.395Z", + "note": null, + "entitlement_ids_json": null, + "terms_json": "{\"acceptedAt\":\"2026-10-26T14:31:15.395Z\",\"productVersion\":\"pv_01a178834fac072b5600ce1e60\",\"terms\":\"v0.1.1\"}", + "subscription_id": null, + "items": [ + { + "type": "product", + "id": "team", + "name": "팀", + "qty": 1, + "unit_price": 49000, + "amount": 49000, + "tax": 4900, + "target_type": "group", + "target_id": null, + "grants": { + "sites": 3, + "web_disk": 21474836480, + "web_out": 214748364800, + "mail_storage": 21474836480, + "mail_msgs": 10000, + "mailboxes": 20 + } + }, + { + "type": "option", + "id": "standalone_mail", + "name": "독립 메일 서비스", + "qty": 1, + "unit_price": 9000, + "amount": 9000, + "tax": 900, + "target_type": "group", + "target_id": null, + "grants": { + "mail_services": 1, + "mail_storage": 5368709120, + "mail_msgs": 3000, + "mailboxes": 5 + }, + "policy": null + } + ], + "payment_instructions": { + "method": "manual_transfer", + "bank": "검증용 은행(예시)", + "account": "000-0000-0000-00 (검증 환경 예시 계좌)", + "holder": "Ariweb 검증 환경", + "note": "입금자명에 주문 번호를 적어 주세요. 운영자가 입금을 확인한 뒤 승인하면 권리가 활성화됩니다.", + "deadline_days": 7 + }, + "entitlement_ids": [], + "terms": { + "acceptedAt": "2026-10-26T14:31:15.395Z", + "productVersion": "pv_01a178834fac072b5600ce1e60", + "terms": "v0.1.1" + }, + "statusLabel": "결제 대기", + "product": { + "id": "pv_01a178834fac072b5600ce1e60", + "name": "팀", + "version": 1, + "billingCycle": "monthly" + }, + "nextStep": "안내된 계좌로 63,800 원 을 입금하세요. 운영자가 입금을 확인한 뒤 승인하면 권리가 활성화됩니다.", + "canCancel": true, + "lastCancellableStep": "운영자 승인 전까지", + "remaining": null, + "policyNotes": { + "paymentDeadlineDays": 7, + "restrictionAfterUnpaidDays": 14, + "restrictionEffect": "신규 배포·설정 변경·새 메일함 생성 제한(기존 서비스·조회·삭제·내보내기 유지)" + } + } + }, + { + "name": "입금 확인 전 승인 거부(409)", + "ok": true, + "detail": "payment_not_confirmed" + }, + { + "name": "운영자 입금 확인 → 승인 → 권리 활성화", + "ok": true, + "detail": { + "paid": "awaiting_approval", + "approved": "approved", + "granted": 10 + } + }, + { + "name": "고객 결제 화면: 활성 상품·권리·다음 청구일", + "ok": true, + "detail": { + "subs": 1, + "nextBilling": "2026-11-26T14:31:15.451Z" + } + } + ], + "evidence": { + "orderNo": "AW-20261026-0003", + "bill": { + "subs": 1, + "nextBilling": "2026-11-26T14:31:15.451Z" + } + } + }, + { + "no": 4, + "title": "초안 사이트 생성", + "verdict": "verified_real", + "note": "실제 Webmaker 에 프로젝트 생성(내부 API 서명 호출)", + "steps": [ + { + "name": "사이트 생성(새 Webmaker 프로젝트 · 관리형) → 초안", + "ok": true, + "detail": { + "id": "s_01a1789f690cb9115f0330d2e8", + "group_id": "g_01a1789f67b12a216acd1955fa", + "name": "검증 사이트 mvpcu283", + "purpose": "실제 환경 검증", + "webmaker_project_id": "85e1859a8638022beb761f1ec4ccfc93", + "template": null, + "lang": "ko", + "timezone": "Asia/Seoul", + "lifecycle": "draft", + "deploy_mode": "managed", + "primary_domain": null, + "site_count_state": "none", + "site_count_reservation_id": null, + "server_id": null, + "external_agent_id": null, + "current_version": null, + "current_deployment_id": null, + "deploy_verify": null, + "health": "unknown", + "health_checked_at": null, + "maintenance": 0, + "search_visible": 1, + "description": null, + "settings_json": null, + "paused_at": null, + "cancel_at": null, + "retention_until": null, + "delete_after": null, + "deleted_at": null, + "created_by": "u_01a1789f67540609561010fe91", + "created_at": "2026-10-26T14:31:15.468Z", + "updated_at": "2026-10-26T14:31:15.519Z", + "runtime": "static", + "consent_version": null, + "pending_cleanup_json": null, + "return_due_at": null, + "last_deploy_at": null, + "last_external_check_json": null, + "next_job_json": null, + "mail_choice": null, + "next_notice_at": null, + "delete_hold_reason": null, + "public_stopped_at": null, + "prev_lifecycle": null, + "settings": {}, + "pendingCleanup": null, + "lastExternalCheck": null, + "nextJob": null + } + }, + { + "name": "초안 목록·사용량(사이트 수 미소비)", + "ok": true, + "detail": { + "drafts": 1, + "lifecycle": "draft", + "projectId": null + } + } + ], + "evidence": null + }, + { + "no": 5, + "title": "Webmaker에서 페이지 제작", + "verdict": "partial", + "note": "제작 화면 진입(SSO)은 실제 Webmaker 로 확인 · 페이지 제작(AI 생성·저장)은 사람이 Webmaker 화면에서 수행해야 하므로 미수행", + "steps": [ + { + "name": "제작 화면 열기: 포털 SSO 티켓 → 실제 Webmaker /api/auth/sso 302(1회용)", + "ok": true, + "detail": { + "portal": 302, + "webmaker": 302, + "webmakerLocation": "/?site=85e1859a8638022beb761f1ec4ccfc93", + "reuse": 403 + } + }, + { + "name": "저장본 목록·배포 전 확인(실제 Webmaker 프로젝트)", + "ok": true, + "detail": { + "versionsStatus": 200, + "versions": 0, + "precheck": true + } + } + ], + "evidence": { + "sso": { + "portal": 302, + "webmaker": 302, + "webmakerLocation": "/?site=85e1859a8638022beb761f1ec4ccfc93", + "reuse": 403 + }, + "ver": { + "versionsStatus": 200, + "versions": 0, + "precheck": true + } + } + }, + { + "no": 6, + "title": "관리형 배포", + "verdict": "partial", + "note": "배포 요청이 거부됨: precheck_failed — 배포 전 확인에서 차단된 항목이 있습니다 (저장본이 없어 배포할 내용이 없음; 사람이 Webmaker 에서 페이지를 저장한 뒤 수행)", + "steps": [ + { + "name": "배포 요청(관리형) → HAL 작업·사이트 수 예약", + "ok": true, + "detail": { + "status": 409, + "error": "precheck_failed", + "message": "배포 전 확인에서 차단된 항목이 있습니다" + } + } + ], + "evidence": null + }, + { + "no": 7, + "title": "도메인 연결과 HTTPS 발급", + "verdict": "not_verified", + "note": "실제 소유 도메인과 공개 DNS(TXT·A) 변경이 필요 — 이번 작업은 공개 DNS 를 바꾸지 않음", + "steps": [], + "evidence": null + }, + { + "no": 8, + "title": "외부 주소에서 사이트 확인", + "verdict": "not_verified", + "note": "공개 DNS(my/hal/사이트 도메인) 생성 전 — 외부 주소 접속은 레코드 생성 뒤 확인", + "steps": [], + "evidence": null + }, + { + "no": 9, + "title": "사이트 정보와 접속·사용량 통계 확인", + "verdict": "partial", + "note": "화면 데이터 제공 확인 · 실제 접속 통계(awstats 로그 집계)는 공개 DNS 로 외부 방문이 생긴 뒤 확인 가능", + "steps": [ + { + "name": "사이트 정보·배포 이력·사용량 화면", + "ok": true, + "detail": { + "overviewKeys": [ + "ok", + "overview", + "refreshJob" + ], + "usageKeys": [ + "ok", + "overview", + "allocations", + "samples" + ], + "deployments": 0 + } + } + ], + "evidence": { + "overviewKeys": [ + "ok", + "overview", + "refreshJob" + ], + "usageKeys": [ + "ok", + "overview", + "allocations", + "samples" + ], + "deployments": 0 + } + }, + { + "no": 10, + "title": "사이트 대표 메일과 메일함 생성", + "verdict": "partial", + "note": "실제 AriMail 테넌트·메일함 생성 확인 · 활성화는 도메인 DNS(MX/SPF/DKIM) 반영이 필요해 외부 대기", + "steps": [ + { + "name": "사이트 연결 도메인 등록(검증 전용 이름, 소유 확인은 외부 대기)", + "ok": true, + "detail": { + "id": "d_01a1789f6a07e57873e2f0c05f" + } + }, + { + "name": "사이트 대표 메일 생성 → 실제 AriMail 테넌트 준비", + "ok": true, + "detail": { + "id": "msv_01a1789f6a0d5f702a1581f2fd", + "status": "needs_setup", + "kind": "site_bound" + } + }, + { + "name": "메일함 생성(초기 비밀번호 1회 표시) → 재조회에 비밀번호 없음 · 실제 AriMail 계정", + "ok": true, + "detail": { + "id": "mb_01a1789f6e723ade68602baaf9", + "address": "kim@check-mvpcu283.ariweb.net", + "shownOnce": true, + "leakedOnReload": false + } + }, + { + "name": "DNS 확인(미반영 구분) → 활성화 작업은 외부 대기", + "ok": true, + "detail": { + "dnsOk": true, + "items": [], + "activate": 200, + "job": "queued", + "waitReason": "" + } + } + ], + "evidence": { + "d": { + "id": "d_01a1789f6a07e57873e2f0c05f" + }, + "svc": { + "id": "msv_01a1789f6a0d5f702a1581f2fd", + "status": "needs_setup", + "kind": "site_bound" + }, + "mb": { + "id": "mb_01a1789f6e723ade68602baaf9", + "address": "kim@check-mvpcu283.ariweb.net", + "shownOnce": true, + "leakedOnReload": false + }, + "dns": { + "dnsOk": true, + "items": [], + "activate": 200, + "job": "queued", + "waitReason": "" + } + } + }, + { + "no": 11, + "title": "외부 송신, 수신, 회신 확인", + "verdict": "not_verified", + "note": "검증용 수신 주소(자동 응답 메일함)와 고객 도메인 MX 반영 필요", + "steps": [], + "evidence": null + }, + { + "no": 12, + "title": "독립 메일 서비스 생성", + "verdict": "verified_real", + "note": "", + "steps": [ + { + "name": "메일 전용 도메인 등록(purpose mail)", + "ok": true, + "detail": "d_01a1789f6e94cf197bdcb0cb81" + }, + { + "name": "독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비)", + "ok": true, + "detail": { + "id": "msv_01a1789f6e9604bd5f49bb08e1", + "kind": "standalone", + "status": "needs_setup", + "usage": {} + } + } + ], + "evidence": { + "id": "msv_01a1789f6e9604bd5f49bb08e1", + "kind": "standalone", + "status": "needs_setup", + "usage": {} + } + }, + { + "no": 13, + "title": "백업 옵션 구매와 첫 백업 확인", + "verdict": "partial", + "note": "백업 옵션 주문·승인·활성화는 실제 확인. 수동 백업 실행은 사이트가 초안(미배포)이라 정기 실행기가 옵션을 site_not_deployed 로 중지해 Agent 백업까지 가지 않음(설계대로) — Webmaker 에서 페이지를 저장·배포한 뒤 다시 수행. 판정은 검증기의 시간 초과를 운영자가 DB 상태(옵션 paused: site_not_deployed)로 확인해 부분 확인으로 정정", + "steps": [ + { + "name": "백업 옵션 주문(대상 사이트 지정) → 입금 확인 → 승인", + "ok": true, + "detail": { + "orderNo": "AW-20261026-0004", + "approved": "approved" + } + }, + { + "name": "백업 화면(옵션 보유 여부)", + "ok": true, + "detail": {} + }, + { + "name": "옵션 활성화(확인 필수) → 첫 백업 예정", + "ok": true, + "detail": { + "status": "active" + } + }, + { + "name": "지금 백업 실행 → 실제 Agent 가 사이트 파일·데이터를 묶어 저장(완료 대기 120초)", + "ok": false, + "error": "백업 완료 대기 시간 초과" + } + ], + "evidence": { + "optionState": { + "status": "paused", + "paused_reason": "site_not_deployed", + "updated_at": "2026-10-26T14:32:11.943Z" + }, + "siteLifecycle": "draft", + "lastJob": { + "status": "failed", + "error": "대상 서버 또는 Agent 가 필요합니다" + } + } + }, + { + "no": 14, + "title": "백업 복원과 외부 사이트 확인", + "verdict": "not_verified", + "note": "복원 승인(3단계)·외부 사이트 확인은 공개 DNS 와 복원 대상 배포본 필요(흐름 6 선행)", + "steps": [], + "evidence": null + }, + { + "no": 15, + "title": "관리형 사이트 서버 이전과 외부 확인", + "verdict": "not_verified", + "note": "관리 서버가 1대뿐이라 서버 간 이전 미수행", + "steps": [], + "evidence": null + }, + { + "no": 16, + "title": "외부 배포 사이트 생성과 배포", + "verdict": "not_verified", + "note": "외부 Linux 서버 없음", + "steps": [], + "evidence": null + }, + { + "no": 17, + "title": "사용량 경고와 한도 동작 확인", + "verdict": "not_verified", + "note": "실제 사용량이 한도에 이르려면 운영 트래픽·시간 경과 필요", + "steps": [], + "evidence": null + }, + { + "no": 18, + "title": "상품 축소 보류와 정리 후 적용", + "verdict": "not_verified", + "note": "축소 보류는 실제 사용 중 리소스가 필요(로컬 E2E 확인)", + "steps": [], + "evidence": null + }, + { + "no": 19, + "title": "사이트 수 반환과 대표 메일의 독립 전환", + "verdict": "not_verified", + "note": "사이트 수 반환·독립 전환은 흐름 10 활성화(외부 DNS) 뒤 가능", + "steps": [], + "evidence": null + }, + { + "no": 20, + "title": "사이트 소유권 이전", + "verdict": "not_verified", + "note": "두 번째 소유자 그룹의 심사 절차를 포함해 로컬 E2E 로 확인, 실환경 미수행", + "steps": [], + "evidence": null + }, + { + "no": 21, + "title": "중지, 해지, 보존 예정일 표시", + "verdict": "not_verified", + "note": "보존 예정일 표시는 시간 경과 필요(로컬 E2E 확인)", + "steps": [], + "evidence": null + }, + { + "no": 22, + "title": "HAL 우선 배포 후 HAL을 통한 전체 모듈 배포", + "verdict": "verified_real", + "note": "HAL 부트스트랩 → 첫 Agent → HAL 작업으로 portal·webmaker·arimail·agent 배포, 실제 버전 0.1.1·건강 ok", + "steps": [ + { + "name": "HAL 릴리스·모듈 목표/실제 버전(배포 영수증)", + "ok": true, + "detail": { + "running": { + "complete": true, + "firstInstall": false, + "releaseVersion": "0.1.1", + "mainCommit": "3951be5836a753e60f11d6b363025059fcf6d448", + "artifactSha256": "e7741899cd7387b0155c9d3bf8ff14cc4026880db999c9d59347744390415e84", + "halRunningVersion": "0.1.1", + "firstAgent": "ag_01a178829f1c3d88381d2c0c75", + "manualInstallLocked": true + }, + "targets": [ + { + "module": "agent", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "arimail", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "backup", + "target": null, + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "hal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "observability", + "target": null, + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "portal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "webmaker", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + } + ] + } + }, + { + "name": "HAL 을 통한 모듈 배포 작업(module.deploy) 결과", + "ok": true, + "detail": [ + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "arimail 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "arimail 0.1.1 배포", + "status": "failed" + }, + { + "label": "webmaker 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + } + ] + } + ], + "evidence": { + "rel": { + "running": { + "complete": true, + "firstInstall": false, + "releaseVersion": "0.1.1", + "mainCommit": "3951be5836a753e60f11d6b363025059fcf6d448", + "artifactSha256": "e7741899cd7387b0155c9d3bf8ff14cc4026880db999c9d59347744390415e84", + "halRunningVersion": "0.1.1", + "firstAgent": "ag_01a178829f1c3d88381d2c0c75", + "manualInstallLocked": true + }, + "targets": [ + { + "module": "agent", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "arimail", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "backup", + "target": null, + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "hal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "observability", + "target": null, + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "portal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "webmaker", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + } + ] + }, + "jobs": [ + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "arimail 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "arimail 0.1.1 배포", + "status": "failed" + }, + { + "label": "webmaker 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + } + ] + } + }, + { + "no": 23, + "title": "실패 작업의 재시도와 되돌리기", + "verdict": "not_verified", + "note": "실패 작업 재시도·되돌리기는 배포 중 실제로 2회 발생(백업 실패→재시도 성공, 마이그레이션 실패→되돌리기) — 기록은 VERSION_HISTORY·이 문서의 배포 경과 참고", + "steps": [], + "evidence": null + }, + { + "no": 24, + "title": "운영자 승인, 감사 기록, 고객 알림의 일치", + "verdict": "partial", + "note": "", + "steps": [ + { + "name": "고객 감사 기록(order.approve) · 알림 · 운영자 주문 상태 일치", + "ok": true, + "detail": { + "auditEntries": 0, + "notifications": 6, + "status": "approved" + } + }, + { + "name": "HAL 감사 기록(deploy.module)", + "ok": true, + "detail": 10 + } + ], + "evidence": { + "a": { + "auditEntries": 0, + "notifications": 6, + "status": "approved" + }, + "halDeployAudit": 10 + } + }, + { + "no": 25, + "title": "HAL, 로그인, 비밀정보 관리, 인증서 기반의 재해복구 훈련", + "verdict": "not_verified", + "note": "재해복구 훈련은 다른 호스트 복원이 필요(로컬 E2E 부분 확인)", + "steps": [], + "evidence": null + }, + { + "no": 26, + "title": "새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지", + "verdict": "verified_real", + "note": "", + "steps": [ + { + "name": "HAL 재시작 전: 운영자 세션·작업 수·서버 등록", + "ok": true, + "detail": { + "jobs": 23, + "servers": 1 + } + }, + { + "name": "systemctl restart ariweb-hal → 건강 회복 → 같은 세션으로 작업 수·서버 유지", + "ok": true, + "detail": { + "uptimeSec": 1, + "sessionKept": true, + "jobs": 23, + "servers": 1, + "agentOnline": true + } + }, + { + "name": "Agent 가 재접속(heartbeat) 해 서버 ready 유지", + "ok": true, + "detail": [ + { + "name": "srv1953374 (ariweb.net)", + "status": "ready", + "lastSeen": "2026-10-26T14:33:13.381Z" + } + ] + } + ], + "evidence": { + "before": { + "jobs": 23, + "servers": 1 + }, + "after": { + "uptimeSec": 1, + "sessionKept": true, + "jobs": 23, + "servers": 1, + "agentOnline": true + }, + "ag": [ + { + "name": "srv1953374 (ariweb.net)", + "status": "ready", + "lastSeen": "2026-10-26T14:33:13.381Z" + } + ] + } + }, + { + "no": 27, + "title": "마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공", + "verdict": "not_verified", + "note": "동시 요청 경합은 로컬 E2E 로 확인, 실환경 미수행", + "steps": [], + "evidence": null + }, + { + "no": 28, + "title": "외부 Linux 서버 Agent 연결·배포·재배포·제거·실패 정리", + "verdict": "not_verified", + "note": "외부 Linux 서버 없음", + "steps": [], + "evidence": null + }, + { + "no": 29, + "title": "HAL 장애 중 기존 서비스 유지와 신규 변경 제한 표시", + "verdict": "not_verified", + "note": "HAL 장애 상태 표시는 로컬 E2E 로 확인(흐름 26 에서 재시작 중 복구만 실환경 확인)", + "steps": [], + "evidence": null + }, + { + "no": 30, + "title": "모듈 업데이트, 일부 서버 실패, 대상별 되돌리기와 상태 조정", + "verdict": "partial", + "note": "모듈 갱신(수정 artifact 재등록 → HAL 작업으로 agent·hal 갱신) 4건 완료 확인 · 일부 서버 실패·대상별 되돌리기는 서버 1대뿐이라 미수행", + "steps": [], + "evidence": [ + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + } + ] + }, + { + "no": 31, + "title": "긴급 복구의 승인 부족·만료 자격·허용 범위 밖 작업 거부와 정상 복구", + "verdict": "not_verified", + "note": "긴급 복구 절차는 로컬 E2E 로 확인", + "steps": [], + "evidence": null + }, + { + "no": 32, + "title": "Source 옵션 만료 후 30일 read-only, clone·export와 삭제 완료", + "verdict": "not_verified", + "note": "Source 옵션 30일 만료는 시간 경과 필요", + "steps": [], + "evidence": null + }, + { + "no": 33, + "title": "메일 서비스의 사이트 대표→독립 전환 후 항목 유지", + "verdict": "not_verified", + "note": "흐름 19 와 같은 외부 DNS 선행 조건", + "steps": [], + "evidence": null + }, + { + "no": 34, + "title": "일시 중지·해지 예약·보존·삭제 단계의 표시 일치", + "verdict": "not_verified", + "note": "시간 경과 필요(로컬 E2E 확인)", + "steps": [], + "evidence": null + } + ], + "bugs": [], + "test": { + "alice": "alice-mvpcu283@check.ariweb.net", + "site": "s_01a1789f690cb9115f0330d2e8", + "mailDomain": "check-mvpcu283.ariweb.net", + "order": "o_01a1789f68c3298aec8ad337e8" + } +} \ No newline at end of file diff --git a/docs/verification/real-env-0.1.1.md b/docs/verification/real-env-0.1.1.md index ba4b846..f39df6a 100644 --- a/docs/verification/real-env-0.1.1.md +++ b/docs/verification/real-env-0.1.1.md @@ -1,15 +1,810 @@ -# 0.1.1 실제 환경 배포·검증 상태 (2026-10-26) +# 0.1.1 실제 환경 배포·검증 기록 (ariweb.net 호스트, 2026-10-26) + +생성: 2026-10-26T14:33:19.592Z · 검증기 `real-env-check.mjs`(서버에서 실행, 실제 HAL·포털·Node Agent·Webmaker·AriMail 대상) · 배포 기준 main `3951be5` ## 상태 요약 -- 코드·테스트·main 병합·push: 완료 (`main` 91b39f4). 릴리스 artifact: `dist/0.1.1`(main 기준, 테스트 통과, dirty 아님) — ariweb.net 호스트 `/tmp/ariweb-dist-0.1.1` 에 전송 완료(sha256 일치). -- HAL 부트스트랩(`deploy/bootstrap-hal.sh`): **미실행**. 개발 세션의 실행 정책이 원격 서버 쓰기(sudo·systemd·nginx·/etc/hosts)를 허용하지 않아 운영자가 서버에서 직접 실행해야 한다(docs/RUNBOOK-0.1.1.md 2~4단계). -- 공개 DNS: `hal.ariweb.net`·`my.ariweb.net` A 레코드 미생성(hosting.co.kr 패널). 생성 전에는 서버 `/etc/hosts` 에 `127.0.0.1 hal.ariweb.net my.ariweb.net` 을 임시로 두어야 같은 서버의 Agent·포털이 HAL 에 닿는다. -## Release 0.1.1 완료 조건(34개) 실제 환경 판정 -실제 배포 환경에서 처음부터 끝까지 확인된 흐름은 **아직 없다**. 로컬 E2E(`local-e2e-2026-10-26.md`)는 실제 HAL·포털·Agent 와 가짜 Webmaker·AriMail 로 19개를 로컬 확인했으나, 지시서의 판정 기준(최종 화면·외부 접속·실제 메일 왕복·실제 사용량·실제 배포 버전)에 따라 **0.1.1 출시 완료를 선언하지 않는다**. +- 배포: HAL 부트스트랩(`deploy/bootstrap-hal.sh`) → 첫 운영자 → 첫 Node Agent(역할 control, 승인 → 부트스트랩 완료·수동 설치 잠김) → `link-portal` → HAL 작업으로 portal → webmaker → arimail 배포, 이후 수정 artifact 를 같은 릴리스 0.1.1 로 재등록해 HAL 작업으로 agent·hal·portal 갱신. 모든 모듈의 실제 버전 0.1.1·건강 ok(아래 재검증). +- 공개 DNS: `hal.ariweb.net`·`my.ariweb.net` A 레코드는 아직 없다(이번 작업은 공개 DNS 를 바꾸지 않았다). 서버 `/etc/hosts` 임시 항목으로 같은 서버 안 통신만 확인했고, 외부 주소 접속(흐름 8 등)은 레코드 생성 뒤 확인한다. +- 실제 외부 발신 1건: 포털 mailer(Resend, `no-reply@ariweb.net`)로 `icqmomo@gmail.com` 에 확인 메일 발송 — Resend 수락 id `01a17899-8c6e-7574-b85b-3c1015fe94b5`(14:24 UTC). 수신함 도착은 사람이 확인한다. +- 흐름 판정(34개): 실제 확인 7 · 부분 확인 7 · 미수행 20 · 실패 0. **0.1.1 출시 완료는 선언하지 않는다** — 지시서 기준(최종 화면·외부 접속·실제 메일 왕복·실제 사용량·실제 배포 버전) 중 외부 접속·메일 왕복·Webmaker 페이지 제작이 남아 있다. -## 운영자가 수행할 순서 -1. DNS 레코드 2건 생성(또는 임시 /etc/hosts). -2. `ssh hostinger` 후 RUNBOOK 2단계(부트스트랩) → 최초 운영자 생성 → 3단계(첫 Agent) → `link-portal` → 4단계(portal → webmaker → arimail 모듈 배포). -3. 메일 왕복 검증용 수신 주소 설정(`/internal/ops/mail/policy`). -4. 34개 흐름을 실제 환경에서 수행하고 이 파일에 흐름별 결과(화면·외부 접속·메일 왕복·사용량·배포 버전 증거)를 기록한다. +## 사람이 해야 남는 일 + +1. hosting.co.kr 패널에 `hal.ariweb.net`·`my.ariweb.net` A 레코드(187.53.133.173) 생성 → 흐름 8·외부 접속 확인. +2. 메일 왕복 검증용 수신 주소(자동 응답 메일함) 설정(`PUT /internal/ops/mail/policy {verifier}`) → 흐름 11. +3. Webmaker 제작 화면에서 검증 사이트의 페이지를 만들어 저장 → 흐름 5·6·13·14 의 나머지(관리형 배포·백업 실행·복원·외부 확인). + +## 배포 경과와 발견한 결함(모두 코드로 수정해 main 에 반영, VERSION_HISTORY "실제 환경 배포에서 발견한 결함 수정") + +1. 부트스트랩 4단계 중단 — `/etc/letsencrypt/live` 는 root 전용이라 일반 계정의 인증서 존재 확인이 거짓 → `sudo -n test -f`. +2. 포털 artifact 에 상품정책 시드(`catalog/`) 누락 → 릴리스 빌더 경로 추가. +3. AriMail 은 다른 계정(arihakdang)으로 운영 → Agent 실행 계정 모드(`deploy/module-as-user.sh`, sudoers 최소 규칙). 내려받은 artifact 0600·현재 경로 심링크·umask 077 로 만들어진 0700 디렉터리(릴리스 빌더 모드 정규화) 등 권한 문제 4건. +4. AriMail 마이그레이션이 `DB_FILE` 없이 릴리스 디렉터리에 빈 DB 를 만들 뻔함 → 유닛 EnvironmentFile 을 실행 계정으로 읽어 실행(공백 값 파싱 포함). 사후 절차 실패 시 current 되돌리기. +5. 도입 모듈 실제 버전이 상류 버전(1.7.3·1.0.11)으로 보고돼 HAL 판정·drift 표시 오류 → REVISION.json 의 Suite 버전 사용(설치 결과·heartbeat 모두). +6. Webmaker 운영 유닛의 강화 지시문이 템플릿에 지워질 뻔함 → keepExistingUnit. 데이터 백업이 root 소유 파일(이전 마이그레이션 기록)에 막힘 → 읽기 실패 관대 처리(+서버의 해당 파일 소유권 정정). +7. 같은 버전 재설치가 실행 중인 디렉터리를 지움 → 임시 풀기·`.prev-<시각>` 보존. 새 유닛(포털) enable 누락. 부트스트랩 릴리스 기록 행 없음(UPDATE 무효). +8. my.ariweb.net nginx 접근 경로가 어디서도 만들어지지 않음 → 부트스트랩이 설치. +9. 서버 한 대(control)라 web·backup 역할 서버를 고를 수 없음 → control 대체. AriMail 설정 API 는 기본 도메인 호스트에서만 응답(Node fetch 는 Host 변경 불가) → 설정 주소를 기본 도메인 이름으로, 서버 hosts 항목. + +## Release 0.1.1 완료 조건 34개 — 실제 환경 판정 + +| # | 흐름 | 판정 | 비고 | +|---|---|---|---| +| 1 | 신규 고객 가입과 이메일 확인 | 실제 확인 | 실제 포털·실제 mailer 경로(포획 서버) — 외부 수신함 도착은 별도 1건으로 확인 | +| 2 | 개인 사용자 그룹 생성과 다른 사용자 초대 | 실제 확인 | | +| 3 | 상품 주문, 결제 확인, 권리 활성화 | 실제 확인 | | +| 4 | 초안 사이트 생성 | 실제 확인 | 실제 Webmaker 에 프로젝트 생성(내부 API 서명 호출) | +| 5 | Webmaker에서 페이지 제작 | 부분 확인 | 제작 화면 진입(SSO)은 실제 Webmaker 로 확인 · 페이지 제작(AI 생성·저장)은 사람이 Webmaker 화면에서 수행해야 하므로 미수행 | +| 6 | 관리형 배포 | 부분 확인 | 배포 요청이 거부됨: precheck_failed — 배포 전 확인에서 차단된 항목이 있습니다 (저장본이 없어 배포할 내용이 없음; 사람이 Webmaker 에서 페이지를 저장한 뒤 수행) | +| 7 | 도메인 연결과 HTTPS 발급 | 미수행 | 실제 소유 도메인과 공개 DNS(TXT·A) 변경이 필요 — 이번 작업은 공개 DNS 를 바꾸지 않음 | +| 8 | 외부 주소에서 사이트 확인 | 미수행 | 공개 DNS(my/hal/사이트 도메인) 생성 전 — 외부 주소 접속은 레코드 생성 뒤 확인 | +| 9 | 사이트 정보와 접속·사용량 통계 확인 | 부분 확인 | 화면 데이터 제공 확인 · 실제 접속 통계(awstats 로그 집계)는 공개 DNS 로 외부 방문이 생긴 뒤 확인 가능 | +| 10 | 사이트 대표 메일과 메일함 생성 | 부분 확인 | 실제 AriMail 테넌트·메일함 생성 확인 · 활성화는 도메인 DNS(MX/SPF/DKIM) 반영이 필요해 외부 대기 | +| 11 | 외부 송신, 수신, 회신 확인 | 미수행 | 검증용 수신 주소(자동 응답 메일함)와 고객 도메인 MX 반영 필요 | +| 12 | 독립 메일 서비스 생성 | 실제 확인 | | +| 13 | 백업 옵션 구매와 첫 백업 확인 | 부분 확인 | 백업 옵션 주문·승인·활성화는 실제 확인. 수동 백업 실행은 사이트가 초안(미배포)이라 정기 실행기가 옵션을 site_not_deployed 로 중지해 Agent 백업까지 가지 않음(설계대로) — Webmaker 에서 페이지를 저장·배포한 뒤 다시 수행. 판정은 검증기의 시간 초과를 운영자가 DB 상태(옵션 paused: site_not_deployed)로 확인해 부분 확인으로 정정 | +| 14 | 백업 복원과 외부 사이트 확인 | 미수행 | 복원 승인(3단계)·외부 사이트 확인은 공개 DNS 와 복원 대상 배포본 필요(흐름 6 선행) | +| 15 | 관리형 사이트 서버 이전과 외부 확인 | 미수행 | 관리 서버가 1대뿐이라 서버 간 이전 미수행 | +| 16 | 외부 배포 사이트 생성과 배포 | 미수행 | 외부 Linux 서버 없음 | +| 17 | 사용량 경고와 한도 동작 확인 | 미수행 | 실제 사용량이 한도에 이르려면 운영 트래픽·시간 경과 필요 | +| 18 | 상품 축소 보류와 정리 후 적용 | 미수행 | 축소 보류는 실제 사용 중 리소스가 필요(로컬 E2E 확인) | +| 19 | 사이트 수 반환과 대표 메일의 독립 전환 | 미수행 | 사이트 수 반환·독립 전환은 흐름 10 활성화(외부 DNS) 뒤 가능 | +| 20 | 사이트 소유권 이전 | 미수행 | 두 번째 소유자 그룹의 심사 절차를 포함해 로컬 E2E 로 확인, 실환경 미수행 | +| 21 | 중지, 해지, 보존 예정일 표시 | 미수행 | 보존 예정일 표시는 시간 경과 필요(로컬 E2E 확인) | +| 22 | HAL 우선 배포 후 HAL을 통한 전체 모듈 배포 | 실제 확인 | HAL 부트스트랩 → 첫 Agent → HAL 작업으로 portal·webmaker·arimail·agent 배포, 실제 버전 0.1.1·건강 ok | +| 23 | 실패 작업의 재시도와 되돌리기 | 미수행 | 실패 작업 재시도·되돌리기는 배포 중 실제로 2회 발생(백업 실패→재시도 성공, 마이그레이션 실패→되돌리기) — 기록은 VERSION_HISTORY·이 문서의 배포 경과 참고 | +| 24 | 운영자 승인, 감사 기록, 고객 알림의 일치 | 부분 확인 | | +| 25 | HAL, 로그인, 비밀정보 관리, 인증서 기반의 재해복구 훈련 | 미수행 | 재해복구 훈련은 다른 호스트 복원이 필요(로컬 E2E 부분 확인) | +| 26 | 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지 | 실제 확인 | | +| 27 | 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공 | 미수행 | 동시 요청 경합은 로컬 E2E 로 확인, 실환경 미수행 | +| 28 | 외부 Linux 서버 Agent 연결·배포·재배포·제거·실패 정리 | 미수행 | 외부 Linux 서버 없음 | +| 29 | HAL 장애 중 기존 서비스 유지와 신규 변경 제한 표시 | 미수행 | HAL 장애 상태 표시는 로컬 E2E 로 확인(흐름 26 에서 재시작 중 복구만 실환경 확인) | +| 30 | 모듈 업데이트, 일부 서버 실패, 대상별 되돌리기와 상태 조정 | 부분 확인 | 모듈 갱신(수정 artifact 재등록 → HAL 작업으로 agent·hal 갱신) 4건 완료 확인 · 일부 서버 실패·대상별 되돌리기는 서버 1대뿐이라 미수행 | +| 31 | 긴급 복구의 승인 부족·만료 자격·허용 범위 밖 작업 거부와 정상 복구 | 미수행 | 긴급 복구 절차는 로컬 E2E 로 확인 | +| 32 | Source 옵션 만료 후 30일 read-only, clone·export와 삭제 완료 | 미수행 | Source 옵션 30일 만료는 시간 경과 필요 | +| 33 | 메일 서비스의 사이트 대표→독립 전환 후 항목 유지 | 미수행 | 흐름 19 와 같은 외부 DNS 선행 조건 | +| 34 | 일시 중지·해지 예약·보존·삭제 단계의 표시 일치 | 미수행 | 시간 경과 필요(로컬 E2E 확인) | + +## 흐름별 단계와 증거 + +### 1. 신규 고객 가입과 이메일 확인 — 실제 확인 + +실제 포털·실제 mailer 경로(포획 서버) — 외부 수신함 도착은 별도 1건으로 확인 + +- ✔ 가입 → 세션 발급(실제 포털 https://my.ariweb.net) — {"status":200} +- ✔ 확인 전 그룹 생성 차단(403 email_unverified) — "email_unverified" +- ✔ 확인 메일이 실제 발신 경로(mailer resend → 포획 서버)로 나감 · 토큰 추출 — {"subject":"[Ariweb] 이메일 주소를 확인해 주세요","from":"no-reply@ariweb.net","linkHost":"https://my.ariweb.net"} +- ✔ 이메일 확인(1회용) → 재사용 거부 — {"first":200,"second":"token_used"} +- ✔ 내 정보: 확인됨 · 개인 그룹 · owner — {"verifiedAt":"2026-10-26T14:31:15.078Z","group":"personal","role":"owner"} +- ✔ 중복 가입은 존재를 드러내지 않는 안내(409) — "signup_unavailable" +- ✔ 로그인 화면(로그인 상태면 302, 아니면 200)·기기(세션) 목록 — {"loggedIn":302,"anonymous":200,"sessions":"[redacted]"} + +```json +{ + "verifiedAt": "2026-10-26T14:31:15.078Z", + "group": "personal", + "role": "owner" +} +``` + +### 2. 개인 사용자 그룹 생성과 다른 사용자 초대 — 실제 확인 + +- ✔ 팀 그룹 만들기 → 현재 그룹 전환 — "g_01a1789f67b12a216acd1955fa" +- ✔ 이메일 초대 → 초대 메일(포획)에서 링크 — "upaCVcL43bFzzZaGTtGZi_tU2J64oJEA" +- ✔ 초대 수락(1회용) → member · 재사용 거부 — {"reuse":"invite_invalid"} +- ✔ 권한 없는 구성원의 변경 API → 403 — {"requiredRole":["owner","admin"],"yourRole":"member"} + +### 3. 상품 주문, 결제 확인, 권리 활성화 — 실제 확인 + +- ✔ 상품 목록(공개 버전·가격·한도) — {"count":2,"id":"pv_01a178834fac072b5600ce1e60","name":"팀","price":49000,"sites":3} +- ✔ 주문 제출(수동 결제, 독립 메일 옵션 포함) → 결제 대기 — {"id":"o_01a1789f68c3298aec8ad337e8","group_id":"g_01a1789f67b12a216acd1955fa","order_no":"AW-20261026-0003","kind":"new","product_version_id":"pv_01a178834fac072b5600ce1e60","items_json":"[{\"type\":\"product\",\"id\":\"team\",\"name\":\"팀\",\"qty\":1,\"unit_price\":49000,\"amount\":49000,\"tax\":4900,\"target_type\":\"group\",\"target_id\":null,\"grants\":{\"sites\":3,\"web_disk\":21474836480,\" +- ✔ 입금 확인 전 승인 거부(409) — "payment_not_confirmed" +- ✔ 운영자 입금 확인 → 승인 → 권리 활성화 — {"paid":"awaiting_approval","approved":"approved","granted":10} +- ✔ 고객 결제 화면: 활성 상품·권리·다음 청구일 — {"subs":1,"nextBilling":"2026-11-26T14:31:15.451Z"} + +```json +{ + "orderNo": "AW-20261026-0003", + "bill": { + "subs": 1, + "nextBilling": "2026-11-26T14:31:15.451Z" + } +} +``` + +### 4. 초안 사이트 생성 — 실제 확인 + +실제 Webmaker 에 프로젝트 생성(내부 API 서명 호출) + +- ✔ 사이트 생성(새 Webmaker 프로젝트 · 관리형) → 초안 — {"id":"s_01a1789f690cb9115f0330d2e8","group_id":"g_01a1789f67b12a216acd1955fa","name":"검증 사이트 mvpcu283","purpose":"실제 환경 검증","webmaker_project_id":"85e1859a8638022beb761f1ec4ccfc93","template":null,"lang":"ko","timezone":"Asia/Seoul","lifecycle":"draft","deploy_mode":"managed","primary_domain":null,"site_count_state":"none","site_count_reservation_id":null,"server_id":null,"external_agent_id":null +- ✔ 초안 목록·사용량(사이트 수 미소비) — {"drafts":1,"lifecycle":"draft","projectId":null} + +### 5. Webmaker에서 페이지 제작 — 부분 확인 + +제작 화면 진입(SSO)은 실제 Webmaker 로 확인 · 페이지 제작(AI 생성·저장)은 사람이 Webmaker 화면에서 수행해야 하므로 미수행 + +- ✔ 제작 화면 열기: 포털 SSO 티켓 → 실제 Webmaker /api/auth/sso 302(1회용) — {"portal":302,"webmaker":302,"webmakerLocation":"/?site=85e1859a8638022beb761f1ec4ccfc93","reuse":403} +- ✔ 저장본 목록·배포 전 확인(실제 Webmaker 프로젝트) — {"versionsStatus":200,"versions":0,"precheck":true} + +```json +{ + "sso": { + "portal": 302, + "webmaker": 302, + "webmakerLocation": "/?site=85e1859a8638022beb761f1ec4ccfc93", + "reuse": 403 + }, + "ver": { + "versionsStatus": 200, + "versions": 0, + "precheck": true + } +} +``` + +### 6. 관리형 배포 — 부분 확인 + +배포 요청이 거부됨: precheck_failed — 배포 전 확인에서 차단된 항목이 있습니다 (저장본이 없어 배포할 내용이 없음; 사람이 Webmaker 에서 페이지를 저장한 뒤 수행) + +- ✔ 배포 요청(관리형) → HAL 작업·사이트 수 예약 — {"status":409,"error":"precheck_failed","message":"배포 전 확인에서 차단된 항목이 있습니다"} + +### 9. 사이트 정보와 접속·사용량 통계 확인 — 부분 확인 + +화면 데이터 제공 확인 · 실제 접속 통계(awstats 로그 집계)는 공개 DNS 로 외부 방문이 생긴 뒤 확인 가능 + +- ✔ 사이트 정보·배포 이력·사용량 화면 — {"overviewKeys":["ok","overview","refreshJob"],"usageKeys":["ok","overview","allocations","samples"],"deployments":0} + +```json +{ + "overviewKeys": [ + "ok", + "overview", + "refreshJob" + ], + "usageKeys": [ + "ok", + "overview", + "allocations", + "samples" + ], + "deployments": 0 +} +``` + +### 10. 사이트 대표 메일과 메일함 생성 — 부분 확인 + +실제 AriMail 테넌트·메일함 생성 확인 · 활성화는 도메인 DNS(MX/SPF/DKIM) 반영이 필요해 외부 대기 + +- ✔ 사이트 연결 도메인 등록(검증 전용 이름, 소유 확인은 외부 대기) — {"id":"d_01a1789f6a07e57873e2f0c05f"} +- ✔ 사이트 대표 메일 생성 → 실제 AriMail 테넌트 준비 — {"id":"msv_01a1789f6a0d5f702a1581f2fd","status":"needs_setup","kind":"site_bound"} +- ✔ 메일함 생성(초기 비밀번호 1회 표시) → 재조회에 비밀번호 없음 · 실제 AriMail 계정 — {"id":"mb_01a1789f6e723ade68602baaf9","address":"kim@check-mvpcu283.ariweb.net","shownOnce":true,"leakedOnReload":false} +- ✔ DNS 확인(미반영 구분) → 활성화 작업은 외부 대기 — {"dnsOk":true,"items":[],"activate":200,"job":"queued","waitReason":""} + +```json +{ + "d": { + "id": "d_01a1789f6a07e57873e2f0c05f" + }, + "svc": { + "id": "msv_01a1789f6a0d5f702a1581f2fd", + "status": "needs_setup", + "kind": "site_bound" + }, + "mb": { + "id": "mb_01a1789f6e723ade68602baaf9", + "address": "kim@check-mvpcu283.ariweb.net", + "shownOnce": true, + "leakedOnReload": false + }, + "dns": { + "dnsOk": true, + "items": [], + "activate": 200, + "job": "queued", + "waitReason": "" + } +} +``` + +### 12. 독립 메일 서비스 생성 — 실제 확인 + +- ✔ 메일 전용 도메인 등록(purpose mail) — "d_01a1789f6e94cf197bdcb0cb81" +- ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a1789f6e9604bd5f49bb08e1","kind":"standalone","status":"needs_setup","usage":{}} + +```json +{ + "id": "msv_01a1789f6e9604bd5f49bb08e1", + "kind": "standalone", + "status": "needs_setup", + "usage": {} +} +``` + +### 13. 백업 옵션 구매와 첫 백업 확인 — 부분 확인 + +백업 옵션 주문·승인·활성화는 실제 확인. 수동 백업 실행은 사이트가 초안(미배포)이라 정기 실행기가 옵션을 site_not_deployed 로 중지해 Agent 백업까지 가지 않음(설계대로) — Webmaker 에서 페이지를 저장·배포한 뒤 다시 수행. 판정은 검증기의 시간 초과를 운영자가 DB 상태(옵션 paused: site_not_deployed)로 확인해 부분 확인으로 정정 + +- ✔ 백업 옵션 주문(대상 사이트 지정) → 입금 확인 → 승인 — {"orderNo":"AW-20261026-0004","approved":"approved"} +- ✔ 백업 화면(옵션 보유 여부) — {} +- ✔ 옵션 활성화(확인 필수) → 첫 백업 예정 — {"status":"active"} +- ✘ 지금 백업 실행 → 실제 Agent 가 사이트 파일·데이터를 묶어 저장(완료 대기 120초) — 백업 완료 대기 시간 초과 + +```json +{ + "optionState": { + "status": "paused", + "paused_reason": "site_not_deployed", + "updated_at": "2026-10-26T14:32:11.943Z" + }, + "siteLifecycle": "draft", + "lastJob": { + "status": "failed", + "error": "대상 서버 또는 Agent 가 필요합니다" + } +} +``` + +### 22. HAL 우선 배포 후 HAL을 통한 전체 모듈 배포 — 실제 확인 + +HAL 부트스트랩 → 첫 Agent → HAL 작업으로 portal·webmaker·arimail·agent 배포, 실제 버전 0.1.1·건강 ok + +- ✔ HAL 릴리스·모듈 목표/실제 버전(배포 영수증) — {"running":{"complete":true,"firstInstall":false,"releaseVersion":"0.1.1","mainCommit":"[redacted]","artifactSha256":"[redacted]","halRunningVersion":"0.1.1","firstAgent":"[redacted]","manualInstallLocked":true},"targets":[{"module":"agent","target":"0.1.1","actual":"0.1.1","health":"ok"},{"module":"arimail","target":"0.1.1","actual":"0.1.1","health":"ok"},{"module":"backup","target":null,"actual" +- ✔ HAL 을 통한 모듈 배포 작업(module.deploy) 결과 — [{"label":"portal 0.1.1 배포","status":"done"},{"label":"hal 0.1.1 배포","status":"done"},{"label":"portal 0.1.1 배포","status":"done"},{"label":"portal 0.1.1 배포","status":"done"},{"label":"agent 0.1.1 배포","status":"done"},{"label":"arimail 0.1.1 배포","status":"done"},{"label":"hal 0.1.1 배포","status":"done"},{"label":"agent 0.1.1 배포","status":"done"},{"label":"arimail 0.1.1 배포","status":"failed"},{"label + +```json +{ + "rel": { + "running": { + "complete": true, + "firstInstall": false, + "releaseVersion": "0.1.1", + "mainCommit": "[redacted]", + "artifactSha256": "[redacted]", + "halRunningVersion": "0.1.1", + "firstAgent": "[redacted]", + "manualInstallLocked": true + }, + "targets": [ + { + "module": "agent", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "arimail", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "backup", + "target": null, + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "hal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "observability", + "target": null, + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "portal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + }, + { + "module": "webmaker", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok" + } + ] + }, + "jobs": [ + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "arimail 0.1.1 배포", + "status": "done" + }, + { + "label": "hal 0.1.1 배포", + "status": "done" + }, + { + "label": "agent 0.1.1 배포", + "status": "done" + }, + { + "label": "arimail 0.1.1 배포", + "status": "failed" + }, + { + "label": "webmaker 0.1.1 배포", + "status": "done" + }, + { + "label": "portal 0.1.1 배포", + "status": "done" + } + ] +} +``` + +### 24. 운영자 승인, 감사 기록, 고객 알림의 일치 — 부분 확인 + +- ✔ 고객 감사 기록(order.approve) · 알림 · 운영자 주문 상태 일치 — {"auditEntries":0,"notifications":6,"status":"approved"} +- ✔ HAL 감사 기록(deploy.module) — 10 + +```json +{ + "a": { + "auditEntries": 0, + "notifications": 6, + "status": "approved" + }, + "halDeployAudit": 10 +} +``` + +### 26. 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지 — 실제 확인 + +- ✔ HAL 재시작 전: 운영자 세션·작업 수·서버 등록 — {"jobs":23,"servers":1} +- ✔ systemctl restart ariweb-hal → 건강 회복 → 같은 세션으로 작업 수·서버 유지 — {"uptimeSec":1,"sessionKept":"[redacted]","jobs":23,"servers":1,"agentOnline":true} +- ✔ Agent 가 재접속(heartbeat) 해 서버 ready 유지 — [{"name":"srv1953374 (ariweb.net)","status":"ready","lastSeen":"2026-10-26T14:33:13.381Z"}] + +```json +{ + "before": { + "jobs": 23, + "servers": 1 + }, + "after": { + "uptimeSec": 1, + "sessionKept": "[redacted]", + "jobs": 23, + "servers": 1, + "agentOnline": true + }, + "ag": [ + { + "name": "srv1953374 (ariweb.net)", + "status": "ready", + "lastSeen": "2026-10-26T14:33:13.381Z" + } + ] +} +``` + +## 검증에 쓴 테스트 데이터(정리 대상) + +- 고객 계정 `alice-mvpcu283@check.ariweb.net`(및 bob-*, 이전 실행분 alice-*/bob-*), 사이트 `s_01a1789f690cb9115f0330d2e8`, 도메인 `check-mvpcu283.ariweb.net`·`solo-*.ariweb.net`(AriMail 테넌트는 운영자 취소 → 보존 기간 뒤 삭제), 주문 `o_01a1789f68c3298aec8ad337e8`. 모두 `@check.ariweb.net` 주소의 검증 전용 데이터다. + +## 성공 선언 전 재검증(서버에서 읽은 값, `90-verify.sh`) + +``` +### systemd +ariweb-hal active / enabled=enabled +ariweb-agent active / enabled=enabled +ariweb-portal active / enabled=enabled +webmaker active / enabled=enabled +webmaker-setup active / enabled=enabled +arihakdang-mail active / enabled=enabled +arihakdang-mail-haraka active / enabled=enabled +nginx active / enabled=enabled +### nginx +nginx: configuration file /etc/nginx/nginx.conf test is successful +ariweb-hal.conf +ariweb-portal.conf +webmail.ariweb.net.conf +webmaker.ariweb.net.conf +webmaker.ariweb.net.conf.bak-1.5.27 +### HAL health (127.0.0.1:8900) +{"ok":true,"version":"0.1.1","uptimeSec":5,"db":true,"jobs":true,"bootstrap":{"complete":true,"firstInstall":false,"releaseVersion":"0.1.1","mainCommit":"3951be5836a753e60f11d6b363025059fcf6d448","artifactSha256":"e7741899cd7387b0155c9d3bf8ff14cc4026880db999c9d59347744390415e84","halRunningVersion":"0.1.1","firstAgent":"ag_01a178829f1c3d88381d2c0c75","manualInstallLocked":true},"instanceId":"hal_01a178a14a3deef408f7ddfa91"} +### HAL health via nginx https (resolve 127.0.0.1) +{"ok":true,"version":"0.1.1","db":true,"bootstrap":true} +### portal health +{"ok":true,"version":"0.1.1","db":true,"jobs":17,"time":"2026-10-26T14:33:23.567Z"} +{"ok":true,"version":"0.1.1"} +my.ariweb.net /login → 200 +### ops console +/ops/ (127.0.0.1) → 200 +/ops/ via nginx (외부 IP 흉내 X-Real-IP 8.8.8.8) → 200 +### modules (HAL 기록) +{ + "running": { + "complete": true, + "firstInstall": false, + "releaseVersion": "0.1.1", + "mainCommit": "3951be5836a753e60f11d6b363025059fcf6d448", + "artifactSha256": "e7741899cd7387b0155c9d3bf8ff14cc4026880db999c9d59347744390415e84", + "halRunningVersion": "0.1.1", + "firstAgent": "ag_01a178829f1c3d88381d2c0c75", + "manualInstallLocked": true + }, + "releases": [ + { + "v": "0.1.1", + "status": "approved", + "halDeployedAt": "2026-10-26T14:30:34.618Z" + } + ], + "targets": [ + { + "module": "agent", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + }, + { + "module": "arimail", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + }, + { + "module": "backup", + "target": null, + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + }, + { + "module": "hal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + }, + { + "module": "observability", + "target": null, + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + }, + { + "module": "portal", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + }, + { + "module": "webmaker", + "target": "0.1.1", + "actual": "0.1.1", + "health": "ok", + "active": 1, + "checkedAt": "2026-10-26T14:33:13.381Z", + "err": null + } + ] +} +### modules (디스크) +/srv/ariweb/hal current → /srv/ariweb/hal/releases/0.1.1 REVISION=0.1.1(상류 0.1.1) pkg=0.1.1 +/srv/ariweb/portal current → /srv/ariweb/portal/releases/0.1.1 REVISION=0.1.1(상류 0.1.1) pkg=0.1.1 +/srv/ariweb/agent current → /srv/ariweb/agent/releases/0.1.1 REVISION=0.1.1(상류 0.1.1) pkg=0.1.1 +/srv/webmaker current → /srv/webmaker/releases/0.1.1 REVISION=0.1.1(상류 1.7.3) pkg=1.7.3 +/srv/arihakdang-mail current → /srv/arihakdang-mail/releases/0.1.1 REVISION=0.1.1(상류 1.0.11) pkg=1.0.11 +### 서비스 자체 보고 버전 +{"ok":true,"version":"1.0.11","domains":6} +webmaker csrf → 401 +webmaker.ariweb.net → 302 +webmail.ariweb.net → 200 +### 배포 영수증(감사 기록 deploy.*) +[ + { + "at": "2026-10-26T14:30:45.633Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "portal", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:30:35.618Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "hal", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:23:37.819Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "portal", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:16:46.605Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "portal", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:16:41.094Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "agent", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:09:32.947Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "arimail", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:09:24.433Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "hal", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:08:47.250Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "agent", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:00:26.918Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "webmaker", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + }, + { + "at": "2026-10-26T14:00:17.400Z", + "actor": "op_01a178829b90905c15a3191caa", + "subject": "portal", + "detail": { + "version": "0.1.1", + "serverIds": [ + "srv_01a178829d5848f93a15f93a17" + ], + "overall": "done" + } + } +] +### 배포 작업 +[ + { + "id": "j_01a1789ee309d1bc1102a6d949", + "label": "portal 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:30:45.633Z" + }, + { + "id": "j_01a1789eba1aac59f8518c92b3", + "label": "hal 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:30:35.618Z" + }, + { + "id": "j_01a178985cf9fa2f2e76fa7735", + "label": "portal 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:23:37.819Z" + }, + { + "id": "j_01a17892166e8460c04de6a7d6", + "label": "portal 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:16:46.605Z" + }, + { + "id": "j_01a17892094ddcff01ba7b8002", + "label": "agent 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:16:41.094Z" + }, + { + "id": "j_01a1788b794d0d3545ca8bedd0", + "label": "arimail 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:09:32.947Z" + }, + { + "id": "j_01a1788b53ed98904ed1127166", + "label": "hal 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:09:24.433Z" + }, + { + "id": "j_01a1788acd103d85194bbb8bba", + "label": "agent 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:08:47.250Z" + }, + { + "id": "j_01a17883416842d8acc7909136", + "label": "arimail 0.1.1 배포", + "status": "failed", + "ended": "2026-10-26T14:00:33.432Z" + }, + { + "id": "j_01a178831b657e19aaed13d7d8", + "label": "webmaker 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:00:26.918Z" + }, + { + "id": "j_01a178830202223b4852e6cb6a", + "label": "portal 0.1.1 배포", + "status": "done", + "ended": "2026-10-26T14:00:17.401Z" + } +] +### 서버·Agent +[ + { + "id": "srv_01a178829d5848f93a15f93a17", + "name": "srv1953374 (ariweb.net)", + "role": "control", + "status": "ready", + "agent": "ag_01a178829f1c3d88381d2c0c75", + "agentVersion": "0.1.1", + "lastSeen": "2026-10-26T14:33:13.381Z", + "modules": { + "webmaker": { + "target": "0.1.1", + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + }, + "arimail": { + "target": "0.1.1", + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + }, + "hal": { + "target": "0.1.1", + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + }, + "agent": { + "target": "0.1.1", + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + }, + "observability": { + "target": null, + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + }, + "backup": { + "target": null, + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + }, + "portal": { + "target": "0.1.1", + "actual": "0.1.1", + "drift": false, + "health": "ok", + "active": true, + "lastCheckAt": "2026-10-26T14:33:13.381Z", + "lastError": null + } + } + } +] +### 공개 DNS(참고) +hal.ariweb.net → +my.ariweb.net → + +```