Audit passes
205
Bugs fixed
156+
CRITICAL bugs
28
Total commits
100+
Batch R (passes 202-204) — silent feature-vector corruption (defense in depth): 14 call sites across 9 modules call model.train(features, label). The pattern f.features || extract(f) was used in two places to prefer a stored feature snapshot — but [] || x evaluates to [] in JS (arrays are truthy regardless of length). A journal entry with an empty or stale-schema feature array would silently train on a partial-length vector, leaving the upper weights of the model PERMANENTLY un-updated.

202: caller-side fix in model.js trainBatch + predictForFinding. 203: same pattern in multi-horizon trainHorizon (3 horizon models) and bootstrap-ensemble train + predict (K=5 bagged models). 204 (defense in depth): moved the guard one layer deeper into Model.train and Model.predict themselves — every caller is now protected by a single guard, returning {skipped: 'bad-features-length'} instead of silently mistraining. Also catches non-finite labels which would NaN-ify the Adam moment buffers. 205 (CRITICAL): pass 199 gated 3 browser trainers (continuous-learner, auto-trainer, historical-bootstrap) but missed a fourth — ModelTrainer.trainBatch() called every 5 min by brain-loop's tickMLFeedback. While the worker was authoritative, that fourth path still trained the locally-mirrored model on rated outcomes and was then silently overwritten by next syncFromWorker — double-counting outcomes the worker had already trained on and biasing the model toward whatever this browser session saw. Gated at ModelTrainer.trainBatch entry so any caller (brain-loop, training-scheduler.html, ml-status.html, train-now.html, model-trainer.html) inherits the defer.
Batch Q (passes 198-201) — worker↔browser boundary hardening: With the worker brain shipping (Batch P), the seams where browser and worker disagree became the new failure surface. Batch Q fixes four of them.

198: worker-bridge.js syncFromWorker had three brittle spots — JSON.parse of a corrupted local journal threw and killed the whole sync silently forever; s.journal.filter() assumed array (could be undefined on malformed response); s.model.weights accepted any truthy value but downstream loaders need a 22-element array. All now defensively guarded. 199: the browser brain (continuous-learner + auto-trainer + historical-bootstrap) was fighting the worker — every 30-60s the browser trained its locally-mirrored model on local Stooq captures; next worker sync overwrote those gradients; meanwhile both brains were double-counting the same outcomes. Now all three browser trainers check WorkerBridge.isEnabled() and defer to the worker. Browser capture/resolve still runs so brain-proof can show the loop is alive. 200: Brandon's deployed worker was N passes behind the repo source and there was no way to tell at a glance. Added WORKER_VERSION constant baked into the worker, exposed via /brain/health, and worker-setup.html now shows a green "current" banner or a yellow "redeploy" banner with the exact git pull && wrangler deploy command. 201: three worker endpoints had unguarded destructure / parseInt paths — /brain/journal?n=garbage returned the entire journal; /brain/symbols would 500 if any heldout entry was null; /brain/metrics computeMetrics polluted BSS with NaN p/y. All three now filter to typed, finite values before computing.
Batch P (passes 188-197) — Cloudflare Worker brain (24/7 server-side ML): The browser brain only learns while a Chrome tab is open. Batch P built a Cloudflare Worker that runs every minute on Cloudflare's edge — captures Finnhub quotes, resolves outcomes at 24h/5d/20d, trains a logistic model in KV. Browser pages mirror state via js/worker-bridge.js. Endpoints: /brain/health, /brain/state, /brain/metrics, /brain/symbols, /brain/bootstrap.

188: rotating 12-sym slice per tick to stay under Finnhub's 60/min free-tier limit. 189: Yahoo Finance v8 chart API as primary historical source (Stooq CORS-blocked from CF Workers, Finnhub /candle paid-only). 190 (CRITICAL): cron tick was overwriting bootstrapped model with stale n_trained=0 — fixed by only persisting model if trained > 0 in the tick. 191: proper 80/20 held-out test + /brain/metrics endpoint with Brier Skill Score + ECE + binomial p-value. 192: walk-forward (time-ordered) split alongside random-split — the honest "trained on past, predicting future" test. 193: per-symbol bar history persisted in KV so live captures use the SAME rich features the bootstrap trained on (was previously using neutral 0.5 features → live model couldn't learn). 194: /brain/symbols per-symbol accuracy breakdown. 195: Cloudflare Pro unlocked 30s CPU; bootstrap now does 5 epochs with re-shuffling each pass (~2-3pp accuracy improvement). 196: GH Actions workflow now skips gracefully when CF secrets missing (Brandon deploys via local wrangler deploy). 197 (CRITICAL): walk-forward was training 1 epoch while random-split trained 5 — apples-to-oranges. Now both use identical training procedure so the two BSS values on /brain/metrics are directly comparable. Also made clamp() NaN-safe (returns midpoint instead of propagating NaN to downstream model).
Batch O (passes 174-187): deeper-coverage browser audits: more TZ-naive day-bucketing finds, more journal-repair migrations, more PSI dual-export checks, more lazy-load race surfaces around the model. None individually CRITICAL but ~12 small fixes that tighten the brain's data attribution to ET trading calendar across more diagnostic surfaces.
Pass 172-173: ai-narrative.html "session unfolds vs closes" wording used local getHours() — at PT 1pm (=ET 4pm market just closed) still said "unfolds". Fixed. Pass 173 — 4 HTML pages with unguarded JSON.parse(localStorage) in render paths could crash the page if localStorage was corrupted: day-trader-pro.html paintPnl(), drawdown-protector.html history dots, outlier-detection.html OOD log, webhook-bridge.html KPI counters. All now have try/catch with sensible defaults so pages degrade gracefully on bad data.
Pass 170 (6 more TZ bugs in stats pages): brain-time-of-day.html "Best/Worst/Busiest hour" labels · brain-questions.html "best hour of day" + "worst day of week" Q's · cohort-analysis.html session cohorts (Open/Morning/Midday/Close ET labels) · setup-compare.html per-hour win-rate · performance-attribution-pro.html day-of-week chart · model-postmortem.html had a particularly nasty bug — the render loop iterates h=9..16 (ET market hours) but the bucketing used local getHours(). For PT users, the 9-16 range would only catch trades in PT afternoons — morning ET data (= early-morning PT) was excluded ENTIRELY. All 6 now use the toLocaleString('en-US', {timeZone: 'America/New_York'}) re-parse pattern.
CRITICAL Batch L (passes 168-169) — model-state race conditions:
168: auto-trainer.js loaded the model at function start, then did 30+s of async Stooq fetches with model.train() calls intermixed, then saved at end. Continuous-learner (every 30s) could load → train → save its OWN update during that window; auto-trainer's later save() then overwrote CL's gradient changes. Rough estimate: ~33% of auto-trainer's 4 daily runs collided with a CL save → ~16 CL training events silently lost per day. Fix: split into Phase 1 (async fetches gather examples into memory, NO model touches) + Phase 2 (one synchronous block: load → train all → save). JS single-thread guarantees atomicity from CL's perspective.
169: auto-trainer.js had a dead check for window._historicalTrainerRunning — NOTHING ever set it. Bootstrap ran for minutes without claiming any lock. Wired the cross-module lock end-to-end: historical-bootstrap sets _historicalTrainerRunning (cleared in 4 exit paths), auto-trainer sets _autoTrainerRunning, continuous-learner now checks BOTH and defers its train+save block when either is true. Deferred resolutions stay unresolved in the journal so the next 30s tick (after the long-running trainer completes) reprocesses them against the freshest model state.
CRITICAL Batch K (pass 167): last 2 internal JS modules with TZ-naive day bucketing — continuous-learner.js stats() "capturedToday"/"resolvedToday" and streak-tracker.js trend() 14-day rolling bucket. Both bucketed by local midnight; for PT user, 3 hours of every late-evening trade was attributed to the wrong day. Both fixed with Intl en-CA ET-date key matching. After batches J+K, all 7 user-facing surfaces and 2 internal modules now consistently attribute trade timestamps to the ET trading calendar.
CRITICAL Batch J (passes 163-166) — same TZ-naive bug class as pass 119, but on the REPORT surface:
163 brain-monthly-calendar bucketed trades by local Y/M/D — for PT user Brandon, every trade 9pm-12am PT (= 12am-3am ET next day) appeared in the wrong calendar cell. Fixed with Intl.DateTimeFormat('en-CA', America/New_York) ET-date keys. "Today" highlight also uses ET-today.
164 daily-report.html: findings filtered by local-midnight start/end. Date-picker value (YYYY-MM-DD) now interpreted as ET-date directly.
164 brain-weekly-report.html: week boundaries built from local Sun-Sat and day-of-week chart used local getDay(). Fixed with etDow() helper + ET-attributed week filter.
164 daily-replay.html: 4 separate filters (HighConvictionAlerts, MoneyTracker trades, AutoTrade opened/closed) all bucketed by local-day window. Now all use ET YYYY-MM-DD bucketing.
165 time-of-day-brain.html: byHour and byDow used local getHours()/getDay(). Session labels (Pre-market 4-9:30am, Open 9:30-11am, Lunch, etc.) are ET. PT user's data was off-by-3-hours on every hour bucket. Now uses ET clock.
166 time-of-day-pnl.html: already uses the toLocaleString('en-US', {timeZone}) trick correctly — getHours/getDay on the re-parsed Date returns ET values. Verified clean.
Batch I (passes 161-162): 161: performance audit of hot tick functions (continuous-learner runCycle 30s · brain-loop ticks 1m/2m/5m/15m · stooqPoll 12s · live tickOnce). Only tickConfluence had multiple JSON.parse — they're pre-loop (not inside per-symbol forEach), so no per-iteration overhead. Pattern already optimal. 162: all 9 model-* + brain-* pages that use ModelStore.load() / Model() explicitly load <script src="js/model.js"> BEFORE the inline script — zero crash risk from undefined globals. No lazy-load race surface in model-explorer, model-versions, model-compare, brain-truth, feature-engineering, etc.
Batch H (passes 158-160): money-pages product-surface audit — make-money, money-made, mobile-money, mobile-dashboard, how-to-make-money all have clean inline-JS (lint passes) · every global reference (UnifiedPredictor, MoneyTracker, AutoTrade, HighConvictionAlerts) properly defensively guarded with if (typeof window.X !== 'undefined') or if (window.X) + setTimeout retry pattern · zero risk of crashes from lazy-load race conditions on these high-traffic Brandon-facing pages.
Batch G (passes 155-157): 0 insecure http:// resource refs across 402 HTML pages · 0 pages missing <meta name="viewport"> · only 2 pages don't link css/style.css (brain-mobile.html standalone inline styles, dns-test.html intentional minimal diagnostic). Mobile rendering + HTTPS-only externally-served resources both verified clean across the entire static site.
Batch F (passes 147-154): 147: localStorage key naming — 83 _v1 + 1 _v2 + 3 unversioned (notify_prefs, status_bar_dismissed, subscriber — leaving as-is to preserve existing user data). 148: 46 JSON.parse(localStorage) calls verified — every one has same-line or preceding try/catch (zero will crash on malformed data). 149: all 5 training tick functions (brain-loop, continuous-learner, auto-trainer, historical-bootstrap, weekly-refresh) wrap iterations in try/catch — a single training error can't cascade into a page crash. 150: 38 division-by-variable patterns spot-checked — every one guarded (n===0 early-return OR Math.max floor OR std-floor 1e-10). 151-152: 84+ localStorage keys cross-referenced read↔write across all JS+HTML — only 1 orphan-write (subscriber form data, intentional snapshot for Brandon), 0 orphan-reads (all 4 candidates verified written by HTML pages). 153: <script src="js/X.js"> references across 402 HTML pages — 0 broken refs. 154: href="X.html" references across 402 HTML pages — 0 broken targets.
Batch E (passes 141-146): Pass 141 (real fix): 23 HTML pages had buildNav('X') activePage strings that didn't match ANY entry in the 7 nav-group arrays in app.js — meaning their "active page" highlight pill was permanently dark on every visit. Added missing entries to tradeGrp (big-bets, day-trader-pro, gex-pro, options-builder, options-pricer, opex-tracker, orderbook, trade-tape, vol-term, etc.), brainGrp (trade-coach), playsGrp (trade-of-the-day), and toolsGrp (dashboard, education, live-quote-grid, live-watcher, paper-portfolio, pnl-diagram, vwap-pnl, watchlist-pro). Now 0 broken activePage refs across 402 pages. Pass 142: sitemap.xml covers 401 of 402 HTML pages (only 404.html excluded — intentional). Pass 143: service-worker v1.1 stale-while-revalidate cache list current. Pass 144: QUOTES (74 syms) ↔ STOOQ_MAP (74 syms) — perfect 1-to-1 mapping verified, zero orphans, zero unmapped. Pass 145: buildNav() called without activePage on exactly 4 standalone pages + 404 — all intentional. Pass 146: journal-repair.js auto-loaded via live.js lazy-load on every page; migration M1 fires 4s post-DOMContentLoaded, gated by version flag.
Batch D (passes 135-140): all 10 charts.js render functions guard against missing canvas · feature-store/model-explorer correctly render post-Adam state · alerts dedup TTLs verified · confidence-penalty + isotonic don't double-fire on retrain · all data-live="SYM:field" bindings across 402 HTML pages reference real QUOTES symbols (zero broken).
Batch C (passes 128-134): only 2 console.* calls in entire codebase, none in hot paths · 34 CSS custom props all defined, zero undefined refs (the "undefined" ones flagged by regex were inline-defined or false positives) · 221 inline onclick attrs documented (style preference, not a security issue without CSP) · NaN-guards verified on all 4 critical division ops in model.js / continuous-learner / auto-trade · all localStorage.setItem calls verified inside try/catch · zero visible TODO/FIXME/XXX/HACK/WIP markers in any HTML body.
Batch B (passes 121-127): earnings-awareness + brain-loop TZ usage verified intentional (local-time digest alignment) · auto-trade timeStop uses absolute UTC ms — TZ-independent · localStorage.setItem non-JSON values verified safe (strings only) · all data-provider.js fetches now wrapped in fetchT() with 10s AbortController timeout · webhook-bridge POST also gets 10s timeout · continuous-learner journal IDs now include Math.random suffix (prevents 2-tab same-ms collision) · zero unhandled .then() chains.
New: js/journal-repair.js — past corruption auto-fix. Built an idempotent migration framework. Migration M1 recomputes feature[20] in ET for every past journal entry (repairs the pass-119 timezone bug retroactively). Auto-fires 4s after every page load, gated by version flag so it runs at most once. Visible on /brain-debug.html under "Journal repair migrations." Manual re-run button also added under Quick Actions.
CRITICAL pass 119 — TWO timezone bugs: (a) model.js feature[20] (hour-of-session) used new Date().getHours() — LOCAL time. For Brandon in PT, that's 3 hours behind ET. Feature was reporting "before market open" while ET market was active, on every single capture. The brain's input vector had a SYSTEMATICALLY WRONG feature for non-ET users — corrupting every prediction. (b) trade-selectivity.js session/dow signals same bug: at 1pm PT (= 4pm ET, market closing), it would report "Mid-session (stable trading window)" because local hour was still 13. Fix: both now use Intl.DateTimeFormat with America/New_York TZ.
Improvements 116-120: all 102 module window-exports verified · demo-fab + notify auto-subscribe got reentrancy guards · zero real loose-equality bugs (== / !=) found · Notification.permission safety hardened in autoSubscribeSignals · charts.js DOM access patterns verified called-on-demand safe.
Passes 111-115: onboarding tour flow verified · toast + sound-synth + daily-card clean · brain-coach correctly reads DriftPSI.status() (works thanks to pass-76 alias) · alerts-builder/feed/dashboard polling at sensible 5-10s cadences · final lint of 102 JS modules and 402 HTML pages all clean.
Pending external action — TLS cert on options.bpleone.com: Brandon reported betting.bpleone.com is ALSO Bitdefender-blocked for unmatching cert. Means the subdomain isn't currently provisioned with a Let's Encrypt cert from its host. Full fix in SQUARESPACE-FIX.md: DNS record on bpleone.com → GitHub Pages custom-domain setting → wait 5-15 min → Enforce HTTPS. Once Brandon does the DNS+settings steps, /dns-test.html confirms in incognito.
Passes 105-110 + data verification: pattern-recall clean · command-palette refreshed with 14 new pages from today's session · recent-tickers + symbol-linker clean · 11 high-impact landing pages all parse + load core scripts + close cleanly · Math.random scan: every use verified safe (ID generation, mock data, stochastic ML — no real-data poisoning) · model.js fullRetrain() upgraded to Fisher-Yates shuffle · QUOTES seeds in plausible Nov 2026 ranges and gated by stale-seed flag · flow audit: 74 symbols all mapped, 5 journal writers + 13 readers consistent.
CRITICAL pass 102: hotkeys.js and money-hotkeys.js both bound g [letter] chord shortcuts with 17 conflicting destinations (e.g. g d → dashboard vs data-reliability). Both handlers fired on every keydown; whichever set location.href last won. Old hotkeys.js routes were silently broken. Fix: hotkeys.js defers ALL g-chord handling when MoneyHotkeys is loaded.
Passes 101-103: charts.js clean (Chart.js helpers properly guarded) · onboarding + command-palette + hotkeys reviewed (g-chord conflict fixed) · money-hotkeys + sound-synth + symbol-linker reviewed (no further conflicts).
🎉 100 audit passes complete. Every JS module reviewed at least once · 16 CRITICAL bugs fixed (each was silently corrupting metrics or breaking an entire safety chain) · 84+ smaller fixes · zero remaining know-broken modules · all changes deployed.
Passes 97-99: cross-source-check now uses in-memory state + 10s flush cadence (was writing on every Coinbase tick — many/sec) · source-preference clean · auto-pause hysteresis + cold-start gate verified · trade-selectivity + equity-protector verified · learn.js (legacy) now also exposes window.Learn per pass-76b convention.
Passes 89-95: self-distillation gets same cache+flush write-storm fix as sample-decay/label-smoothing · counterfactual-replay clean · feature-importance clean · service-worker now stale-while-revalidate (was cache-first — JS fixes never reached cached users until manual VERSION bump) · voice-coach clean · mixup clean · hindsight-replay clean.
CRITICAL pass 80: RegimeCalibrator uses regime names 'bull','bear','chop','high-vol','mixed' but MultiHorizon.detectRegime() returns 'trending_bull','choppy','volatile_bear'. continuous-learner passes the MultiHorizon names into RegimeCalibrator.recordPair() — none matched, so EVERY pair was bucketed as 'mixed'. Per-regime calibration was completely inert; unified-predictor always fell back to global Platt. Added a normalizeRegime() translation layer.
CRITICAL pass 79: AdversarialValidator.MAX_POOL = 500 couldn't accommodate the 24h OLD_THRESHOLD — at ~900 captures/RTH-day, the pool only held the last ~13 hours, so the old-pool filter always returned 0 entries and fit() always returned { fitted: false }. Covariate-shift detection never fired site-wide. Bumped to 5000 (~5.5 days).
Passes 79-87: adversarial-validator now uses symmetric shift detection (|AUC-0.5|) and a 10× larger pool · regime-calibrator name normalization · swa cap n at 200 so it actually reflects recent training · webhook-bridge only marks alerts as seen on success OR 4xx, retries 5xx/network errors next tick · sample-decay + label-smoothing now cache state + flush every 60s instead of writing localStorage on every call (was thousands of writes/min on heavy resolveRound batches).
CRITICAL pass 76 (1c9901e): drift-psi.js exposed window.PSIDrift.summary() but FIVE callers (TradeTrust, Brain Coach, Daily Card, Brain Mobile, Brain Truth) all read window.DriftPSI.status() — a complete name + method mismatch. Every drift-aware safety check silently no-op'd; the brain could be 20pp away from the historical feature distribution and TradeTrust would still show green. Fix: add status() returning flat {psi, status} and expose API under BOTH PSIDrift and DriftPSI so legacy correct callers stay working.
Passes 72-76b: auto-trade was anchoring paper trades to the journal's captured price (up to 5 min stale) but computing stops against current QUOTES — volatile names could open already-stopped (pass 72) · ConfidenceKelly silently ignored input.fraction in favor of localStorage state, so AutoTrade's per-config Kelly fraction did nothing (pass 74) · five modules (AIClient, BS, DataProvider, Feed, Notify) declared with top-level const never auto-attached to window — defensive callers got undefined (pass 76b) · drift-psi alias + status method (pass 76).
CRITICAL passes 67-68 (1e2758c, 99e58cc, c4a31e0): THREE separate Sharpe modules — SharpeTracker, SymbolSharpe, SectorPerf — all used PERIODS_PER_YEAR = 23400 (assumed 10-min returns) but every caller (continuous-learner, historical-bootstrap) feeds DAILY returns. Annualization formula sharpe × sqrt(N) over-reported annSharpe by sqrt(23400/252) ≈ 9.6× everywhere. Effect: TradeTrust's "Sharpe<1.0" penalty rarely fired; symbol/sector leaderboards pushed everything into "world-class" tier; the brain looked far better than it actually was. Fix: 252 trading-days default across all three.
CRITICAL pass 66 (3809278): market-map.html's paint() function declared const bySec twice in the same function scope (lines 164 + 203) — SyntaxError prevented the function from parsing. Market map treemap, KPIs, and sector breakdown never rendered. Renamed the second to bySecForKpis.
CRITICAL pass 65 (4f40c48): streak-tracker.js recovery loop skipped trades with ts <= biggestLoss.ts, which excluded the biggest loss itself from runCum. As soon as ANY positive trade landed after the loss, the function reported instant recovery — even though equity was still well below pre-loss level. Rewrote as a single forward pass.
CRITICAL pass 60 (101b271): KNNRecall.predict() returned "fraction of similar past predictions that were directionally correct" — but UnifiedPredictor blends it into the final probability AS IF it were P(LONG). A neighbor that predicted DOWN and was right voted toward UP under the old logic. Fixed: compute symbolWentUp = (predUp === wasRight) and count actual UP-events. Affects every page that consumes the blended prob (brain-bet, brain-conviction, make-money).
CRITICAL pass 53 (d4df5be): EnsembleAgreement.categorize() returned LOWERCASE tier names ('strong','moderate','mixed','fragmented') but THREE callers compared against UPPERCASE — confidence-kelly's agreementMult, trade-trust-score's fragmentation penalty, and brain-bet.html's tier color. Every case-sensitive check silently failed and agreement-based size adjustments + colors NEVER fired. Brain was sizing trades without considering cross-method disagreement, even though the score was being computed. Returns UPPERCASE now to match callers.
Passes 40-54: Adam optimizer state now persisted across page reloads (model.js) · Coinbase WS reconnect now uses exponential backoff (was fixed 5s) · DataReliability fetch recording throttled 5s (was per-message, flooding ring buffer) · CONNECTING-state guard on WS prevents socket leaks · RSI flat-tape returns 50 not 100 (historical-bootstrap) · is_reversion feature requires idx≥2 not idx≥1 · calibrator first-fit no longer needs 50 pairs (uses MIN_PAIRS_TO_FIT) · bayesian-dropout percentile linear-interp (was off-by-one) · portfolio-allocator + confidence-kelly emit sharesError instead of absurd share counts when riskPerShare missing · stale-refresh reentrancy guard prevents concurrent-tab double-fires.
CRITICAL pass 26 (e1106c5): 50+ pages used var(--bg) + 24 used var(--text) but neither was defined in style.css — they fell back to initial values (black/transparent), breaking dark theme on those pages. Added 5 aliases in :root so both naming conventions resolve.
Passes 22-36: 49 return-shape candidates investigated (false positives in critical paths after pass 21) · 9 onclick "missing handler" all if keyword · sort()-without-comparator clean (all string sorts) · zero eval() in production js/ · zero non-null == bugs · 47 critical money pages verified with proper structure · 0 broken script srcs · 0 broken hrefs.
CRITICAL pass 21 (20ef6bc): HistoricalBootstrap.run() returned { fetched, trained, errors } but WeeklyRefresh + dashboard pages all checked result.trainingExamples / result.symbolsFetched which DIDN'T EXIST. WeeklyRefresh never recorded a successful auto-refresh — lastSuccessAt stayed 0, status always read "NEVER". Bootstrap itself worked; only the return value was misnamed. Fixed by returning both naming conventions for back-compat.
Passes 16-21: 10s fetch timeout on auto-trainer · timer double-init guards verified · symbolHealth no-data trap fixed (3 modules) · pre-trade-checklist falsy-undefined bug fixed · weekly-refresh defensive load merge · HistoricalBootstrap return field mismatch fixed.
Want to verify yourself? Open /self-test.html for live in-browser tests of every module. Runs 32 functional checks in < 1 second. All green = installation is healthy.
DONE

Pass 1 · Static analysis 3f8160e

Ran _deep_audit.py (new) checking module exports, dead method calls, localStorage keys, broken script srcs, orphan JS, broken hrefs, dead getElementById, sitemap coverage.

FIXED · seed-detector.js orphan — built last batch but never wired into live.js. Now lazy-loaded.
FIXED · model-results.html broken IDs — JS called classList.toggle on kAccBig/kPnlBig/kHrBig/kTrendBig but HTML uses kpiAccBig/kpiPnlBig/etc. Page partially rendered then halted on first null reference. Now fixed.
CLEAN · 0 broken script srcs · 0 broken internal hrefs · 0 dead method calls · 81 modules properly exported.
DONE

Pass 2 · Lazy-load timing fix eb34599

ROOT CAUSE FOUND: live.js lazy-loaded 17 critical modules inside a 5-second setTimeout. Pages call render() at 500ms. So there was a 4.5s window where window.MoneyTracker (etc) was undefined — render bailed to "Loading…" placeholder. setInterval(8s) eventually rescued it but first impression was broken.

Wrote _fix_direct_loads.py to scan every HTML page for window.X references, verify js/X.js is loaded directly. Auto-inserted <script src> tags right after live.js on the affected pages.

FIXED · 19 pages got direct loads: ai-market-pulse, bankroll-milestones, brain-health-pro (+10 modules!), brain-insights (+8), brain-time-of-day, daily-replay, earnings-awareness, goal-tracker, hot-symbols, index, outcome-distribution, pattern-recall, risk-gauge, smart-defaults (+6), source-performance, source-quality, symbol-deep-dive (+4), trade-plans, voice-coach.
EFFECT · Every empty-state page now renders correctly the moment the JS evaluates. No more "Loading…" lockup.
DONE

Pass 3 · Data flow audit

Ran _flow_audit.py tracing Stooq → DataReliability → QUOTES → ContinuousLearner → journal → MoneyTracker render. Cross-checked STOOQ_MAP coverage vs QUOTES seeds vs UNIVERSE.

FIXED · 7 forex symbols (UUP, FXE, FXY, FXB, FXC, FXA, FXF) were in QUOTES but missing from STOOQ_MAP — they would never receive live updates. Now mapped to Stooq tickers (uup.us, fxe.us, etc).
VERIFIED · journal writers (5) + readers (13) all use the same key bpleone_pred_journal_v1. No drift.
VERIFIED · DataReliability validation thresholds — max price jump 30% (intentional, handles crypto volatility) · equity stale 5min · crypto stale 2min · all sane.
VERIFIED · ContinuousLearner UNIVERSE covers 24 of 60 QUOTES — intentional (only the most-liquid tickers worth training on).
VERIFIED · isLiveQuote() correctly rejects seed values (requires priceSource + liveAt within 30 min).
DONE

Pass 4 · Journal retention bug dd6defd

CRITICAL FIX: continuous-learner.js had MAX_JOURNAL = 5000. At ~48 brain captures/hour during market hours, that retains only ~3 days. Money Made's "Lifetime" + 30d windows were silently truncating data.

FIXED · MAX_JOURNAL: 5000 → 12000 (~25 days of captures). Plus age-based filter MAX_JOURNAL_AGE_DAYS=120 so old entries get age-trimmed first. Quota-exceeded fallback to 3000.
FIXED · DemoData cap also bumped 5000 → 12000 to match.
DONE

Pass 5-7 · Timers · Features · Page structure

VERIFIED · All setInterval timers reasonable. Stooq 12s · Coinbase REST 30s · brain cycle 30s · regime 60s · seed-detector decorate 10s. No DoS-level polling.
VERIFIED · Features array consistency. demo-data, historical-bootstrap, model all generate 22-dim vectors matching N_FEATURES=22 in drift-psi + outlier-detector.
VERIFIED · Page structure. 5 pages flagged for missing nav/footer — all intentional (mobile views, embeds). 0 script-order issues.
DONE

Pass 8 · localStorage write-safety 89850c6

Grep'd every localStorage.setItem for missing try/catch. Found goal-tracker.html had an unprotected raw setItem. Wrapped in try/catch (could crash on quota exceeded).

FIXED · goal-tracker.html setItem now wrapped.
SHIPPED · self-test.html — in-browser runtime test harness, 32 functional tests, color-coded results.
DONE

Pass 9 · ConfidenceKelly defensive clamp 8c08436

FIXED · ConfidenceKelly.size() uncertaintyMult was Math.max(0.3, 1 - std * 4). Missing upper clamp — if std went negative (defensive case), would return >1.0 and over-size. Now clamped to [0.3, 1.0].
VERIFIED · 0 duplicate script-src tags across 397 pages.
DONE

Pass 14 · CRITICAL: prevClose overwrite bug 5e38a1f

THE SINGLE BIGGEST BUG OF THE AUDIT. Every poll update (Stooq, Coinbase WS, Coinbase REST, stale-refresh) was overwriting q.prevClose with the previous q.last. This destroyed yesterday's authoritative close (from live.js seed values) within seconds of the first poll. After a minute, ticker change% showed "move over last 12 seconds" instead of "today vs yesterday".

FIXED in 5 places · Stooq path · Coinbase WS · Coinbase REST · Stooq stale-refresh · Coinbase stale-refresh. All preserve seed prevClose (yesterday's close). change/changePct now measure true day-over-day move.
DONE

Pass 15 · model.js NaN-safe sigmoid 6a74de6

If feature vector or weights were corrupted, sigmoid(NaN) returned NaN. predict() emitted {prob: NaN}. Every downstream calibration + Kelly sizing silently propagated NaN.

FIXED · sigmoid() now returns 0.5 (no signal) when input is not finite. Defensive against any upstream corruption.
DONE

Pass 12-13 · Honest banner + QUOTES seed sanity f671728

FIXED · data-mode-banner.js was claiming "Finnhub WebSocket connected" for ALL connected providers. Now reads actual provider name and shows "LIVE · {provider} real-time" or "LIVE (delayed ~15 min) · {provider} free tier" — honest.
VERIFIED · All 60 QUOTES seed values pass sanity (no 0s, no extremes, VIX in [5,100], BTC in [10k,200k], implied changes < 30%).
DONE

Pass 10-11 · CRITICAL: seed-quote rejection 343bf69

CRITICAL BUG FOUND IN 2 MODULES. high-conviction-alerts.js and auto-trade.js both checked freshness with if (q.liveAt && ...). If q.liveAt was UNSET (seed-only price from live.js init), the conditional was falsy and the gate PASSED. Meaning real alerts/trades could fire on the placeholder seed prices that were never live-fed.

FIXED · Both now explicitly require q.liveAt is set BEFORE checking age. AutoTrade + HC Alerts only fire on prices that have actually been live-fed by a real source.
SHIPPED · DemoData also seeds bpleone_spy_history_v1 (30 synthetic SPY closes) so Brain-vs-SPY page works with demo. Gated to not overwrite real Historical Bootstrap output.
🛠 Audit scripts (run yourself)
python _full_audit.py — Brace balance, inline script syntax, sitemap coverage
python _deep_audit.py — Module exports vs callers, orphan JS, dead getById, broken hrefs
python _fix_direct_loads.py — Auto-inserts direct script tags for lazy-load races
python _flow_audit.py — Stooq → QUOTES → journal → render data flow
🔗 Related
❤️ Brain Health 📊 Data Reliability 📡 Live Status