xbp_analysis/application/rules/
non_idempotent_retry.rs1use super::{base_caps, concepts_of, finding_from_concept};
2use crate::domain::concepts::ConceptKind;
3use crate::domain::model::LanguageModel;
4use crate::domain::rule::{Rule, RuleMeta};
5use crate::domain::types::{Confidence, Finding, Severity};
6use std::sync::OnceLock;
7
8#[derive(Default)]
9pub struct NonIdempotentRetryRule;
10
11impl Rule for NonIdempotentRetryRule {
12 fn meta(&self) -> &RuleMeta {
13 static META: OnceLock<RuleMeta> = OnceLock::new();
14 META.get_or_init(|| RuleMeta {
15 id: "non-idempotent-retry".into(),
16 name: "Non-idempotent retry".into(),
17 description: "Retry of an operation that may not be safe to repeat.".into(),
18 default_severity: Severity::High,
19 required_capabilities: base_caps(),
20 autofix_safe: false,
21 })
22 }
23
24 fn evaluate(&self, model: &LanguageModel) -> Vec<Finding> {
25 concepts_of(model, ConceptKind::Retry)
26 .filter(|(_, c)| c.idempotent == Some(false))
27 .map(|(lang, c)| {
28 finding_from_concept(
29 &self.meta().id,
30 lang,
31 c,
32 Severity::High,
33 Confidence::Low,
34 "Retry may re-execute a non-idempotent side effect.",
35 "Duplicate charges, double writes, or split-brain updates.",
36 "Make the operation idempotent or use a single-flight / ledger guard.",
37 )
38 })
39 .collect()
40 }
41}