None of these touch the signal path. The 12-month M15 book
(2024-03-31..2025-03-30, $0.65, cached Oanda bars) is byte-identical
before and after (md5 c59d3e7e685c3c880b614640adfea74b, 375 trades),
and signal/indicator logging to logs/signals.db is unchanged — verified
end to end: per-TF indicators for all 3 TFs, 14 candles each, MTF and
reasons all still persisted.
agent_panel: the >=2-take quorum guards the CHAIR's synthesizer spend,
but ran BEFORE the macro branch — so the shipped default
(verdict_source="macro", which never calls the synthesizer) silently
dropped a good macro verdict whenever the other three personas were
slow. Quorum moved below the macro path.
analyze_signal_log: _forward_return clamped to the last available bar
when the horizon had not elapsed, reporting a near-zero "forward move"
for every row logged within `hours` of the end of the data — on every
run, blended into the hit-rate/mean-move aggregate. Now returns None.
analyze_signal_log: persona_scorecard iterated raw rows through an index
that maps EVERY member row of an event, so a re-logged scan appended
the same event R twice. One vote per event, matching
news_outcome_buckets. Measured inflation on the live DB: 1.03x.
signal_log: dedup required a non-negative logged_at gap, but logged_at is
stamped before the row wins the write lock (busy_timeout 5 s), so two
processes logging one setup at a boundary can invert timestamp order
against id order and seed a second event. Now abs(gap); same in
backfill_seeds. Zero occurrences in the current 366 setup rows.
advisory_outcome: the catch-all turned real evaluator faults into the
same UNRESOLVABLE used for legitimate weekend gaps, with nothing in the
log. Still fail-safe, now logged.
llm_client: only the "cap" warn-once flag was ever reset, so in a daemon
the second and every later network/http/parse outage logged at DEBUG
only. Cleared on UTC-day roll and re-armed by a recovered call.
dashboard.js: renderCards() redraws the selector for every timeframe but
was gated on the SELECTED one changing, leaving the other buttons stale
(up to 4 h on H4) while renderHero's rail already showed the new value.
news_summary.php: atomic_write treated a short write (disk full) as
success, publishing a truncated body the cache-first branch then served
forever; and the daily cap was charged before the no-key check and
before the stampede lock re-checked the cache, so a click burst could
exhaust the 150/day budget with zero LLM calls.
migrate_signal_log_news: logged_at is ISO with 'T' while SQLite's
datetime() uses a space, so string-comparing them put the whole cutoff
calendar day on the wrong side and it never pruned. NOTE: this now
deletes one day more than before — it only runs when invoked by hand.
Tests 414 -> 422 (new Module 28, one per fix). The existing
"<2 valid takes" test was retargeted at the chair — the case it actually
protects — rather than deleted, with new coverage for macro-alone.
Not fixed, recorded for the owner:
- Gate 6c's boost condition is textually identical to the C-grade
definition, so every C in a "transitioning" regime is promoted to B
before gate 6e reads it, making sell_min_quality's exclusion of "C" a
no-op on M15/H4 (H1 and M5 are immune by other means). The backtester
runs the same code, so every pinned book already includes this —
changing it is a rule-1 revalidation event.
- Safe-haven/geopolitical headlines score exactly 0.00 in the keyword
fallback and are now dropped entirely by the ±0.1 neutral filter,
contradicting the platform's own gold doctrine. Bites only when
DeepSeek is capped/disabled/failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ah5EB1goi5inQmSd37Ne6a
Add panel_verdict, panel_confidence, panel_rationale, panel_takes_json
columns to the signals table (additive, idempotent ALTER TABLE migration
for existing DBs). _report_row now reads the panel fields attach_panel
writes onto the SignalReport, so LLM panel verdicts stop evaporating on
process exit. Also documents the Oanda re-pin (round_trip_cost stays
$0.30 for cross-book comparability and the live TP gate; $0.65 is the
measured headline) and adds the feed-divergence / parity / shadow-scan
study tooling referenced by CLAUDE.md.