Skip to main content

mempill_types/
claim.rs

1//! Claim and associated value objects: the atomic committed assertion.
2//!
3//! A `Claim` is write-once — private fields with a constructor and read-only getters only.
4//! This enforces at compile time that claims are immutable after commitment (non-destruction
5//! invariant: writes are INSERT-only, provenance is immutable).
6
7use crate::identity::{AgentId, ClaimRef};
8use crate::provenance::{ExternalAnchor, ProvenanceLabel};
9use crate::time::{TransactionTime, ValidTime};
10
11/// The atomic asserted statement: a (subject, predicate, value) triple.
12#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
13pub struct Fact {
14    /// The entity being described (e.g. `"user"`, `"acme:ceo"`).
15    pub subject: String,
16    /// The aspect being asserted (e.g. `"city"`, `"held_by"`).
17    pub predicate: String,
18    /// The asserted value as a JSON value.
19    pub value: serde_json::Value,
20}
21
22/// Cardinality of the (subject, predicate) subject-line — always a caller proposal; the gate decides.
23///
24/// `Unknown` is the default and routes to the non-destructive branch. The engine treats the
25/// caller's cardinality hint as advisory: if evidence is insufficient, it surfaces to the oracle.
26#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
27pub enum Cardinality {
28    /// At most one value is valid at a time. Bounding a new value supersedes prior.
29    Functional,
30    /// Multiple simultaneous values are valid. Bounding requires explicit negative assertion.
31    SetValued,
32    /// Default. Routes to non-destructive path + surfaces to oracle.
33    #[default]
34    Unknown,
35}
36
37/// Two separate confidence scores: one for the value itself, one for the valid-time extraction.
38///
39/// Note: `Eq` is intentionally not implemented because the inner fields are `f32`,
40/// which does not satisfy the `Eq` contract (`NaN != NaN`). Use `PartialEq` comparisons
41/// with an appropriate epsilon if you need approximate equality in tests.
42#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
43pub struct Confidence {
44    /// Confidence in the value itself (0.0–1.0).
45    pub value_confidence: f32,
46    /// Confidence in the valid-time extraction (0.0–1.0). May be 0.0 = "unknown".
47    pub valid_time_confidence: f32,
48}
49
50/// Criticality class — reflects the importance of the claim, distinct from its freshness (currency).
51#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
52pub enum Criticality {
53    /// Low importance. May be evicted or down-weighted under pressure.
54    Low,
55    /// Standard importance. Default for most claims.
56    Medium,
57    /// High importance. Errors should be surfaced promptly.
58    High,
59    /// Safety-relevant (e.g., allergy, medication).
60    Critical,
61}
62
63/// A committed claim — write-once and immutable after it is appended to the store.
64///
65/// All fields are set at injection time via `Claim::new`; no field may be mutated after commit.
66/// Fields are private to enforce the write-once invariant at compile time:
67/// non-destruction (all writes are INSERT-only) and provenance immutability are both
68/// upheld by the absence of setters.
69#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
70pub struct Claim {
71    claim_ref: ClaimRef,
72    agent_id: AgentId,
73    fact: Fact,
74    cardinality: Cardinality,
75    provenance: ProvenanceLabel,
76    external_anchor: ExternalAnchor,
77    transaction_time: TransactionTime,
78    valid_time: ValidTime,
79    confidence: Confidence,
80    criticality: Criticality,
81    derived_from: Vec<ClaimRef>,
82    metadata: Option<serde_json::Value>,
83    snapshot_schema_version: Option<u32>,
84}
85
86impl Claim {
87    /// Construct a fully-formed, frozen Claim. The only constructor.
88    #[allow(clippy::too_many_arguments)]
89    pub fn new(
90        claim_ref: ClaimRef,
91        agent_id: AgentId,
92        fact: Fact,
93        cardinality: Cardinality,
94        provenance: ProvenanceLabel,
95        external_anchor: ExternalAnchor,
96        transaction_time: TransactionTime,
97        valid_time: ValidTime,
98        confidence: Confidence,
99        criticality: Criticality,
100        derived_from: Vec<ClaimRef>,
101        metadata: Option<serde_json::Value>,
102        snapshot_schema_version: Option<u32>,
103    ) -> Self {
104        Self {
105            claim_ref,
106            agent_id,
107            fact,
108            cardinality,
109            provenance,
110            external_anchor,
111            transaction_time,
112            valid_time,
113            confidence,
114            criticality,
115            derived_from,
116            metadata,
117            snapshot_schema_version,
118        }
119    }
120
121    // ── Getters (read-only; no setters by design) ─────────────────────────────
122
123    /// Return the stable claim reference (UUID).
124    pub fn claim_ref(&self) -> &ClaimRef { &self.claim_ref }
125    /// Return the agent that owns this claim.
126    pub fn agent_id(&self) -> &AgentId { &self.agent_id }
127    /// Return the (subject, predicate, value) triple.
128    pub fn fact(&self) -> &Fact { &self.fact }
129    /// Return the caller-supplied cardinality hint.
130    pub fn cardinality(&self) -> &Cardinality { &self.cardinality }
131    /// Return the provenance label.
132    pub fn provenance(&self) -> &ProvenanceLabel { &self.provenance }
133    /// Return the external anchor (source document, URL, etc.).
134    pub fn external_anchor(&self) -> &ExternalAnchor { &self.external_anchor }
135    /// Return the transaction time (when the claim was written to the store).
136    pub fn transaction_time(&self) -> &TransactionTime { &self.transaction_time }
137    /// Return the valid-time window (when the claim holds in the world).
138    pub fn valid_time(&self) -> &ValidTime { &self.valid_time }
139    /// Return the dual confidence scores.
140    pub fn confidence(&self) -> &Confidence { &self.confidence }
141    /// Return the criticality class.
142    pub fn criticality(&self) -> &Criticality { &self.criticality }
143    /// Return the list of upstream claims this claim was derived from.
144    pub fn derived_from(&self) -> &[ClaimRef] { &self.derived_from }
145    /// Return the optional caller-supplied metadata blob.
146    pub fn metadata(&self) -> Option<&serde_json::Value> { self.metadata.as_ref() }
147    /// Return the optional snapshot schema version for migration support.
148    pub fn snapshot_schema_version(&self) -> Option<u32> { self.snapshot_schema_version }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::identity::AgentId;
155    use crate::provenance::{ExternalAnchor, ProvenanceLabel};
156    use crate::time::{TransactionTime, ValidTime};
157    use chrono::Utc;
158
159    fn make_claim() -> Claim {
160        Claim::new(
161            ClaimRef::new_random(),
162            AgentId("agent-42".into()),
163            Fact {
164                subject: "user".into(),
165                predicate: "email".into(),
166                value: serde_json::json!("alice@example.com"),
167            },
168            Cardinality::Functional,
169            ProvenanceLabel::ModelDerived,
170            ExternalAnchor { nearest_external_anchor: None, derivation_depth: 0 },
171            TransactionTime(Utc::now()),
172            ValidTime { start: None, end: None, valid_time_confidence: 0.0 },
173            Confidence { value_confidence: 0.9, valid_time_confidence: 0.0 },
174            Criticality::Low,
175            vec![],
176            None,
177            None,
178        )
179    }
180
181    #[test]
182    fn claim_constructed_and_readable() {
183        let c = make_claim();
184        assert_eq!(c.agent_id(), &AgentId("agent-42".into()));
185        assert_eq!(c.fact().subject, "user");
186        assert_eq!(c.cardinality(), &Cardinality::Functional);
187    }
188
189    #[test]
190    fn claim_is_immutable_no_setters() {
191        // This test is a compile-time proof: if you can build it, there are no setters.
192        let c = make_claim();
193        // We can only read:
194        let _ = c.claim_ref();
195        let _ = c.provenance();
196    }
197
198    #[test]
199    fn cardinality_unknown_is_default() {
200        let c: Cardinality = Default::default();
201        assert_eq!(c, Cardinality::Unknown);
202    }
203
204    #[test]
205    fn claim_round_trip_serde() {
206        let c = make_claim();
207        let json = serde_json::to_string(&c).unwrap();
208        let back: Claim = serde_json::from_str(&json).unwrap();
209        assert_eq!(c.claim_ref(), back.claim_ref());
210        assert_eq!(c.agent_id(), back.agent_id());
211        assert_eq!(c.fact(), back.fact());
212    }
213
214    #[test]
215    fn criticality_ordering() {
216        assert!(Criticality::Low < Criticality::Medium);
217        assert!(Criticality::Medium < Criticality::High);
218        assert!(Criticality::High < Criticality::Critical);
219    }
220}