@@ -140,6 +140,25 @@ func collectProgramsForMonitor() []ProgramSummary {
140140 return all
141141}
142142
143+ // programMonitorKey returns a unique-per-program key for the program_assets store.
144+ // p.ID (the platform's stable UUID/ID) is preferred when present so a future handle
145+ // parsing bug can't collapse multiple programs into one bucket — that exact mistake
146+ // is what caused the Intigriti "/detail" flood: every IT program ended up with
147+ // Handle="detail" and shared a single program_assets row.
148+ func programMonitorKey (p ProgramSummary ) string {
149+ id := strings .TrimSpace (p .ID )
150+ if id != "" {
151+ return strings .ToLower (p .Platform ) + ":id:" + id
152+ }
153+ return strings .ToLower (p .Platform ) + ":" + p .Handle
154+ }
155+
156+ // floodSuspectThreshold caps a single program's "new asset" alert. Real scope
157+ // additions are 1-5 assets at a time; a huge "diff" on a watched catalogue is
158+ // almost always an identifier-collision/key bug. Above this we silently re-baseline
159+ // and log a warning instead of flooding Discord.
160+ const floodSuspectThreshold = 20
161+
143162// sweepProgram fetches one program's current in-scope assets and alerts on new ones.
144163func sweepProgram (p ProgramSummary ) {
145164 assets := fetchProgramAssets (p )
@@ -148,7 +167,7 @@ func sweepProgram(p ProgramSummary) {
148167 // otherwise the next successful fetch would report every asset as "new".
149168 return
150169 }
151- key := programScopeCacheKey ( p . Platform , p . Handle )
170+ key := programMonitorKey ( p )
152171 newAssets , firstRun , err := db .RecordProgramScopeAssets (key , assets )
153172 if err != nil {
154173 logger .GetLogger ().Infof ("[PROGRAM-MONITOR] record failed for %s: %v" , key , err )
@@ -157,6 +176,14 @@ func sweepProgram(p ProgramSummary) {
157176 if firstRun || len (newAssets ) == 0 {
158177 return // baseline run, or nothing new
159178 }
179+ // Safety cap: a real scope change is incremental. A huge sudden delta almost
180+ // always means a key collision (multiple programs mapped to one row) or a
181+ // platform API change. Skip the alert rather than spam.
182+ if len (newAssets ) >= floodSuspectThreshold || len (newAssets ) == len (assets ) {
183+ logger .GetLogger ().Infof ("[PROGRAM-MONITOR] WARN suspicious delta for %s (%s): %d/%d assets reported new — silently re-baselining, no alert sent" ,
184+ p .Handle , p .Platform , len (newAssets ), len (assets ))
185+ return
186+ }
160187 alertNewAssets (p , newAssets )
161188}
162189
0 commit comments