Archived
Cycle complete. For current operations see
prod-launch-runbook and
prod-deployment-runbook.
Remediation 2026-06-02 — Staging / Local Test Checklist¶
Verification checklist for the logical-gap remediation cycle on
dev-amal-integration-staging-2026-06-02 (review: ~/.claude/plans/do-a-code-review-on-steady-pinwheel.md).
Tick each item on staging (or the local stack on :8080/:8081/:5173) before promoting to
release/staging → release/prod.
Legend: [BE] backend · [FE] frontend · [DOC] doc-only · [DEPLOY] ordering/ops step.
0. Pre-flight (deploy ordering — do these FIRST)¶
- [ ] [DEPLOY] B3 ROI backfill — immediately after deploy, call
POST /api/admin/leaderboard/rebuild-roi(admin JWT). The leaderboard read paths rank on theleaderboard:roi:*zsets unconditionally; without the backfill the board is sparse. Expect{"rebuilt": N}where N ≈ active user count. Spot-check 5 users' ROI vs their portfolio equity. - [ ] [DEPLOY]
ROI_FEED_ENABLED— leave unset for now (the per-tick evaluator ROI write does a per-eval PG district lookup, ~5K/s at 50K users). Only flip to1after the district-cache follow-up lands. The cheap close-path ROI write is always on. - [ ] [DEPLOY] Run the regression suites (re-source from the repos —
/tmp/ftl-test/is gone):backend-tests.sh,ws-tests.py,concurrency-tests.sh(expect ≥82 BE, 15/15 WS, T1–T7 green). - [ ] [DEPLOY] Confirm
activeRevisionsMode == "Single"onftl-stg-apiafter any scale toggle.
1. Trade window — knockout states (B1) [BE+FE]¶
- [ ] Drive a fixture to
INPLAY_PENALTIES(usescripts/replay-match/ local-replay, or a staged Sportmonks state). Trade button is ENABLED andGET /api/trade-window/:fixture(or equivalent) returns open while prices keep ticking. (Pre-fix: window closed, 403 on open.) - [ ] Repeat for
INPLAY_ET_2ND_HALF,PEN_BREAK,BREAK→ all tradeable. - [ ] After
FINISHED/FT_PEN→ window closed (no new opens). - [ ] Numeric Sportmonks state ids
2/3→ live;5/6/7→ completed (if your feed emits numeric ids).
2. CONTRACT_SIZE = 5 (D1) [DOC]¶
- [ ] No code change expected — confirm
CONTRACT_SIZE = 5still inposition_open/close.lua,margin/types.go,CFDTradeForm.helpers.ts. Specs (cfd-pricing-and-wallet.md,wallet-and-margin.md,amm-pricing.md) + ADR-0004 correction note now show ×5 numbers. - [ ] Sanity: 0.5-lot @ 200 → margin
200×0.5×5/10 = 50; 20-pt move → realized PnL20×0.5×5 = 50.
3. Cooldown = per-instrument min-hold-on-open (D2) [BE]¶
- [ ] Open a position on instrument A → attempt to close A within 180s → rejected with
cooldown_active+retry_after_secs. After 180s → close succeeds. - [ ] Open A, then 30s later open B → closing B is gated for 180s from B's open; closing A (after A's 180s) is not blocked by B. (Closing A never gates B.)
- [ ] A system close (SL/TP/washout/FT) bypasses the hold (closes immediately).
- [ ] Opening is never blocked by any cooldown.
4. XP awards (B2) [BE]¶
- [ ] Open a position →
GET /meshowsxp > 0(XPPerTrade). Close at a profit → additional XP (XPProfitableSell). First trade of the day → first-of-day bonus. - [ ] Level badge advances past LV1 after enough XP (pre-fix: stuck at LV1/0 forever).
- [ ] Referral XP is not double-paid (referral reward is wallet credits, not XP — by design).
5. CFD legacy-field reads (B4) [BE+FE]¶
- [ ] B4a — open a position, then
GET /api/instruments/:id→currentPricereflects the open-position imbalance (NOT a flat base price). Market page price moves with trades. - [ ] B4b — cold-restart api-server, then open the FIRST position on an instrument → the AMM
slope used is the real
k_mod(e.g. 0.10), not the0.01fallback (check the post-trade broadcast price vs the ticker; no discontinuity). - [ ] B4c — set an SL on an open position, kill+restart the StopWatch leader (or bounce pub/sub) across a price that crosses the SL → the SL fires on catch-up (pre-fix: silently never fired).
- [ ] B4d — hard-refresh the app with open CFD positions → TopBar equity/margin/free-margin are correct before the first WS frame (no wrong/zero margin, no false insufficient-margin gate).
- [ ] B4e — after a match ends, the post-match price broadcast to chart subscribers is correct
(uses
net_position_imbalance+k_mod, not legacy fields).
6. FT settlement price (B5) [BE]¶
- [ ] With net open imbalance on an instrument, let a match reach FT → open positions auto-exit at
live_base_price + k_mod×imbalance(the price the UI showed), not the bare base price. Longs not underpaid / shorts not overcharged. Compare closed-positionclose_priceto the last shown price.
7. Wallet UI — margin/equity (FE)¶
- [ ] Open CFD positions, switch tabs repeatedly → equity does not reset to balance (unrealized PnL is preserved). (Pre-fix: tab switch erased unrealized.)
- [ ] On bootstrap, TopBar margin/equity come from the CFD model, not legacy share holdings.
8. Trade form margin (FE)¶
- [ ] Open the trade form → the displayed Margin required equals the actual server debit (no +1% inflation). Confirm the position opens and the wallet debit matches the shown number.
- [ ] The Confirm button still pre-disables when free margin is genuinely insufficient (the 1% buffer remains on the gate only — try a near-exhausted account).
9. Referral earnings (referral) [BE+FE]¶
- [ ] "Coins Earned" on InviteFriends counts only credited referrals (referee placed first trade). An invited-but-not-registered friend does not inflate it.
- [ ] Backend
GET /api/referrals→summary.pendingexcludes unclaimed invites (registered=false);total_creditedmatches the sum of credited rewards.
10. Leaderboard ROI (B3) [BE+FE]¶
Requires the backfill from §0 to be run first.
- [ ] A user holding a winning open position who has never closed appears on the ROI board with a positive ROI (pre-fix: absent / shown 0%).
- [ ] Primary board ranks by ROI%;
GET /api/leaderboard/mereturns ROI% asscore; the sticky "me" row renders e.g. "+15.3%" (not "+NNNk"). - [ ] NearMiss banner shows the gap in ROI% (not "pts").
- [ ] District matchup: a small district of high-ROI traders beats a large district of low-ROI traders (ranked by average member ROI, not summed PnL). DistrictCard shows "avg ROI".
- [ ] Tiebreaker: two users at the same ROI% → the one with the higher win rate (wins/sells) ranks above the one with higher absolute PnL.
- [ ] Synthesised backfill rows (no ROI yet) render "—", not "+0.0%".
- [ ] After a Redis flush + recovery,
Snapshotrestores the ROI ranking (not the PnL board). - [ ] Score convention is raw percent everywhere (no ×100 / ÷100 drift) — spot-check a ZSCORE vs the displayed ROI%.
11. PriceTicker arrow (ticker) [FE]¶
- [ ] Hold a short position; when its price falls the ticker shows ▲/green (you're in profit). When price rises → ▼/red. Longs unchanged (▲ when price rises). No arrow before the first live tick.
Not in this cycle (do not expect changes)¶
- B6 wallet-PnL durability (Redis-eviction rollback) — design/impl pending.
- Replay CFD liquidation (B4f) — replay-only, pending.
- Spinner week-window, flusher atomicity, tutorial server-persist — pending.
- YourTeam position % stays raw price-move% (product decision — unchanged).
- Per-commit HTML walkthroughs — skipped this cycle.
Regression / smoke¶
- [ ]
/healthgreen on api + ws after deploy. - [ ] Open → modify SL/TP → close a position end-to-end; wallet balance reconciles.
- [ ] Leaderboard, Squad, YourTeam, Profile pages load without console errors.
- [ ] WS reconnect after a network blip resumes price updates.