SPEC shield-infrastructure v1 · cập nhật 2026-05-14 ·5 specs·22 stories·69 AS Draft

shield-infrastructure

Backend provision WordPress “shield” decoy site mỗi customer: domain pool (Spaceship + Cloudflare + acme.sh), VPS pool + deploy orchestration, và PHP plugin rotor-shield-core expose REST cho buyer-checkout flow.

TL;DR

Phase 1, manual ops trigger, single-region SGP, 1:1 shield ↔ tenant. 4 sub-spec hợp thành critical path: domain → VPS → plugin core → plugin PayPal. Crypto/outbox/webhook hardening đã pass /mf-challenge (15 findings → C-009..C-018 root + cross-spec invariants).

  • Domain Pool — Spaceship register + Cloudflare orange-cloud proxy bắt buộc + acme.sh 3-CA fallback (LE → ZeroSSL → Google), state FSM available → reserved → assigned → cooldown, 180d cooldown sau burn (7 stories, 20 AS).
  • VPS Deploy — DO 4GB / 5 shield cap, capacity picker đếm từ shields table (no denormalized drift), deploy-shield.sh SSH orchestrate WP-CLI + AliDropship + health poll, idempotent re-run (6 stories, 20 AS).
  • Plugin Corers/v1 namespace, iframe_token RS256 verify-only (control plane sign), single-use jti dedup, service token 24h rotate, system cron + outbox cron (4 stories, 13 AS).
  • Plugin PayPal — 5 checkout endpoint + webhook receiver với cert-URL allowlist + dedup, webhook subscribe NOT fire-and-forget (retry × 10 + Telegram + block ads), account status probe (4 stories, 14 AS).
  • Root — Ops dashboard /admin/shields + /admin/infra đọc state (P2 deferred). Critical invariants C-001..C-018 govern toàn bộ shield surface area.

Sub-specs

Sub-specScopePhase 1
shield-domain-poolSpaceship adapter, domain lifecycle FSM, Cloudflare zone + A record + orange-cloud proxy, acme.sh cert + 3-CA fallbackP0 (block deploy)
shield-vps-deployVPS pool table, capacity picker, deploy-shield.sh, master copy Docker image, WP-CLI placeholder fill, AliDropship import, health checkP0 (block deploy)
shield-plugin-corerotor-shield-core scaffold, namespace rs/v1, health endpoint, iframe_token JWT middleware (single-use 10min), service token outbound + auto-refreshP0 (block plugin-paypal)
shield-plugin-paypal6 PayPal flow endpoints (get-form / create / return / cancel / complete) + webhook receiver với signature verify + dedup + forward, account status probeP0 (block buyer checkout)

Shared Data Model

5 tables / extends click expand

domain_pool — new

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).

paypal_accounts — extend

Add brand_name, soft_descriptor, invoice_prefix, currency cho anti-correlation. WP plugin-side: custom tables wp_rs_iframe_token_used (jti PK), wp_rs_outbox, wp_rs_webhook_dedup, WP option rs_config giữ iframe_token_public_key + shield_service_token + webhook_id.

SUB-SPECDomain Pool

Lifecycle: register qua Spaceship → Cloudflare DNS + orange-cloud proxy → acme.sh cert (3-CA fallback) → state machine available → reserved → assigned → cooldown → released. Phase 1 manual replenishment.

P0S-001Register fresh domain via Spaceship adapter4 AS

Ops trigger rotor cli pool replenish <N> → adapter pick name candidate → call Spaceship register API → INSERT domain_pool với status='available'. Failure modes: 429 rate limit, 400 taken, 5xx server.

AS-001Happy path register .com
Given
Name generator output acme-shield.com, Spaceship API healthy, balance đủ.
When
Adapter call register("acme-shield.com", 1).
Then
Spaceship trả {success:true, expires_at:...} < 60s. INSERT row (domain, tld='com', registrar='spaceship', status='available', cost_cents=290).
Data: TLD .com, years=1.
AS-002Rate limit 429 retry với Retry-After
Given
Spaceship trả 429 {retry_after: 5}.
When
Adapter call register.
Then
Wait retry_afters, retry tối đa 3 lần. Sau 3 fail → throw SpaceshipRateLimitError, không INSERT.
Setup: mock Spaceship 429 × 2 rồi 200.
AS-003Domain taken trong race (400 domain_unavailable)
Given
Spaceship trả 400 {code:"domain_unavailable"}.
When
Adapter call register("taken.com").
Then
Throw DomainTakenError. Caller regen candidate + retry.
AS-004Spaceship 5xx → 3 retry backoff
Given
Spaceship trả 503 3 lần liên tiếp.
When
Adapter call register.
Then
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.

AS-006Generate fresh candidate theo TLD weight
Given
Adjective + noun pool, TLD weight config, blacklist, domain_pool 100 rows.
When
Gọi generateCandidate().
Then
Trả {adj}-{noun}.{tld} không trùng trademark, không trùng existing row. TLD phân bố theo weight.
Data: seed RNG fixed cho test reproducibility.
AS-007Trademark filter block pokemon-store.com
Given
RNG generate pokemon-store.com, blacklist chứa pokemon.
When
generateCandidate().
Then
Discard, regenerate. 100 lần generate verify pokemon không xuất hiện.
P0S-003Domain lifecycle state machine4 AS

Atomic transition: reserve 5-min TTL, auto-rollback nếu deploy fail. Burned domain → cooldown 180d. Phase 1 cooldown release manual.

AS-008Reserve domain với 5min lock + concurrent
Given
Domain acme-shield.com status='available'.
When
Deployer call reserveDomain('acme-shield.com').
Then
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.
When
acme.sh --issue --dns dns_cf -d acme-shield.com -d *.acme-shield.com --server letsencrypt.
Then
Cert issued 30–90s. UPDATE cert_ca='letsencrypt', cert_expires_at=now()+90d. nginx reload qua --reloadcmd.
AS-017LE rate limit → ZeroSSL fallback
Given
Let's Encrypt trả 429 (50/week registered domain).
When
acme.sh issue.
Then
Retry --server zerossl. UPDATE cert_ca='zerossl'.
Data: mock LE 429 response.
AS-018ZeroSSL fail → Google fallback
Given
LE + ZeroSSL đều fail.
When
Issue.
Then
Retry --server google. UPDATE cert_ca='google'.
AS-019All 3 CA fail → Telegram urgent
Given
3 CA đều fail, cert hiện tại còn < 7 ngày.
When
Cron renew.
Then
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.

AS-021bPool empty → register on-demand 15min lock
Given
domain_pool không có row available, deploy call reserveDomainOrRegister().
When
Call.
Then
Generator → Spaceship register (S-001 happy path) → INSERT row status='reserved', reserved_until=now()+15min. Trả domain. Caller gửi email shield_deploy_delayed.
Data: pool count = 0; Spaceship mock success.

SUB-SPECVPS + Deploy Orchestration

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.
AS-003Atomic increment counter — 2 concurrent → 1 wins
Given
V1 = 3/5, 2 concurrent deploy.
When
Cả 2 call pickVps() rồi incrementShieldCount(V1).
Then
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.
P0S-002deploy-shield.sh orchestration5 AS

Bash script ops machine. Input: domain, tenant_id, currency, store_name, niche. Steps: validate → reserve domain → pickVps → SSH → docker compose up → WP-CLI fill → AliDropship → curl health → INSERT shields + assign domain → email customer.

AS-004Happy path end-to-end < 8 phút
Given
Domain pool có available, VPS có capacity, shield-master:v1.0.0 đã push, CF zone + cert ready.
When
deploy-shield.sh acme-shield.com <tenant_uuid> USD "Acme Coffee" pod_apparel.
Then
5-8 phút: container running, health {status:'ok'}, INSERT shields status='ready', domain_pool.status='assigned', current_shields += 1. Email shield_deploy_success.
AS-005Input validation reject domain regex
Given
Ops gõ deploy-shield.sh BadDomain.COM ....
When
Script run.
Then
Regex [a-z0-9-]+\.(com|store|shop|live) fail → exit 1. Không SSH, không touch DB.
AS-006Shell injection guard — allowlist + structured args
Given
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ẤM bash -c "...$var..."; SSH dùng args array hoặc JSON stdin + jq -r. WP-CLI gọi qua --prompt stdin, KHÔNG CLI arg.
Data: 10 payload variants test sandbox.
AS-006bEnum validation reject (currency, niche, tenant_id, vps_id)
Given
Input currency=VND, niche=nft, tenant_id="not-uuid", hoặc vps_id không tồn tại.
When
Validation step.
Then
Mỗi case exit 1 message field nào fail. Currency ∈ {USD,AUD,EUR,GBP,CAD}. Niche ∈ {pod_apparel,digital_files,home_decor,pet_accessories,tech_accessories,generic_gifts}. tenant_id UUID v4. vps_id SELECT exists check.
AS-007SSH fail → halt + Telegram alert
Given
VPS IP unreachable hoặc SSH key rejected.
When
Script SSH step.
Then
Retry 3 lần (10s backoff) → fail → exit 2 + Telegram SSH_FAIL vps=V1 domain=X. Không INSERT. Reservation auto-expire sau 5min.
P0S-003Master copy + WP-CLI placeholder fill4 AS

Inside container, WP-CLI install WP + Storefront + WooCommerce + rotor-shield-core + wp-cron-control. Replace __MC_*__ placeholders trong wp-config.php + wp_options. Random admin password generate client-side tại control plane, NEVER shield POST plaintext về.

AS-008WP install + plugin activate
Given
Container running, env vars set (TENANT_ID, BRAND_NAME, CURRENCY, ...).
When
Entrypoint script chạy.
Then
wp core install OK, wp theme activate storefront, wp plugin activate woocommerce aliexpress-dropshipping-and-fulfillment-for-woocommerce rotor-shield-core wp-cron-control. Verify wp plugin list --status=active trả 4 plugin.
AS-009Placeholder fill complete (9 placeholder)
Given
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.
AS-010bSeed legal pages + Storefront child theme
Given
Master copy có Privacy / Terms / Refund template + Storefront child theme stub.
When
Entrypoint sau placeholder fill chạy seed step.
Then
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.

AS-011Import 40 products (pod_apparel)
Given
niche=pod_apparel, query template = "trending pokemon t-shirt".
When
wp adp-import --query="..." --count=40.
Then
30–50 product rows trong wp_posts. wp post list --post_type=product --format=count ≥ 30.
AS-012AliExpress rate limit → fallback CSV
Given
AliDropship trả rate_limit.
When
Entrypoint detect fail.
Then
Load /opt/shield/seed/products-pod_apparel.csv qua wp wc product import. Count ≥ 30, log warning fallback_csv used.
AS-013Both fail → mark catalog_empty
Given
AliDropship fail + CSV missing.
When
Entrypoint cuối flow.
Then
Không halt. shields.status='ready', catalog_empty=true.
P0S-005Health check loop after deploy3 AS

Deploy script curl https://{domain}/wp-json/rs/v1/health poll 5s × 12 (60s max). Pass → mark shields.status=ready; fail → cleanup + abort.

AS-014Health OK trong 30s
Given
Container up, cert ready, DNS propagated.
When
Curl loop.
Then
Poll #6 nhận 200 + {status:"ok",db_ok:true}. UPDATE shields.status='ready', health_status='ok', deployed_at=now().
AS-015Health fail sau 60s timeout
Given
Container crash, nginx serve 502.
When
Curl loop tới 12 lần.
Then
Exit 3 + Telegram HEALTH_TIMEOUT domain=X. Cleanup docker compose down -v. Reservation expire tự nhiên. Không INSERT.
AS-016db_ok=false → fail
Given
Container up, MySQL không start được.
When
Health endpoint trả {status:"ok",db_ok:false}.
Then
Treat as fail (db_ok must be true). Cleanup + alert.
P1S-006Cleanup on partial deploy failure — idempotency2 AS

Re-run cùng command an toàn. Detect partial qua docker ps + shields row check.

AS-017Re-run sau crash → cleanup + redeploy
Given
Previous crash sau docker compose up, trước health check. Container running, không shields row.
When
Ops re-run cùng deploy-shield.sh ....
Then
Detect existing container name → docker compose down -v → fresh deploy. Final state đúng.
AS-018Re-run sau success → no-op + warning
Given
Shields row tồn tại, status=ready.
When
Ops re-run.
Then
Warning Shield already deployed for tenant X domain Y, exit 0 no action.

SUB-SPECPlugin Core (rotor-shield-core)

Core scaffolding: registration, REST namespace rs/v1, health, iframe_token middleware (single-use, RS256 verify-only, 10min TTL), service token outbound 24h rotate.

P0S-001Plugin scaffold + REST namespace + activation3 AS

Plugin file + autoloader + rest_api_init register rs/v1. Activation hook tạo custom table + check PHP ≥ 8.1. Deactivation giữ table.

AS-001Plugin activate tạo table
Given
WP fresh, plugin copy wp-content/plugins/rotor-shield-core/.
When
wp plugin activate rotor-shield-core.
Then
DB có wp_rs_iframe_token_used + wp_rs_outbox. Plugin active. PHP ≥ 8.1 check pass.
AS-002REST namespace listed
Given
Plugin active.
When
GET /wp-json/.
Then
Response chứa rs/v1 trong namespaces array.
AS-003PHP < 8.1 reject activation
Given
PHP 7.4 runtime.
When
Activate.
Then
Aborted, admin notice Requires PHP 8.1+, inactive.
P0S-002Health endpoint /rs/v1/health2 AS

Public, no auth, dùng cho deploy script + ops monitor. JSON với DB connectivity.

AS-004Health OK + db_ok:true
Given
Plugin active, MySQL reachable.
When
GET /wp-json/rs/v1/health.
Then
200 JSON {status:"ok",version:"1.0.0",uptime,db_ok:true}. Response < 100ms.
AS-005DB down → 200 degraded
Given
MySQL không phản hồi.
When
GET health.
Then
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

Outbound call shield → control plane đính kèm Bearer JWT 24h. System cron /etc/cron.d/shield-tick chạy wp cron event run --due-now mỗi phút (KHÔNG dựa WP-Cron tick-on-request). Refresh threshold ≤ 6h (tolerate cron lag). 401 outbound → immediate refresh-then-retry. Outbox cron rs_forward_outbox chạy mỗi 30s (root C-012).

AS-011Outbound với token valid
Given
rs_config.shield_service_token còn 23h.
When
Plugin call POST /api/shield/event.
Then
Header Authorization: Bearer <jwt>, control plane verify + 2xx.
AS-012Token sắp hết → auto-refresh
Given
Token còn 30min (< 1h threshold).
When
wp-cron rs_refresh_service_token.
Then
GET /api/shield/refresh-token với current token, UPDATE rs_config. Plain token transient, không log.
AS-013Refresh fail 3 lần → alert
Given
Control plane down, 3 cron tick liên tiếp fail.
When
Hourly cron.
Then
WP admin notice + error_log entry. Subsequent call sẽ fail 401 control plane.

SUB-SPECPlugin PayPal Endpoints

6 endpoint cho buyer checkout flow (iframe form, create, return, cancel, complete), webhook receiver với signature verify + dedup + forward, account status probe. Depends on plugin-core middleware.

P0S-001PayPal flow endpoints — get-form / create / return / cancel / complete5 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.
AS-002/create-paypal-order server-side — shield OAuth in-memory
Given
SDK POST sau button click; iframe_token valid (Authorization header).
When
POST /wp-json/rs/v1/create-paypal-order body {amount,currency,brand_name}.
Then
Plugin: (1) lookup client_id + client_secret_enc qua account_credentials_ref; (2) decrypt in-memory; (3) PayPal POST /v1/oauth2/token grant_type=client_credentialsaccess_token cached APCu/memory 8min (KHÔNG persist DB/file); (4) PayPal POST /v2/checkout/orders với invoice_id="${invoice_prefix}-${wc_order_id}". Trả {id, links} cho SDK.
AS-003/paypal-return-url buyer approve
Given
Buyer approve, PayPal redirect ?token=<paypal_token>&PayerID=....
When
GET.
Then
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.

AS-006Valid webhook → forward (cert URL allowlist + rate limit + outbox)
Given
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.
AS-014Subscribe fail → retry × 10 + Telegram + block ads
Given
PayPal 5xx hoặc URL unreachable.
When
Subscribe call.
Then
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.

ROOTCross-cutting

Ops dashboard read-only + manual action button. Phase 1 P2 deferred — operator dùng SSH + CLI cho mọi thao tác.

P2S-001Ops dashboard listing2 AS

Ops xem shields, VPS, domain pool trên admin UI để monitor + manual control. Phase 1 read-only + minimal action (mark burned, force health re-check).

AS-001/admin/shields table
Given
Ops authenticated.
When
Mở /admin/shields.
Then
Table với cột domain, tenant_id, status, vps_id, deployed_at, cert_expires_at. Filter theo status. Sort created_at desc.
AS-002/admin/infra VPS + Domain pool
Given
Ops authenticated.
When
Mở /admin/infra.
Then
2 panel: VPS list (capacity vs current_shields), Domain pool (status breakdown: available/reserved/assigned/cooldown).

Constraints

Root (C-001..C-018) 18 invariants

C-001 · 1 shield = 1 apex

Không share apex giữa tenant. Vi phạm = CardsShield Gen 1 mistake.

C-002 · CF orange-cloud bắt buộc

DNS-only mode = anti-correlation fail trên mọi shield apex.

C-003 · Cooldown 180d

Domain sau khi shield burned → cooldown 180 ngày trước khi release.

C-004 · Secrets AES-256-GCM

Shield credentials (admin password, service tokens) encrypt tại rest qua apps/api/src/lib/crypto.ts.

C-005 · VPS density 5/4GB

Tối đa 5 shield / 4GB DigitalOcean droplet.

C-006 · 1:1 tenant Phase 1

Multi-tenant shield defer V2+.

C-007 · Performance SLA

Health p95 < 100ms; shield REST p95 < 500ms cold / < 100ms warm; Spaceship register p95 < 60s; full deploy (pool ready) p95 < 10min; cold start (pool empty + VPS full) p95 < 20min (nới < 30min với NS propagation per C-114).

C-008 · Audit invariant

Mọi ops action qua deploy-shield.sh log SSH session + container stdout vào VPS sshd log (Phase 1). Centralized log shipping defer.

C-009 · iframe_token RS256/EdDSA

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.

C-010 · iframe_token payload

Claims chỉ chứa sub, exp, jti, paypal_client_id, account_credentials_ref (opaque ID), amount_cents, currency, brand_name, invoice_prefix, wc_order_id, return_url. CẤM embed access_token, client_secret, raw credentials.

C-011 · iframe_token transport

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.

VPS Deploy (C-201..C-209) 9 invariants

C-201 · Capacity hard cap

vps_servers.current_shields ≤ capacity. Atomic update enforce.

C-202 · 1 shield = 1 VPS

Phase 1, không multi-VPS replication.

C-203 · admin_password never plaintext

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).

C-308 · System cron required

/etc/cron.d/shield-tick mỗi phút kick WP-Cron (cross-spec vps-deploy C-209).

C-309 · Refresh threshold 6h

Không 1h — tolerate cron lag + immediate force-refresh on 401 response.

Plugin PayPal (C-401..C-410) 10 invariants

C-401 · Webhook 200 ≤ 30s

PayPal timeout. Forward control plane async.

C-402 · Dedup window 72h

Theo paypal_event_id.

C-403 · No client_secret log

Tồn tại transient trong memory call PayPal.

C-404 · iframe_token required

get-form, create-order, check-account-status require iframe_token (S-003 core).

C-405 · rs_session cookie required

return-url, cancel-url, complete-order require signed rs_session từ get-form.

C-406 · webhook no iframe_token

paypal-webhook-return PayPal là caller; chỉ dùng PayPal signature verify.

C-407 · Webhook critical loop

Fire-and-forget BANNED (root C-013). Account activewebhook_status='active'. Subscribe fail → cron retry × 10 + Telegram + block ads. Control plane cron hourly verify + test event.

C-408 · Capture outbox-or-fail

Buyer KHÔNG redirect success tới khi wp_rs_outbox row commit (root C-012).

C-409 · Receiver hardening

Rate limit 60 req/min/IP nginx. paypal-cert-url host allowlist ^https://api(\.sandbox)?\.paypal\.com/. Egress block RFC1918 + metadata 169.254.169.254. Chống SSRF + DoS.

C-410 · No client_secret leak

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).
  • Burn detector cron, auto-migration — defer V1+.
  • Daily snapshot S3 backup — defer V1+ (design ready trong explore doc).
  • Multi-cloud diversity (Vultr/Hetzner) — DigitalOcean only Phase 1.
  • Multi-region rotation — single region (SGP).
  • Theme/plugin pool diversity, diversification engine — defer V1+.
  • Stripe/Klarna/Square endpoints — defer V2+ (8 PayPal-only Phase 1).
  • Multi-tenant shield — defer V2+.
  • Aggressive Spider catalog regenerate — defer V2+ Pro tier.
  • ML burn prediction — defer V2+.
  • Hot standby (Pro tier) — defer V2+.
  • Shield refund endpoint /wp-json/rs/v1/paypal-refund — Phase 1 refund qua control plane direct.
  • Master image CI pipeline — Phase 1 manual docker build local + docker push private registry. CI automate defer V1+.
  • Centralized SSH audit log shipping (Loki/Better Stack) — defer V1+ (Phase 1 sshd log per-VPS đủ).
  • VPS quarterly SSH key rotation automation — defer V1+ (Phase 1 calendar reminder).
  • Real-time SBOM scanning / signed plugin manifest — defer V1+ (Phase 1 sha256 pin trong Dockerfile, manual review per release).
  • ACME multi-account pool automation — defer V1+ (Phase 1 manual 2-3 ACME accounts, rotate by domain hash).
  • Multi-registrar fallback (Porkbun, Cloudflare Registrar) — defer V1+.
  • Cooldown auto-release 180d → available — defer V1+ (Phase 1 ops manual flip qua CLI).
  • Cert renewal alert dashboard — defer V1+ (Phase 1 chỉ Telegram).
  • CF zone quota monitoring (free tier limit 25) + Business plan upgrade auto — defer V1+ (Phase 1 ops manual watch).
  • Multi-currency conversion — currency pin per-shield deploy time.
  • Per-buyer rate limit on shield — control plane đã có, shield trust.
  • Plugin admin UI — không có user (operator dùng SSH).
  • Plugin auto-update qua WP — defer V1+.
  • HSM-backed key — defer V2+ (Phase 1 shared secret encrypted in DB).
  • WP admin password rotation cron — defer V1+ (Phase 1 quarterly manual).
  • Theme customization beyond Storefront child 20-line CSS — defer V1+.

Change Log

10 entries across 5 specs expand
DateSpecChangeRef
2026-05-14rootApply 15 mf-challenge findings: 7 Critical (C-009..C-015 crypto/outbox/webhook/CF token/liveness/rollout) + 8 High constraints (C-016..C-018 supply chain/master image hardening)/mf-challenge shield-infrastructure
2026-05-14rootInitial creation từ explore docdocs/explore/shield-infrastructure.md
2026-05-14domain-poolmf-challenge fixes: C-107..C-114 (LE rate limit 4 dimensions, ACME pool, cron jitter, pre-emptive renewal 21d, EAB pre-register, CF token per-zone, advisory lock concurrent deploy, NS propagation pre-flight, trademark filter min 500 + USPTO check)/mf-challenge
2026-05-14domain-poolInitial creationdocs/explore/shield-infrastructure.md §1, §4
2026-05-14vps-deploymf-challenge fixes: AS-006 allowlist regex shell injection, AS-010 password client-side at control plane (KHÔNG shield POST plaintext), S-001 counter từ COUNT (drift fix), C-206..C-209 (advisory lock concurrent deploy, system cron in container)/mf-challenge
2026-05-14vps-deployInitial creationdocs/explore/shield-infrastructure.md §1-§3
2026-05-14plugin-coremf-challenge fixes: RS256 asymmetric thay HS256 (shield verify only, control plane sign), iframe_token transport header/body (CẤM query string), wp_rs_outbox table durable queue, system cron container, token refresh threshold 6h + force-on-401/mf-challenge
2026-05-14plugin-coreInitial creation (split from shield-plugin)docs/explore/shield-infrastructure.md §2
2026-05-14plugin-paypalmf-challenge fixes: AS-001/AS-002 (no access_token claim, shield OAuth in-memory only, Authorization header transport), AS-005 outbox-or-fail trước redirect buyer, AS-006 cert URL allowlist + rate limit + outbox, AS-014 webhook subscribe retry × 10 + block ads, C-407..C-410 critical invariants/mf-challenge
2026-05-14plugin-paypalInitial creation (split from shield-plugin)docs/explore/shield-infrastructure.md §2