bear hal21.ariweb.net HAL 전용 부트스트랩 기록: RUNBOOK §7(절차·검증·되돌리기), VERSION_HISTORY, 로컬 E2E 보고서 갱신(빌드 ab75b7d)

- docs/RUNBOOK-0.1.1.md §7: 호스트별 certbot(webroot) 인증서, --acme /var/www/html --portal-host none 부트스트랩, 검증 결과, 되돌리기
- VERSION_HISTORY.md: 배포 artifact(main ab75b7d, hal sha256 f574d875…)와 5단계 거짓 경고 결함 기록

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zzabisagent-lab
2026-10-27 03:12:18 +00:00
co-authored by Claude Fable 5.1
parent ab75b7db4f
commit 83e3604b24
4 changed files with 214 additions and 183 deletions
+1 -1
View File
@@ -116,7 +116,7 @@
### HAL 전용 호스트 부트스트랩(2026-10-27, bear `hal21.ariweb.net`)
- **`deploy/bootstrap-hal.sh --portal-host none`**: 포털을 다른 호스트에서 운영하는 HAL 전용 호스트에는 `my.ariweb.net` nginx 접근 경로를 만들지 않는다(이전에는 항상 만들어 포털이 없는 호스트에도 죽은 vhost 가 남았다). 기본값(`my.ariweb.net`)의 동작은 그대로다.
- bear(Ubuntu 24.04, nginx 1.24, Node 24.22, certbot nginx 플러그인 운영)에 HAL 만 최초 부트스트랩: 인증서는 호스트별 certbot(webroot `/var/www/html`, ECDSA, 갱신 훅 `systemctl reload nginx`) — `--cert /etc/letsencrypt/live/hal21.ariweb.net --acme /var/www/html`. 기존 Webmaker·Gitea·Matrix·기본 서버는 건드리지 않았다. 절차와 검증은 `docs/RUNBOOK-0.1.1.md` §7.
- bear(Ubuntu 24.04, nginx 1.24, Node 24.22, certbot nginx 플러그인 운영)에 HAL 만 최초 부트스트랩: 인증서는 호스트별 certbot(webroot `/var/www/html`, ECDSA, 갱신 훅 `systemctl reload nginx`) — `--cert /etc/letsencrypt/live/hal21.ariweb.net --acme /var/www/html`. 기존 Webmaker·Gitea·Matrix·기본 서버는 건드리지 않았다. 절차·검증·되돌리기는 `docs/RUNBOOK-0.1.1.md` §7. 배포 artifact: main `ab75b7d`, hal sha256 `f574d875…`(deploy/ 변경으로 ariweb.net 호스트의 `e7741899…` 와 다름). 알려진 사소한 결함: 부트스트랩 5단계의 HTTPS 자체 확인이 nginx reload 직후 실행되어 거짓 경고가 날 수 있다(결과에 영향 없음).
### 도입 소스
- 가져오기: `node scripts/import-sources.mjs` 가 고정 커밋(Webmaker `3b10859884ef2fd0f2af11934ab6c16370fd64b8`, AriMail `a07f69f1ffb6667de6e66b3d1ec43c1c66fab924`)을 검사하고 실행에 필요 없는 문서·보고서·샘플을 제외한 전체를 `modules/webmaker`(176 파일)·`modules/arimail`(88 파일)로 복사, 파일별 원본 경로·대상 경로·수정 여부·원본 해시·라이선스 검토를 `IMPORTED_SOURCES.md`/`.json` 에 기록한다.
+31
View File
@@ -54,3 +54,34 @@ curl -fsSL https://hal.ariweb.net/agent/v1/install.sh | bash -s -- --hal https:/
## 6. 되돌리기
- 모듈: 운영자 콘솔 › 모듈 › 되돌리기(이전 릴리스 디렉터리 유지, 3개 보관).
- HAL: `deploy/hal-restore.sh` + 재해복구 절차(§34, 기존 제어 위치 중지 확인 뒤 `--takeover`).
## 7. HAL 전용 호스트 부트스트랩 — bear `hal21.ariweb.net` (2026-10-27)
대상: bear(`ssh bear`, 192.168.20.23 / 공인 121.152.149.45, master 계정, sudo NOPASSWD, Ubuntu 24.04, nginx 1.24, Node v24.22(엔진 요구 ≥22.13 충족), certbot 2.9 nginx 플러그인·`certbot.timer`). 기존 운영(Webmaker `webmaker.ibit.kr`·`hs-company.ariweb.net`, Gitea `source.ibit.kr`, Matrix, board/file/office/xmap, 기본 서버 `wh-default.conf`)은 건드리지 않는다. 이 호스트에는 **HAL 만** 둔다(포털·Webmaker·AriMail·Agent 는 다음 단계).
```bash
# 개발 PC: main 이 깨끗한 상태(미추적 파일 없음)에서 빌드 — 전체 테스트 뒤 dist/0.1.1 (manifest.mainCommit = HEAD, dirty=false)
node scripts/release.mjs build && node scripts/release.mjs verify --dist dist/0.1.1
scp -r dist/0.1.1 bear:ariweb-deploy-0.1.1/dist
# bear: 호스트별 인증서(기존 인증서는 ibit.kr 7개뿐, ariweb.net 와일드카드 없음). 기본 서버가 /.well-known/acme-challenge/ 를 /var/www/html 로 서비스하므로 webroot 로 발급한다(nginx 설정을 건드리지 않음)
sudo certbot certonly --webroot -w /var/www/html -d hal21.ariweb.net --non-interactive --keep-until-expiring
sudo certbot reconfigure --cert-name hal21.ariweb.net --deploy-hook "systemctl reload nginx" # 갱신 뒤 reload(installer 가 없으므로)
# bear: 부트스트랩(출력에 1회 설정 토큰이 있으므로 0600 파일로만 받는다)
cd ~/ariweb-deploy-0.1.1 && mkdir -p -m 700 src && tar -xzf dist/hal-0.1.1.tar.gz -C src
umask 077; bash src/deploy/bootstrap-hal.sh --dist ~/ariweb-deploy-0.1.1/dist --host hal21.ariweb.net \
--cert /etc/letsencrypt/live/hal21.ariweb.net --acme /var/www/html --portal-host none > bootstrap.log 2>&1
```
- `--acme /var/www/html`: HAL vhost 의 acme 경로를 webroot 발급 경로와 같게 둔다(아니면 vhost 가 생긴 뒤 갱신 실패).
- 만든 것: `/srv/ariweb/hal/{releases/0.1.1,current,data(0700)}`, `/var/www/ariweb-sites`, 유닛 `ariweb-hal`(enabled, 127.0.0.1:8900), nginx `sites-available/ariweb-hal.conf`(+enabled 링크), `conf.d/awstats.conf`(log_format awstats — 기존 wmstats 와 이름이 다름), `/etc/sudoers.d/ariweb-agent`(master 는 이미 NOPASSWD ALL 이라 추가 권한 없음). 포털 vhost 는 만들지 않았다.
- 비밀값: 설정 토큰은 `/srv/ariweb/hal/data/setup.token`(0600)과 `~/ariweb-deploy-0.1.1/bootstrap.log`(0600)에만 있다. 첫 운영자 생성(`POST /ops/api/setup`)은 이 단계에서 하지 않았다 — `/ops` 는 허용 네트워크(기본 사설망 CIDR) 또는 접근권이 필요하다.
- 알려진 사소한 결함: 5단계의 `https://<host>` 자체 확인은 `nginx reload` 직후 바로 실행되어 새 vhost 가 아직 적용되기 전에 "unrecognized name" 경고가 날 수 있다(거짓 경고, 몇 초 뒤 정상). 다음 릴리스에서 reload 뒤 짧은 대기를 넣는다.
검증(2026-10-27 03:08~03:12 UTC, 모두 통과): `systemctl is-active/is-enabled ariweb-hal` active/enabled · 127.0.0.1:8900 리슨(MainPID) · `nginx -t` 성공 후 reload, 기존 nginx 설정 파일 12개 sha256 변화 없음 · `https://hal21.ariweb.net/api/health` 외부(LAN 밖 fetch·LAN NAT 양쪽)에서 200, 인증서 CN=hal21.ariweb.net(Let's Encrypt, 2027-01-25 만료), HTTP→HTTPS 301 · 건강 응답 version 0.1.1·mainCommit ab75b7d·artifactSha256 f574d875…·bootstrap.complete=false(첫 Agent 전) · `systemctl restart ariweb-hal` 뒤 재기동·건강 ok(NRestarts 0, enabled 로 재부팅 지속; 서버 재부팅은 기존 서비스 중단이라 하지 않음) · 기존 서비스 회귀: nginx/webmaker/webmaker-setup/webmaker-gitea-reverse-tunnel/docker/certbot.timer active, 기존 vhost 8개 응답 코드 배포 전과 동일 · `certbot reconfigure` 의 모의 갱신 성공, `renew_hook = systemctl reload nginx`.
되돌리기(HAL 만 제거, 기존 서비스 영향 없음):
```bash
sudo systemctl disable --now ariweb-hal && sudo rm /etc/systemd/system/ariweb-hal.service && sudo systemctl daemon-reload
sudo rm /etc/nginx/sites-enabled/ariweb-hal.conf /etc/nginx/sites-available/ariweb-hal.conf /etc/nginx/conf.d/awstats.conf && sudo nginx -t && sudo systemctl reload nginx
sudo rm /etc/sudoers.d/ariweb-agent; sudo rm -rf /srv/ariweb /var/www/ariweb-sites # 데이터 포함 — 보관이 필요하면 /srv/ariweb/hal/data 먼저 복사
sudo certbot delete --cert-name hal21.ariweb.net # 인증서까지 지울 때만
```
+109 -109
View File
@@ -1,14 +1,14 @@
{
"generatedAt": "2026-10-27T03:03:32.350Z",
"startedAt": "2026-10-27T03:01:10.843Z",
"generatedAt": "2026-10-27T03:07:19.831Z",
"startedAt": "2026-10-27T03:05:00.466Z",
"env": {
"agents": 4,
"ports": {
"hal": 33115,
"portal": 36953,
"wm": 41065,
"am": 35845,
"mail": 35043
"hal": 39747,
"portal": 33873,
"wm": 44993,
"am": 41235,
"mail": 45413
},
"mailServerRegistered": true,
"node": "v24.22.1"
@@ -99,12 +99,12 @@
{
"name": "팀 그룹 만들기 → 현재 그룹 전환",
"ok": true,
"detail": "g_01a17b4e0e173faf6cbcb4ef49"
"detail": "g_01a17b518ed6c27ab829fa80d4"
},
{
"name": "이메일 초대(7일) → 초대 메일 링크",
"ok": true,
"detail": "WXryn3p_v1Xa2vpHCGRZdselUZtX7f14"
"detail": "buOtFSj9rEzpY8jtjncVE9uLJxHHOYIu"
},
{
"name": "초대 수락(1회용) → 별칭 user-02 · 역할 member",
@@ -162,7 +162,7 @@
"detail": {
"count": 2,
"team": {
"id": "pv_01a17b4e0ca92282c16d900239",
"id": "pv_01a17b518d55e97d5c10bd0bda",
"price": 49000,
"sites": 3
},
@@ -179,7 +179,7 @@
"orderNo": "AW-20261027-0001",
"status": "awaiting_payment",
"total": 63800,
"dueAt": "2026-11-03T03:01:15.526Z",
"dueAt": "2026-11-03T03:05:05.095Z",
"hasInstructions": true
}
},
@@ -221,7 +221,7 @@
"detail": {
"sites": 3,
"subs": 1,
"nextBilling": "2026-11-27T03:01:15.590Z"
"nextBilling": "2026-11-27T03:05:05.159Z"
}
}
],
@@ -233,7 +233,7 @@
"bill": {
"sites": 3,
"subs": 1,
"nextBilling": "2026-11-27T03:01:15.590Z"
"nextBilling": "2026-11-27T03:05:05.159Z"
}
}
},
@@ -248,7 +248,7 @@
"name": "사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내",
"ok": true,
"detail": {
"id": "s_01a17b4e0f0596e869c55a6f32",
"id": "s_01a17b518fc8d5127c10415dcf",
"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:41065/api/auth/sso?ticket=…",
"portalRedirect": "http://127.0.0.1:44993/api/auth/sso?ticket=…",
"webmaker": 302,
"reuse": 401
}
@@ -299,7 +299,7 @@
],
"evidence": {
"sso": {
"portalRedirect": "http://127.0.0.1:41065/api/auth/sso?ticket=…",
"portalRedirect": "http://127.0.0.1:44993/api/auth/sso?ticket=…",
"webmaker": 302,
"reuse": 401
},
@@ -320,7 +320,7 @@
"name": "배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나",
"ok": true,
"detail": {
"jobId": "j_01a17b4e0f4d10386855ea18f3",
"jobId": "j_01a17b519012bfc98402956999",
"steps": 10,
"again": false,
"siteCount": "reserved",
@@ -351,7 +351,7 @@
"ok": true,
"detail": {
"lifecycle": "needs_domain",
"version": "20261027030116-rev-c1-000000",
"version": "20261027030506-rev-c1-000000",
"verify": "needs_domain",
"health": "ok",
"agentHasSite": true
@@ -360,7 +360,7 @@
],
"evidence": {
"d": {
"jobId": "j_01a17b4e0f4d10386855ea18f3",
"jobId": "j_01a17b519012bfc98402956999",
"steps": 10,
"again": false,
"siteCount": "reserved",
@@ -383,7 +383,7 @@
},
"after": {
"lifecycle": "needs_domain",
"version": "20261027030116-rev-c1-000000",
"version": "20261027030506-rev-c1-000000",
"verify": "needs_domain",
"health": "ok",
"agentHasSite": true
@@ -401,7 +401,7 @@
"name": "도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT)",
"ok": true,
"detail": {
"domainId": "d_01a17b4e1f1e0d74d8b336d2c1",
"domainId": "d_01a17b519fe52da880f4a82e08",
"txtName": "_ariweb-verify.aw-e2e.localhost",
"txtValueShape": "ariweb-verify=…",
"copyable": true
@@ -422,7 +422,7 @@
],
"evidence": {
"dm": {
"domainId": "d_01a17b4e1f1e0d74d8b336d2c1",
"domainId": "d_01a17b519fe52da880f4a82e08",
"txtName": "_ariweb-verify.aw-e2e.localhost",
"txtValueShape": "ariweb-verify=…",
"copyable": true
@@ -500,7 +500,7 @@
"name": "사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시",
"ok": true,
"detail": {
"id": "msv_01a17b4ea797d7fb719692b670",
"id": "msv_01a17b52287a808c772429e86b",
"status": "needs_setup",
"tenant": true,
"ariwebKind": "site_bound",
@@ -517,7 +517,7 @@
"name": "메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음",
"ok": true,
"detail": {
"id": "mb_01a17b4ea7ca732850f5d9165a",
"id": "mb_01a17b5228b49e1be9da6f5ed8",
"address": "kim@aw-e2e.localhost",
"passwordShownOnce": "[redacted]",
"mustChange": true,
@@ -584,7 +584,7 @@
],
"evidence": {
"svc": {
"id": "msv_01a17b4ea797d7fb719692b670",
"id": "msv_01a17b52287a808c772429e86b",
"status": "needs_setup",
"tenant": true,
"ariwebKind": "site_bound",
@@ -592,7 +592,7 @@
"siteCountAllocated": 0
},
"mb": {
"id": "mb_01a17b4ea7ca732850f5d9165a",
"id": "mb_01a17b5228b49e1be9da6f5ed8",
"address": "kim@aw-e2e.localhost",
"passwordShownOnce": "[redacted]",
"mustChange": true,
@@ -644,7 +644,7 @@
"name": "메일 전용 도메인 등록(solo-e2e.localhost, purpose mail)",
"ok": true,
"detail": {
"id": "d_01a17b4ec9cf77dcb746d36826",
"id": "d_01a17b5252c2383cb57c62cea1",
"jobCancelled": null
}
},
@@ -652,7 +652,7 @@
"name": "독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비)",
"ok": true,
"detail": {
"id": "msv_01a17b4ec9d72d484b64e3eaa2",
"id": "msv_01a17b5252caf9ee021a3b4674",
"kind": "standalone",
"mailServicesAllocated": 1,
"sitesAllocated": 0
@@ -661,7 +661,7 @@
],
"evidence": {
"r": {
"id": "msv_01a17b4ec9d72d484b64e3eaa2",
"id": "msv_01a17b5252caf9ee021a3b4674",
"kind": "standalone",
"mailServicesAllocated": 1,
"sitesAllocated": 0
@@ -701,8 +701,8 @@
"ok": true,
"detail": {
"status": "active",
"firstDue": "2026-10-28T03:02:03.627Z",
"nextRun": "2026-10-27T03:02:03.627Z"
"firstDue": "2026-10-28T03:05:55.299Z",
"nextRun": "2026-10-27T03:05:55.299Z"
}
},
{
@@ -712,7 +712,7 @@
"timedOut": false,
"optionStatus": "active",
"lastJob": null,
"siteServer": "srv_01a17b4e0679cb56194ed495da",
"siteServer": "srv_01a17b518749e52e0c3ff114c9",
"backup": {
"status": "done",
"tier": "monthly",
@@ -733,14 +733,14 @@
},
"opt": {
"status": "active",
"firstDue": "2026-10-28T03:02:03.627Z",
"nextRun": "2026-10-27T03:02:03.627Z"
"firstDue": "2026-10-28T03:05:55.299Z",
"nextRun": "2026-10-27T03:05:55.299Z"
},
"run1": {
"timedOut": false,
"optionStatus": "active",
"lastJob": null,
"siteServer": "srv_01a17b4e0679cb56194ed495da",
"siteServer": "srv_01a17b518749e52e0c3ff114c9",
"backup": {
"status": "done",
"tier": "monthly",
@@ -763,19 +763,19 @@
"ok": true,
"detail": {
"backup": {
"id": "bk_01a17b4ee3ee59485506d00291",
"createdAt": "2026-10-27T03:02:03.633Z",
"id": "bk_01a17b526498da95f409725dc6",
"createdAt": "2026-10-27T03:05:55.307Z",
"scope": {
"files": true,
"db": true,
"uploads": true,
"config": true,
"storeServerId": "srv_01a17b4e0721657bf3ba051c81",
"storeServerId": "srv_01a17b5187f8c4f905c13a6f75",
"verifyMethod": "sha256 + 격리 환경 읽기 확인"
},
"sizeBytes": 1899,
"sizeBytes": 1902,
"verifyState": "verified",
"expiresAt": "2027-01-28T03:02:10.158Z",
"expiresAt": "2027-01-28T03:05:59.704Z",
"tier": "monthly"
},
"included": [
@@ -809,7 +809,7 @@
"name": "복원 요청 → 운영자 승인 대기 · 중복 요청 없음",
"ok": true,
"detail": {
"id": "rs_01a17b4ee8062b5cf5f2a8f0c8",
"id": "rs_01a17b52653b647f3cd00ad2b0",
"status": "awaiting_approval",
"dup": false,
"finalConfirmation": {
@@ -838,19 +838,19 @@
"evidence": {
"info": {
"backup": {
"id": "bk_01a17b4ee3ee59485506d00291",
"createdAt": "2026-10-27T03:02:03.633Z",
"id": "bk_01a17b526498da95f409725dc6",
"createdAt": "2026-10-27T03:05:55.307Z",
"scope": {
"files": true,
"db": true,
"uploads": true,
"config": true,
"storeServerId": "srv_01a17b4e0721657bf3ba051c81",
"storeServerId": "srv_01a17b5187f8c4f905c13a6f75",
"verifyMethod": "sha256 + 격리 환경 읽기 확인"
},
"sizeBytes": 1899,
"sizeBytes": 1902,
"verifyState": "verified",
"expiresAt": "2027-01-28T03:02:10.158Z",
"expiresAt": "2027-01-28T03:05:59.704Z",
"tier": "monthly"
},
"included": [
@@ -880,7 +880,7 @@
"deferred": []
},
"rq": {
"id": "rs_01a17b4ee8062b5cf5f2a8f0c8",
"id": "rs_01a17b52653b647f3cd00ad2b0",
"status": "awaiting_approval",
"dup": false,
"finalConfirmation": {
@@ -970,8 +970,8 @@
"name": "외부 배포 사이트 생성(초안) + 저장본",
"ok": true,
"detail": {
"id": "s_01a17b4fd3693432c3393d56d1",
"group_id": "g_01a17b4e0e173faf6cbcb4ef49",
"id": "s_01a17b5350ab6323ad5f7122f2",
"group_id": "g_01a17b518ed6c27ab829fa80d4",
"name": "E2E 외부 사이트",
"purpose": "e2e",
"webmaker_project_id": "00000000000000000000000000000004",
@@ -999,9 +999,9 @@
"retention_until": null,
"delete_after": null,
"deleted_at": null,
"created_by": "u_01a17b4e0d6dd0f50fadcba9f7",
"created_at": "2026-10-27T03:03:11.465Z",
"updated_at": "2026-10-27T03:03:11.471Z",
"created_by": "u_01a17b518e36fb0f05105dd2d3",
"created_at": "2026-10-27T03:07:00.140Z",
"updated_at": "2026-10-27T03:07:00.149Z",
"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:33115/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:33115 --token … --external --site s_01a17b4fd3693432c3393d56d1",
"expiresAt": "2026-10-27T03:13:11.692Z",
"install": "curl -fsSL http://127.0.0.1:39747/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:39747 --token … --external --site s_01a17b5350ab6323ad5f7122f2",
"expiresAt": "2026-10-27T03:17:00.349Z",
"status": "pending"
}
},
@@ -1126,8 +1126,8 @@
"agreed": false
},
"tok": {
"install": "curl -fsSL http://127.0.0.1:33115/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:33115 --token … --external --site s_01a17b4fd3693432c3393d56d1",
"expiresAt": "2026-10-27T03:13:11.692Z"
"install": "curl -fsSL http://127.0.0.1:39747/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:39747 --token … --external --site s_01a17b5350ab6323ad5f7122f2",
"expiresAt": "2026-10-27T03:17:00.349Z"
},
"conn": {
"status": "connected",
@@ -1311,7 +1311,7 @@
"approve": 200,
"sites": 1,
"mailboxes": 5,
"product": "pv_01a17b4e0ca933a6d20a4e961a"
"product": "pv_01a17b518d552f6fab4af3028d"
}
}
],
@@ -1347,7 +1347,7 @@
"approve": 200,
"sites": 1,
"mailboxes": 5,
"product": "pv_01a17b4e0ca933a6d20a4e961a"
"product": "pv_01a17b518d552f6fab4af3028d"
}
}
},
@@ -1430,9 +1430,9 @@
"name": "요청(보내는 그룹, 초안 사이트) → 받는 사용자 알림",
"ok": true,
"detail": {
"id": "tr_01a17b4ff6608d214265ad8214",
"id": "tr_01a17b536bc42f2755059af110",
"status": "requested",
"expiresAt": "2026-11-03T03:03:20.416Z",
"expiresAt": "2026-11-03T03:07:07.076Z",
"bobNotified": true
}
},
@@ -1460,7 +1460,7 @@
"bad": "final_confirmation_mismatch",
"ok": 200,
"status": "completed",
"cutover": "2026-10-27T03:03:20.464Z"
"cutover": "2026-10-27T03:07:07.125Z"
}
},
{
@@ -1489,9 +1489,9 @@
"excluded": 3
},
"rq": {
"id": "tr_01a17b4ff6608d214265ad8214",
"id": "tr_01a17b536bc42f2755059af110",
"status": "requested",
"expiresAt": "2026-11-03T03:03:20.416Z",
"expiresAt": "2026-11-03T03:07:07.076Z",
"bobNotified": true
},
"acc": {
@@ -1503,7 +1503,7 @@
"bad": "final_confirmation_mismatch",
"ok": 200,
"status": "completed",
"cutover": "2026-10-27T03:03:20.464Z"
"cutover": "2026-10-27T03:07:07.125Z"
},
"after": {
"aliceSees": 404,
@@ -1781,7 +1781,7 @@
"detail": {
"status": "done",
"canRetry": false,
"jobId": "j_01a17b4e0f4d10386855ea18f3"
"jobId": "j_01a17b519012bfc98402956999"
}
},
{
@@ -1806,7 +1806,7 @@
"dep": {
"status": "done",
"canRetry": false,
"jobId": "j_01a17b4e0f4d10386855ea18f3"
"jobId": "j_01a17b519012bfc98402956999"
},
"rt": {
"status": 400,
@@ -1832,7 +1832,7 @@
"detail": {
"title": "주문 AW-20261027-0001 승인 — 권리가 활성화되었습니다",
"category": "billing",
"link": "/orders/o_01a17b4e0e86b38c3bd0b1daea"
"link": "/orders/o_01a17b518f474b86f010e23bac"
}
},
{
@@ -1848,7 +1848,7 @@
"ok": true,
"detail": {
"status": "approved",
"approvedAt": "2026-10-27T03:01:15.590Z"
"approvedAt": "2026-10-27T03:05:05.159Z"
}
}
],
@@ -1856,7 +1856,7 @@
"n": {
"title": "주문 AW-20261027-0001 승인 — 권리가 활성화되었습니다",
"category": "billing",
"link": "/orders/o_01a17b4e0e86b38c3bd0b1daea"
"link": "/orders/o_01a17b518f474b86f010e23bac"
},
"a": {
"action": "order.approve",
@@ -1864,7 +1864,7 @@
},
"same": {
"status": "approved",
"approvedAt": "2026-10-27T03:01:15.590Z"
"approvedAt": "2026-10-27T03:05:05.159Z"
}
}
},
@@ -1907,9 +1907,9 @@
"rpo_minutes": 30,
"snapshot_interval_minutes": 60,
"retention_count": 3,
"external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261027030110/dr-external",
"updated_at": "2026-10-27T03:03:29.403Z",
"updated_by": "op_01a17b4e057cee750a084a7d49"
"external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261027030500/dr-external",
"updated_at": "2026-10-27T03:07:16.869Z",
"updated_by": "op_01a17b5186508745922b082baf"
}
},
{
@@ -1969,9 +1969,9 @@
"rpo_minutes": 30,
"snapshot_interval_minutes": 60,
"retention_count": 3,
"external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261027030110/dr-external",
"updated_at": "2026-10-27T03:03:29.403Z",
"updated_by": "op_01a17b4e057cee750a084a7d49"
"external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261027030500/dr-external",
"updated_at": "2026-10-27T03:07:16.869Z",
"updated_by": "op_01a17b5186508745922b082baf"
},
"snap": {
"verify": "ok",
@@ -2005,7 +2005,7 @@
"ok": true,
"detail": {
"mailJob": {
"id": "j_01a17b4eb7ea9023c2227280d3",
"id": "j_01a17b523cef114f09b2efdd4f",
"status": "awaiting_external"
},
"halJobs": 18
@@ -2017,7 +2017,7 @@
"detail": {
"sessionKept": "[redacted]",
"mailJob": {
"id": "j_01a17b4eb7ea9023c2227280d3",
"id": "j_01a17b523cef114f09b2efdd4f",
"status": "awaiting_external"
},
"same": true
@@ -2036,7 +2036,7 @@
"evidence": {
"before": {
"mailJob": {
"id": "j_01a17b4eb7ea9023c2227280d3",
"id": "j_01a17b523cef114f09b2efdd4f",
"status": "awaiting_external"
},
"halJobs": 18
@@ -2044,7 +2044,7 @@
"pr": {
"sessionKept": "[redacted]",
"mailJob": {
"id": "j_01a17b4eb7ea9023c2227280d3",
"id": "j_01a17b523cef114f09b2efdd4f",
"status": "awaiting_external"
},
"same": true
@@ -2075,8 +2075,8 @@
"ok": true,
"detail": [
{
"id": "s_01a17b4e9b9b08c08759ebfed6",
"group_id": "g_01a17b4e0e42dc2e48343c90d8",
"id": "s_01a17b521c7ac52cc816c76c3b",
"group_id": "g_01a17b518f0265b557c15c1355",
"name": "동시 A",
"purpose": "e2e",
"webmaker_project_id": "00000000000000000000000000000002",
@@ -2104,9 +2104,9 @@
"retention_until": null,
"delete_after": null,
"deleted_at": null,
"created_by": "u_01a17b4e0e427b45cdaa500d2f",
"created_at": "2026-10-27T03:01:51.643Z",
"updated_at": "2026-10-27T03:01:51.651Z",
"created_by": "u_01a17b518f02df498b213bb9c6",
"created_at": "2026-10-27T03:05:41.242Z",
"updated_at": "2026-10-27T03:05:41.251Z",
"runtime": "static",
"consent_version": null,
"pending_cleanup_json": null,
@@ -2125,8 +2125,8 @@
"nextJob": null
},
{
"id": "s_01a17b4e9b9fa42778ffcab812",
"group_id": "g_01a17b4e0e42dc2e48343c90d8",
"id": "s_01a17b521c7f20ea469b968d70",
"group_id": "g_01a17b518f0265b557c15c1355",
"name": "동시 B",
"purpose": "e2e",
"webmaker_project_id": "00000000000000000000000000000003",
@@ -2154,9 +2154,9 @@
"retention_until": null,
"delete_after": null,
"deleted_at": null,
"created_by": "u_01a17b4e0e427b45cdaa500d2f",
"created_at": "2026-10-27T03:01:51.647Z",
"updated_at": "2026-10-27T03:01:51.654Z",
"created_by": "u_01a17b518f02df498b213bb9c6",
"created_at": "2026-10-27T03:05:41.247Z",
"updated_at": "2026-10-27T03:05:41.254Z",
"runtime": "static",
"consent_version": null,
"pending_cleanup_json": null,
@@ -2214,8 +2214,8 @@
"name": "외부 배포 사이트 생성(초안) + 저장본",
"ok": true,
"detail": {
"id": "s_01a17b4fd3693432c3393d56d1",
"group_id": "g_01a17b4e0e173faf6cbcb4ef49",
"id": "s_01a17b5350ab6323ad5f7122f2",
"group_id": "g_01a17b518ed6c27ab829fa80d4",
"name": "E2E 외부 사이트",
"purpose": "e2e",
"webmaker_project_id": "00000000000000000000000000000004",
@@ -2243,9 +2243,9 @@
"retention_until": null,
"delete_after": null,
"deleted_at": null,
"created_by": "u_01a17b4e0d6dd0f50fadcba9f7",
"created_at": "2026-10-27T03:03:11.465Z",
"updated_at": "2026-10-27T03:03:11.471Z",
"created_by": "u_01a17b518e36fb0f05105dd2d3",
"created_at": "2026-10-27T03:07:00.140Z",
"updated_at": "2026-10-27T03:07:00.149Z",
"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:33115/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:33115 --token … --external --site s_01a17b4fd3693432c3393d56d1",
"expiresAt": "2026-10-27T03:13:11.692Z",
"install": "curl -fsSL http://127.0.0.1:39747/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:39747 --token … --external --site s_01a17b5350ab6323ad5f7122f2",
"expiresAt": "2026-10-27T03:17:00.349Z",
"status": "pending"
}
},
@@ -2483,8 +2483,8 @@
"overall": "partial",
"children": [
"srv_missing_e2e:failed:서버가 없습니다",
"srv_01a17b4e0721657bf3ba051c81:done:",
"srv_01a17b4e0679cb56194ed495da:done:"
"srv_01a17b518749e52e0c3ff114c9:done:",
"srv_01a17b5187f8c4f905c13a6f75:done:"
],
"timedOut": false
}
@@ -2522,8 +2522,8 @@
"overall": "partial",
"children": [
"srv_missing_e2e:failed:서버가 없습니다",
"srv_01a17b4e0721657bf3ba051c81:done:",
"srv_01a17b4e0679cb56194ed495da:done:"
"srv_01a17b518749e52e0c3ff114c9:done:",
"srv_01a17b5187f8c4f905c13a6f75:done:"
],
"timedOut": false
},
@@ -2562,7 +2562,7 @@
"name": "critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회)",
"ok": true,
"detail": {
"id": "em_01a17b501a8b833d135c751329",
"id": "em_01a17b5393336100b485ff41d8",
"noCred": "approvals_insufficient",
"wrongStep": "wrong_step",
"badIds": "identifiers_mismatch",
@@ -2570,7 +2570,7 @@
"approved": "approved",
"approvals": 3,
"credential": "[redacted]",
"expiresAt": "2026-10-27T03:33:29.701Z"
"expiresAt": "2026-10-27T03:37:17.199Z"
}
},
{
@@ -2591,14 +2591,14 @@
"evidence": {
"healthy": "hal_healthy",
"sess": {
"id": "em_01a17b501a8b833d135c751329",
"id": "em_01a17b5393336100b485ff41d8",
"noCred": "approvals_insufficient",
"wrongStep": "wrong_step",
"badIds": "identifiers_mismatch",
"badText": "final_confirm_mismatch",
"approved": "approved",
"approvals": 3,
"expiresAt": "2026-10-27T03:33:29.701Z"
"expiresAt": "2026-10-27T03:37:17.199Z"
},
"perf": {
"forbidden": "action_forbidden",
@@ -2650,8 +2650,8 @@
"ok": true,
"detail": {
"status": "readonly",
"deleteAfter": "2026-11-26T03:03:20.348Z",
"readonlyUntil": "2026-11-26T03:03:20.348Z"
"deleteAfter": "2026-11-26T03:07:07.005Z",
"readonlyUntil": "2026-11-26T03:07:07.005Z"
}
},
{
@@ -2677,8 +2677,8 @@
},
"un": {
"status": "readonly",
"deleteAfter": "2026-11-26T03:03:20.348Z",
"readonlyUntil": "2026-11-26T03:03:20.348Z"
"deleteAfter": "2026-11-26T03:07:07.005Z",
"readonlyUntil": "2026-11-26T03:07:07.005Z"
},
"sweep": {
"sweep": 200,
+73 -73
View File
@@ -1,6 +1,6 @@
# 로컬 종단 간 검증 기록 — 2026-10-27
생성: 2026-10-27T03:03:32.350Z · `node --no-warnings test/e2e.test.mjs` (test/e2e/)
생성: 2026-10-27T03:07:19.831Z · `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_01a17b4e0e173faf6cbcb4ef49
- ✔ 이메일 초대(7일) → 초대 메일 링크 — WXryn3p_v1Xa2vpHCGRZdselUZtX7f14
- ✔ 팀 그룹 만들기 → 현재 그룹 전환 — g_01a17b518ed6c27ab829fa80d4
- ✔ 이메일 초대(7일) → 초대 메일 링크 — buOtFSj9rEzpY8jtjncVE9uLJxHHOYIu
- ✔ 초대 수락(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_01a17b4e0ca92282c16d900239","price":49000,"sites":3},"deferred":3,"paymentMethods":["manual_transfer"]}
- ✔ 주문 제출(수동 결제) → 결제 대기·주문 번호·입금 안내 · 같은 키 재제출은 중복 없음 — {"orderNo":"AW-20261027-0001","status":"awaiting_payment","total":63800,"dueAt":"2026-11-03T03:01:15.526Z","hasInstructions":true}
- ✔ 상품 목록(공개 버전만·가격·한도·미확정 정책 표시) — {"count":2,"team":{"id":"pv_01a17b518d55e97d5c10bd0bda","price":49000,"sites":3},"deferred":3,"paymentMethods":["manual_transfer"]}
- ✔ 주문 제출(수동 결제) → 결제 대기·주문 번호·입금 안내 · 같은 키 재제출은 중복 없음 — {"orderNo":"AW-20261027-0001","status":"awaiting_payment","total":63800,"dueAt":"2026-11-03T03:05:05.095Z","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-27T03:01:15.590Z"}
- ✔ 고객 결제 화면: 활성 상품·권리·다음 청구일 — {"sites":3,"subs":1,"nextBilling":"2026-11-27T03:05:05.159Z"}
```json
{
@@ -112,25 +112,25 @@
"bill": {
"sites": 3,
"subs": 1,
"nextBilling": "2026-11-27T03:01:15.590Z"
"nextBilling": "2026-11-27T03:05:05.159Z"
}
}
```
### 4. 초안 사이트 생성
- ✔ 사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내 — {"id":"s_01a17b4e0f0596e869c55a6f32","lifecycle":"draft","projectId":"00000000000000000000000000000001","nextSteps":["build","import","domain","mode","perms"]}
- ✔ 사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내 — {"id":"s_01a17b518fc8d5127c10415dcf","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:41065/api/auth/sso?ticket=…","webmaker":302,"reuse":401}
- ✔ 제작 화면 열기: 포털 SSO 티켓 → Webmaker /api/auth/sso 302(1회용) — {"portalRedirect":"http://127.0.0.1:44993/api/auth/sso?ticket=…","webmaker":302,"reuse":401}
- ✔ Webmaker 에서 페이지 저장(저장본 2개) → 포털 저장본 목록·배포 전 확인 통과 — {"versions":1,"precheckOk":true}
```json
{
"sso": {
"portalRedirect": "http://127.0.0.1:41065/api/auth/sso?ticket=…",
"portalRedirect": "http://127.0.0.1:44993/api/auth/sso?ticket=…",
"webmaker": 302,
"reuse": 401
},
@@ -143,14 +143,14 @@
### 6. 관리형 배포
- ✔ 배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나 — {"jobId":"j_01a17b4e0f4d10386855ea18f3","steps":10,"again":false,"siteCount":"reserved","server":null}
- ✔ 배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나 — {"jobId":"j_01a17b519012bfc98402956999","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":"20261027030116-rev-c1-000000","verify":"needs_domain","health":"ok","agentHasSite":true}
- ✔ 배포 뒤 사이트 상태·버전·건강 — {"lifecycle":"needs_domain","version":"20261027030506-rev-c1-000000","verify":"needs_domain","health":"ok","agentHasSite":true}
```json
{
"d": {
"jobId": "j_01a17b4e0f4d10386855ea18f3",
"jobId": "j_01a17b519012bfc98402956999",
"steps": 10,
"again": false,
"siteCount": "reserved",
@@ -173,7 +173,7 @@
},
"after": {
"lifecycle": "needs_domain",
"version": "20261027030116-rev-c1-000000",
"version": "20261027030506-rev-c1-000000",
"verify": "needs_domain",
"health": "ok",
"agentHasSite": true
@@ -183,14 +183,14 @@
### 7. 도메인 연결과 HTTPS 발급
- ✔ 도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT) — {"domainId":"d_01a17b4e1f1e0d74d8b336d2c1","txtName":"_ariweb-verify.aw-e2e.localhost","txtValueShape":"ariweb-verify=…","copyable":true}
- ✔ 도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT) — {"domainId":"d_01a17b519fe52da880f4a82e08","txtName":"_ariweb-verify.aw-e2e.localhost","txtValueShape":"ariweb-verify=…","copyable":true}
- ✔ 도메인 목록·상세: 소유 확인·DNS·인증서 상태 표시 — {}
- ✔ (정리) 도메인 연결 작업 취소 — 포털이 외부 대기 중에도 5초마다 HAL domain.probe 를 새로 만들어 큐를 채우므로(결함 항목) 뒤 흐름을 위해 멈춘다 — {"queuedProbes":0}
```json
{
"dm": {
"domainId": "d_01a17b4e1f1e0d74d8b336d2c1",
"domainId": "d_01a17b519fe52da880f4a82e08",
"txtName": "_ariweb-verify.aw-e2e.localhost",
"txtValueShape": "ariweb-verify=…",
"copyable": true
@@ -233,9 +233,9 @@
### 10. 사이트 대표 메일과 메일함 생성
- ✔ 사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시 — {"id":"msv_01a17b4ea797d7fb719692b670","status":"needs_setup","tenant":true,"ariwebKind":"site_bound","limitAccounts":6,"siteCountAllocated":0}
- ✔ 사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시 — {"id":"msv_01a17b52287a808c772429e86b","status":"needs_setup","tenant":true,"ariwebKind":"site_bound","limitAccounts":6,"siteCountAllocated":0}
- ✔ 같은 사이트에 두 번째 대표 메일 거부 — mail_domain_in_use
- ✔ 메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음 — {"id":"mb_01a17b4ea7ca732850f5d9165a","address":"kim@aw-e2e.localhost","passwordShownOnce":"[redacted]","mustChange":true,"imapHost":"127.0.0.1","leakedOnReload":false,"inArimail":true}
- ✔ 메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음 — {"id":"mb_01a17b5228b49e1be9da6f5ed8","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_01a17b4ea797d7fb719692b670",
"id": "msv_01a17b52287a808c772429e86b",
"status": "needs_setup",
"tenant": true,
"ariwebKind": "site_bound",
@@ -252,7 +252,7 @@
"siteCountAllocated": 0
},
"mb": {
"id": "mb_01a17b4ea7ca732850f5d9165a",
"id": "mb_01a17b5228b49e1be9da6f5ed8",
"address": "kim@aw-e2e.localhost",
"passwordShownOnce": "[redacted]",
"mustChange": true,
@@ -287,13 +287,13 @@
### 12. 독립 메일 서비스 생성
- ✔ 메일 전용 도메인 등록(solo-e2e.localhost, purpose mail) — {"id":"d_01a17b4ec9cf77dcb746d36826","jobCancelled":null}
- ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a17b4ec9d72d484b64e3eaa2","kind":"standalone","mailServicesAllocated":1,"sitesAllocated":0}
- ✔ 메일 전용 도메인 등록(solo-e2e.localhost, purpose mail) — {"id":"d_01a17b5252c2383cb57c62cea1","jobCancelled":null}
- ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a17b5252caf9ee021a3b4674","kind":"standalone","mailServicesAllocated":1,"sitesAllocated":0}
```json
{
"r": {
"id": "msv_01a17b4ec9d72d484b64e3eaa2",
"id": "msv_01a17b5252caf9ee021a3b4674",
"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-28T03:02:03.627Z","nextRun":"2026-10-27T03:02:03.627Z"}
- ✔ 지금 백업 실행 → HAL backup.create(저장 서버 ≠ 운영 서버) → Agent → 검증 — {"timedOut":false,"optionStatus":"active","lastJob":null,"siteServer":"srv_01a17b4e0679cb56194ed495da","backup":{"status":"done","tier":"monthly","location":null},"job":null,"optionFailures":0}
- ✔ 옵션 활성화(확인 필수) → 첫 백업 예정 시각 — {"status":"active","firstDue":"2026-10-28T03:05:55.299Z","nextRun":"2026-10-27T03:05:55.299Z"}
- ✔ 지금 백업 실행 → HAL backup.create(저장 서버 ≠ 운영 서버) → Agent → 검증 — {"timedOut":false,"optionStatus":"active","lastJob":null,"siteServer":"srv_01a17b518749e52e0c3ff114c9","backup":{"status":"done","tier":"monthly","location":null},"job":null,"optionFailures":0}
```json
{
@@ -321,14 +321,14 @@
},
"opt": {
"status": "active",
"firstDue": "2026-10-28T03:02:03.627Z",
"nextRun": "2026-10-27T03:02:03.627Z"
"firstDue": "2026-10-28T03:05:55.299Z",
"nextRun": "2026-10-27T03:05:55.299Z"
},
"run1": {
"timedOut": false,
"optionStatus": "active",
"lastJob": null,
"siteServer": "srv_01a17b4e0679cb56194ed495da",
"siteServer": "srv_01a17b518749e52e0c3ff114c9",
"backup": {
"status": "done",
"tier": "monthly",
@@ -342,8 +342,8 @@
### 14. 백업 복원과 외부 사이트 확인
- ✔ 복원 안내(포함 데이터·영향·보호 시점 유지·되돌릴 수 있는 마지막 시각·완료 방식) — {"backup":{"id":"bk_01a17b4ee3ee59485506d00291","createdAt":"2026-10-27T03:02:03.633Z","scope":{"files":true,"db":true,"uploads":true,"config":true,"storeServerId":"srv_01a17b4e0721657bf3ba051c81","verifyMethod":"sha256 + 격리 환경 읽기 확인"},"sizeBytes":1899,"verifyState":"verified","expiresAt":"2027-01-2
- ✔ 복원 요청 → 운영자 승인 대기 · 중복 요청 없음 — {"id":"rs_01a17b4ee8062b5cf5f2a8f0c8","status":"awaiting_approval","dup":false,"finalConfirmation":{"group":"E2E 팀","site":"E2E 사이트","domain":null}}
- ✔ 복원 안내(포함 데이터·영향·보호 시점 유지·되돌릴 수 있는 마지막 시각·완료 방식) — {"backup":{"id":"bk_01a17b526498da95f409725dc6","createdAt":"2026-10-27T03:05:55.307Z","scope":{"files":true,"db":true,"uploads":true,"config":true,"storeServerId":"srv_01a17b5187f8c4f905c13a6f75","verifyMethod":"sha256 + 격리 환경 읽기 확인"},"sizeBytes":1902,"verifyState":"verified","expiresAt":"2027-01-2
- ✔ 복원 요청 → 운영자 승인 대기 · 중복 요청 없음 — {"id":"rs_01a17b52653b647f3cd00ad2b0","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_01a17b4ee3ee59485506d00291",
"createdAt": "2026-10-27T03:02:03.633Z",
"id": "bk_01a17b526498da95f409725dc6",
"createdAt": "2026-10-27T03:05:55.307Z",
"scope": {
"files": true,
"db": true,
"uploads": true,
"config": true,
"storeServerId": "srv_01a17b4e0721657bf3ba051c81",
"storeServerId": "srv_01a17b5187f8c4f905c13a6f75",
"verifyMethod": "sha256 + 격리 환경 읽기 확인"
},
"sizeBytes": 1899,
"sizeBytes": 1902,
"verifyState": "verified",
"expiresAt": "2027-01-28T03:02:10.158Z",
"expiresAt": "2027-01-28T03:05:59.704Z",
"tier": "monthly"
},
"included": [
@@ -393,7 +393,7 @@
"deferred": []
},
"rq": {
"id": "rs_01a17b4ee8062b5cf5f2a8f0c8",
"id": "rs_01a17b52653b647f3cd00ad2b0",
"status": "awaiting_approval",
"dup": false,
"finalConfirmation": {
@@ -445,10 +445,10 @@
### 16. 외부 배포 사이트 생성과 배포
- ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a17b4fd3693432c3393d56d1","group_id":"g_01a17b4e0e173faf6cbcb4ef49","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_01a17b5350ab6323ad5f7122f2","group_id":"g_01a17b518ed6c27ab829fa80d4","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:33115/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:33115 --token … --external --site s_01a17b4fd3693432c3393d56d1","expiresAt":"2026-10-27T03:13:11.692Z","status":"pending"}
- ✔ 외부 Agent 일회용 토큰·설치 명령(토큰은 저장하지 않음) — {"token":"[redacted]","install":"curl -fsSL http://127.0.0.1:39747/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:39747 --token … --external --site s_01a17b5350ab6323ad5f7122f2","expiresAt":"2026-10-27T03:17:00.349Z","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:33115/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:33115 --token … --external --site s_01a17b4fd3693432c3393d56d1",
"expiresAt": "2026-10-27T03:13:11.692Z"
"install": "curl -fsSL http://127.0.0.1:39747/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:39747 --token … --external --site s_01a17b5350ab6323ad5f7122f2",
"expiresAt": "2026-10-27T03:17:00.349Z"
},
"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_01a17b4e0ca933a6d20a4e961a"}
- ✔ 축소 주문 입금 확인·승인 → 권리 교체(베이직) — {"approve":200,"sites":1,"mailboxes":5,"product":"pv_01a17b518d552f6fab4af3028d"}
```json
{
@@ -589,7 +589,7 @@
"approve": 200,
"sites": 1,
"mailboxes": 5,
"product": "pv_01a17b4e0ca933a6d20a4e961a"
"product": "pv_01a17b518d552f6fab4af3028d"
}
}
```
@@ -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_01a17b4ff6608d214265ad8214","status":"requested","expiresAt":"2026-11-03T03:03:20.416Z","bobNotified":true}
- ✔ 요청(보내는 그룹, 초안 사이트) → 받는 사용자 알림 — {"id":"tr_01a17b536bc42f2755059af110","status":"requested","expiresAt":"2026-11-03T03:07:07.076Z","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-27T03:03:20.464Z"}
- ✔ 운영자 심사(소유 증빙·도메인 확인) 미입력 거부 → 최종 확인 일치 승인 → 원자적 완료·청구 전환 시각 — {"bad":"final_confirmation_mismatch","ok":200,"status":"completed","cutover":"2026-10-27T03:07:07.125Z"}
- ✔ 완료 뒤: 보내는 쪽은 접근 불가(404), 받는 쪽 대시보드·사용량 일치, 불변 감사 — {"aliceSees":404,"bobSees":200,"bobUsageSites":0,"audit":true}
```json
@@ -645,9 +645,9 @@
"excluded": 3
},
"rq": {
"id": "tr_01a17b4ff6608d214265ad8214",
"id": "tr_01a17b536bc42f2755059af110",
"status": "requested",
"expiresAt": "2026-11-03T03:03:20.416Z",
"expiresAt": "2026-11-03T03:07:07.076Z",
"bobNotified": true
},
"acc": {
@@ -659,7 +659,7 @@
"bad": "final_confirmation_mismatch",
"ok": 200,
"status": "completed",
"cutover": "2026-10-27T03:03:20.464Z"
"cutover": "2026-10-27T03:07:07.125Z"
},
"after": {
"aliceSees": 404,
@@ -798,7 +798,7 @@
### 23. 실패 작업의 재시도와 되돌리기
- ✔ 배포 이력: 실패/성공 기록 · 재시도 가능 여부 · 문의용 작업 번호 — {"status":"done","canRetry":false,"jobId":"j_01a17b4e0f4d10386855ea18f3"}
- ✔ 배포 이력: 실패/성공 기록 · 재시도 가능 여부 · 문의용 작업 번호 — {"status":"done","canRetry":false,"jobId":"j_01a17b519012bfc98402956999"}
- ✔ 재시도 요청(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_01a17b4e0f4d10386855ea18f3"
"jobId": "j_01a17b519012bfc98402956999"
},
"rt": {
"status": 400,
@@ -823,16 +823,16 @@
### 24. 운영자 승인, 감사 기록, 고객 알림의 일치
- ✔ 고객 알림 센터에 승인 알림 — {"title":"주문 AW-20261027-0001 승인 — 권리가 활성화되었습니다","category":"billing","link":"/orders/o_01a17b4e0e86b38c3bd0b1daea"}
- ✔ 고객 알림 센터에 승인 알림 — {"title":"주문 AW-20261027-0001 승인 — 권리가 활성화되었습니다","category":"billing","link":"/orders/o_01a17b518f474b86f010e23bac"}
- ✔ 감사 기록: order.approve 는 삭제 불가(immutable) — {"action":"order.approve","immutable":true}
- ✔ 운영자 화면과 고객 주문 화면의 상태 일치 — {"status":"approved","approvedAt":"2026-10-27T03:01:15.590Z"}
- ✔ 운영자 화면과 고객 주문 화면의 상태 일치 — {"status":"approved","approvedAt":"2026-10-27T03:05:05.159Z"}
```json
{
"n": {
"title": "주문 AW-20261027-0001 승인 — 권리가 활성화되었습니다",
"category": "billing",
"link": "/orders/o_01a17b4e0e86b38c3bd0b1daea"
"link": "/orders/o_01a17b518f474b86f010e23bac"
},
"a": {
"action": "order.approve",
@@ -840,7 +840,7 @@
},
"same": {
"status": "approved",
"approvedAt": "2026-10-27T03:01:15.590Z"
"approvedAt": "2026-10-27T03:05:05.159Z"
}
}
```
@@ -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-20261027030110/dr-external","updated_at":"2026-10-27T03:03:29.403Z","updated_by":"op_01a17b4e057cee750a084a7d49"}
- ✔ 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-20261027030500/dr-external","updated_at":"2026-10-27T03:07:16.869Z","updated_by":"op_01a17b5186508745922b082baf"}
- ✔ 복구 사본 생성·검증·외부 보관(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-20261027030110/dr-external",
"updated_at": "2026-10-27T03:03:29.403Z",
"updated_by": "op_01a17b4e057cee750a084a7d49"
"external_store": "/home/master/work/Ariweb-suite/test/.tmp/e2e-20261027030500/dr-external",
"updated_at": "2026-10-27T03:07:16.869Z",
"updated_by": "op_01a17b5186508745922b082baf"
},
"snap": {
"verify": "ok",
@@ -907,15 +907,15 @@
### 26. 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지
- ✔ 재시작 전: 포털 진행 작업(메일 활성화 외부 대기)·HAL 작업 목록 — {"mailJob":{"id":"j_01a17b4eb7ea9023c2227280d3","status":"awaiting_external"},"halJobs":18}
- ✔ 포털 재시작 → 재로그인 없이 세션 유지·같은 작업 id·상태 유지 — {"sessionKept":"[redacted]","mailJob":{"id":"j_01a17b4eb7ea9023c2227280d3","status":"awaiting_external"},"same":true}
- ✔ 재시작 전: 포털 진행 작업(메일 활성화 외부 대기)·HAL 작업 목록 — {"mailJob":{"id":"j_01a17b523cef114f09b2efdd4f","status":"awaiting_external"},"halJobs":18}
- ✔ 포털 재시작 → 재로그인 없이 세션 유지·같은 작업 id·상태 유지 — {"sessionKept":"[redacted]","mailJob":{"id":"j_01a17b523cef114f09b2efdd4f","status":"awaiting_external"},"same":true}
- ✔ HAL 재시작 → 운영자 세션·작업 목록·서버 등록 유지 — {"opsSessionKept":"[redacted]","halJobs":18,"sameCount":true}
```json
{
"before": {
"mailJob": {
"id": "j_01a17b4eb7ea9023c2227280d3",
"id": "j_01a17b523cef114f09b2efdd4f",
"status": "awaiting_external"
},
"halJobs": 18
@@ -923,7 +923,7 @@
"pr": {
"sessionKept": "[redacted]",
"mailJob": {
"id": "j_01a17b4eb7ea9023c2227280d3",
"id": "j_01a17b523cef114f09b2efdd4f",
"status": "awaiting_external"
},
"same": true
@@ -939,7 +939,7 @@
### 27. 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공
- ✔ 밥 개인 그룹: 베이직(사이트 1) 주문·승인 — {"granted":6}
- ✔ 사이트 2개 생성·저장본 — [{"id":"s_01a17b4e9b9b08c08759ebfed6","group_id":"g_01a17b4e0e42dc2e48343c90d8","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_01a17b521c7ac52cc816c76c3b","group_id":"g_01a17b518f0265b557c15c1355","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_01a17b4fd3693432c3393d56d1","group_id":"g_01a17b4e0e173faf6cbcb4ef49","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_01a17b5350ab6323ad5f7122f2","group_id":"g_01a17b518ed6c27ab829fa80d4","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:33115/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:33115 --token … --external --site s_01a17b4fd3693432c3393d56d1","expiresAt":"2026-10-27T03:13:11.692Z","status":"pending"}
- ✔ 외부 Agent 일회용 토큰·설치 명령(토큰은 저장하지 않음) — {"token":"[redacted]","install":"curl -fsSL http://127.0.0.1:39747/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:39747 --token … --external --site s_01a17b5350ab6323ad5f7122f2","expiresAt":"2026-10-27T03:17:00.349Z","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_01a17b4e0721657bf3ba051c81:done:","srv_01a17b4e0679cb56194ed495da:done:"],"timedOut":false}
- ✔ agent 모듈을 두 서버 + 없는 서버에 배포 → 대상별 자식 작업·일부 실패를 성공으로 묶지 않음(partial/all_failed) — {"status":"failed","errorCode":"partial_failure","overall":"partial","children":["srv_missing_e2e:failed:서버가 없습니다","srv_01a17b518749e52e0c3ff114c9:done:","srv_01a17b5187f8c4f905c13a6f75: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_01a17b4e0721657bf3ba051c81:done:",
"srv_01a17b4e0679cb56194ed495da:done:"
"srv_01a17b518749e52e0c3ff114c9:done:",
"srv_01a17b5187f8c4f905c13a6f75:done:"
],
"timedOut": false
},
@@ -1079,21 +1079,21 @@
### 31. 긴급 복구의 승인 부족·만료 자격·허용 범위 밖 작업 거부와 정상 복구
- ✔ HAL 정상 상태에서 선언 거부(hal_healthy) — hal_healthy
- ✔ critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회) — {"id":"em_01a17b501a8b833d135c751329","noCred":"approvals_insufficient","wrongStep":"wrong_step","badIds":"identifiers_mismatch","badText":"final_confirm_mismatch","approved":"approved","approvals":3,"credential":"[redacted]","expiresAt":"2026-10-27T03:33:29.701Z"}
- ✔ critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회) — {"id":"em_01a17b5393336100b485ff41d8","noCred":"approvals_insufficient","wrongStep":"wrong_step","badIds":"identifiers_mismatch","badText":"final_confirm_mismatch","approved":"approved","approvals":3,"credential":"[redacted]","expiresAt":"2026-10-27T03:37:17.199Z"}
- ✔ 일반 배포(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_01a17b501a8b833d135c751329",
"id": "em_01a17b5393336100b485ff41d8",
"noCred": "approvals_insufficient",
"wrongStep": "wrong_step",
"badIds": "identifiers_mismatch",
"badText": "final_confirm_mismatch",
"approved": "approved",
"approvals": 3,
"expiresAt": "2026-10-27T03:33:29.701Z"
"expiresAt": "2026-10-27T03:37:17.199Z"
},
"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-26T03:03:20.348Z","readonlyUntil":"2026-11-26T03:03:20.348Z"}
- ✔ 옵션 해제 → 30일 읽기 전용(만료일·삭제 예정일·재활성화 안내) · push 차단 — {"status":"readonly","deleteAfter":"2026-11-26T03:07:07.005Z","readonlyUntil":"2026-11-26T03:07:07.005Z"}
- ✔ 삭제 정리 작업(sweep)은 삭제 예정일 전이면 지우지 않음 — {"sweep":200,"statusAfter":"readonly"}
```json
@@ -1131,8 +1131,8 @@
},
"un": {
"status": "readonly",
"deleteAfter": "2026-11-26T03:03:20.348Z",
"readonlyUntil": "2026-11-26T03:03:20.348Z"
"deleteAfter": "2026-11-26T03:07:07.005Z",
"readonlyUntil": "2026-11-26T03:07:07.005Z"
},
"sweep": {
"sweep": 200,