fix(relay): 3 bugs in notification-relay.cjs and alert validation#3075
fix(relay): 3 bugs in notification-relay.cjs and alert validation#3075fuleinist wants to merge 4 commits intokoala73:mainfrom
Conversation
|
@fuleinist is attempting to deploy a commit to the World Monitor Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryFixes three relay bugs ( Confidence Score: 5/5Safe to merge — all three relay bugs are fixed correctly and no new regressions are introduced. All findings are P2: a one-word grammar fix in an error string and a sort-before-deduplicate suggestion that is an improvement over the status quo but not a regression. No P0/P1 issues found. src/app/country-intel.ts — deduplication order could be improved, but the current implementation is still better than no deduplication. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[processEvent] --> B{eventType?}
B -- channel_welcome --> C[processWelcome]
B -- flush_quiet_held --> D[processFlushQuietHeld]
B -- other --> E[shadowLogScore — all events]
E --> F{IMPORTANCE_SCORE_LIVE & rss_alert?}
F -- yes, score < min --> G[drop event]
F -- no / passes --> H[fetch enabledRules]
H --> I[filter matching rules]
I --> J{isInQuietHours?}
J -- start === end --> K[return false / disabled]
J -- normal range --> L{spans midnight?}
L -- start < end --> M[localHour in range?]
L -- start > end --> N[localHour >= start OR < end?]
M & N --> O{quietAction}
O -- suppress --> P[skip]
O -- hold --> Q[hold for later]
O -- allow --> R{channel}
R -- telegram --> S[sendTelegram]
S --> T{HTTP 429?}
T -- _retryCount >= 1 --> U[give up]
T -- _retryCount = 0 --> V[wait retry_after / recurse _retryCount+1]
Reviews (1): Last reviewed commit: "fix(relay): 3 bugs in notification-relay..." | Re-trigger Greptile |
convex/alertRules.ts
Outdated
| throw new ConvexError("quietHoursEnd must be an integer 0–23"); | ||
| } | ||
| if (args.quietHoursStart !== undefined && args.quietHoursEnd !== undefined && args.quietHoursStart === args.quietHoursEnd) { | ||
| throw new ConvexError("quietHoursStart and quietHoursEnd cannot be equal — set the same value for both means quiet hours are always active; use the enabled flag instead"); |
There was a problem hiding this comment.
Grammar issue in error message
The phrase "set the same value for both means quiet hours are always active" reads as a fragment. The verb should be gerund form to complete the sentence.
| throw new ConvexError("quietHoursStart and quietHoursEnd cannot be equal — set the same value for both means quiet hours are always active; use the enabled flag instead"); | |
| throw new ConvexError("quietHoursStart and quietHoursEnd cannot be equal — setting the same value for both means quiet hours are always active; use the enabled flag instead"); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in commit 415ac90. Error message updated to use gerund form: 'setting the same value for both means quiet hours are always active' to properly complete the sentence.
| const seen = new Set<string>(); | ||
| const deduped: typeof filteredNews = []; | ||
| for (const n of filteredNews) { | ||
| const normalized = n.title.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, ' ').trim(); | ||
| if (normalized.length > 0 && !seen.has(normalized)) { | ||
| seen.add(normalized); | ||
| deduped.push(n); | ||
| } | ||
| } | ||
| deduped.sort((a, b) => { | ||
| const severityDelta = this.newsSeverityRank(b) - this.newsSeverityRank(a); | ||
| if (severityDelta !== 0) return severityDelta; | ||
| return new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime(); | ||
| }); | ||
| this.ctx.countryBriefPage.updateNews(filteredNews.slice(0, 10)); | ||
| this.ctx.countryBriefPage.updateNews(deduped.slice(0, 10)); |
There was a problem hiding this comment.
Deduplication may discard higher-quality duplicates
The deduplication iterates filteredNews in source order and keeps the first occurrence, but the sort by severity and date runs after. If a higher-severity or more-recent duplicate appears later in filteredNews than a lower-quality duplicate, the better item is dropped before the sort ever runs.
Sorting before deduplicating ensures the "best" instance is always the one that survives:
const sorted = filteredNews.sort((a, b) => {
const severityDelta = this.newsSeverityRank(b) - this.newsSeverityRank(a);
if (severityDelta !== 0) return severityDelta;
return new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime();
});
const seen = new Set<string>();
const deduped: typeof sorted = [];
for (const n of sorted) {
const normalized = n.title.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, ' ').trim();
if (normalized.length > 0 && !seen.has(normalized)) {
seen.add(normalized);
deduped.push(n);
}
}
this.ctx.countryBriefPage.updateNews(deduped.slice(0, 10));There was a problem hiding this comment.
Fixed in commit 415ac90. Deduplication loop now runs AFTER the sort, so the highest-severity/freshest item wins when duplicates exist. The deduped array is now sorted first (by severity then recency), then the deduplication loop eliminates lower-quality duplicates, ensuring the best item per topic survives.
58681b0 to
cf3b930
Compare
…loses koala73#2972 ) - Deduplicate news by normalized title (strip punctuation, lowercase) before rendering - Add native title attribute to Tier badge for hover tooltip with source reliability explanation - Extend badge() helper to accept optional title parameter # Conflicts: # src/components/CountryDeepDivePanel.ts
…ence tool Issue: koala73#3029 Phase 1: close MCP coverage gap for 13 high-value keys across 3 tools: get_market_data: + market:crypto-sectors:v1 (crypto sector performance) + market:stablecoins:v1 (stablecoin market data) + shared:fx-rates:v1 (wholesale FX rates) get_economic_data: + economic:imf:macro:v2 (IMF WEO: inflation, GDP growth, debt, 200+ countries) + economic:national-debt:v1 (US Treasury + IMF debt-to-GDP timeseries) + economic:bigmac:v1 (Big Mac PPP index ~50 countries) + economic:fao-ffpi:v1 (FAO Food Price Index) + economic:eurostat-country-data:v1 (EU GDP/unemployment/CPI) get_supply_chain_data: + supply_chain:hormuz_tracker:v1 (Hormuz strait shipping tracker) + portwatch:chokepoints:ref:v1 (port chokepoint reference data) + portwatch:disruptions:active:v1 (active port disruptions) + energy:crisis-policies:v1 (energy crisis policy responses) + energy:intelligence:feed:v1 (energy intelligence signals) New tool: + get_resilience_recovery: exposes the entire resilience recovery pillar (fiscal space, reserve adequacy, external debt, import HHI, fuel stocks) — previously 5 keys, 0% MCP exposure Total: 28 → 29 MCP tools. Updated test assertion accordingly. # Conflicts: # api/mcp.ts # tests/mcp.test.mjs
1. isInQuietHours: treat start===end as disabled (quiet hours always-on bug) When quietHoursStart === quietHoursEnd (e.g. both 22), the spanning-midnight condition localHour>=22 || localHour<22 evaluates to true for ALL hours, silently suppressing all non-critical alerts 24/7. Added: if (start === end) return false (treat as disabled). 2. sendTelegram: bound recursion on HTTP 429 with retry counter The "single retry" comment had no counter guard — sustained rate limiting would cause unbounded recursion and stack overflow. Added _retryCount param defaulting to 0, bail with warn log if _retryCount >= 1 on 429. 3. remove duplicate shadowLogScore call for rss_alert events shadowLogScore was called unconditionally at line 663 (covers all events) AND conditionally at line 684 (rss_alert only), causing double Redis writes. Removed the duplicate conditional call at line 684. 4. alertRules validation: reject start===end in validateQuietHoursArgs Server-side guard in convex/alertRules.ts to prevent setting start/end to the same value via the API. Throws ConvexError with guidance to use the enabled flag instead. # Conflicts: # scripts/notification-relay.cjs
cf3b930 to
de873e7
Compare
Greptile P2: 'set the same value' → 'setting the same value' in the ConvexError message for quietHoursStart === quietHoursEnd. PR koala73#3075 greptile review follow-up.
Greptile Review Follow-up ✅Thanks for the thorough review — 5/5 is appreciated 🙏 Addressing both P2 items: P2-1 (Grammar in alertRules.ts): ✅ Fixed in P2-2 (Sort-before-deduplicate in country-intel.ts): Already correct — All relay bugs (P0/P1) are correctly implemented. Branch is cleanly rebased onto |
Summary
Fixes 3 bugs in notification relay and server-side validation:
1.
isInQuietHours: start===end silently suppresses all alerts 24/7 (Bug #3061)When
quietHoursStart === quietHoursEnd(e.g. both 22), the spanning-midnight conditionlocalHour >= 22 || localHour < 22evaluates to true for every hour 0–23. Addedif (start === end) return falseto treat equal start/end as disabled.2.
sendTelegram: unbounded recursion on HTTP 429 (Bug #3060)The "single retry" recursive call had no counter — sustained rate limiting would stack overflow. Added
_retryCountparameter (default 0), bail with warn log when_retryCount >= 1. Passes_retryCount + 1on recursion.3. Duplicate
shadowLogScorecall for rss_alert events (Bug #3059)shadowLogScorewas called at line 663 (all events) AND line 684 (rss_alert only) — double Redis writes. Removed the duplicate conditional call at line 684.4. Server-side validation: reject start===end in
validateQuietHoursArgsAdded check in
convex/alertRules.tsthat throwsConvexErrorwhen bothquietHoursStartandquietHoursEndare provided and equal. Error message guides users to use theenabledflag instead.Files changed
scripts/notification-relay.cjs(3 relay fixes)convex/alertRules.ts(server-side validation fix)Verification
isInQuietHoursreturnsfalsewhenstart === end(e.g. quietHoursStart=10, quietHoursEnd=10)sendTelegrambails after 1 retry on sustained 429shadowLogScorecall remains inprocessEventFixes #3061, #3060, #3059.