Skip to main content

made_core/value_objects/
evidence_grounding_rule.rs

1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::DomainError;
6use crate::value_objects::SemanticSupportRule;
7
8use super::output_contract_validation::{
9    validate_text, MAX_ALLOWED_EVIDENCE_REFS, MAX_ALLOWED_VALUE_LEN, MAX_FIELD_NAME_LEN,
10};
11
12/// Field shape and evidence pack required to ground structured claims.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct EvidenceGroundingRule {
15    claims_field: String,
16    refs_field: String,
17    allowed_refs: BTreeSet<String>,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    semantic_support: Option<SemanticSupportRule>,
20}
21
22impl EvidenceGroundingRule {
23    pub fn new(
24        claims_field: impl Into<String>,
25        refs_field: impl Into<String>,
26        allowed_refs: impl IntoIterator<Item = impl Into<String>>,
27    ) -> Result<Self, DomainError> {
28        let claims_field = validate_text(
29            &claims_field.into(),
30            "output_contract.evidence.claims_field",
31            MAX_FIELD_NAME_LEN,
32        )?;
33        let refs_field = validate_text(
34            &refs_field.into(),
35            "output_contract.evidence.refs_field",
36            MAX_FIELD_NAME_LEN,
37        )?;
38        let allowed_refs = allowed_refs
39            .into_iter()
40            .map(|reference| {
41                validate_text(
42                    &reference.into(),
43                    "output_contract.evidence.allowed_ref",
44                    MAX_ALLOWED_VALUE_LEN,
45                )
46            })
47            .collect::<Result<BTreeSet<_>, _>>()?;
48        if allowed_refs.is_empty() {
49            return Err(DomainError::EmptyField {
50                field: "output_contract.evidence.allowed_refs",
51            });
52        }
53        if allowed_refs.len() > MAX_ALLOWED_EVIDENCE_REFS {
54            return Err(DomainError::OutOfRange {
55                field: "output_contract.evidence.allowed_refs",
56                value: allowed_refs.len() as f64,
57                min: 1.0,
58                max: MAX_ALLOWED_EVIDENCE_REFS as f64,
59            });
60        }
61        Ok(Self {
62            claims_field,
63            refs_field,
64            allowed_refs,
65            semantic_support: None,
66        })
67    }
68
69    pub fn with_semantic_support(mut self, rule: SemanticSupportRule) -> Result<Self, DomainError> {
70        if self
71            .allowed_refs
72            .iter()
73            .any(|reference| !rule.bodies.contains_key(reference))
74        {
75            return Err(DomainError::EmptyField {
76                field: "output_contract.evidence.semantic_support.bodies",
77            });
78        }
79        self.semantic_support = Some(rule);
80        Ok(self)
81    }
82
83    #[must_use]
84    pub fn claims_field(&self) -> &str {
85        &self.claims_field
86    }
87
88    #[must_use]
89    pub fn refs_field(&self) -> &str {
90        &self.refs_field
91    }
92
93    #[must_use]
94    pub fn allowed_refs(&self) -> &BTreeSet<String> {
95        &self.allowed_refs
96    }
97
98    #[must_use]
99    pub fn semantic_support(&self) -> Option<&SemanticSupportRule> {
100        self.semantic_support.as_ref()
101    }
102}