Lifecycle FSM. Key columns: domain UNIQUE, status ∈ {available, reserved, assigned, cooldown, released}, reserved_until, cooldown_until, cf_zone_id, cert_ca, cert_expires_at, assigned_shield_id. Indices: (status, created_at) cho picker, (cert_expires_at) cho renewal cron.
vps_servers — new
DO droplet pool. Key columns: ip UNIQUE, capacity (default 5), current_shields (perf hint — picker counts live from shields), status ∈ {provisioning, active, draining, terminated}.
shields — new
One row per tenant. domain UNIQUE (semantic FK → domain_pool), tenant_id FK, vps_id FK, status ∈ {provisioning, ready, assigned, degraded, burning, burned}, health_status, admin_password_enc + service_token_enc AES-256-GCM. Indices: (vps_id, status), (tenant_id).
endpoints — extend
Add shield_id uuid (soft FK → shields.id, circular ref).
Retry với backoff 1s/5s/15s. Sau 3 fail → throw SpaceshipServerError, alert ops Telegram.
P0S-002Name generator với trademark filter2 AS
Generate {adj}-{noun}.{tld} từ pool 200×200×4 TLD (com 50% / store 25% / shop 20% / live 5%). Filter trademark blacklist min 500 từ + bigram patterns (*-disney-*, *-pokemon-*, *-funko-*...). Mỗi candidate run USPTO TSDR check trước register, reject nếu match Madrid Protocol top 1000.
UPDATE atomic SET status='reserved', reserved_until=now()+5min WHERE status='available'. 2 concurrent reserve → chỉ 1 thành công, kia nhận DomainAlreadyReservedError.
AS-009Assign sau reserve thành công›
Given
Domain reserved, reserved_until chưa hết hạn, deploy OK.
When
assignDomain(domain, shield_id).
Then
UPDATE SET status='assigned', assigned_shield_id, reserved_until=null. Constraint reserved AND reserved_until > now() fail → throw DomainReserveExpiredError.
AS-010Reserve expire tự về available›
Given
Domain reserved 6 phút trước, deploy crash không gọi assign/release.
When
Background expireReservations() chạy mỗi phút.
Then
UPDATE SET status='available', reserved_until=null WHERE status='reserved' AND reserved_until < now().
AS-011Shield burned → cooldown 180d›
Given
Shield s1 burned, domain assigned tới s1.
When
Ops trigger markShieldBurned(s1).
Then
UPDATE SET status='cooldown', cooldown_until=now()+180d WHERE assigned_shield_id=s1.
P0S-004Cloudflare zone + DNS + orange-cloud proxy3 AS
Sau register, set Spaceship NS → Cloudflare, create zone, add A record root + www tới VPS IP, bật orange-cloud proxy. NS propagation 5-10 phút block deploy.
AS-012Tạo CF zone + nameserver setup›
Given
Domain registered, CF token có Zone:Edit.
When
setupCloudflareZone(domain).
Then
CF API POST /zones → zone_id + 2 NS. UPDATE domain_pool.cf_zone_id. Call spaceship.setNameservers(domain, [ns1, ns2]).
AS-013A record + orange-cloud proxy bật›
Given
Zone created, VPS IP 159.65.10.20.
When
setDnsRecord(domain, vps_ip).
Then
CF API POST /zones/{id}/dns_records với {type:'A', name:'@', content:..., proxied:true} + record www. Verify proxied=true (C-002).
AS-014DNS-only mode → reject + retry›
Given
Record tạo nhưng proxied=false.
When
Verify step sau create.
Then
Detect → PATCH set proxied=true. Vẫn fail → throw CloudflareProxyError + alert ops. KHÔNG go-live với proxy off (C-002).
P0S-005Issue cert via acme.sh DNS-01 với 3-CA fallback5 AS
acme.sh trên host VPS, DNS-01 challenge qua CF token. Fallback chain: Let's Encrypt → ZeroSSL → Google Trust Services. Daily cron renew nếu cert_expires_at < 21d (C-110).
AS-016Happy path Let's Encrypt›
Given
CF zone setup OK, token có DNS:Edit, acme.sh installed.
Không update DB, Telegram urgent ALL_CA_FAILED domain=X expires_in=Y.
AS-020Daily cron renew nếu < 30 ngày›
Given
Cert cert_expires_at = now()+25d.
When
Daily cron acme.sh --cron.
Then
Trigger renew → cert mới → UPDATE cert_expires_at.
P1S-006Manual pool replenishment CLI1 AS
Phase 1 ops chạy rotor cli pool replenish <N> register N domain mới. Phase V1 cron tự maintain buffer ≥10.
AS-021Replenish 5 domain›
Given
Pool có 3 available, ops muốn buffer 8.
When
rotor cli pool replenish 5.
Then
Generator + Spaceship register 5 domain mới (throttle 30/30s). CLI in summary 5 registered, 0 failed. Pool available = 8.
P1S-007On-demand register khi pool empty1 AS
Khi deploy-shield.sh call reserveDomain() mà pool empty → adapter tự register fresh on-the-fly. Customer wizard nhận signal DEPLOYING_FRESH, email ETA 15 phút.
DO 4GB droplet pool, 5 shield/VPS, capacity-aware picker, deploy-shield.sh SSH → docker compose → WP-CLI fill → AliDropship import → health → INSERT shields. Phase 1 ops trigger thủ công.
P0S-001VPS capacity picker (count from shields, no denorm drift)3 AS
pickVps() chọn VPS active có capacity còn = capacity - COUNT(shields WHERE vps_id=X AND status NOT IN ('burned','terminated')). Bỏ denormalized current_shields hoặc INSERT shields atomic với increment trong cùng transaction; cleanup path AS-007/AS-015 phải decrement compensate.
AS-001Pick VPS with lowest load›
Given
3 VPS active: V1=4/5, V2=2/5, V3=5/5.
When
pickVps().
Then
Trả V2 (lowest under capacity).
Data: seed 3 rows.
AS-002All VPS full → NoVpsCapacityError›
Given
Mọi VPS active current_shields = capacity.
When
pickVps().
Then
Throw NoVpsCapacityError. Ops nhận Telegram alert.
SQL UPDATE ... SET current_shields = current_shields + 1 WHERE id=V1 AND current_shields < capacity RETURNING ... — final 5/5. Lần 3 song song pick V khác hoặc fail.
Payload store_name="Acme'; wp user create attacker ..." hoặc unicode fullwidth ;, ${IFS}cmd, argument injection --allow-root --path=/etc/passwd.
When
Input validation.
Then
Allowlist regex ^[A-Za-z0-9 .&'-]{1,60}$. Reject còn lại 400 INVALID_INPUT. CẤMbash -c "...$var..."; SSH dùng args array hoặc JSON stdin + jq -r. WP-CLI gọi qua --prompt stdin, KHÔNG CLI arg.
Master copy có 9 __MC_*__ trong wp-config.php + wp_options.
When
Entrypoint replace step.
Then
sed substitution wp-config.php; wp option update cho mỗi key. Verify grep __MC_ → 0 match.
AS-010Admin password client-side tại control plane, NEVER plaintext về›
Given
deploy-shield.sh trước SSH.
When
Generate password.
Then
Control plane gen 32-char random CLIENT-SIDE. Pass xuống shield qua SSH stdin (one-time), KHÔNG bake image, KHÔNG log. Shield wp user create admin --user_pass="$ADMIN_PASS". Plain store control plane DB AES-256-GCM. Shield endpoint /api/internal/shield-ready chỉ POST {shield_id, domain, vps_id, deploy_status} — KHÔNG password. Verify image: WP_DEBUG=false, WP_DEBUG_LOG=false, display_errors=Off (root C-018).
Data: one-time SSH stdin, terminal history scrubbed.
wp post create --post_type=page 3 page với brand_name + currency substituted. Set woocommerce_refund_returns_page_id v.v. Storefront child activate, CSS var --primary-color theo niche default. GET /refund-policy trả 200 chứa brand_name.
P1S-004AliDropship import + fallback CSV3 AS
Inside container, gọi wp adp-import --query=<niche query> --count=40 import 30–50 fake POD product. Fail → fallback CSV pre-bundled.
200 JSON {status:"degraded",db_ok:false} (200 không 500 để deploy script phân biệt).
P0S-003iframe_token JWT middleware — RS256 verify-only5 AS
Middleware verify JWT RS256/EdDSA với iframe_token_public_key từ rs_config. Shield CHỈ verify, KHÔNG sign. Check exp ≤ 10min, single-use qua jti. Transport: Authorization: Bearer header HOẶC POST body. CẤM query string ?token= (root C-011).
AS-006Valid token pass — RS256 verify với public key›
Given
JWT signed bằng control plane private key RS256, exp = now()+5min, jti mới.
When
Request endpoint với Authorization: Bearer <jwt>.
Then
Verify signature với rs_config.iframe_token_public_key. Pass → INSERT (jti, used_at=now()), forward handler. Token từ ?token= reject 400 INVALID_TRANSPORT (C-011).
AS-007Expired token → 401 TOKEN_EXPIRED›
Given
exp = now()-1min.
When
Request.
Then
401 {code:"TOKEN_EXPIRED"}. Không INSERT.
AS-008Replay (jti dùng rồi)›
Given
jti tồn tại trong dedup table.
When
Request lần 2.
Then
401 {code:"TOKEN_ALREADY_USED"}.
AS-009Bad signature›
Given
JWT sign bằng key khác.
When
Request.
Then
401 {code:"INVALID_SIGNATURE"}.
AS-010wp-cron cleanup hourly›
Given
Rows used_at < now()-15min.
When
rs_cleanup_tokens tick.
Then
DELETE expired. Table bounded.
P0S-004Shield service token + auto-refresh + outbox flush3 AS
5 endpoint orchestrate PayPal Smart Button. get-form + create-order dùng iframe_token; return-url / cancel-url / complete-order dùng signed session cookie từ get-form.
AS-001/woo-paypal-get-form render SDK — token via header/body›
Given
iframe_token valid, claim chứa paypal_client_id, account_credentials_ref (opaque ID, NOT access_token, NOT client_secret per root C-010), amount, currency, brand_name, invoice_prefix, wc_order_id, return_url.
When
POST /wp-json/rs/v1/woo-paypal-get-form với Authorization: Bearer <jwt> (C-011; GET ?token= REJECT).
Then
Trả HTML chứa PayPal SDK với client-id đúng. Set HTTP-only signed rs_session cookie (HMAC bind token claims). Shield resolve client_secret_enc qua account_credentials_ref lookup, decrypt in-memory ONLY tại OAuth call.
Verify rs_session HMAC, verify state nonce, redirect /paypal-complete-order (auto-submit form).
AS-004/paypal-cancel-url buyer cancel›
Given
Buyer click Cancel.
When
GET.
Then
Render Order canceled page + fire-and-forget POST /api/shield/event {type:"cancel"}.
AS-005/paypal-complete-order capture — outbox before redirect›
Given
Session cookie valid, PayPal order approved.
When
POST.
Then
(1) POST /v2/checkout/orders/{id}/capture với PayPal-Request-Id: <checkout_id>. (2) Success → INSERT wp_rs_outbox{event_type:"capture",payload:{...}} cùng DB tx. (3) Outbox cron forward control plane qua service token; 401 → immediate refresh-retry; 5xx → exp backoff. (4) Buyer chỉ redirect return_url SAU KHI outbox commit (root C-012). Container restart resume từ pending row.
P0S-002Webhook receiver /paypal-webhook-return5 AS
PayPal call về cho mỗi event subscribed. Verify signature, dedup paypal_event_id, INSERT outbox, trả 200 ngay sau verify+outbox.
PayPal POST với headers paypal-transmission-sig, paypal-cert-url, paypal-transmission-id, paypal-transmission-time, body event.
When
POST /wp-json/rs/v1/paypal-webhook-return.
Then
(1) Rate limit 60 req/min/IP — vượt → 429. (2) Validate paypal-cert-url host match ^https://api(\.sandbox)?\.paypal\.com/ — fail → 401 INVALID_CERT_URL (chống SSRF qua 169.254.169.254). (3) Egress allowlist VPS block RFC1918 + metadata. (4) Verify sig qua PayPal cert. (5) Check paypal_event_id dedup. (6) INSERT dedup + outbox cùng tx (C-012). (7) Outbox cron forward. (8) Trả 200 cho PayPal ngay.
AS-007Bad signature reject›
Given
Sig header sai.
When
POST.
Then
401 {code:"INVALID_WEBHOOK_SIG"}. Không INSERT, không forward.
AS-008Duplicate event (PayPal retry)›
Given
paypal_event_id đã có trong dedup.
When
POST.
Then
200 + {deduped:true}. Không forward lần 2.
AS-009Control plane unreachable → outbox retry›
Given
Forward call nhận 5xx / timeout.
When
Webhook handler.
Then
Schedule wp-cron retry (1min/5min/30min backoff). Trả 200 PayPal ngay. Sau 3 fail → error_log + alert qua control plane khi online.
AS-010Dedup cleanup daily > 72h›
Given
Rows received_at < now()-72h.
When
Daily rs_cleanup_webhook_dedup.
Then
DELETE. Bounded.
P0S-003Webhook subscription per account (control plane track)2 AS
Control plane subscribe shield URL https://{shield_domain}/wp-json/rs/v1/paypal-webhook-return qua PayPal POST /v1/notifications/webhooks. Lưu webhook_id vào paypal_accounts.webhook_id. Fire-and-forget BANNED (root C-013).
AS-013Happy path subscribe›
Given
Customer add PayPal credentials, shield ready, control plane có OAuth token.
When
Control plane call PayPal POST /v1/notifications/webhooks body {url, event_types:[CHECKOUT.ORDER.APPROVED, PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.REFUNDED, CUSTOMER.DISPUTE.CREATED, ...]}.
Then
201 {id:"WH-...",url,event_types}. UPDATE paypal_accounts.webhook_id, webhook_status='active'. Push xuống shield qua POST /api/internal/shield-config-update → shield UPDATE rs_config.webhook_id.
UPDATE webhook_status='pending_retry'. Schedule cron rs_retry_webhook_subscribe mỗi 5min × 10 với backoff 300/600/1200/2400s. Pre-flight probe /wp-json/rs/v1/health. Sau 10 fail → Telegram urgent + email customer Don't run ads yet — webhook setup pending. webhook_status='failed'. Account status='active' block nếu webhook_status NOT IN ('active','pending_retry') (root C-013). Control plane cron webhook_health_check hourly + send test event. Dashboard badge webhook unverified + nút manual retry.
P1S-004Account status probe /paypal-check-account-status2 AS
Control plane call shield probe PayPal OAuth health (dùng outbound IP của VPS — PayPal validate IP pattern). Trả pass/fail + paypal error nếu restricted.
AS-015OAuth pass›
Given
Valid client_id + client_secret từ token claim.
When
GET /wp-json/rs/v1/paypal-check-account-status với iframe_token.
Then
Plugin call PayPal POST /v1/oauth2/token grant_type=client_credentials. Trả {ok:true,scope,app_id,nonce}.
AS-016Account restricted → ACCOUNT_RESTRICTED›
Given
PayPal trả invalid_client hoặc 403.
When
Probe.
Then
Trả {ok:false,code:"ACCOUNT_RESTRICTED",paypal_error:<body>}. Control plane consume signal cho health-monitor workflow.
Asymmetric JWT — control plane giữ private key, shield CHỈ public key. HS256 BANNED. Per-shield unique key trong shields.iframe_jwt_public_key. Private key KHÔNG bao giờ rời control plane process memory.
MUST qua header Authorization: Bearer <jwt> HOẶC POST body. CẤM query string ?token= (nginx log, Referer, browser history leak).
C-012 · Outbox pattern
Mọi shield outbound event MUST INSERT wp_rs_outbox cùng DB tx với source event, separate cron forward. Buyer KHÔNG redirect success tới khi outbox commit.
C-013 · Webhook subscribe critical loop
paypal_accounts.status='active' ⇒ webhook_status='active'. Fire-and-forget BANNED. wp-cron retry 5min × 10. Sau fail → Telegram urgent + email customer block ads. Control plane cron webhook_health_check hourly + test event.
C-014 · Cloudflare token scope per-VPS
Token scoped chỉ tới zone(s) của shield trên VPS đó. Multi-zone / account-wide token BANNED trên VPS. Provision at deploy, revoke at burn.
C-015 · Active liveness probe
Control plane cron 60s SELECT shields ready → curl /wp-json/rs/v1/health → UPDATE health_status. 3 fail liên tiếp → Telegram. Phase 1 KHÔNG defer.
C-016 · Master image rollout
Stop accepting request 30s trước SIGTERM (preStop hook nginx 503 cho /create-paypal-order). Wait inflight done. Outbox absorb partial flush.
C-017 · Supply chain hardening
Plugin trong image MUST pin version + sha256 trong Dockerfile. Disable WP auto-update. Egress allowlist VPS: chỉ *.paypal.com, control plane URL, Cloudflare API; block aliexpress.com ngoài deploy-time. CI build pipeline ship NGAY Phase 1.
C-018 · Master image hardening
WP_DEBUG=false, WP_DEBUG_LOG=false, display_errors=Off MUST verified by build test scanning image FS.
Domain Pool (C-101..C-114)14 invariants
C-101 · 1 domain = 1 row
UNIQUE constraint trên domain.
C-102 · Reserved invariant
status='reserved' chỉ valid khi reserved_until > now(). Background expirer enforce.
C-103 · Assigned invariant
status='assigned' ⇒ assigned_shield_id IS NOT NULL.
C-104 · Cooldown invariant
status='cooldown' ⇒ cooldown_until > now(). Không skip cooldown.
C-105 · cert_ca enum
∈ {letsencrypt, zerossl, google}.
C-106 · CF proxied invariant
proxied=true trên mọi assigned domain (alignment root C-002).
C-107 · ACME rate limit awareness
Đếm cả 4 LE limits: 5 duplicate cert/week per set; 300 New Orders/3h per account; 5 Failed Validation/hour per (account, hostname); burst per VPS IP.
C-108 · ACME account pool
Phase 1 manual register 2-3 ACME accounts (3 email khác). Rotate by hash(domain) % accounts_count. Tránh single account exhaustion.
C-109 · Cron jitter cert renewal
acme.sh --cron MUST có jitter sleep $((RANDOM % 14400)) (0-4h). N shield cùng VPS không hit LE đồng giây.
C-110 · Pre-emptive renewal 21d
Trigger renew khi cert_expires_at < 21d (KHÔNG 30d). Buffer 21d cho fallback retry trước urgent alert.
C-111 · ACME EAB pre-register
ZeroSSL + Google EAB credentials MUST pre-register trong master image setup (per CA cần register + EAB key trước acme.sh fallback work). Phase 1 ops manual.
C-112 · CF token scope per-zone
Token trên VPS scoped per-zone (Account API Token + Zone Resources filter). Multi-zone BANNED trên VPS. (Alignment root C-014.)
C-113 · Concurrent deploy lock
deploy-shield.sh acquire pg_advisory_xact_lock(vps_id) trước SSH; hold suốt script. Tránh acme.sh ~/.acme.sh/account.conf race.
C-114 · NS propagation pre-flight
Trước issue cert, MUST dig +short NS <domain> từ 3 public resolver (Google 8.8.8.8, CF 1.1.1.1, Quad9) trả CF NS. Timeout 5 phút, retry. SLA cold start < 30 phút p95.
shields.admin_password_enc không bao giờ tồn tại plaintext trong DB hoặc log.
C-204 · ready ⇒ health_status='ok'
Tại thời điểm transition.
C-205 · Deploy idempotent
Re-run cùng input không tạo duplicate state.
C-206 · Concurrent deploy serialize
deploy-shield.sh acquire pg_advisory_xact_lock(vps_id) trước SSH (cross-spec với domain-pool C-113).
C-207 · No counter drift
vps_servers.current_shields compute từ shields table. Hoặc atomic increment + INSERT shields trong cùng tx; cleanup AS-007/AS-015 decrement compensate.
C-208 · Control plane authoritative
Plain password CHỈ tồn tại transient trong SSH stdin + control plane memory. Shield KHÔNG bao giờ POST plaintext về.
C-209 · System cron required
Container entrypoint MUST /etc/cron.d/shield-tick chạy wp cron event run --due-now --allow-root mỗi phút. Tick-on-request không đủ cho zero-traffic decoy.
Plugin Core (C-301..C-309)9 invariants
C-301 · Namespace cố định
rs/v1 (root C-001 alignment).
C-302 · Single-use jti
iframe_token enforce qua jti dedup PK INSERT.
C-303 · Service token rotate ≤24h
Compromise window bounded.
C-304 · PHP 8.1+
Required.
C-305 · No plain token log
rs_config.iframe_token_public_key là PUBLIC key only — leak không cho phép forge. Service token vẫn sensitive, encrypted-at-rest.
C-306 · Verify-only, sign never
Shield KHÔNG có khả năng signing. Bootstrap public key push qua SSH inside deploy-shield.sh (cross-spec vps-deploy AS-009).
C-307 · Outbox-or-fail
Mọi outbound event MUST persist wp_rs_outbox row trước HTTP forward. Container restart không lost data (root C-012).
Decrypt CHỈ in-memory tại OAuth call. access_token cached APCu/memory 8min, KHÔNG file/DB. PHP error_log không dump request body (master image WP_DEBUG=false).
What Already Exists
Reuse hoặc port có chủ đích, không rebuild: apps/api/src/lib/paypal.ts (OAuth + capture client), apps/api/src/lib/paypalWebhookHandler.ts + signature verify (port TS → PHP cho shield plugin), apps/api/src/lib/crypto.ts (AES-256-GCM cho secrets at rest), apps/api/src/db/schema.ts (Drizzle pattern cho 3 bảng mới), apps/api/src/middleware/verifySecret.ts (reference pattern adapt sang PHP với firebase/php-jwt), plugin/rotor-paypal/ (scaffold pattern — composer.json, autoload, phpunit), email infra Resend API cho template shield_deploy_success, Telegram alert helper (verify khi build).
Not in Scope
Domain pool replenishment cron — manual Phase 1 (ops chạy rotor cli pool replenish 5).
VPS auto-scaling Terraform — manual Phase 1 (ops provision DO droplet thủ công).