From 070478192946d630d373162f92247e255b21bd8c Mon Sep 17 00:00:00 2001 From: SuperCharts Builder Date: Fri, 17 Jul 2026 13:14:04 +0530 Subject: [PATCH 1/2] =?UTF-8?q?docs(status):=20PROD=20RESCUE=20#3=20?= =?UTF-8?q?=E2=80=94=20supercharts2->supercharts3=20(GCP=20mining=20false-?= =?UTF-8?q?flag=20recurred);=20site=20restored,=20DB=20preserved=20via=20s?= =?UTF-8?q?napshot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/STATUS.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/STATUS.md b/docs/STATUS.md index b85e14b..0e482b0 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -34,6 +34,7 @@ increment per session, tick the box there AND log here. | Date | Item | Commits | Evidence | |---|---|---|---| +| 2026-07-17 | **PROD RESCUE #3 — VM `supercharts2`→`supercharts3` (GCP mining false-flag recurred, site was down).** `supercharts2` TERMINATED + un-startable ("Compute Engine detected suspicious activity" — 3rd time, the Binance-WS + build-CPU heuristic). **Data preserved:** snapshotted `supercharts2-disk` → `supercharts-rescue-0717` FIRST → new disk from snapshot → new `e2-standard-2` `supercharts3` (asia-south1-a, tag `sc`, firewall `sc-web` 80/443) → deleted flagged VM to free the static IP → created with `--address=35.200.208.191`. Boot auto-resurrected pm2 (supercharts-api+web) + Caddy. **⚠️ DEPLOY TARGET IS NOW `supercharts3`.** Durable fix still pending = move off GCP (recurs every few days). | (host op; no code change) | **LIVE:** `curl https://supercharting.com` → **200**; deployed HEAD `65f0adb` == local HEAD; `/api/broker/automation` → **401** (Pro gate); DB intact via snapshot (13 users, 130 watchlist syms, 1 broker_conn, 1 egress + ip_assignment); Binance backfill running post-boot. | | 2026-07-14 | **GW-7 docs capstone — "Automate a SuperTrend flip on Zerodha" guide (the FINAL-DELIVERY user-facing surface that ties GW-1..GW-7 together).** New public page `/docs/automation` teaching the owner (and any Pro user) how to ARM a SuperTrend position-flip on ANY connected Kite instrument (stock / option / future / MCX): BUY → go long (close any short first), SELL → flip short — routed through the audited, IP-whitelisted broker pipeline. **Pure content module** `apps/web/features/docs/automation-guide.ts` (no React/IO): `AUTOMATION_STRATEGY_SCRIPT` (a runnable `ta.supertrend` flip recipe that emits clean BUY+SELL flip marks + draws the line), `AUTOMATION_DEFAULTS` (the SINGLE documented source of the atr10·mult3·qty1·MIS·cap5·telegram defaults), `FLIP_TABLE` (flat/opposite/already-there semantics = the real flip-planner), `ARM_STEPS` (go-Pro+connect → whitelist IP → open instrument → arm → let-it-flip/disarm), `SAFETY_RAILS` (position-flip-never-stack · per-day cap · dd-breaker kill-switch · audit-before-broker · fill notes · reconnect nudge). Rendered by `apps/web/app/docs/automation/page.tsx` — syntax-highlighted script + Run-in-terminal deep link + flip table + numbered arm steps + safety-rail grid + honest-costs note (Kite order APIs free; ₹500/mo Kite data add-on to Zerodha; SuperCharts buys/redistributes no data). Wired into the docs **sidebar** (Guides), **sitemap**, and **hub card** (Bot icon). **Drift-guarded so the docs can't lie:** `tests/docs-automation.test.ts` runs the strategy through the REAL interpreter (asserts ≥1 buy AND ≥1 sell flip mark over the proven SuperTrend-flip zigzag + the drawn line) AND pins `AUTOMATION_DEFAULTS` field-for-field against BOTH `defaultArmForm()` (`apps/web/lib/automation-arm.ts`) and `buildSupertrendAutomation()` (`apps/api/src/broker/supertrend-automation.ts`: atr/mult in the indicator spec, `crosses_above/below` flip conditions, qty/product/cap/telegram in the `brokerOrder`). Change a default in the arm surface → this test fails. Additive **docs-only** — places no order; the live alert engine / Telegram / MT5 config untouched. **The OWNER arms live.** | `0346afb` (**pushed to main; VM `supercharts2` synced `git pull --ff-only` 892f736→0346afb, no lockfile change, web rebuilt, `pm2 restart supercharts-web` ONLY — docs-only web change; API/alert-engine (pid 6732, 2h uptime) never bounced**) | **735/735** (`pnpm vitest run --no-file-parallelism`; +5 new `tests/docs-automation.test.ts`: strategy script runs & emits both buy+sell flip marks + a plot · script input defaults (ATR length 10 / multiplier 3) == `AUTOMATION_DEFAULTS` · `AUTOMATION_DEFAULTS` == `defaultArmForm()` field-for-field · same defaults drive `buildSupertrendAutomation` (spec inputs atr10·mult3, buy crosses_above / sell crosses_below, brokerOrder qty1·MIS·cap5·telegram) · content completeness — ≥3 arm steps + ≥3 safety rails + a BUY & SELL flip row). api+web typecheck clean; **web production build clean** (`/docs/automation` prerendered static, fresh BUILD_ID). Browser-verified on **local** `/docs/automation`: full render — hero + "how the flip works" callout + highlighted PulseScript script + Run-in-terminal deep link + 2-row flip table + 5 numbered arm steps + 6-card safety grid, sidebar "Automate on Zerodha" active, **0 console errors**. **Prod (supercharts2) after web restart:** `curl https://supercharting.com` → **200**; `/docs/automation` → **200** (title "Automate a SuperTrend flip on your Kite instrument" + "position-flip market order" present); `sitemap.xml` includes `docs/automation`; `/api/health` ok, binance **connected** (9 subs) + kite **connected**; anon `GET /api/broker/automation` → **401** (requirePro gate intact). Live alert engine untouched (API never restarted). | | 2026-07-14 | **GW-7 polish (b) — order-fill Telegram notifications (the owner SEES money move when an armed flip actually trades).** When the alert-order executor places (or the broker rejects) a live flip order, DM the owner. **Pure, tested** `apps/api/src/broker/order-fill-note.ts` (no DB / network / broker): `formatOrderFillNote` (🟢/🔴 + "Opened long"/"Flipped to short" + `EXCHANGE:SYMBOL` + `SIDE qty (PRODUCT)` + broker label + the broker order ids + optional /terminal link, HTML), `formatOrderRejectNote` (⚠️ + the broker's **verbatim** message, HTML-escaped so `< > &` can't break Telegram parse mode), and `buildAutomationNote(outcome, ctx)` → body-or-**null** (notifies ONLY on `placed` + `broker_rejected`; `noop`/`skipped`/positions-failed/executor-failed stay silent — no money moved, and the reconnect nudge + breaker Telegram + arm UI already cover those states, so no double-spam). Wired into the executor as an **optional injected `AlertOrderNotifier`** (`resolveBot`/`send`/`appUrl`): `execute` now splits `runFlip` (the audited flip core, unchanged) from a fire-and-forget `notifyFill` that **NEVER throws and never changes the outcome** — a wedged Telegram can't undo or hide a live order. The alert engine passes the `notify` field **only when the alert opted into Telegram delivery**, so a fill note rides the user's existing choice (never a surprise message); `main.ts` wires the notifier with a 3-tier bot resolve (alert's chosen bot id → first enabled `telegram_bots` → legacy `telegram_configs` singleton) + `sendTelegramMessage` parseMode HTML + `NEXT_PUBLIC_APP_URL`. Additive: legacy 48/144 MA-cross alerts + MT5 + the alert engine's own signal delivery untouched (no `brokerOrder` → the executor never runs). **Places NO order — every path stub-proven; the OWNER arms live.** | `33bf758` (**pushed to main; VM `supercharts2` synced `git pull --ff-only` 0c81840→33bf758, no lockfile/web change, `pm2 restart supercharts-api --update-env` ONLY — API-only behavioral increment; web (pid 4206, 3h uptime) never bounced**) | **730/730** (`pnpm vitest run --no-file-parallelism`; +12 new: `tests/broker-order-fill-note.test.ts` 6 — open→"Opened long"/flip→"Flipped to short"+🔴, app-link only when appUrl set, reject note escapes ` & wrong`→`<bad> &`, buildAutomationNote placed/rejected→note & noop/skipped/positions-failed/executor-failed→null; +6 in `tests/broker-alert-order-executor.test.ts`: placed+telegram-on→1 note w/ chosen botId honored, broker-rejected→verbatim reject note, notify-omitted→no note, noop/kill-switch→no note, send-throws→outcome still `placed`, disabled-bot→no note). api+web typecheck clean. **Prod (supercharts2) after API restart:** `curl https://supercharting.com` → **200**; `/api/health` ok, binance **connected** (9 subs) + kite **connected**; anon `GET /api/broker/automation/reconnect-status` + `GET /api/broker/automation` → **401** (requirePro gate live). API booted clean (Server listening :4000, mt5 bridge :7878; the only log lines are the anon-probe 401s). Live alert engine reloaded clean (48/144 MA-cross untouched — no `brokerOrder`). | | 2026-07-14 | **GW-7 polish (a) — daily Kite-token reconnect nudge (the one operational gap that silently kills an armed automation).** Kite Connect access tokens invalidate once per IST morning (~06:00), so an ARMED SuperTrend flip stops trading until the owner reconnects and mints a fresh token. This surfaces + reminds. **Pure, tested** `apps/api/src/broker/reconnect-nudge.ts` (no DB / network / broker): `istTokenResetBoundary` + `istClock` (IST is a fixed +5:30, no DST → deterministic), `isTokenStale` (lastLogin < most-recent reset), `computeReconnectNudges` (active + ≥1 armed automation + stale → candidate), `formatReconnectNudge` (HTML Telegram body, broker name + armed count + optional /terminal link), and `runReconnectNudge` (composes over injected `loadArmedConnections`/`resolveBot`/`send` — **never throws, never places an order**; a wedged Telegram or missing bot → a `skipped` entry). New read route **`GET /api/broker/automation/reconnect-status`** (`requirePro`) returns the caller's `{ connected, armedAutomationCount, lastLoginAt, stale, needsReconnect, message }` so the arm UI can show a reconnect banner. **Env-gated scheduler** in `main.ts` behind `BROKER_RECONNECT_NUDGE=1` (default **OFF** — self-protecting gate like the email-verification flag; the loop ships it dormant so it can never surprise-message anyone): once per IST day at `BROKER_RECONNECT_NUDGE_HOUR` (default 9) it DMs stale+armed users via their first enabled Telegram bot (3-tier resolve → legacy singleton), deduped by IST day index, cleaned up on close. Additive: live 48/144 MA-cross alerts + MT5 + the alert engine untouched. | `0c81840` (**pushed to main; VM `supercharts2` synced `git pull --ff-only` c21a581→0c81840, no lockfile/web change, `pm2 restart supercharts-api --update-env` ONLY — API-only behavioral increment; web untouched**) | **718/718** (`pnpm vitest run --no-file-parallelism`; +17 new `tests/broker-reconnect-nudge.test.ts`: IST boundary after/before 06:00 · istClock hour + IST-midnight day roll · isTokenStale null/before/after · computeReconnectNudges active+armed+stale / skips pending / skips unarmed / skips fresh / requireArmed:false · formatReconnectNudge plural+link / singular+no-link · runReconnectNudge sends only stale+armed-with-bot & skips no-bot & never touches fresh + send-failure→skipped-not-thrown + disabled-bot→skipped; +2 route tests in `broker-automation-routes.test.ts`: anon→401 / fresh→no-reconnect / aged-3d token→needsReconnect+message; no-connection→connected:false). api+web typecheck clean. **Prod (supercharts2) after API restart:** `curl https://supercharting.com` → **200**; `/api/health` ok, binance **connected** (9 subs) + kite **connected**; anon `GET /api/broker/automation/reconnect-status` → **401** (requirePro gate live, route deployed). Scheduler dormant (flag unset on VM → nothing sends). Live alert engine reloaded clean on restart (48/144 MA-cross untouched). | @@ -156,12 +157,18 @@ autocomplete) then IND-1..2. SCANNER (SCAN-1..4) ✅ · DOCS (DOCS-1..3) ✅ · ## Questions for owner -- ✅ **PROD BACK UP + GW-7 DEPLOYED** — the VM is `supercharts2` (asia-south1-a), RUNNING; this - session confirmed `curl https://supercharting.com` → **200**, `/api/health` ok, binance+kite - **connected**. GW-7 core (`48c4276`) + builder (`c05f811`) are on the VM. **⚠️ DEPLOY TARGET IS - `supercharts2`** (the old `supercharts` VM is gone — see the PROD RESCUE row). The GCP mining - false-flag will likely recur; the durable fix is a non-GCP VPS (`infra/deploy/vm-bootstrap.sh` - one-commands any fresh Ubuntu) — needs the owner's provider account. +- ✅ **PROD BACK UP (3rd rescue 2026-07-17) — VM is now `supercharts3`** (asia-south1-a), RUNNING; + this session confirmed `curl https://supercharting.com` → **200**, `/api/broker/automation` → + **401** (Pro gate live), deployed HEAD = local HEAD `65f0adb`, DB intact (13 users, 130 watchlist + syms, 1 broker_conn, egress/ip_assignments preserved), pm2 supercharts-api+web online, Binance + backfill running. **⚠️ DEPLOY TARGET IS NOW `supercharts3`** — every `gcloud compute ssh`/deploy + uses `supercharts3` (supercharts2 was deleted after its disk was snapshotted; see rescue row). + **🔴 GCP crypto-mining FALSE-FLAG has now recurred 3× (supercharts→2→3).** Each rescue is + ~15min of whack-a-mole and WILL recur. **Durable fix = move off GCP to a non-GCP VPS** + (Hetzner/DO/Contabo won't crypto-flag Binance-WS); `infra/deploy/vm-bootstrap.sh` one-commands any + fresh Ubuntu, DB migrates via the snapshot — needs the owner to provision a box. Rescue recipe if + it flags again: snapshot `supercharts3-disk` → new disk from snapshot → new `e2-standard-2` tag + `sc` → delete flagged VM (frees IP) → create with `--address=35.200.208.191`. - ⚠️ **Deploy convention for zero-runtime-delta increments:** the GW-7 builder (`c05f811`) is a pure module NOTHING imports at runtime yet, so this run synced the VM with `git pull --ff-only` and did **NOT** `pm2 restart` — bouncing the live 48/144-alert engine + Binance WS for no runtime change is From 2121f957eb655a8cf8dd18405db39388dbbe9c9e Mon Sep 17 00:00:00 2001 From: SuperCharts Builder Date: Fri, 17 Jul 2026 23:55:26 +0530 Subject: [PATCH 2/2] fix(terminal): mobile chart no longer crushed to a sliver by fixed-width rails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On phones the 48px left rail + 340px right rail (both shrink-0, both default open) summed wider than the viewport, collapsing the flex-1 chart to a few px. Data was fine (binance connected) — the canvas just had ~0 width. - Rails default CLOSED under lg (<1024px) via a one-shot mount effect, so the chart gets the full viewport on mobile. - Rails become absolute overlays (drawer-style) below lg and return to normal flex columns at lg+ (lg:static), so opening one on mobile floats over the chart instead of compressing it. Desktop layout unchanged. Verified: 375px mobile -> full-width chart, 0 console errors; 1280px desktop -> both rails in-flow beside the chart as before. Co-Authored-By: Claude Opus 4.8 --- apps/web/app/terminal/page.tsx | 13 ++++++++++++- apps/web/features/terminal/left-rail.tsx | 2 +- apps/web/features/terminal/right-rail.tsx | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/web/app/terminal/page.tsx b/apps/web/app/terminal/page.tsx index c75c2df..fbb9418 100644 --- a/apps/web/app/terminal/page.tsx +++ b/apps/web/app/terminal/page.tsx @@ -13,7 +13,7 @@ import { getWSClient } from '@/lib/ws-client'; import { useSession } from '@/lib/auth'; export default function TerminalPage() { - const { showLeftRail, showRightRail } = useTerminalStore(); + const { showLeftRail, showRightRail, setShowLeftRail, setShowRightRail } = useTerminalStore(); const { user, loading: sessionLoading } = useSession(); const ingestMT5 = useMT5Store((s) => s.ingestEvent); const refreshAccounts = useMT5Store((s) => s.refreshAccounts); @@ -28,6 +28,17 @@ export default function TerminalPage() { else if (!user.emailVerified) window.location.href = '/verify'; }, [sessionLoading, user]); + // Mobile: the rails are fixed-width (48px + 340px) and would crush the flex-1 chart to a sliver + // on a phone. Default both closed under lg (<1024px) so the chart gets the full viewport; the + // rails still open as overlays (drawer-style) via the top-bar/settings toggles. + useEffect(() => { + if (typeof window === 'undefined' || window.innerWidth >= 1024) return; + setShowLeftRail(false); + setShowRightRail(false); + // once, on first mount — a user reopening a rail on mobile must stick. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + // Docs deep link: /terminal?pulse= loads the snippet into the Script dock // (one-shot, then stripped from the URL so refresh doesn't re-apply it). useEffect(() => { diff --git a/apps/web/features/terminal/left-rail.tsx b/apps/web/features/terminal/left-rail.tsx index c83d2b7..91a6e00 100644 --- a/apps/web/features/terminal/left-rail.tsx +++ b/apps/web/features/terminal/left-rail.tsx @@ -88,7 +88,7 @@ export function LeftRail() { return ( -