Skip to main content

nodedb_crdt/validator/
core.rs

1// SPDX-License-Identifier: BUSL-1.1
2
3//! Validator struct, constructors, and accessors.
4
5use std::collections::{HashMap, HashSet};
6
7use crate::constraint::ConstraintSet;
8use crate::dead_letter::DeadLetterQueue;
9use crate::deferred::DeferredQueue;
10use crate::policy::PolicyRegistry;
11use crate::signing::DeltaSigner;
12
13/// The constraint validator.
14///
15/// Validates proposed changes against a set of constraints and the current
16/// committed state. Violations are resolved via declarative policies:
17/// - AUTO_RESOLVED — policy handles it (e.g., LAST_WRITER_WINS)
18/// - DEFERRED — queued for exponential backoff retry (CASCADE_DEFER)
19/// - WEBHOOK_REQUIRED — caller must POST to webhook for decision
20/// - ESCALATE — route to dead-letter queue (fallback)
21pub struct Validator {
22    pub(super) constraints: ConstraintSet,
23    pub(super) dlq: DeadLetterQueue,
24    pub(super) policies: PolicyRegistry,
25    pub(super) deferred: DeferredQueue,
26    /// Monotonic suffix counter: (collection, field) -> next suffix number
27    pub(super) suffix_counter: HashMap<(String, String), u64>,
28    /// Optional delta signature verifier. When set, signed deltas are
29    /// verified before constraint validation.
30    pub(super) delta_verifier: Option<DeltaSigner>,
31    /// Collections known to be bitemporal. UNIQUE checks for rows in these
32    /// collections scope to currently-live rows (open `_ts_valid_until`)
33    /// so superseded versions don't falsely collide with live writes.
34    pub(super) bitemporal_collections: HashSet<String>,
35}
36
37impl Validator {
38    /// Create a new validator with default (ephemeral) policies.
39    pub fn new(constraints: ConstraintSet, dlq_capacity: usize) -> Self {
40        Self::new_with_policies(constraints, dlq_capacity, PolicyRegistry::new(), 1000)
41    }
42
43    /// Create a new validator with custom policies and deferred queue.
44    pub fn new_with_policies(
45        constraints: ConstraintSet,
46        dlq_capacity: usize,
47        policies: PolicyRegistry,
48        deferred_capacity: usize,
49    ) -> Self {
50        Self {
51            constraints,
52            dlq: DeadLetterQueue::new(dlq_capacity),
53            policies,
54            deferred: DeferredQueue::new(deferred_capacity),
55            suffix_counter: HashMap::new(),
56            delta_verifier: None,
57            bitemporal_collections: HashSet::new(),
58        }
59    }
60
61    /// Register a collection as bitemporal. UNIQUE constraints for rows in
62    /// this collection will scope to currently-live rows only.
63    pub fn mark_bitemporal(&mut self, collection: impl Into<String>) {
64        self.bitemporal_collections.insert(collection.into());
65    }
66
67    /// Is the given collection registered as bitemporal?
68    pub fn is_bitemporal(&self, collection: &str) -> bool {
69        self.bitemporal_collections.contains(collection)
70    }
71
72    /// Access the dead-letter queue.
73    pub fn dlq(&self) -> &DeadLetterQueue {
74        &self.dlq
75    }
76
77    /// Mutable access to the DLQ (for dequeue/retry).
78    pub fn dlq_mut(&mut self) -> &mut DeadLetterQueue {
79        &mut self.dlq
80    }
81
82    /// Access the policy registry.
83    pub fn policies(&self) -> &PolicyRegistry {
84        &self.policies
85    }
86
87    /// Mutable access to the policy registry.
88    pub fn policies_mut(&mut self) -> &mut PolicyRegistry {
89        &mut self.policies
90    }
91
92    /// Access the deferred queue.
93    pub fn deferred(&self) -> &DeferredQueue {
94        &self.deferred
95    }
96
97    /// Mutable access to the deferred queue.
98    pub fn deferred_mut(&mut self) -> &mut DeferredQueue {
99        &mut self.deferred
100    }
101
102    /// Set the delta signature verifier. When set, deltas with non-zero
103    /// signatures in their CrdtAuthContext will be verified before validation.
104    pub fn set_delta_verifier(&mut self, verifier: DeltaSigner) {
105        self.delta_verifier = Some(verifier);
106    }
107
108    /// Access the delta verifier.
109    pub fn delta_verifier(&self) -> Option<&DeltaSigner> {
110        self.delta_verifier.as_ref()
111    }
112
113    /// Mutable access to the delta verifier.
114    pub fn delta_verifier_mut(&mut self) -> Option<&mut DeltaSigner> {
115        self.delta_verifier.as_mut()
116    }
117}