@@ -130,6 +130,24 @@ def delete_preset_analyses_for_product(preset_id, product, vulnerability_ids):
130130 ).delete ()
131131
132132
133+ def delete_triage_records_for_assignment (ruleset , product ):
134+ """
135+ Delete triage records and associated preset analyses for a ruleset being
136+ unassigned from a product.
137+ """
138+ matching_records = TriageRecord .objects .filter (ruleset = ruleset , product = product )
139+ stale_vulnerability_ids = list (matching_records .values_list ("vulnerability_id" , flat = True ))
140+ # Records with an open Request are kept so reassigning the ruleset reconnects to it
141+ # instead of opening a duplicate Request.
142+ matching_records .filter (request__isnull = True ).delete ()
143+ if ruleset .analysis_preset_id and stale_vulnerability_ids :
144+ delete_preset_analyses_for_product (
145+ preset_id = ruleset .analysis_preset_id ,
146+ product = product ,
147+ vulnerability_ids = stale_vulnerability_ids ,
148+ )
149+
150+
133151def sync_triage_records (ruleset , product , matched_rules_per_vulnerability_id , apply_preset = True ):
134152 """
135153 Create or update one TriageRecord per matching vulnerability, then
@@ -228,6 +246,33 @@ def evaluate_ruleset(ruleset, product, apply_preset=True):
228246 )
229247
230248
249+ def delete_preset_analyses_before_ruleset_delete (ruleset ):
250+ """
251+ Delete VulnerabilityAnalysis records applied by the ruleset's preset, for every
252+ product currently assigned to it.
253+
254+ Must run before the ruleset itself is deleted: its TriageRecords have no signal of
255+ their own, so Django cascade-deletes them via a fast bulk query before a post_delete
256+ signal on the ruleset or its assignments would even get a chance to see them.
257+ """
258+ if not ruleset .analysis_preset_id :
259+ return
260+
261+ assignments = ProductTriageRuleset .objects .filter (ruleset = ruleset ).select_related ("product" )
262+ for assignment in assignments :
263+ vulnerability_ids = list (
264+ TriageRecord .objects .filter (
265+ ruleset = ruleset , product = assignment .product
266+ ).values_list ("vulnerability_id" , flat = True )
267+ )
268+ if vulnerability_ids :
269+ delete_preset_analyses_for_product (
270+ preset_id = ruleset .analysis_preset_id ,
271+ product = assignment .product ,
272+ vulnerability_ids = vulnerability_ids ,
273+ )
274+
275+
231276def reevaluate_product_rulesets (product , apply_preset = True ):
232277 """Re-evaluate all enabled triage rulesets currently assigned to the product."""
233278 assignments = ProductTriageRuleset .objects .filter (
0 commit comments