Files
AriWeb-Suite/docs/verification/local-e2e-2026-10-26.md
T

53 KiB

로컬 종단 간 검증 기록 — 2026-10-26

생성: 2026-10-26T13:41:15.840Z · node --no-warnings test/e2e.test.mjs (test/e2e/)

실행 환경: 실제 HAL·고객 포털·Node Agent(명령 stub 모드) 프로세스 4대 + 가짜 Webmaker/AriMail/발신 메일 서버(127.0.0.1). 실 DNS·실 메일 왕복·실 서버는 포함하지 않는다.

요약

판정 수
로컬 확인 19
부분 확인 14
로컬 확인 불가 1
실패(결함) 0
unrecorded 0

흐름별 판정(Release 0.1.1 완료 조건 34개)

# 흐름 판정 확인 내용 / 비고
1 신규 고객 가입과 이메일 확인 로컬 확인 가입→확인 메일(포획)→1회용 토큰 확인→개인 그룹·별칭→중복 가입 안내→기기 목록·세션 종료
2 개인 사용자 그룹 생성과 다른 사용자 초대 로컬 확인 팀 그룹 생성·초대 메일·1회용 수락·별칭 user-02·역할 거부·그룹 전환 격리
3 상품 주문, 결제 확인, 권리 활성화 로컬 확인 상품 목록→주문(멱등·수동 결제)→입금 확인 전 승인 거부→입금 확인→승인→권리 활성화→재승인 중복 없음→청구 화면
4 초안 사이트 생성 로컬 확인 초안 생성·Webmaker 프로젝트 연결·사이트 수 미소비·목록 필터
5 Webmaker에서 페이지 제작 로컬 확인 SSO 티켓(1회용) 진입·저장본 목록·배포 전 확인(가짜 Webmaker — 실제 편집 UI 는 포함하지 않음)
6 관리형 배포 로컬 확인 배포 요청→예약→HAL 작업→Agent(stub) 배포→도메인 설정 필요 상태(대표 도메인 없음)
7 도메인 연결과 HTTPS 발급 부분 확인 도메인 추가·복사 가능한 TXT/DNS 값·외부 대기 상태까지 확인. 실 DNS 반영·인증서 발급·대표 도메인 확정은 로컬 확인 불가
8 외부 주소에서 사이트 확인 부분 확인 플랫폼 확인 주소(preview.localhost)로 HAL 외부 확인을 시도했으나 로컬에는 80 포트 웹서버가 없어 외부 확인 결과만 기록됨
9 사이트 정보와 접속·사용량 통계 확인 부분 확인 개요·사용량 조회(Agent stub 가 수집한 값)
10 사이트 대표 메일과 메일함 생성 부분 확인 대표 메일 생성(AriMail 테넌트·보장량·ariweb 표시)·메일함(비밀번호 1회·초대)·DNS 미반영 구분·활성화 작업 외부 대기까지 확인. DNS 반영 뒤 활성화 완료는 실 DNS 필요
11 외부 송신, 수신, 회신 확인 로컬 확인 불가 외부 송신·수신·회신은 실제 SMTP 제출(587)·수신함·외부 자동 회신 주소가 필요하다. 포털은 검증 주소 미설정 시 needs_user, 왕복 실패 시 활성화하지 않는 규칙(단위 테스트 test/mail.test.mjs)으로만 확인됨
12 독립 메일 서비스 생성 로컬 확인 독립 메일 서비스 생성: 독립 옵션 1 소비, 운영 사이트 수 미소비, 목록에 백업 미제공 문구
13 백업 옵션 구매와 첫 백업 확인 로컬 확인 옵션 구매·활성화·첫 백업(HAL→운영 Agent 생성→저장 Agent 보관→검증)
14 백업 복원과 외부 사이트 확인 부분 확인 복원 요청·운영자 최종 확인(불일치 거부·승인 멱등)·단계 진행까지. 외부 사이트 확인은 공인 서버 필요
15 관리형 사이트 서버 이전과 외부 확인 부분 확인 이전 경계값·요청 규칙 확인. 실제 두 Agent 간 사전 복사·전환·외부 확인은 배포된 사이트(흐름 6)와 공인 확인 주소가 필요
16 외부 배포 사이트 생성과 배포 로컬 확인 동의→토큰→실제 외부 Agent 등록·연결→배포→개요(제공되지 않음)→제거
17 사용량 경고와 한도 동작 확인 로컬 확인 메일 저장 공간 95%·월간 통수 100% 를 수집해 구간·제한 사유·KST 초기화·알림을 확인(웹 디스크/아웃바운드는 배포가 있어야 수집)
18 상품 축소 보류와 정리 후 적용 로컬 확인 축소 미리보기→보류→정리(배정 축소·독립 메일 해지)→재검증 적합→축소 주문·승인→권리 교체
19 사이트 수 반환과 대표 메일의 독립 전환 부분 확인 반환 점검표와 메일 유지/종료 분기 규칙 확인. 실제 반환은 운영 사이트(사이트 수 사용 중) 필요(단위 테스트 test/lifecycle.test.mjs·test/mail.test.mjs 로 확인)
20 사이트 소유권 이전 로컬 확인 미리보기→요청·알림→수락(보류·재확인)→심사·최종 확인→원자적 완료→접근 전환·감사
21 중지, 해지, 보존 예정일 표시 부분 확인 중지·재개·생명주기 화면 확인
22 HAL 우선 배포 후 HAL을 통한 전체 모듈 배포 로컬 확인 HAL 우선 순서·릴리스 승인·모듈 설치
23 실패 작업의 재시도와 되돌리기 부분 확인 실패 기록·재시도 API·되돌리기 거부/허용 조건 확인. 성공한 재배포 뒤 실제 되돌리기는 배포 성공이 필요
24 운영자 승인, 감사 기록, 고객 알림의 일치 로컬 확인 승인 → 고객 알림·불변 감사 기록·운영자/고객 화면 상태 일치(주문 기준)
25 HAL, 로그인, 비밀정보 관리, 인증서 기반의 재해복구 훈련 부분 확인 DR 설정(RTO/RPO/주기/보존)·사본 생성·검증·외부 보관·복원 승인/기존 제어 중지 확인/완료 항목·훈련 실패/성공→준비 완료 기록까지 확인. 실제 다른 호스트 복원은 실 서버 필요
26 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지 로컬 확인 포털·HAL 재시작 뒤 세션·진행 작업(외부 대기)·작업 목록 유지 확인. 진행 중(running) 배포의 단계 재개는 배포 성공(흐름 6) 뒤 확인 필요
27 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공 로컬 확인 마지막 사이트 수 1개를 두 배포가 동시에 요청 → 하나만 예약 성공, 다른 쪽은 부족 사유(§13.3)
28 외부 Linux 서버 Agent 연결·배포·재배포·제거·실패 정리 로컬 확인 외부 Agent 연결·제거·해제 API 확인. 재배포·실패 정리는 배포 성공이 필요(증거 참조)
29 HAL 장애 중 기존 서비스 유지와 신규 변경 제한 표시 로컬 확인 HAL 중단 중 포털 조회 유지·신규 배포 차단(정보 확인 지연), HAL 재기동 뒤 서버·Agent 복귀
30 모듈 업데이트, 일부 서버 실패, 대상별 되돌리기와 상태 조정 로컬 확인 대상별 자식 작업·일부 실패 partial 요약(전체 성공으로 묶지 않음)·건강 확인·drift 조정·되돌리기 3단계 확인 거부
31 긴급 복구의 승인 부족·만료 자격·허용 범위 밖 작업 거부와 정상 복구 부분 확인 정상 상태 선언 거부·3단계 승인(순서/식별정보/문구 거부)·자격 1회 발급·일반 배포/범위 밖/잘못된 자격 거부·수행·폐기 뒤 거부·조정·종료 확인. 자격 만료 거부는 유효 시간(분 단위 설정) 때문에 로컬 실행에서 생략(단위 테스트 test/hal.test.mjs 로 확인)
32 Source 옵션 만료 후 30일 read-only, clone·export와 삭제 완료 부분 확인 활성화·export·해제→읽기 전용(삭제 예정일)까지 확인. 30일 뒤 삭제 완료는 시간 경과가 필요(단위 테스트 test/sites.test.mjs 에서 sweep 로 확인)
33 메일 서비스의 사이트 대표→독립 전환 후 항목 유지 부분 확인 전환 점검표 8항목과 미충족 시 차단 확인. 전후 실제 송수신 확인·DNS 유지 검증은 실 DNS/AriMail 필요(단위 테스트 test/mail.test.mjs 에서 가짜로 전환 완료·되돌리기 확인)
34 일시 중지·해지 예약·보존·삭제 단계의 표시 일치 부분 확인 해지된 메일 서비스의 읽기 전용 보존·삭제 예정 표시와 사이트 생명주기 화면의 단계별 항목 확인. 사이트의 해지 예약→보존→삭제 전이는 운영 중 사이트 필요(단위 테스트 test/lifecycle.test.mjs 로 확인)

발견한 결함(재현 정보)

없음

실제 인프라가 필요한 항목

  • 도메인 소유 확인(TXT)·DNS 반영·HTTPS 발급·외부 접속 확인은 실제 DNS 와 공인 서버가 필요하다(흐름 7·8·11·14·15)
  • 메일 DNS(MX·SPF·DKIM·DMARC) 반영과 외부 송신·수신·회신 왕복은 실 도메인·실 AriMail(SMTP 587/수신함)·외부 검증 주소가 필요하다(흐름 10 활성화 완료·11·33 완료)
  • 재해복구의 실제 복원(hal-restore.sh 로 다른 호스트에 HAL 재기동)·실제 운영자 로그인·HTTPS·고객 서비스 외부 확인은 실 서버가 필요하다(흐름 25 기록·훈련 절차만 로컬 확인)

흐름별 증거(요약)

1. 신규 고객 가입과 이메일 확인

  • ✔ 가입(이메일·표시 이름·비밀번호·필수 동의) → 세션 발급 — {"status":200,"hasSession":"[redacted]"}
  • ✔ 확인 전 유료·운영 기능 차단(그룹 생성 403 email_unverified) — email_unverified
  • ✔ 확인 메일 수신(발신 포획)에서 토큰 추출 — {"subject":"[Ariweb] 이메일 주소를 확인해 주세요"}
  • ✔ 이메일 확인(1회용) → 재사용 거부 — {"first":200,"second":"token_used"}
  • ✔ 내 정보: 이메일 확인됨 · 개인 그룹 · owner · 별칭 user-01 — {"verified":true,"groupKind":"personal","role":"owner","alias":"user-01"}
  • ✔ 중복 가입은 존재를 드러내지 않는 안내(409 signup_unavailable) — signup_unavailable
  • ✔ 로그인 기기 목록 · 다른 세션 종료 — {"sessionsBefore":"[redacted]","revoked":2}
{
 "me": {
  "verified": true,
  "groupKind": "personal",
  "role": "owner",
  "alias": "user-01"
 }
}

2. 개인 사용자 그룹 생성과 다른 사용자 초대

  • ✔ 팀 그룹 만들기 → 현재 그룹 전환 — g_01a1786f982923b9f2a5d42da0
  • ✔ 이메일 초대(7일) → 초대 메일 링크 — 1Q4qKwkMTVqYSZzHwOiCiXEDb2bTjzyT
  • ✔ 초대 수락(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_01a1786f96aeaf5dbc68cb97ca","price":49000,"sites":3},"deferred":3,"paymentMethods":["manual_transfer"]}
  • ✔ 주문 제출(수동 결제) → 결제 대기·주문 번호·입금 안내 · 같은 키 재제출은 중복 없음 — {"orderNo":"AW-20261026-0001","status":"awaiting_payment","total":63800,"dueAt":"2026-11-02T13:39:01.930Z","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-26T13:39:01.993Z"}
{
 "ap": {
  "granted": 10,
  "already": true
 },
 "bill": {
  "sites": 3,
  "subs": 1,
  "nextBilling": "2026-11-26T13:39:01.993Z"
 }
}

4. 초안 사이트 생성

  • ✔ 사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내 — {"id":"s_01a1786f992b08508f0cad57b8","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:35879/api/auth/sso?ticket=…","webmaker":302,"reuse":401}
  • ✔ Webmaker 에서 페이지 저장(저장본 2개) → 포털 저장본 목록·배포 전 확인 통과 — {"versions":1,"precheckOk":true}
{
 "sso": {
  "portalRedirect": "http://127.0.0.1:35879/api/auth/sso?ticket=…",
  "webmaker": 302,
  "reuse": 401
 },
 "ver": {
  "versions": 1,
  "precheckOk": true
 }
}

6. 관리형 배포

  • ✔ 배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나 — {"jobId":"j_01a1786f997765631aebc605be","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":"20261026133903-rev-c1-000000","verify":"needs_domain","health":"ok","agentHasSite":true}
{
 "d": {
  "jobId": "j_01a1786f997765631aebc605be",
  "steps": 10,
  "again": false,
  "siteCount": "reserved",
  "server": null
 },
 "deployJob": {
  "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"
  ]
 },
 "after": {
  "lifecycle": "needs_domain",
  "version": "20261026133903-rev-c1-000000",
  "verify": "needs_domain",
  "health": "ok",
  "agentHasSite": true
 }
}

7. 도메인 연결과 HTTPS 발급

  • ✔ 도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT) — {"domainId":"d_01a1786fa94c717fb17b4eda3a","txtName":"_ariweb-verify.aw-e2e.localhost","txtValueShape":"ariweb-verify=…","copyable":true}
  • ✔ 도메인 목록·상세: 소유 확인·DNS·인증서 상태 표시 — {}
  • ✔ (정리) 도메인 연결 작업 취소 — 포털이 외부 대기 중에도 5초마다 HAL domain.probe 를 새로 만들어 큐를 채우므로(결함 항목) 뒤 흐름을 위해 멈춘다 — {"queuedProbes":0}
{
 "dm": {
  "domainId": "d_01a1786fa94c717fb17b4eda3a",
  "txtName": "_ariweb-verify.aw-e2e.localhost",
  "txtValueShape": "ariweb-verify=…",
  "copyable": true
 }
}

8. 외부 주소에서 사이트 확인

{}

9. 사이트 정보와 접속·사용량 통계 확인

  • ✔ 개요: 배포 전/후에 따라 준비 단계 또는 실제 통계 — {"kind":"managed","lifecycle":"needs_domain","hasStats":false,"readiness":null,"disk":{"used":null,"limit":1073741824}}
  • ✔ 사용량 탭(배정·실제 사용·마지막 계측) — {"allocations":["web_disk","web_out"]}
{
 "ov": {
  "kind": "managed",
  "lifecycle": "needs_domain",
  "hasStats": false,
  "readiness": null,
  "disk": {
   "used": null,
   "limit": 1073741824
  }
 },
 "us": {
  "allocations": [
   "web_disk",
   "web_out"
  ]
 }
}

10. 사이트 대표 메일과 메일함 생성

  • ✔ 사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시 — {"id":"msv_01a1787031d54f3f250dac0ce2","status":"needs_setup","tenant":true,"ariwebKind":"site_bound","limitAccounts":6,"siteCountAllocated":0}
  • ✔ 같은 사이트에 두 번째 대표 메일 거부 — mail_domain_in_use
  • ✔ 메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음 — {"id":"mb_01a17870320f11eedf7f6c6e3b","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"]}
  • ✔ 보안 보고서(SPF·DKIM·DMARC·MX 상태 + 복사 값) — {"items":["sender:error","mx:error","spf:error","dkim:error","dmarc:recommended","tls:error","roundtrip:recommended"],"records":4}
{
 "svc": {
  "id": "msv_01a1787031d54f3f250dac0ce2",
  "status": "needs_setup",
  "tenant": true,
  "ariwebKind": "site_bound",
  "limitAccounts": 6,
  "siteCountAllocated": 0
 },
 "mb": {
  "id": "mb_01a17870320f11eedf7f6c6e3b",
  "address": "kim@aw-e2e.localhost",
  "passwordShownOnce": "[redacted]",
  "mustChange": true,
  "imapHost": "127.0.0.1",
  "leakedOnReload": false,
  "inArimail": true
 },
 "dns": {
  "ok": false,
  "missing": 3,
  "items": [
   "mx:missing",
   "spf:missing",
   "dkim:missing",
   "dmarc:recommended"
  ]
 },
 "act": {
  "status": "awaiting_external",
  "waitReason": "DNS 레코드가 아직 반영되지 않았습니다(누락 3, 잘못된 값 0). 도메인 DNS 에 안내된 값을 넣은 뒤 '이어서 진행' 을 누르세요",
  "sameJob": true,
  "steps": [
   "entitlement:done",
   "dns:waiting",
   "prepare:pending",
   "verify:pending",
   "activate:pending"
  ]
 }
}

12. 독립 메일 서비스 생성

  • ✔ 메일 전용 도메인 등록(solo-e2e.localhost, purpose mail) — {"id":"d_01a178705c269810ff7cbfe471","jobCancelled":null}
  • ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a178705c2f17aa866ea77237","kind":"standalone","mailServicesAllocated":1,"sitesAllocated":0}
{
 "r": {
  "id": "msv_01a178705c2f17aa866ea77237",
  "kind": "standalone",
  "mailServicesAllocated": 1,
  "sitesAllocated": 0
 },
 "listCount": 2,
 "noBackupNote": "메일 원문은 고객용 백업·복원에 포함되지 않으며, 메일 백업 기능을 제공하지 않습니다."
}

13. 백업 옵션 구매와 첫 백업 확인

  • ✔ 백업 옵션 주문(대상 사이트)·입금 확인·승인 → backup 권리 — {"granted":7,"orderStatus":"approved"}
  • ✔ 백업 화면: 구매 가능 여부·저장 한도·일정·보존·첫 백업 기한·검증 방식(상품정책에서) — {"eligible":true,"reasons":[],"deferred":[],"policy":null,"status":200}
  • ✔ 옵션 활성화(확인 필수) → 첫 백업 예정 시각 — {"status":"active","firstDue":"2026-10-27T13:39:52.132Z","nextRun":"2026-10-26T13:39:52.132Z"}
  • ✔ 지금 백업 실행 → HAL backup.create(저장 서버 ≠ 운영 서버) → Agent → 검증 — {"timedOut":false,"optionStatus":"active","lastJob":null,"siteServer":"srv_01a1786f908cc425993b51166d","backup":{"status":"done","tier":"monthly","location":null},"job":null,"optionFailures":0}
{
 "el": {
  "eligible": true,
  "reasons": [],
  "deferred": [],
  "policy": null,
  "status": 200
 },
 "opt": {
  "status": "active",
  "firstDue": "2026-10-27T13:39:52.132Z",
  "nextRun": "2026-10-26T13:39:52.132Z"
 },
 "run1": {
  "timedOut": false,
  "optionStatus": "active",
  "lastJob": null,
  "siteServer": "srv_01a1786f908cc425993b51166d",
  "backup": {
   "status": "done",
   "tier": "monthly",
   "location": null
  },
  "job": null,
  "optionFailures": 0
 }
}

14. 백업 복원과 외부 사이트 확인

  • ✔ 복원 안내(포함 데이터·영향·보호 시점 유지·되돌릴 수 있는 마지막 시각·완료 방식) — {"backup":{"id":"bk_01a178706df58e2ec6b8315964","createdAt":"2026-10-26T13:39:52.139Z","scope":{"files":true,"db":true,"uploads":true,"config":true,"storeServerId":"srv_01a1786f913e88093d5c9ab3a8","verifyMethod":"sha256 + 격리 환경 읽기 확인"},"sizeBytes":1891,"verifyState":"verified","expiresAt":"2027-01-2
  • ✔ 복원 요청 → 운영자 승인 대기 · 중복 요청 없음 — {"id":"rs_01a178706e9a3a6cc10e6874f3","status":"awaiting_approval","dup":false,"finalConfirmation":{"group":"E2E 팀","site":"E2E 사이트","domain":null}}
  • ✔ 운영자 최종 확인(그룹·사이트·백업 시각) 불일치 거부 → 일치 승인(멱등) — {"bad":"final_confirmation_mismatch","ok":400}
  • ✔ 9단계 작업 진행 → 사용자 확인 필요(보호 시점·되돌리기 가능) — {"steps":[]}
{
 "info": {
  "backup": {
   "id": "bk_01a178706df58e2ec6b8315964",
   "createdAt": "2026-10-26T13:39:52.139Z",
   "scope": {
    "files": true,
    "db": true,
    "uploads": true,
    "config": true,
    "storeServerId": "srv_01a1786f913e88093d5c9ab3a8",
    "verifyMethod": "sha256 + 격리 환경 읽기 확인"
   },
   "sizeBytes": 1891,
   "verifyState": "verified",
   "expiresAt": "2027-01-27T13:39:56.534Z",
   "tier": "monthly"
  },
  "included": [
   "현재 배포본(파일)",
   "데이터베이스",
   "업로드 데이터",
   "복원에 필요한 사이트 설정"
  ],
  "excluded": [
   "메일 원문",
   "소스 저장소",
   "Webmaker 프로젝트 이력",
   "외부 배포 데이터",
   "플랫폼 내부 복구 사본"
  ],
  "impact": "복원 중 사이트가 일시적으로 이전 상태로 바뀌며, 복원 뒤 생성된 데이터는 보호 시점으로만 되돌릴 수 있습니다",
  "estimatedDowntimeMin": null,
  "cost": 0,
  "currency": "KRW",
  "costDeferred": false,
  "completionMode": "user_confirm",
  "protectPointDays": 7,
  "revertHours": 72,
  "approvalRequired": true,
  "conflicts": [],
  "canRequest": true,
  "deferred": []
 },
 "rq": {
  "id": "rs_01a178706e9a3a6cc10e6874f3",
  "status": "awaiting_approval",
  "dup": false,
  "finalConfirmation": {
   "group": "E2E 팀",
   "site": "E2E 사이트",
   "domain": null
  }
 },
 "ap": {
  "bad": "final_confirmation_mismatch",
  "ok": 400
 },
 "j": {
  "steps": []
 }
}

15. 관리형 사이트 서버 이전과 외부 확인

  • ✔ 이전 화면 경계값: 취소 가능 마지막 단계·쓰기 제한·목표 중단 시간·되돌림 기간·이전 위치 보존·비용(플랫폼 사유 0원) — {"auto":{"ok":false,"reasons":["사이트 대표 메일이 있는 사이트는 승인형으로 진행합니다","진행 중인 배포·복원·이전이 있습니다"]},"boundaries":{"lastCancel":"새 위치 확인(연결 전환 직전)까지 취소할 수 있습니다","revertHours":72,"cost":10000,"platformCost":0}}
  • ✔ 고객 요청(유료·승인 대기) 또는 조건 미충족 사유 — {"status":409,"error":"conflict","relocation":null,"message":"진행 중인 배포·복원·이전이 끝나야 이전을 시작할 수 있습니다"}
{
 "b": {
  "auto": {
   "ok": false,
   "reasons": [
    "사이트 대표 메일이 있는 사이트는 승인형으로 진행합니다",
    "진행 중인 배포·복원·이전이 있습니다"
   ]
  },
  "boundaries": {
   "lastCancel": "새 위치 확인(연결 전환 직전)까지 취소할 수 있습니다",
   "revertHours": 72,
   "cost": 10000,
   "platformCost": 0
  }
 },
 "req": {
  "status": 409,
  "error": "conflict",
  "relocation": null,
  "message": "진행 중인 배포·복원·이전이 끝나야 이전을 시작할 수 있습니다"
 }
}

16. 외부 배포 사이트 생성과 배포

  • ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a178715a0ac510fc2723b7d8","group_id":"g_01a1786f982923b9f2a5d42da0","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:36777/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:36777 --token … --external --site s_01a178715a0ac510fc2723b7d8","expiresAt":"2026-10-26T13:50:57.173Z","status":"pending"}
  • ✔ 고객 서버 역할의 외부 Agent 프로세스 등록·실행(HAL 등록, 사이트 귀속) — {"agentId":"[redacted]"}
  • ✔ 포털 연결 확인(외부 Agent 상태 connected) — {"status":"connected","stale":false,"halAgentId":"[redacted]"}
  • ✔ 배포 전 확인(외부: 관리형 용량 확인 없음) — {"ok":true,"blocks":[]}
  • ✔ 외부 배포 → 외부 Agent 가 site.deploy 수행 — {"status":"done","error":null,"errorCode":null,"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"]}
  • ✔ 외부 사이트 개요: 관리형 지표 없음·제공되지 않음 표기·Agent 상태·마지막 배포 결과 — {"kind":"external","notProvided":"[redacted]","agent":"connected","lastDeployOk":true}
  • ✔ 배포 제거(외부 서버 파일·실행 상태 제거, 실패 잔여 정리) — {"status":"done","error":null}
  • ✔ Agent 연결 해제(revoke) → 상태 없음 — {"status":200,"agent":null}
{
 "mode": {
  "notProvided": "[redacted]",
  "consentVersion": "external-deploy-2026-10-0.1.1",
  "agreed": false
 },
 "tok": {
  "install": "curl -fsSL http://127.0.0.1:36777/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:36777 --token … --external --site s_01a178715a0ac510fc2723b7d8",
  "expiresAt": "2026-10-26T13:50:57.173Z"
 },
 "conn": {
  "status": "connected",
  "stale": false,
  "halAgentId": "[redacted]"
 },
 "pc": {
  "ok": true,
  "blocks": []
 },
 "dep": {
  "status": "done",
  "error": null,
  "errorCode": null,
  "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"
  ]
 },
 "ov": {
  "kind": "external",
  "notProvided": "[redacted]",
  "agent": "connected",
  "lastDeployOk": true
 }
}

17. 사용량 경고와 한도 동작 확인

  • ✔ 가짜 AriMail 사용량 95%/월간 100% → 수집(refresh) → 구간·제한 사유·다음 KST 초기화·마지막 계측 표시 — {"storageLevel":95,"monthlyLevel":100,"nextReset":"2026-10-31T15:00:00.000Z","measuredAt":true,"limit100":"월간 통수 100%: 새 발신을 막고 새 수신은 임시 실패로 응답합니다. 다음 KST 월 시작 또는 추가 용량 적용 후 복구됩니다.","countingRule":"per_final_recipient","evaluate":{"status":200,"notified":2,"limited":1}}
  • ✔ 알림 센터: 95%·100% 경고가 사건으로 묶여 표시(필수 알림) — {"count":3,"titles":["aw-e2e.localhost 메일 저장 공간 96% — 곧 제한됩니다(최종 경고)","aw-e2e.localhost 월간 메일 통수(송신+수신) 100% — 제한 적용","메일함 kim@aw-e2e.localhost 저장 공간 95% 경고"],"mandatory":true}
  • ✔ 사용량 화면 제한 플래그(/api/usage/limits)·그룹 요약 — {"limits":["mail_msgs:100","mail_storage:95"],"warnLevels":[70,80,90,95]}
{
 "warn": {
  "storageLevel": 95,
  "monthlyLevel": 100,
  "nextReset": "2026-10-31T15:00:00.000Z",
  "measuredAt": true,
  "limit100": "월간 통수 100%: 새 발신을 막고 새 수신은 임시 실패로 응답합니다. 다음 KST 월 시작 또는 추가 용량 적용 후 복구됩니다.",
  "countingRule": "per_final_recipient",
  "evaluate": {
   "status": 200,
   "notified": 2,
   "limited": 1
  }
 },
 "notif": {
  "count": 3,
  "titles": [
   "aw-e2e.localhost 메일 저장 공간 96% — 곧 제한됩니다(최종 경고)",
   "aw-e2e.localhost 월간 메일 통수(송신+수신) 100% — 제한 적용",
   "메일함 kim@aw-e2e.localhost 저장 공간 95% 경고"
  ],
  "mandatory": true
 },
 "lim": {
  "limits": [
   "mail_msgs:100",
   "mail_storage:95"
  ],
  "warnLevels": [
   70,
   80,
   90,
   95
  ]
 }
}

18. 상품 축소 보류와 정리 후 적용

  • ✔ 배정 화면 수치(구매·내 배정·다른 서비스·예약·미배정·변경 후 최대) + 마지막 단위 동시 요청 — {"purchased":45,"mine":6,"others":2,"unallocated":37,"race":[409,409],"mineAfter":6}
  • ✔ 축소 미리보기(팀→베이직): 줄여야 할 항목(메일함 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_01a1786f96ae186ef39a5cdc8c"}
{
 "alloc": {
  "purchased": 45,
  "mine": 6,
  "others": 2,
  "unallocated": 37,
  "race": [
   409,
   409
  ],
  "mineAfter": 6
 },
 "pv": {
  "shortages": [
   "mailboxes:1"
  ]
 },
 "hold": {
  "fits": false,
  "hold": "holding",
  "sitesStillPurchased": 3
 },
 "clean": {
  "allocate": 200,
  "fits": true,
  "shortages": [],
  "order": "awaiting_payment"
 },
 "applied": {
  "approve": 200,
  "sites": 1,
  "mailboxes": 5,
  "product": "pv_01a1786f96ae186ef39a5cdc8c"
 }
}

19. 사이트 수 반환과 대표 메일의 독립 전환

  • ✔ §14 반환 점검표(8단계)·차단 사유(대표 메일 유지 선택·독립 옵션) — {"ok":false,"blocking":["public_stopped","domains_unrouted","no_jobs","mail_choice","mail_converted"],"steps":8}
  • ✔ 메일 유지 선택: 독립 옵션 부족이면 반환하지 않음 / 종료 선택 규칙 — {"status":409,"error":"site_public","detail":null}
{
 "ck": {
  "ok": false,
  "blocking": [
   "public_stopped",
   "domains_unrouted",
   "no_jobs",
   "mail_choice",
   "mail_converted"
  ],
  "steps": 8
 },
 "keep": {
  "status": 409,
  "error": "site_public",
  "detail": null
 }
}

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_01a17871751ae3edec78ee78f6","status":"requested","expiresAt":"2026-11-02T13:41:03.898Z","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-26T13:41:03.948Z"}
  • ✔ 완료 뒤: 보내는 쪽은 접근 불가(404), 받는 쪽 대시보드·사용량 일치, 불변 감사 — {"aliceSees":404,"bobSees":200,"bobUsageSites":0,"audit":true}
{
 "pv": {
  "sites": 1,
  "domains": 1,
  "siteMail": 1,
  "needed": {
   "backup": 1,
   "web_disk": 1073741824,
   "mail_msgs": 500,
   "mail_storage": 2147483648,
   "mailboxes": 6
  },
  "excluded": 3
 },
 "rq": {
  "id": "tr_01a17871751ae3edec78ee78f6",
  "status": "requested",
  "expiresAt": "2026-11-02T13:41:03.898Z",
  "bobNotified": true
 },
 "acc": {
  "status": 200,
  "tstatus": "reviewing",
  "hold": null
 },
 "ap": {
  "bad": "final_confirmation_mismatch",
  "ok": 200,
  "status": "completed",
  "cutover": "2026-10-26T13:41:03.948Z"
 },
 "after": {
  "aliceSees": 404,
  "bobSees": 200,
  "bobUsageSites": 0,
  "audit": true
 }
}

21. 중지, 해지, 보존 예정일 표시

  • ✔ §23.3 한 화면 보기: 공개/관리자 접근·사이트 수·청구·도메인/메일/백업 동작·내보내기·되돌릴 수 없는 시점·보존·다음 알림 — {"state":"도메인 설정 필요","siteCount":true,"publicAccess":"제공 중","retention":{"site_days":30,"mail_days":30,"source_days":30,"backup_days":7,"delete_grace_days":1,"notice_before_days":3,"readonly_retention_days":30,"delete_after_days":30,"pause_keeps_site_count":true,"cancel_effective":"next_billing_date
  • ✔ 일시 중지(확인 필수) 또는 현재 상태에서 거부 사유 — {"noConfirm":"confirmation_required","status":409,"error":"conflict"}
  • ✔ 메일 서비스 생명주기 화면(해지된 독립 메일: 송수신 중지·보존 종료·삭제 예정·백업 미제공) — {"status":200,"sendReceive":"송수신 가능","retentionUntil":false,"deleteAfter":false,"backupNote":"고객용 메일 백업·복원은 제공하지 않습니다(§17.4)"}
{
 "v": {
  "state": "도메인 설정 필요",
  "siteCount": true,
  "publicAccess": "제공 중",
  "retention": {
   "site_days": 30,
   "mail_days": 30,
   "source_days": 30,
   "backup_days": 7,
   "delete_grace_days": 1,
   "notice_before_days": 3,
   "readonly_retention_days": 30,
   "delete_after_days": 30,
   "pause_keeps_site_count": true,
   "cancel_effective": "next_billing_date",
   "source": "상품정책 베이직 (retention 정책) v1"
  },
  "pauseImpactKeys": [
   "publicAccess",
   "adminAccess",
   "mail",
   "backups",
   "usageAndCost",
   "resume",
   "siteCount"
  ],
  "cancelImpactKeys": [
   "target",
   "linked",
   "immediateStops",
   "remainingData",
   "retentionUntil",
   "deleteAfter",
   "retentionSource",
   "export",
   "unpaid",
   "refund",
   "pointOfNoReturn",
   "mailOptions",
   "siteCount"
  ],
  "steps14": 8
 },
 "pause": {
  "noConfirm": "confirmation_required",
  "status": 409,
  "error": "conflict"
 },
 "mailLc": {
  "status": 200,
  "sendReceive": "송수신 가능",
  "retentionUntil": false,
  "deleteAfter": false,
  "backupNote": "고객용 메일 백업·복원은 제공하지 않습니다(§17.4)"
 }
}

22. HAL 우선 배포 후 HAL을 통한 전체 모듈 배포

  • ✔ 릴리스 화면: 버전·main 커밋·승인 상태·HAL 실행 버전·부트스트랩(최초 Agent)·배포 순서 — {"releases":["0.1.2:approved","0.1.1:approved"],"running":{"complete":true,"firstInstall":false,"releaseVersion":null,"mainCommit":null,"artifactSha256":null,"halRunningVersion":"0.1.1","firstAgent":"[redacted]","manualInstallLocked":true},"order":["hal","agent","portal","webmaker","arimail","backup
  • ✔ HAL 이 아직 0.1.2 가 아닐 때 다른 모듈 0.1.2 배포 → hal_first 거부 — {"status":"failed","error":"hal_first"}
  • ✔ 승인되지 않은 릴리스는 거부 / 수동 설치 잠김 표시 — {"manualInstallLocked":true,"complete":true}
  • ✔ HAL 모듈 0.1.1 을 제어 서버에 배포(실제 Agent module.install: artifact 내려받기·sha256·releases/current·유닛(stub)·실제 버전 확인) — {"status":"done","steps":["precheck:done","fanout:done","collect:done"],"error":null,"children":["done:"],"agentJobs":[],"timedOut":false}
{
 "rel": {
  "releases": [
   "0.1.2:approved",
   "0.1.1:approved"
  ],
  "running": {
   "complete": true,
   "firstInstall": false,
   "releaseVersion": null,
   "mainCommit": null,
   "artifactSha256": null,
   "halRunningVersion": "0.1.1",
   "firstAgent": "[redacted]",
   "manualInstallLocked": true
  },
  "order": [
   "hal",
   "agent",
   "portal",
   "webmaker",
   "arimail",
   "backup",
   "observability"
  ]
 },
 "hf": {
  "status": "failed",
  "error": "hal_first"
 },
 "na": {
  "manualInstallLocked": true,
  "complete": true
 },
 "dep": {
  "status": "done",
  "steps": [
   "precheck:done",
   "fanout:done",
   "collect:done"
  ],
  "error": null,
  "children": [
   "done:"
  ],
  "agentJobs": [],
  "timedOut": false
 },
 "diag": null
}

23. 실패 작업의 재시도와 되돌리기

  • ✔ 배포 이력: 실패/성공 기록 · 재시도 가능 여부 · 문의용 작업 번호 — {"status":"done","canRetry":false,"jobId":"j_01a1786f997765631aebc605be"}
  • ✔ 재시도 요청(POST /api/sites/:id/jobs/:jobId/retry) — {"status":400,"error":"job_not_retryable"}
  • ✔ 되돌리기 요청(POST /api/sites/:id/rollback) — 이전 버전 없으면 거부 — {"status":409,"error":"no_previous","message":"되돌릴 이전 버전이 없습니다"}
{
 "dep": {
  "status": "done",
  "canRetry": false,
  "jobId": "j_01a1786f997765631aebc605be"
 },
 "rt": {
  "status": 400,
  "error": "job_not_retryable"
 },
 "rb": {
  "status": 409,
  "error": "no_previous",
  "message": "되돌릴 이전 버전이 없습니다"
 }
}

24. 운영자 승인, 감사 기록, 고객 알림의 일치

  • ✔ 고객 알림 센터에 승인 알림 — {"title":"주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다","category":"billing","link":"/orders/o_01a1786f98aada4e1d8cb6a80d"}
  • ✔ 감사 기록: order.approve 는 삭제 불가(immutable) — {"action":"order.approve","immutable":true}
  • ✔ 운영자 화면과 고객 주문 화면의 상태 일치 — {"status":"approved","approvedAt":"2026-10-26T13:39:01.993Z"}
{
 "n": {
  "title": "주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다",
  "category": "billing",
  "link": "/orders/o_01a1786f98aada4e1d8cb6a80d"
 },
 "a": {
  "action": "order.approve",
  "immutable": true
 },
 "same": {
  "status": "approved",
  "approvedAt": "2026-10-26T13:39:01.993Z"
 }
}

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-20261026133857/dr-external","updated_at":"2026-10-26T13:41:12.888Z","updated_by":"op_01a1786f8f8e2933698e86e10b"}
  • ✔ 복구 사본 생성·검증·외부 보관(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":{}}
{
 "r0": {
  "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": "정식 운영 준비 전(복구 훈련 또는 설정 필요)"
 },
 "cfg": {
  "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-20261026133857/dr-external",
  "updated_at": "2026-10-26T13:41:12.888Z",
  "updated_by": "op_01a1786f8f8e2933698e86e10b"
 },
 "snap": {
  "verify": "ok",
  "external": true,
  "sha": true
 },
 "rs": {
  "early": "restore_not_approved",
  "noStop": "old_control_not_confirmed",
  "command": true,
  "incomplete": "checks_incomplete",
  "final": "completed"
 },
 "drill": {
  "first": "failed",
  "second": "success",
  "ready": true,
  "measured": {}
 }
}

26. 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지

  • ✔ 재시작 전: 포털 진행 작업(메일 활성화 외부 대기)·HAL 작업 목록 — {"mailJob":{"id":"j_01a178704650615751827d968f","status":"awaiting_external"},"halJobs":19}
  • ✔ 포털 재시작 → 재로그인 없이 세션 유지·같은 작업 id·상태 유지 — {"sessionKept":"[redacted]","mailJob":{"id":"j_01a178704650615751827d968f","status":"awaiting_external"},"same":true}
  • ✔ HAL 재시작 → 운영자 세션·작업 목록·서버 등록 유지 — {"opsSessionKept":"[redacted]","halJobs":19,"sameCount":true}
{
 "before": {
  "mailJob": {
   "id": "j_01a178704650615751827d968f",
   "status": "awaiting_external"
  },
  "halJobs": 19
 },
 "pr": {
  "sessionKept": "[redacted]",
  "mailJob": {
   "id": "j_01a178704650615751827d968f",
   "status": "awaiting_external"
  },
  "same": true
 },
 "hr": {
  "opsSessionKept": "[redacted]",
  "halJobs": 19,
  "sameCount": true
 }
}

27. 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공

  • ✔ 밥 개인 그룹: 베이직(사이트 1) 주문·승인 — {"granted":6}
  • ✔ 사이트 2개 생성·저장본 — [{"id":"s_01a1787025d63eec09b696bba4","group_id":"g_01a1786f985a2da7ec88926cd7","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}
{
 "race": {
  "ok": 1,
  "blocked": "precheck_failed",
  "shortage": "block"
 }
}

28. 외부 Linux 서버 Agent 연결·배포·재배포·제거·실패 정리

  • ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a178715a0ac510fc2723b7d8","group_id":"g_01a1786f982923b9f2a5d42da0","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:36777/agent/v1/install.sh | bash -s -- --hal http://127.0.0.1:36777 --token … --external --site s_01a178715a0ac510fc2723b7d8","expiresAt":"2026-10-26T13:50:57.173Z","status":"pending"}
  • ✔ 고객 서버 역할의 외부 Agent 프로세스 등록·실행(HAL 등록, 사이트 귀속) — {"agentId":"[redacted]"}
  • ✔ 포털 연결 확인(외부 Agent 상태 connected) — {"status":"connected","stale":false,"halAgentId":"[redacted]"}
  • ✔ 배포 전 확인(외부: 관리형 용량 확인 없음) — {"ok":true,"blocks":[]}
  • ✔ 외부 배포 → 외부 Agent 가 site.deploy 수행 — {"status":"done","error":null,"errorCode":null,"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"]}
  • ✔ 외부 사이트 개요: 관리형 지표 없음·제공되지 않음 표기·Agent 상태·마지막 배포 결과 — {"kind":"external","notProvided":"[redacted]","agent":"connected","lastDeployOk":true}
  • ✔ 배포 제거(외부 서버 파일·실행 상태 제거, 실패 잔여 정리) — {"status":"done","error":null}
  • ✔ Agent 연결 해제(revoke) → 상태 없음 — {"status":200,"agent":null}
{
 "conn": {
  "status": "connected",
  "stale": false,
  "halAgentId": "[redacted]"
 },
 "dep": {
  "status": "done",
  "error": null,
  "errorCode": null,
  "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"
  ]
 },
 "rm": {
  "status": "done",
  "error": null
 },
 "rv": {
  "status": 200,
  "agent": null
 }
}

29. HAL 장애 중 기존 서비스 유지와 신규 변경 제한 표시

  • ✔ HAL 중단 중: 기존 조회(사이트 목록·개요)는 제공, 배포 전 확인/배포는 정보 확인 지연·차단 — {"list":200,"overview":200,"precheck":{"status":200,"blocks":["target:HAL(중앙 관리)에 연결할 수 없어 배포 위치를 확인하지 못했습니다(정"]},"deploy":{"status":409,"error":"precheck_failed","message":"배포 전 확인에서 차단된 항목이 있습니다"}}
  • ✔ 대시보드/알림에 중앙 상태 확인 지연 표시(가능하면) — {"status":200,"halState":null}
  • ✔ HAL 재기동(같은 호스트의 죽은 인스턴스 임대 이어받기) → 서버·Agent 상태 복귀 — {"takeoverNeeded":false,"bootstrap":true,"readyServers":4}
{
 "during": {
  "list": 200,
  "overview": 200,
  "precheck": {
   "status": 200,
   "blocks": [
    "target:HAL(중앙 관리)에 연결할 수 없어 배포 위치를 확인하지 못했습니다(정"
   ]
  },
  "deploy": {
   "status": 409,
   "error": "precheck_failed",
   "message": "배포 전 확인에서 차단된 항목이 있습니다"
  }
 },
 "dash": {
  "status": 200,
  "halState": null
 },
 "back": {
  "takeoverNeeded": false,
  "bootstrap": true,
  "readyServers": 4
 }
}

30. 모듈 업데이트, 일부 서버 실패, 대상별 되돌리기와 상태 조정

  • ✔ agent 모듈을 두 서버 + 없는 서버에 배포 → 대상별 자식 작업·일부 실패를 성공으로 묶지 않음(partial/all_failed) — {"status":"failed","errorCode":"partial_failure","overall":"partial","children":["srv_missing_e2e:failed:서버가 없습니다","srv_01a1786f913e88093d5c9ab3a8:done:","srv_01a1786f908cc425993b51166d: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"}
{
 "d": {
  "status": "failed",
  "errorCode": "partial_failure",
  "overall": "partial",
  "children": [
   "srv_missing_e2e:failed:서버가 없습니다",
   "srv_01a1786f913e88093d5c9ab3a8:done:",
   "srv_01a1786f908cc425993b51166d:done:"
  ],
  "timedOut": false
 },
 "h": {
  "health": "done",
  "drift": 0,
  "reconcile": 200,
  "overviewComponents": [
   "HAL:ok",
   "portal:unknown",
   "webmaker:unknown",
   "arimail:unknown",
   "backup:ok",
   "observability:ok"
  ]
 },
 "rb": {
  "step1": "confirm_impact_required",
  "step2": "identifiers_mismatch"
 }
}

31. 긴급 복구의 승인 부족·만료 자격·허용 범위 밖 작업 거부와 정상 복구

  • ✔ HAL 정상 상태에서 선언 거부(hal_healthy) — hal_healthy
  • ✔ critical 사건 등록 → 선언 → 승인 부족 자격 거부 → 3단계(순서·식별정보·문구) 거부/승인 → 자격 발급(1회) — {"id":"em_01a17871994d6c62aa90109d35","noCred":"approvals_insufficient","wrongStep":"wrong_step","badIds":"identifiers_mismatch","badText":"final_confirm_mismatch","approved":"approved","approvals":3,"credential":"[redacted]","expiresAt":"2026-10-26T14:11:13.193Z"}
  • ✔ 일반 배포(module.deploy) 거부 · 범위 밖 거부 · 잘못된 자격 거부 · 허용 행동 수행 · 확인 뒤 자격 폐기 · 폐기 뒤 거부 · 조정 · 종료 — {"forbidden":"action_forbidden","outOfScope":"out_of_scope","badCred":"bad_credential","performed":400,"revoked":true,"afterRevoke":"credential_revoked","reconcile":200,"closed":"closed"}
{
 "healthy": "hal_healthy",
 "sess": {
  "id": "em_01a17871994d6c62aa90109d35",
  "noCred": "approvals_insufficient",
  "wrongStep": "wrong_step",
  "badIds": "identifiers_mismatch",
  "badText": "final_confirm_mismatch",
  "approved": "approved",
  "approvals": 3,
  "expiresAt": "2026-10-26T14:11:13.193Z"
 },
 "perf": {
  "forbidden": "action_forbidden",
  "outOfScope": "out_of_scope",
  "badCred": "bad_credential",
  "performed": 400,
  "revoked": true,
  "afterRevoke": "credential_revoked",
  "reconcile": 200,
  "closed": "closed"
 }
}

32. Source 옵션 만료 후 30일 read-only, clone·export와 삭제 완료

  • ✔ 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-25T13:41:03.834Z","readonlyUntil":"2026-11-25T13:41:03.834Z"}
  • ✔ 삭제 정리 작업(sweep)은 삭제 예정일 전이면 지우지 않음 — {"sweep":200,"statusAfter":"readonly"}
{
 "act": {
  "activate": 200,
  "status": "active",
  "provider": "gitea",
  "changes": 1,
  "clone": true
 },
 "ex": {
  "status": 200,
  "type": "application/gzip"
 },
 "un": {
  "status": "readonly",
  "deleteAfter": "2026-11-25T13:41:03.834Z",
  "readonlyUntil": "2026-11-25T13:41:03.834Z"
 },
 "sweep": {
  "sweep": 200,
  "statusAfter": "readonly"
 }
}

33. 메일 서비스의 사이트 대표→독립 전환 후 항목 유지

  • ✔ 사이트 대표→독립 전환 점검표(주소·메일함·별칭·전달·비밀번호·DNS·옵션·작업) — {"canConvert":false,"items":["addresses:ok","data:ok","aliases:ok","forwards:ok","passwords:ok","dns:x","entitlement:x","jobs:x"]}
  • ✔ DNS 미확인 상태에서 전환 요청은 차단 — {"status":409,"error":"conversion_blocked"}
{
 "ck": {
  "canConvert": false,
  "items": [
   "addresses:ok",
   "data:ok",
   "aliases:ok",
   "forwards:ok",
   "passwords:ok",
   "dns:x",
   "entitlement:x",
   "jobs:x"
  ]
 },
 "tr": {
  "status": 409,
  "error": "conversion_blocked"
 }
}

34. 일시 중지·해지 예약·보존·삭제 단계의 표시 일치

  • ✔ §23.3 한 화면 보기: 공개/관리자 접근·사이트 수·청구·도메인/메일/백업 동작·내보내기·되돌릴 수 없는 시점·보존·다음 알림 — {"state":"도메인 설정 필요","siteCount":true,"publicAccess":"제공 중","retention":{"site_days":30,"mail_days":30,"source_days":30,"backup_days":7,"delete_grace_days":1,"notice_before_days":3,"readonly_retention_days":30,"delete_after_days":30,"pause_keeps_site_count":true,"cancel_effective":"next_billing_date
  • ✔ 일시 중지(확인 필수) 또는 현재 상태에서 거부 사유 — {"noConfirm":"confirmation_required","status":409,"error":"conflict"}
  • ✔ 메일 서비스 생명주기 화면(해지된 독립 메일: 송수신 중지·보존 종료·삭제 예정·백업 미제공) — {"status":200,"sendReceive":"송수신 가능","retentionUntil":false,"deleteAfter":false,"backupNote":"고객용 메일 백업·복원은 제공하지 않습니다(§17.4)"}
{
 "v": {
  "state": "도메인 설정 필요",
  "siteCount": true,
  "publicAccess": "제공 중",
  "retention": {
   "site_days": 30,
   "mail_days": 30,
   "source_days": 30,
   "backup_days": 7,
   "delete_grace_days": 1,
   "notice_before_days": 3,
   "readonly_retention_days": 30,
   "delete_after_days": 30,
   "pause_keeps_site_count": true,
   "cancel_effective": "next_billing_date",
   "source": "상품정책 베이직 (retention 정책) v1"
  },
  "pauseImpactKeys": [
   "publicAccess",
   "adminAccess",
   "mail",
   "backups",
   "usageAndCost",
   "resume",
   "siteCount"
  ],
  "cancelImpactKeys": [
   "target",
   "linked",
   "immediateStops",
   "remainingData",
   "retentionUntil",
   "deleteAfter",
   "retentionSource",
   "export",
   "unpaid",
   "refund",
   "pointOfNoReturn",
   "mailOptions",
   "siteCount"
  ],
  "steps14": 8
 },
 "mailLc": {
  "status": 200,
  "sendReceive": "송수신 가능",
  "retentionUntil": false,
  "deleteAfter": false,
  "backupNote": "고객용 메일 백업·복원은 제공하지 않습니다(§17.4)"
 }
}