실제 환경 배포·검증 기록(0.1.1): ariweb.net 호스트 배포 완료, 34개 흐름 실환경 판정(실제 7·부분 7·미수행 20), RUNBOOK 운영 사실, 로컬 E2E 보고서 갱신

- 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 <noreply@anthropic.com>
This commit is contained in:
zzabisagent-lab
2026-10-26 14:35:04 +00:00
co-authored by Claude Fable 5.1
parent 3951be5836
commit d1eefaa15f
6 changed files with 2075 additions and 190 deletions
+1
View File
@@ -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 를 읽어 표시만 하며, 초과 사용 과금 라인 생성은 하지 않는다(표시·제한 해제만).
+7
View File
@@ -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(`<appDir>/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`(`<appDir>/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 <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 결과와 실제 환경 확인 기록을 함께 둔다. 각 흐름은 버튼 성공이 아니라 최종 화면·외부 접속·실제 메일 왕복·실제 사용량·실제 배포 버전으로 판정한다. 충족하지 못한 흐름은 완료로 표시하지 않는다.
+107 -107
View File
@@ -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,
+72 -72
View File
@@ -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,
File diff suppressed because it is too large Load Diff
+806 -11
View File
@@ -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. 같은 버전 재설치가 실행 중인 디렉터리를 지움 → 임시 풀기·`<ver>.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 →
```