Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
58 KiB
58 KiB
로컬 종단 간 검증 기록 — 2026-10-26
생성: 2026-10-26T13:02:10.786Z · node --no-warnings test/e2e.test.mjs (test/e2e/)
실행 환경: 실제 HAL·고객 포털·Node Agent(명령 stub 모드) 프로세스 4대 + 가짜 Webmaker/AriMail/발신 메일 서버(127.0.0.1). 실 DNS·실 메일 왕복·실 서버는 포함하지 않는다.
요약
| 판정 | 수 |
|---|---|
| 로컬 확인 | 13 |
| 부분 확인 | 15 |
| 로컬 확인 불가 | 2 |
| 실패(결함) | 4 |
| unrecorded | 0 |
흐름별 판정(Release 0.1.1 완료 조건 34개)
| # | 흐름 | 판정 | 확인 내용 / 비고 |
|---|---|---|---|
| 1 | 신규 고객 가입과 이메일 확인 | 로컬 확인 | 가입→확인 메일(포획)→1회용 토큰 확인→개인 그룹·별칭→중복 가입 안내→기기 목록·세션 종료 |
| 2 | 개인 사용자 그룹 생성과 다른 사용자 초대 | 로컬 확인 | 팀 그룹 생성·초대 메일·1회용 수락·별칭 user-02·역할 거부·그룹 전환 격리 |
| 3 | 상품 주문, 결제 확인, 권리 활성화 | 로컬 확인 | 상품 목록→주문(멱등·수동 결제)→입금 확인 전 승인 거부→입금 확인→승인→권리 활성화→재승인 중복 없음→청구 화면 |
| 4 | 초안 사이트 생성 | 로컬 확인 | 초안 생성·Webmaker 프로젝트 연결·사이트 수 미소비·목록 필터 |
| 5 | Webmaker에서 페이지 제작 | 로컬 확인 | SSO 티켓(1회용) 진입·저장본 목록·배포 전 확인(가짜 Webmaker — 실제 편집 UI 는 포함하지 않음) |
| 6 | 관리형 배포 | 실패(결함) | 시간 초과 — 포털 작업 queued(attempt 8, 배포 서버 작업 진행 중) · HAL site.deploy 작업 8개(최근: ["failed","Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni"]) |
| 7 | 도메인 연결과 HTTPS 발급 | 부분 확인 | 도메인 추가·복사 가능한 TXT/DNS 값·외부 대기 상태까지 확인. 실 DNS 반영·인증서 발급·대표 도메인 확정은 로컬 확인 불가 |
| 8 | 외부 주소에서 사이트 확인 | 로컬 확인 불가 | 외부 주소 확인은 공인 서버·DNS 필요 (선행 흐름 6 에 막힘) |
| 9 | 사이트 정보와 접속·사용량 통계 확인 | 부분 확인 | 미배포 사이트는 통계 대신 준비 단계 표시(§8.1) 확인. 실제 접속·네트워크 통계는 배포 뒤 수집 (선행 흐름 6 에 막힘) |
| 10 | 사이트 대표 메일과 메일함 생성 | 부분 확인 | 대표 메일 생성(AriMail 테넌트·보장량·ariweb 표시)·메일함(비밀번호 1회·초대)·DNS 미반영 구분·활성화 작업 외부 대기까지 확인. DNS 반영 뒤 활성화 완료는 실 DNS 필요 |
| 11 | 외부 송신, 수신, 회신 확인 | 로컬 확인 불가 | 외부 송신·수신·회신은 실제 SMTP 제출(587)·수신함·외부 자동 회신 주소가 필요하다. 포털은 검증 주소 미설정 시 needs_user, 왕복 실패 시 활성화하지 않는 규칙(단위 테스트 test/mail.test.mjs)으로만 확인됨 |
| 12 | 독립 메일 서비스 생성 | 로컬 확인 | 독립 메일 서비스 생성: 독립 옵션 1 소비, 운영 사이트 수 미소비, 목록에 백업 미제공 문구 |
| 13 | 백업 옵션 구매와 첫 백업 확인 | 부분 확인 | 옵션 구매·활성화까지 확인. 실제 백업 생성은 배포된 사이트 파일이 있어야 함(흐름 6 결함에 막힘) (선행 흐름 6 에 막힘) |
| 14 | 백업 복원과 외부 사이트 확인 | 부분 확인 | 완료된 백업이 없어 복원을 요청할 수 없음 (선행 흐름 13 에 막힘) |
| 15 | 관리형 사이트 서버 이전과 외부 확인 | 부분 확인 | 이전 경계값·요청 규칙 확인. 실제 두 Agent 간 사전 복사·전환·외부 확인은 배포된 사이트(흐름 6)와 공인 확인 주소가 필요 (선행 흐름 6 에 막힘) |
| 16 | 외부 배포 사이트 생성과 배포 | 실패(결함) | 동의(버전)·토큰 발급 거부/허용 규칙까지 확인했으나 토큰 발급 API 가 DB 제약으로 500 — 결함 항목 참조 |
| 17 | 사용량 경고와 한도 동작 확인 | 로컬 확인 | 메일 저장 공간 95%·월간 통수 100% 를 수집해 구간·제한 사유·KST 초기화·알림을 확인(웹 디스크/아웃바운드는 배포가 있어야 수집) |
| 18 | 상품 축소 보류와 정리 후 적용 | 로컬 확인 | 축소 미리보기→보류→정리(배정 축소·독립 메일 해지)→재검증 적합→축소 주문·승인→권리 교체 |
| 19 | 사이트 수 반환과 대표 메일의 독립 전환 | 부분 확인 | 반환 점검표와 메일 유지/종료 분기 규칙 확인. 실제 반환은 운영 사이트(사이트 수 사용 중) 필요(단위 테스트 test/lifecycle.test.mjs·test/mail.test.mjs 로 확인) (선행 흐름 6 에 막힘) |
| 20 | 사이트 소유권 이전 | 로컬 확인 | 미리보기→요청·알림→수락(보류·재확인)→심사·최종 확인→원자적 완료→접근 전환·감사 |
| 21 | 중지, 해지, 보존 예정일 표시 | 부분 확인 | 생명주기 화면·중지/해지 영향 안내·보존 정책 표시 확인. 운영 중 사이트의 실제 중지·해지는 배포 성공(흐름 6) 필요 (선행 흐름 6 에 막힘) |
| 22 | HAL 우선 배포 후 HAL을 통한 전체 모듈 배포 | 실패(결함) | HAL 우선 순서(hal_first 거부)·릴리스 승인·부트스트랩 잠김은 확인. 모듈 배포 작업은 완료되지 않음(진단 증거 참조) |
| 23 | 실패 작업의 재시도와 되돌리기 | 부분 확인 | 실패 기록·재시도 API·되돌리기 거부/허용 조건 확인. 성공한 재배포 뒤 실제 되돌리기는 배포 성공이 필요 (선행 흐름 6 에 막힘) |
| 24 | 운영자 승인, 감사 기록, 고객 알림의 일치 | 로컬 확인 | 승인 → 고객 알림·불변 감사 기록·운영자/고객 화면 상태 일치(주문 기준) |
| 25 | HAL, 로그인, 비밀정보 관리, 인증서 기반의 재해복구 훈련 | 부분 확인 | DR 설정(RTO/RPO/주기/보존)·사본 생성·검증·외부 보관·복원 승인/기존 제어 중지 확인/완료 항목·훈련 실패/성공→준비 완료 기록까지 확인. 실제 다른 호스트 복원은 실 서버 필요 |
| 26 | 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지 | 로컬 확인 | 포털·HAL 재시작 뒤 세션·진행 작업(외부 대기)·작업 목록 유지 확인. 진행 중(running) 배포의 단계 재개는 배포 성공(흐름 6) 뒤 확인 필요 |
| 27 | 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공 | 로컬 확인 | 마지막 사이트 수 1개를 두 배포가 동시에 요청 → 하나만 예약 성공, 다른 쪽은 부족 사유(§13.3) |
| 28 | 외부 Linux 서버 Agent 연결·배포·재배포·제거·실패 정리 | 부분 확인 | HAL·외부 Agent 경로(등록·연결·배포·재배포·실패 시 이전 버전 유지와 남은 파일·다른 사이트/허용되지 않는 종류 거부·제거)는 확인. 포털 화면 경로는 토큰 발급 결함으로 막힘 |
| 29 | HAL 장애 중 기존 서비스 유지와 신규 변경 제한 표시 | 로컬 확인 | HAL 중단 중 포털 조회 유지·신규 배포 차단(정보 확인 지연), HAL 재기동 뒤 서버·Agent 복귀 |
| 30 | 모듈 업데이트, 일부 서버 실패, 대상별 되돌리기와 상태 조정 | 실패(결함) | 대상별 자식 작업 생성·되돌리기 3단계 확인 거부는 확인. 일부 실패 요약·건강 확인·drift 조정은 작업 엔진 교착(흐름 22 결함)으로 완료되지 않음 (선행 흐름 22 에 막힘) |
| 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 로 확인) (선행 흐름 6 에 막힘) |
발견한 결함(재현 정보)
- Agent 가 포털 저장본(bundleUrl, 절대 URL)을 내려받지 못함(HAL 주소를 앞에 덧붙임) —
agent/client.mjs (download: fetchImpl(base + path)) / agent/jobs/site.mjs (fetchBundle)(HAL site.deploy → Agent fetch 단계)- 기대: bundleUrl 이 절대 URL 이면 그대로 내려받는다(서명은 HAL 전용이므로 포털 번들 URL 에는 붙이지 않음)
- 실제: Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni
- 재현: 포털 배포 → HAL site.deploy → Agent: "Failed to parse URL from http://http:///internal/bundles/…" · 영향 흐름 6, 8, 9, 13, 14, 15, 23
- 포털 배포 작업이 실패한 HAL 작업을 실패로 보지 않고 5초마다 새 HAL 작업을 무한 재제출(배포가 영원히 "진행 중") —
portal/sites/service.mjs (halJobFor: prev HAL 작업이 failed/cancelled 면 fresh 가 아니어도 새 작업을 만든다 → awaitHal 이 실패를 보지 못함)(POST /api/sites/:id/deploy)- 기대: HAL 작업이 failed 면 포털 작업도 failed(이전 버전 유지·재시도 버튼), 새 HAL 작업은 사용자 재시도(fresh) 때만
- 실제: portal job queued attempt 9 (배포 서버 작업 진행 중); HAL 에 같은 사이트의 site.deploy 실패 작업이 attempt 수만큼 쌓임
- 재현: Agent 가 실패하는 배포를 요청하고 /api/sites/:id/jobs/:id 를 지켜보면 attempt 가 5초마다 증가 · 영향 흐름 6, 23, 13, 20
- 관리형 배포 종단 흐름 실패(포털→HAL→Agent 저장본 전달) —
agent/client.mjs (download: fetchImpl(base + path)) / agent/jobs/site.mjs (fetchBundle: client.download(p.bundleUrl))(site.deploy → Agent fetch 단계)- 기대: 절대 URL(bundleUrl=http:///internal/bundles/)은 HAL base 를 붙이지 않고 그대로 내려받아야 한다
- 실제: 시간 초과 — 포털 작업 queued(attempt 8, 배포 서버 작업 진행 중) · HAL site.deploy 작업 8개(최근: ["failed","Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni"])
- 재현: 사이트 생성→저장본→POST /api/sites/:id/deploy→HAL site.deploy→Agent site.deploy fetch 단계에서 "HAL 내려받기 실패" 또는 잘못된 URL 오류 · 영향 흐름 6, 8, 9, 13, 14, 15, 23
- 취소된 배포 작업의 재시도 요청이 500(서버 오류)으로 응답(작업 상태 오류는 4xx 여야 함) —
portal/sites/api.mjs (jobs/:jobId/:action → lib/jobs JobError 를 HttpError 로 바꾸지 않음)(POST /api/sites/:id/jobs/:jobId/retry)- 기대: 409 job_not_retryable + 안내
- 실제: 500 job_not_retryable
- 재현: 취소된 작업에 retry · 영향 흐름 23
- 외부 Agent 토큰 발급이 DB 제약(external_agents.hal_agent_id NOT NULL)으로 500 —
portal/sites/service.mjs (createExternalAgent) / portal/sites/model.mjs (external_agents.hal_agent_id)(POST /api/sites/:id/external)- 기대: HAL 토큰 발급 응답의 agentId 가 null(등록 전)이어도 토큰·설치 명령을 돌려주고, Agent 등록 뒤 by-site 조회로 hal_agent_id 를 채운다
- 실제: 2026-10-26T12:59:27.404Z [http] POST /api/sites/s_01a1784b5d03e229abe92a46a1/external 500: Error: NOT NULL constraint failed: external_agents.hal_agent_id | at Object.run (file:///home/master/work/Ariweb-suite/lib/db.mjs:52:41) | at Object.createExternalAgent (file:///home/master/work/Ariweb
- 재현: 외부 배포 사이트 → 동의 → POST /api/sites/:id/external → 500 "NOT NULL constraint failed: external_agents.hal_agent_id" · 영향 흐름 16, 28
- HAL 작업 엔진이 작업을 한 번에 하나씩만 실행(tick 이 execute 를 await)해 느린 작업이 큐 전체를 막고, module.deploy 부모가 자식 완료를 기다리면 자식이 실행될 수 없어 교착 —
lib/jobs.mjs (createJobEngine.start/tick) / hal/jobs.mjs (fanout 이 자식 완료를 폴링)(POST /ops/api/modules/:name/deploy)- 기대: 엔진은 running 수가 concurrency 미만이면 실행을 분리(detach)해 여러 작업을 동시에 진행해야 한다(부모가 자식을 기다려도 자식이 실행됨)
- 실제: {"status":"queued","steps":["precheck:pending","fanout:pending","collect:pending"],"error":null,"children":[],"agentJobs":[],"timedOut":true}
- 재현: (1) Agent 가 수십 초 걸리는 domain.probe 가 queued 로 쌓이면 다른 모든 HAL 작업이 기다린다 (2) 큐가 비어도 모듈 배포는 부모 collect:running + 자식 queued 로 영원히 멈춘다(test/hal.test.mjs 는 pump() 로 tick 을 동시에 돌려 통과) · 영향 흐름 22, 30
실제 인프라가 필요한 항목
- 도메인 소유 확인(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_01a17849a2c2dec27ee9f14777
- ✔ 이메일 초대(7일) → 초대 메일 링크 — RAXi6X0pnmr-nCEKiIX63pk26vTbIB5d
- ✔ 초대 수락(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_01a17849a127747d9c2bbbb24c","price":49000,"sites":3},"deferred":3,"paymentMethods":["manual_transfer"]}
- ✔ 주문 제출(수동 결제) → 결제 대기·주문 번호·입금 안내 · 같은 키 재제출은 중복 없음 — {"orderNo":"AW-20261026-0001","status":"awaiting_payment","total":63800,"dueAt":"2026-11-02T12:57:34.263Z","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-26T12:57:34.327Z"}
{
"ap": {
"granted": 10,
"already": true
},
"bill": {
"sites": 3,
"subs": 1,
"nextBilling": "2026-11-26T12:57:34.327Z"
}
}
4. 초안 사이트 생성
- ✔ 사이트 생성(새 Webmaker 프로젝트·배포 방식 관리형) → 초안·사이트 수 미소비·다음 단계 안내 — {"id":"s_01a17849a3be6f07d129dbdb60","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:43619/api/auth/sso?ticket=…","webmaker":302,"reuse":401}
- ✔ Webmaker 에서 페이지 저장(저장본 2개) → 포털 저장본 목록·배포 전 확인 통과 — {"versions":1,"precheckOk":true}
{
"sso": {
"portalRedirect": "http://127.0.0.1:43619/api/auth/sso?ticket=…",
"webmaker": 302,
"reuse": 401
},
"ver": {
"versions": 1,
"precheckOk": true
}
}
6. 관리형 배포
- ✔ 배포 요청 → 10단계 작업·사이트 수 예약·같은 요청 재제출은 하나 — {"jobId":"j_01a17849a4096e33f476b17d1b","steps":10,"again":false,"siteCount":"reserved","server":null}
- ✘ HAL·Agent 를 거쳐 작업 완료 대기(최대 40초) — 시간 초과 — 포털 작업 queued(attempt 8, 배포 서버 작업 진행 중) · HAL site.deploy 작업 8개(최근: ["failed","Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni"])
- ✔ [HAL 직접] 같은 Agent 에 로컬 저장본으로 site.deploy → 성공하면 결함은 포털→Agent 번들 전달 구간 — {"status":"done","error":null,"layout":true,"externalCheck":{"ok":false,"status":404,"ms":19,"url":"http://direct_e2e.preview.localhost/","markerFound":false}}
- ✔ 사용자 취소(진행 중 작업) → 중간 결과 정리·예약 반환 — {"cancel":200,"cleanup":200,"siteCount":"reserved","lifecycle":"building"}
{
"halLog": [
"2026-10-26T12:58:01.231Z [jobs] site.deploy j_01a1784a0781bd4cb2d160762e 실패: Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni",
"2026-10-26T12:58:06.242Z [jobs] site.deploy j_01a1784a1b1b6362b7ba3a604c 실패: Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni",
"2026-10-26T12:58:11.248Z [jobs] site.deploy j_01a1784a2eb913f75494650303 실패: Failed to parse URL from http://127.0.0.1:35361http://127.0.0.1:35287/internal/bundles/v74iR3dd-nVRU4N1LEVDmEe0ZuK300Ni"
]
}
7. 도메인 연결과 HTTPS 발급
- ✔ 도메인 추가(웹·대표 지정) → 소유 확인 TXT 값 안내 → 작업이 외부 대기(TXT) — {"domainId":"d_01a1784a491476e7f378e8217c","txtName":"_ariweb-verify.aw-e2e.localhost","txtValueShape":"ariweb-verify=…","copyable":true}
- ✔ 도메인 목록·상세: 소유 확인·DNS·인증서 상태 표시 — {}
- ✔ (정리) 도메인 연결 작업 취소 — 포털이 외부 대기 중에도 5초마다 HAL domain.probe 를 새로 만들어 큐를 채우므로(결함 항목) 뒤 흐름을 위해 멈춘다 — {"queuedProbes":1}
{
"dm": {
"domainId": "d_01a1784a491476e7f378e8217c",
"txtName": "_ariweb-verify.aw-e2e.localhost",
"txtValueShape": "ariweb-verify=…",
"copyable": true
}
}
9. 사이트 정보와 접속·사용량 통계 확인
- ✔ 개요: 배포 전/후에 따라 준비 단계 또는 실제 통계 — {"kind":"not_deployed","lifecycle":"building","hasStats":false,"readiness":[{"id":"build","label":"Webmaker 에서 사이트 제작","href":"/sites/s_01a17849a3be6f07d129dbdb60?tab=build","done":true},{"id":"import","label":"콘텐츠 가져오기","href":"/sites/s_01a17849a3be6f07d129dbdb60?tab=build","done":false,"unsupporte
- ✔ 사용량 탭(배정·실제 사용·마지막 계측) — {"allocations":["web_disk","web_out"]}
{
"ov": {
"kind": "not_deployed",
"lifecycle": "building",
"hasStats": false,
"readiness": [
{
"id": "build",
"label": "Webmaker 에서 사이트 제작",
"href": "/sites/s_01a17849a3be6f07d129dbdb60?tab=build",
"done": true
},
{
"id": "import",
"label": "콘텐츠 가져오기",
"href": "/sites/s_01a17849a3be6f07d129dbdb60?tab=build",
"done": false,
"unsupported": "이번 버전에서 지원하지 않음"
},
{
"id": "domain",
"label": "도메인 연결",
"href": "/sites/s_01a17849a3be6f07d129dbdb60?tab=domains",
"done": false
},
{
"id": "mode",
"label": "배포 방식 선택",
"href": "/sites/s_01a17849a3be6f07d129dbdb60/deploy/mode",
"done": true
},
{
"id": "perms",
"label": "구성원 권한 설정",
"href": "/sites/s_01a17849a3be6f07d129dbdb60?tab=settings",
"done": false
}
],
"disk": null
},
"us": {
"allocations": [
"web_disk",
"web_out"
]
}
}
10. 사이트 대표 메일과 메일함 생성
- ✔ 사이트 대표 메일 생성(사이트 연결 도메인·배정) → AriMail 테넌트 준비·도메인별 보장량·ariweb 표시 — {"id":"msv_01a1784ad1a3c31d4ea6cb7447","status":"needs_setup","tenant":true,"ariwebKind":"site_bound","limitAccounts":6,"siteCountAllocated":0}
- ✔ 같은 사이트에 두 번째 대표 메일 거부 — mail_domain_in_use
- ✔ 메일함 생성: 초기 비밀번호 1회 표시·접속 안내 → 재조회에는 비밀번호 없음 — {"id":"mb_01a1784ad1df7ea80529a8cf8a","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_01a1784ad1a3c31d4ea6cb7447",
"status": "needs_setup",
"tenant": true,
"ariwebKind": "site_bound",
"limitAccounts": 6,
"siteCountAllocated": 0
},
"mb": {
"id": "mb_01a1784ad1df7ea80529a8cf8a",
"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_01a1784af801ae5cf98ef55e1f","jobCancelled":null}
- ✔ 독립 메일 서비스 생성(독립 옵션 소비·사이트 수 미소비) — {"id":"msv_01a1784af809f14b89bc606f73","kind":"standalone","mailServicesAllocated":1,"sitesAllocated":0}
{
"r": {
"id": "msv_01a1784af809f14b89bc606f73",
"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-27T12:59:01.665Z","nextRun":"2026-10-26T12:59:01.665Z"}
- ✔ 지금 백업 실행 → HAL backup.create(저장 서버 ≠ 운영 서버) → Agent → 검증 — {"timedOut":true,"optionStatus":"active","lastJob":null,"siteServer":null,"backup":null,"job":null}
{
"el": {
"eligible": true,
"reasons": [],
"deferred": [],
"policy": null,
"status": 200
},
"opt": {
"status": "active",
"firstDue": "2026-10-27T12:59:01.665Z",
"nextRun": "2026-10-26T12:59:01.665Z"
},
"run1": {
"timedOut": true,
"optionStatus": "active",
"lastJob": null,
"siteServer": null,
"backup": null,
"job": null
}
}
15. 관리형 사이트 서버 이전과 외부 확인
- ✔ 이전 화면 경계값: 취소 가능 마지막 단계·쓰기 제한·목표 중단 시간·되돌림 기간·이전 위치 보존·비용(플랫폼 사유 0원) — {"auto":{"ok":false,"reasons":["사이트 대표 메일이 있는 사이트는 승인형으로 진행합니다"]},"boundaries":{"lastCancel":"새 위치 확인(연결 전환 직전)까지 취소할 수 있습니다","revertHours":72,"cost":10000,"platformCost":0}}
- ✔ 고객 요청(유료·승인 대기) 또는 조건 미충족 사유 — {"status":200,"relocation":{"status":"awaiting_approval","kind":"customer_paid","cost":10000}}
- ✔ 전환 전 고객 취소 가능(요청 취소 → 소유권 이전 등 다른 작업 차단 해제) — {"status":200,"rstatus":"cancelled"}
{
"b": {
"auto": {
"ok": false,
"reasons": [
"사이트 대표 메일이 있는 사이트는 승인형으로 진행합니다"
]
},
"boundaries": {
"lastCancel": "새 위치 확인(연결 전환 직전)까지 취소할 수 있습니다",
"revertHours": 72,
"cost": 10000,
"platformCost": 0
}
},
"req": {
"status": 200,
"relocation": {
"status": "awaiting_approval",
"kind": "customer_paid",
"cost": 10000
}
}
}
16. 외부 배포 사이트 생성과 배포
- ✔ 외부 배포 사이트 생성(초안) + 저장본 — {"id":"s_01a1784b5d03e229abe92a46a1","group_id":"g_01a17849a2c2dec27ee9f14777","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 일회용 토큰·설치 명령(토큰은 저장하지 않음) — 500 {"ok":false,"error":"ERR_SQLITE_ERROR","message":"일시적인 오류가 발생했습니다. 잠시 후 다시 시도하세요"} | portal: 2026-10-26T12:59:27.404Z [http] POST /api/sites/s_01a1784b5d03e229abe92a46a1/external 500: Error: NOT NULL constraint failed: external_agents.hal_agent_id | at Object.run (file:///home/master/work/Ar
- ✔ [HAL 직접] 외부 Agent 토큰 발급(포털 서비스 키) — {"token":"[redacted]","expiresAt":"2026-10-26T13:09:27.408Z"}
- ✔ [HAL 직접] 외부 Agent 등록·실행(사이트 귀속) → by-site 상태 connected — {"agentId":"[redacted]","status":"connected","siteId":"s_01a1784b5d03e229abe92a46a1"}
- ✔ [HAL 직접] 외부 Agent 배포(site.deploy, 로컬 저장본) → releases/current 생성 — {"status":"done","error":null,"errorCode":null,"result":{"version":"20261026125937-v1","leftovers":[]},"layout":true}
- ✔ [HAL 직접] 재배포(v2) → 이전 버전 기록 — {"status":"done","error":null,"errorCode":null,"result":{"version":"20261026125950-v2","leftovers":[]}}
- ✔ [HAL 직접] 실패 배포(표식 불일치) → 이전 버전 유지·남은 파일(leftovers) 보고 — {"status":"failed","error":"내부 확인 실패(404) — 이전 버전을 유지합니다","errorCode":"internal_check_failed","result":null}
- ✔ [HAL 직접] 다른 사이트 작업 거부(site_mismatch) · 허용되지 않는 종류 거부 — {}
- ✔ [HAL 직접] 배포 제거(site.remove) → 파일·vhost 제거 — {"layoutAfter":false}
{
"mode": {
"notProvided": "[redacted]",
"consentVersion": "external-deploy-2026-10-0.1.1",
"agreed": false
}
}
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_01a17849a126e838aa8bb17065"}
{
"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_01a17849a126e838aa8bb17065"
}
}
19. 사이트 수 반환과 대표 메일의 독립 전환
- ✔ §14 반환 점검표(8단계)·차단 사유(대표 메일 유지 선택·독립 옵션) — {"ok":false,"blocking":["domains_unrouted","mail_choice","mail_converted"],"steps":8}
- ✔ 메일 유지 선택: 독립 옵션 부족이면 반환하지 않음 / 종료 선택 규칙 — {"status":409,"error":"quota_insufficient","detail":["resource","needed","available","link"]}
{
"ck": {
"ok": false,
"blocking": [
"domains_unrouted",
"mail_choice",
"mail_converted"
],
"steps": 8
},
"keep": {
"status": 409,
"error": "quota_insufficient",
"detail": [
"resource",
"needed",
"available",
"link"
]
}
}
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_01a1784c7a9e2007160a39f536","status":"requested","expiresAt":"2026-11-02T13:00:40.478Z","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:00:40.527Z"}
- ✔ 완료 뒤: 보내는 쪽은 접근 불가(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_01a1784c7a9e2007160a39f536",
"status": "requested",
"expiresAt": "2026-11-02T13:00:40.478Z",
"bobNotified": true
},
"acc": {
"status": 200,
"tstatus": "reviewing",
"hold": null
},
"ap": {
"bad": "final_confirmation_mismatch",
"ok": 200,
"status": "completed",
"cutover": "2026-10-26T13:00:40.527Z"
},
"after": {
"aliceSees": 404,
"bobSees": 200,
"bobUsageSites": 0,
"audit": true
}
}
21. 중지, 해지, 보존 예정일 표시
- ✔ §23.3 한 화면 보기: 공개/관리자 접근·사이트 수·청구·도메인/메일/백업 동작·내보내기·되돌릴 수 없는 시점·보존·다음 알림 — {"state":"제작 중","siteCount":false,"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","so
- ✔ 일시 중지(확인 필수) 또는 현재 상태에서 거부 사유 — {"noConfirm":"confirmation_required","status":409,"error":"lifecycle_transition"}
- ✔ 메일 서비스 생명주기 화면(해지된 독립 메일: 송수신 중지·보존 종료·삭제 예정·백업 미제공) — {"status":200,"sendReceive":"송수신 가능","retentionUntil":false,"deleteAfter":false,"backupNote":"고객용 메일 백업·복원은 제공하지 않습니다(§17.4)"}
{
"v": {
"state": "제작 중",
"siteCount": false,
"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": "lifecycle_transition"
},
"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":"queued","steps":["precheck:pending","fanout:pending","collect:pending"],"error":null,"children":[],"agentJobs":[],"timedOut":true}
{
"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": "queued",
"steps": [
"precheck:pending",
"fanout:pending",
"collect:pending"
],
"error": null,
"children": [],
"agentJobs": [],
"timedOut": true
},
"diag": {
"stuckNow": [
{
"id": "j_01a1784d7f02143ed47d1fcaf9",
"kind": "domain.probe",
"status": "queued",
"attempt": 0,
"step": "dispatch",
"detail": "",
"updatedAt": "2026-10-26T13:01:47.138Z"
},
{
"id": "j_01a1784d6b6a20949d03d2dee8",
"kind": "domain.probe",
"status": "queued",
"attempt": 0,
"step": "dispatch",
"detail": "",
"updatedAt": "2026-10-26T13:01:42.122Z"
},
{
"id": "j_01a1784d671c8f951049094a53",
"kind": "module.deploy",
"status": "queued",
"attempt": 0,
"step": "precheck",
"detail": "",
"updatedAt": "2026-10-26T13:01:41.020Z"
},
{
"id": "j_01a1784d57cf28f7528a1a8ccb",
"kind": "domain.probe",
"status": "queued",
"attempt": 0,
"step": "dispatch",
"detail": "",
"updatedAt": "2026-10-26T13:01:37.103Z"
},
{
"id": "j_01a1784d44338bf61b6691d5a8",
"kind": "domain.probe",
"status": "running",
"attempt": 1,
"step": "await",
"detail": "",
"updatedAt": "2026-10-26T13:01:4
23. 실패 작업의 재시도와 되돌리기
- ✔ 배포 이력: 실패/성공 기록 · 재시도 가능 여부 · 문의용 작업 번호 — {"status":"cancelled","failedStep":null,"canRetry":false,"jobId":"j_01a17849a4096e33f476b17d1b"}
- ✔ 재시도 요청(POST /api/sites/:id/jobs/:jobId/retry) — {"status":500,"error":"job_not_retryable"}
- ✔ 되돌리기 요청(POST /api/sites/:id/rollback) — 이전 버전 없으면 거부 — {"status":409,"error":"no_previous","message":"되돌릴 이전 버전이 없습니다"}
{
"dep": {
"status": "cancelled",
"failedStep": null,
"canRetry": false,
"jobId": "j_01a17849a4096e33f476b17d1b"
},
"rt": {
"status": 500,
"error": "job_not_retryable"
},
"rb": {
"status": 409,
"error": "no_previous",
"message": "되돌릴 이전 버전이 없습니다"
}
}
24. 운영자 승인, 감사 기록, 고객 알림의 일치
- ✔ 고객 알림 센터에 승인 알림 — {"title":"주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다","category":"billing","link":"/orders/o_01a17849a33601ca26016fa94a"}
- ✔ 감사 기록: order.approve 는 삭제 불가(immutable) — {"action":"order.approve","immutable":true}
- ✔ 운영자 화면과 고객 주문 화면의 상태 일치 — {"status":"approved","approvedAt":"2026-10-26T12:57:34.327Z"}
{
"n": {
"title": "주문 AW-20261026-0001 승인 — 권리가 활성화되었습니다",
"category": "billing",
"link": "/orders/o_01a17849a33601ca26016fa94a"
},
"a": {
"action": "order.approve",
"immutable": true
},
"same": {
"status": "approved",
"approvedAt": "2026-10-26T12:57:34.327Z"
}
}
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-20261026125729/dr-external","updated_at":"2026-10-26T13:02:07.823Z","updated_by":"op_01a178499a27f1ba88344e1145"}
- ✔ 복구 사본 생성·검증·외부 보관(vault.key 제외) — {"verify":"ok","external":true,"sha":true}
- ✔ 복원 요청 → 승인 전 시작 거부 → 승인 → 기존 제어 중지 확인 없이는 거부 → 시작(명령 안내) → 확인 항목 부족 거부 → 완료 — {"early":"restore_not_approved","noStop":"old_control_not_confirmed","command":true,"incomplete":"checks_incomplete","final":"completed_with_findings"}
- ✔ 복구 훈련: 항목 하나 실패 → 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-20261026125729/dr-external",
"updated_at": "2026-10-26T13:02:07.823Z",
"updated_by": "op_01a178499a27f1ba88344e1145"
},
"snap": {
"verify": "ok",
"external": true,
"sha": true
},
"rs": {
"early": "restore_not_approved",
"noStop": "old_control_not_confirmed",
"command": true,
"incomplete": "checks_incomplete",
"final": "completed_with_findings"
},
"drill": {
"first": "failed",
"second": "success",
"ready": true,
"measured": {}
}
}
26. 새로 고침, 재로그인, HAL 재시작 후 진행 작업 상태 유지
- ✔ 재시작 전: 포털 진행 작업(메일 활성화 외부 대기)·HAL 작업 목록 — {"mailJob":{"id":"j_01a1784ae61b14370f923139a1","status":"awaiting_external"},"halJobs":50}
- ✔ 포털 재시작 → 재로그인 없이 세션 유지·같은 작업 id·상태 유지 — {"sessionKept":"[redacted]","mailJob":{"id":"j_01a1784ae61b14370f923139a1","status":"awaiting_external"},"same":true}
- ✔ HAL 재시작 → 운영자 세션·작업 목록·서버 등록 유지 — {"opsSessionKept":"[redacted]","halJobs":50,"sameCount":true}
{
"before": {
"mailJob": {
"id": "j_01a1784ae61b14370f923139a1",
"status": "awaiting_external"
},
"halJobs": 50
},
"pr": {
"sessionKept": "[redacted]",
"mailJob": {
"id": "j_01a1784ae61b14370f923139a1",
"status": "awaiting_external"
},
"same": true
},
"hr": {
"opsSessionKept": "[redacted]",
"halJobs": 50,
"sameCount": true
}
}
27. 마지막 사이트 수·용량·메일함 동시 요청 시 하나만 성공
- ✔ 밥 개인 그룹: 베이직(사이트 1) 주문·승인 — {"granted":6}
- ✔ 사이트 2개 생성·저장본 — [{"id":"s_01a1784ac5a389af03b57b32a3","group_id":"g_01a17849a2ee454ab48b65c13d","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_01a1784b5d03e229abe92a46a1","group_id":"g_01a17849a2c2dec27ee9f14777","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 일회용 토큰·설치 명령(토큰은 저장하지 않음) — 500 {"ok":false,"error":"ERR_SQLITE_ERROR","message":"일시적인 오류가 발생했습니다. 잠시 후 다시 시도하세요"} | portal: 2026-10-26T12:59:27.404Z [http] POST /api/sites/s_01a1784b5d03e229abe92a46a1/external 500: Error: NOT NULL constraint failed: external_agents.hal_agent_id | at Object.run (file:///home/master/work/Ar
- ✔ [HAL 직접] 외부 Agent 토큰 발급(포털 서비스 키) — {"token":"[redacted]","expiresAt":"2026-10-26T13:09:27.408Z"}
- ✔ [HAL 직접] 외부 Agent 등록·실행(사이트 귀속) → by-site 상태 connected — {"agentId":"[redacted]","status":"connected","siteId":"s_01a1784b5d03e229abe92a46a1"}
- ✔ [HAL 직접] 외부 Agent 배포(site.deploy, 로컬 저장본) → releases/current 생성 — {"status":"done","error":null,"errorCode":null,"result":{"version":"20261026125937-v1","leftovers":[]},"layout":true}
- ✔ [HAL 직접] 재배포(v2) → 이전 버전 기록 — {"status":"done","error":null,"errorCode":null,"result":{"version":"20261026125950-v2","leftovers":[]}}
- ✔ [HAL 직접] 실패 배포(표식 불일치) → 이전 버전 유지·남은 파일(leftovers) 보고 — {"status":"failed","error":"내부 확인 실패(404) — 이전 버전을 유지합니다","errorCode":"internal_check_failed","result":null}
- ✔ [HAL 직접] 다른 사이트 작업 거부(site_mismatch) · 허용되지 않는 종류 거부 — {}
- ✔ [HAL 직접] 배포 제거(site.remove) → 파일·vhost 제거 — {"layoutAfter":false}
{
"halTok": {
"expiresAt": "2026-10-26T13:09:27.408Z"
},
"d1": {
"status": "done",
"error": null,
"errorCode": null,
"result": {
"version": "20261026125937-v1",
"leftovers": []
},
"layout": true
},
"d2": {
"status": "done",
"error": null,
"errorCode": null,
"result": {
"version": "20261026125950-v2",
"leftovers": []
}
},
"d3": {
"status": "failed",
"error": "내부 확인 실패(404) — 이전 버전을 유지합니다",
"errorCode": "internal_check_failed",
"result": null
},
"d4": {},
"d5": {
"layoutAfter": false
}
}
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":"queued","errorCode":null,"overall":"none","children":[],"timedOut":true}
- ✔ 모듈 건강 확인 작업·목표/실제 버전 차이(drift) 표시·다시 맞추기 작업 — {"health":"timeout","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": "queued",
"errorCode": null,
"overall": "none",
"children": [],
"timedOut": true
},
"h": {
"health": "timeout",
"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_01a1784dd0ec3f08f6ddf4281a","noCred":"approvals_insufficient","wrongStep":"wrong_step","badIds":"identifiers_mismatch","badText":"final_confirm_mismatch","approved":"approved","approvals":3,"credential":"[redacted]","expiresAt":"2026-10-26T13:32:08.135Z"}
- ✔ 일반 배포(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_01a1784dd0ec3f08f6ddf4281a",
"noCred": "approvals_insufficient",
"wrongStep": "wrong_step",
"badIds": "identifiers_mismatch",
"badText": "final_confirm_mismatch",
"approved": "approved",
"approvals": 3,
"expiresAt": "2026-10-26T13:32:08.135Z"
},
"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:00:40.412Z","readonlyUntil":"2026-11-25T13:00:40.412Z"}
- ✔ 삭제 정리 작업(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:00:40.412Z",
"readonlyUntil": "2026-11-25T13:00:40.412Z"
},
"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":false,"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","so
- ✔ 일시 중지(확인 필수) 또는 현재 상태에서 거부 사유 — {"noConfirm":"confirmation_required","status":409,"error":"lifecycle_transition"}
- ✔ 메일 서비스 생명주기 화면(해지된 독립 메일: 송수신 중지·보존 종료·삭제 예정·백업 미제공) — {"status":200,"sendReceive":"송수신 가능","retentionUntil":false,"deleteAfter":false,"backupNote":"고객용 메일 백업·복원은 제공하지 않습니다(§17.4)"}
{
"v": {
"state": "제작 중",
"siteCount": false,
"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)"
}
}