Skip to content

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/stagingrelease/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 the leaderboard: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 to 1 after 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" on ftl-stg-api after any scale toggle.

1. Trade window — knockout states (B1) [BE+FE]

  • [ ] Drive a fixture to INPLAY_PENALTIES (use scripts/replay-match / local-replay, or a staged Sportmonks state). Trade button is ENABLED and GET /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 = 5 still in position_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 PnL 20×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 /me shows xp > 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/:idcurrentPrice reflects 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 the 0.01 fallback (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-position close_price to 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/referralssummary.pending excludes unclaimed invites (registered=false); total_credited matches 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/me returns ROI% as score; 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, Snapshot restores 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

  • [ ] /health green 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.