fix: de-risk launch_seed, monitor price feeds, enforce single-process
Three pre-launch audit findings: launch_seed.py — destructive cleanup is now OPT-IN Previously `launch_seed.py --yes` truncated KOL history (divergence / holdings / posts older than 30d) by default — an accidental run erased unrecoverable data, and abort_if_live_user_state() only guards on subscriptions/bindings, not KOL history. Now nothing is deleted unless --wipe is passed; the safe path is --seed-only (pure fetch). Bare/--yes without --wipe refuses and prints guidance. /api/health/deep — monitor the price feeds, not just scrapers The deep healthcheck only watched the (redundant) Trump scrapers + DB, so a dead Binance/HL price feed — which silently stops ALL tp_sl_monitor stop-loss / take-profit firing on live trades — left health green. Added per-feed liveness (binance.last_tick_at, hl_price_feed.last_tick_at) with a 180s boot grace so startup doesn't false-503. Body now includes price_feeds[]. Single-process enforcement (multi-worker safety) The backend is single-process by design (in-memory scheduler, replay cache, tp_sl table, price_store). systemd unit lacked the --workers 1 + rationale that supervisor.conf already had; added it. Added a runtime advisory file lock (app.main._acquire_singleton_lock): only the leader starts background tasks; extra workers serve HTTP reads only and log CRITICAL. health/deep now reports is_leader so the misconfig is visible to monitors. 72 tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+37
-14
@@ -28,25 +28,29 @@ What it INTENTIONALLY DOES NOT touch:
|
||||
baseline for the next on-chain poll (without it, the next snapshot
|
||||
would diff against nothing and produce zero kol_holding_changes).
|
||||
|
||||
Usage:
|
||||
# Preview (no DB writes):
|
||||
DATABASE_URL='sqlite+aiosqlite:///./trumpsignal.db' \\
|
||||
venv/bin/python scripts/launch_seed.py --dry-run
|
||||
DESTRUCTIVE STEPS ARE OPT-IN. By default this script only SEEDS (re-fetches
|
||||
upstream data); it will NOT delete or truncate anything unless you explicitly
|
||||
pass --wipe. This prevents an accidental `launch_seed.py --yes` from erasing
|
||||
KOL history (divergence / holdings / posts) that cannot be recovered.
|
||||
|
||||
# Seed only (pure pre-launch warmup, no truncation / deletion):
|
||||
Usage:
|
||||
# Pure pre-launch warmup — fetch only, NOTHING deleted (the safe default):
|
||||
DATABASE_URL='sqlite+aiosqlite:///./trumpsignal.db' \\
|
||||
venv/bin/python scripts/launch_seed.py --seed-only
|
||||
|
||||
# Execute:
|
||||
# Preview what a wipe WOULD delete (no DB writes):
|
||||
DATABASE_URL='sqlite+aiosqlite:///./trumpsignal.db' \\
|
||||
venv/bin/python scripts/launch_seed.py --yes
|
||||
venv/bin/python scripts/launch_seed.py --wipe --dry-run
|
||||
|
||||
# Skip the SEED step (just clean — useful if you'd rather let the
|
||||
# scheduled poll jobs trigger naturally over the next 24h):
|
||||
DATABASE_URL='...' venv/bin/python scripts/launch_seed.py --yes --no-seed
|
||||
# Execute the destructive wipe + reseed (requires BOTH --wipe and --yes):
|
||||
DATABASE_URL='sqlite+aiosqlite:///./trumpsignal.db' \\
|
||||
venv/bin/python scripts/launch_seed.py --wipe --yes
|
||||
|
||||
NOT safe to re-run after real users are on the platform — the KOL truncation
|
||||
will erase divergence/alignment context that you can't recover.
|
||||
# Wipe only, skip the reseed (let scheduled polls refill over 24h):
|
||||
DATABASE_URL='...' venv/bin/python scripts/launch_seed.py --wipe --yes --no-seed
|
||||
|
||||
The --wipe path is NOT safe to re-run after real users are on the platform —
|
||||
the KOL truncation erases divergence/alignment context you can't recover.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -283,12 +287,15 @@ async def seed_real_data(*, exercise_scanners: bool = False) -> None:
|
||||
async def main() -> int:
|
||||
p = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument("--wipe", action="store_true",
|
||||
help="OPT IN to destructive cleanup (drop obsolete-source posts + "
|
||||
"truncate KOL window). Without this flag nothing is ever deleted.")
|
||||
p.add_argument("--dry-run", action="store_true",
|
||||
help="show what would be deleted, no DB writes")
|
||||
help="with --wipe: show what would be deleted, no DB writes")
|
||||
p.add_argument("--seed-only", action="store_true",
|
||||
help="run upstream fetch/warmup only; skip all wipe/truncation steps")
|
||||
p.add_argument("--yes", action="store_true",
|
||||
help="actually perform the wipe (required without --dry-run)")
|
||||
help="with --wipe: actually perform the wipe (required without --dry-run)")
|
||||
p.add_argument("--no-seed", action="store_true",
|
||||
help="skip the upstream re-fetch step")
|
||||
p.add_argument("--exercise-scanners", action="store_true",
|
||||
@@ -303,6 +310,22 @@ async def main() -> int:
|
||||
print(red("--seed-only conflicts with --no-seed."))
|
||||
return 2
|
||||
|
||||
if args.seed_only and args.wipe:
|
||||
print(red("--seed-only conflicts with --wipe (seed-only never deletes)."))
|
||||
return 2
|
||||
|
||||
# Default to the SAFE path. A bare invocation, or --yes/--dry-run/--no-seed
|
||||
# WITHOUT --wipe, must never delete data. Steer the user to --seed-only.
|
||||
if not args.wipe and not args.seed_only:
|
||||
print(red("Refusing to run a destructive path without --wipe."))
|
||||
print(yellow(
|
||||
"This script seeds by default and only deletes when --wipe is given.\n"
|
||||
" • Pure pre-launch fetch (recommended): --seed-only\n"
|
||||
" • Preview a destructive wipe: --wipe --dry-run\n"
|
||||
" • Execute a destructive wipe + reseed: --wipe --yes"
|
||||
))
|
||||
return 2
|
||||
|
||||
if args.seed_only:
|
||||
before = await report_counts("BEFORE")
|
||||
await seed_real_data(exercise_scanners=args.exercise_scanners)
|
||||
|
||||
Reference in New Issue
Block a user