fix(news): isolate the backtest signals projection#122
Conversation
get_news_sentiment projected via a dict comprehension, so one off-spec ticker raised out of the whole projection and load_news_sentiment's fail-closed except emptied the sentiment slot for every ticker that step. The panel has dropped-and-carried-on since _representative_feed; this is the projection that never learned it. Per-entry drop + the existing _alarm_if_all_dropped, so the adapter reports its own contract breaks. That also removes the reason backtest_backend's except had to explain _project_entry's KeyError from another module, and means a second caller (live paper-trading, per the panel design spec) inherits the reporting instead of re-implementing it. The alarm's raw arg is the post-filter set, not the wire's signals block: a signal outside the universe is filtered by design, not dropped by drift. Mutation-tested: all three new guards fail when reverted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pbs5wnQZhTi9CQ3pb14vDo
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Review of #122. _alarm_if_all_dropped's docstring claimed "the caller escalates a True to `degraded`" — true for the panel's call sites, false for the backtest one this PR adds, which discards the return by design (log-only pending #123). It is the same hazard the PR removes from backtest_backend.py, one function over: a comment that narrates another layer's behavior goes stale the moment that layer moves. #123 already quotes the line as if it were universal, so the drift had started. Also: the isolation docstring cited AAPL, the one watchlist ticker the fixture gives no signals to, where the test and the incident both mean NVDA; and the test docstring re-narrated the history verbatim from _project_step_signals, so it now pins what the test buys and points at the single home for the why. Docs only — no behavior change. Suite 993 passed / 2 skipped, and all three guards still fail under their mutations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Reviewed. One real finding, docs-only, fixed in
Plus two nits: NVDA-for-AAPL in the isolation docstring (AAPL is the one watchlist ticker the fixture gives no signals to — the one that couldn't be deleted this way), and the test docstring no longer re-narrates the history verbatim from Verified rather than assumed, on head
Design call looks right: isolation without the alarm would have converted a loud failure into a quiet one, which is the 2026-07-14 shape. No new security surface — entries are dropped, never coerced, so the LLM context can only get smaller, never malformed. |
load_news_sentiment's docstring spelled out the adapter's drop-and-alarm mechanism in the sentence before warning that a comment here "that explains the adapter's internals goes stale the moment they move" — the rule and its violation two lines apart. Swap the mechanism detail for a pointer, the idiom the file already uses elsewhere (_project_step_signals: "see the caller's _alarm_if_all_dropped"). The rationale now lives once, in get_news_sentiment, where it can't drift out of sync with the code it describes. Docstring-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed ( Skipped — scope. Rejected on merits. Unifying Confirmed correct, not changed. The escalation asymmetry (3 callers OR the bool, this one discards it) — #123 is real and states why deferring is right. Altitude added the clincher: Suite 993 passed / 2 skipped, unchanged from this PR's own baseline. Follow-up (docs, not touched here). This PR makes stale: |
get_news_sentimentprojected via a dict comprehension, so one off-spec ticker raised out of the whole projection andload_news_sentiment's fail-closed except emptied the sentiment slot for every ticker that step — a producer typo on MSFT silently deleting NVDA's news. The panel has dropped-and-carried-on since_representative_feed; this is the projection that never learned it.Per-entry drop + the existing
_alarm_if_all_dropped. Three symptoms, one cause — the adapter left contract-break reporting to its caller:backtest_backend's except no longer has to explain_project_entry's KeyError from another module. That hint is now removed — it would go stale the moment the adapter's internals move, which is what this PR does.get_news_sentimenthas one production caller today. The fail-loud property fix(backtest): log news-sentiment failures #116 added lives in the caller's try/except, so caller Jifeng #2 (live paper-trading — anticipated infinsearch-news-signals-panel-design.md:115) silently regresses unless it remembers to re-implement it. Now there is no exception to forget to catch.Isolation without the alarm would be a regression, not a fix. A rename makes every entry unreadable, so log-and-drop alone yields an empty slot plus a few warnings — indistinguishable from a quiet news day. That is how the 2026-07-14 outage ran for hours. Per
_alarm_if_all_dropped's docstring, the alarm is what separates "one story is off-spec" from "the contract moved".The alarm's
rawarg is the post-filter set, never the wire'ssignalsblock. A signal for a ticker outside the universe is filtered by design, not dropped by drift; feeding the raw block would fire on every narrow-universe run and train the reader to ignore the one alarm that matters.test_universe_filter_alone_is_not_driftpins it._project_entrystays the single source of which fields are required — no duplicated key list, andKeyError's own text names the missing key, so the log readsmissing 'sentiment_score'rather than a bare "malformed".Escalation is log-only, deliberately. The panel escalates drift to a
degradedbadge; the backtest has no equivalent channel.RunManifest.news_sentiment_sourceis the candidate but is a provenance field that is unwired today (alwaysNone) — filing a follow-up rather than half-wiring it into a status flag here.Mutation-tested (all three new guards fail when reverted; the alarm-deletion mutation was re-run after the first attempt produced a vacuous syntax error rather than a real failure). Suite green, delta is exactly +3 and nothing else moves (main 990 -> 993 passed, 2 skipped). Absolute counts vary with optional deps —
discordinstalled or not swings the total, which is where an earlier 980/4 reading came from.Review fix (
ca36d12, docs only)._alarm_if_all_dropped's docstring claimed "the caller escalates a True todegraded" — true for the panel's call sites, false for the one this PR adds, which discards the return by design. Same hazard this PR removes frombacktest_backend.py, one function over; #123 already quotes the line as if it were universal. Also NVDA-for-AAPL (AAPL is the one watchlist ticker the fixture gives no signals to) and the test docstring no longer re-narrates the history verbatim.Re #120: independent (
get_news_sentimentvsget_latest_panel_payload), and test-merged clean. Both widen_alarm_if_all_droppedtoUnion[list, dict]; this makes that change byte-identical to #120's so it merges either order. #120's "applied to ALL THREE projections" is left alone — it enumerates the panel's projections and stays accurate.🤖 Generated with Claude Code
https://claude.ai/code/session_01Pbs5wnQZhTi9CQ3pb14vDo