English {#english}
Purpose
Safely remove test-only emails and related assets in production after an explicit inventory and sign-off. This runbook does not replace legal/finance review; do not bulk-delete real customer data.
Related: Trial email (www) and admin handoff, Control Plane deletion-policy.ts (staged deletion with retention). For emergency teardown, align with ops and document deviation from the 30-day queue.
SQL helpers (replace placeholders, never commit secrets): prego-control-plane/scripts/production-test-data-reset/ — 01-inventory-by-email.sql, 02-inventory-by-tenant.sql, 03-delete-tenant-data.sql, 04-delete-trial-rows-by-email.sql.
Operator UI (optional): When TEST_DATA_RESET_ENABLED and allowlists are set on the Control Plane Worker, open /cp/test-data-reset in the operator React app. It calls GET/POST /internal/test-data-reset/* (same D1 + TENANT_ORIGINS scope as the SQL helpers). Frappe, Auth KV, and Stripe remain manual steps below.
1. Inventory (tests emails, tenant_id, site_name, Stripe ids)
-
Build a written list: normalized emails,
tenant_id,site_name,host, Stripe customer/subscription ids, and confirmation these rows are test-only (not production customers). -
From Control Plane D1 (read-only), run inventory scripts after substituting
__EMAIL_LOWER__/__TENANT_ID__:Terminal window cd /path/to/prego-control-plane# Example: copy SQL, replace placeholder, then:wrangler d1 execute prego-d1 --remote --file=scripts/production-test-data-reset/01-inventory-by-email.sql -
Cross-check
tenant_runtime.site_nameandhostfor Frappe/MariaDB teardown on the correct node. -
Gate: two-person review — inventory matches intent; no production tenant ids on the list.
2. Backup (D1, KV, MariaDB, snapshots)
- D1: Export or snapshot per Cloudflare / team procedure (
wrangler d1 backup/ dashboard) immediately before any write. - MariaDB / Frappe: Dump the site database or take a VM snapshot per infra policy; record site name and node.
- KV: List keys to be deleted (
wrangler kv key list --prefix=…forTENANT_ORIGINS,AUTH_STORE, prego_aierp_cred:prefix if used); save output. - Record timestamp and owner of each artifact.
3. Infra: Frappe bench and MariaDB
- On the app/db node identified from
tenant_runtime, use your standard procedure (aligned with Ansiblefrappe_sitelayout):bench drop-site SITE_NAME(or equivalent), drop the MariaDB database for that site, remove the corresponding directory undersites/if still present. - Confirm no remaining site:
bench list-sites/ directory check. - Order: typically after blocking new traffic for that tenant (optional gateway flag) and before or in parallel with CP row deletes — coordinate so URLs do not point at removed sites while KV still serves an origin.
4. Edge: KV (TENANT_ORIGINS, AUTH_STORE, prego_ai cache)
- TENANT_ORIGINS: delete keys for the tenant subdomain/origin per your key naming convention.
- Auth Worker
AUTH_STORE: remove keys such asuser:map:plus the normalized email, andpasscode_reset:*entries for test users (seetest-passcode-reset-flow.mdfor key patterns). - prego_ai: if
connector_credential_ref/erp_cred:KV entries were used for this tenant, remove those keys out-of-band (prego_ai deployment docs).
5. Control Plane D1 deletes
- Prefer official staged deletion flows (
requestTenantDeletion/ ops tooling) when they meet your timeline; otherwise use scripted SQL in a transaction after backups. - For a full tenant row bundle, use
03-delete-tenant-data.sql(replace__TENANT_ID__). It removes traces, jobs, billing rows, contracts, allocations,tenants_master, etc., in dependency-safe order. - For email-only trial rows (no tenant), use
04-delete-trial-rows-by-email.sql(replace__EMAIL_LOWER__). - Orphan users: if a
usersrow has no remaininguser_tenants, optionally deletesessions,otps, and theusersrow per privacy policy (see comments at end of03-delete-tenant-data.sql). - SQLite: if your environment enforces
FOREIGN KEY, verifyPRAGMA foreign_keys=ONand test on a copy first.
6. Stripe (production)
- With finance approval: cancel subscriptions, issue refunds if required by policy, then archive or delete test Stripe customers only.
- Keep audit records required for tax/accounting even if CP rows are removed.
7. Verification
- D1: re-run inventory queries — zero rows for the target email /
tenant_id. - KV: prefix list shows no keys for that tenant/email.
- Node: site and DB absent.
- Optional: repeat a clean trial signup with the same email (new browser profile) to confirm no stale state.
8. Governance and rollback
| Artifact | Action |
|---|---|
| Change record | Date, operator, approver, list of tenant ids/emails, ticket link |
| Rollback | Restore D1/KV/DB from backups taken in §2; document if only full snapshot restore is possible |
| Legal / DPA | Required if any row could be real customer or PII |
Rollback: restoring D1 from export is the primary path; partial KV/DB restore may require manual key restore from saved lists.
한국어 {#korean}
목적
프로덕션에서 테스트용으로만 쓰인 이메일·테넌트 자산을 인벤토리·승인 후 안전하게 제거합니다. 실제 고객 데이터 일괄 삭제 용도가 아닙니다.
참고: Trial email (www) and admin handoff, Control Plane 삭제 정책(deletion-policy.ts, 30일 보존 단계). 긴급 삭제 시 운영 합의 및 기록 필수.
SQL 보조 스크립트: prego-control-plane/scripts/production-test-data-reset/ (01~04, 플레이스홀더 치환 후 사용).
운영자 UI(선택): Worker에 TEST_DATA_RESET_ENABLED와 허용 목록이 있으면 Control Plane React 앱 /cp/test-data-reset 에서 동일 범위(D1 + TENANT_ORIGINS)를 호출할 수 있습니다.
1. 인벤토리
정규화 이메일, tenant_id, site_name, Stripe id 목록을 문서화하고, 테스트 전용임을 두 명이 확인합니다. wrangler d1 execute prego-d1 --remote --file=... 로 01·02 SQL을 실행합니다.
2. 백업
D1 익스포트, MariaDB 덤프 또는 노드 스냅샷, KV 키 목록 저장. 시각·담당자 기록.
3. 인프라 (Frappe / MariaDB)
tenant_runtime 기준 노드에서 bench drop-site, DB 삭제, sites/ 정리. Ansible frappe_site 경로와 일치하는지 확인.
4. KV
TENANT_ORIGINS, AUTH_STORE(user:map:, passcode_reset:), 필요 시 prego_ai erp_cred: 키 삭제.
5. D1 삭제
공식 삭제 큐/도구 우선. 단건 테넌트는 03-delete-tenant-data.sql, 이메일만 트라이얼 잔여면 04-delete-trial-rows-by-email.sql. 트랜잭션·백업 후 실행.
6. Stripe
재무 합의 하에 테스트 고객만 구독 해지·정리. 회계·세무상 보관이 필요한 기록은 유지.
7. 검증
인벤토리 쿼리 재실행(0건), KV·사이트 확인, 선택적 클린 재가입 스모크.
8. 거버넌스·롤백
변경 기록(일시·승인자·대상 id/이메일), D1/KV/DB 백업에서 복구 절차, 필요 시 법무·개인정보 검토.