1use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Default)]
7pub struct HmsConfig {
8 pub text_encoder: super::encoding::TextEncoder,
9 pub ivf: IVFConfig,
10 pub nsg: NSGConfig,
11 pub shard: ShardConfig,
12 pub query: QueryConfig,
13 pub concepts: ConceptsConfig,
14 pub diffusion: DiffusionConfig,
15 pub security: SecurityConfig,
16 pub provenance: ProvenanceConfig,
17 pub privacy: PrivacyConfig,
18 pub meaning: MeaningConfig,
19 pub cognition: CognitionConfig,
20 pub hopfield: super::hopfield::HopfieldConfig,
21 pub embedding_space: Option<super::schema::EmbeddingSpace>,
22 #[cfg(feature = "local-embed")]
25 pub local_embed: Option<super::embed::EmbedOptions>,
26}
27
28#[derive(Clone, Debug)]
29pub struct MeaningConfig {
30 pub enabled: bool,
31 pub beta: f64,
32 pub algebraic_max_fanout: usize,
33 pub auto_decompose: bool,
34 pub max_hop_depth: usize,
35 pub max_rule_depth: usize,
36 pub idf_clip_factor: f64,
37}
38
39impl Default for MeaningConfig {
40 fn default() -> Self {
41 Self {
42 enabled: false,
43 beta: 24.0,
44 algebraic_max_fanout: 40,
45 auto_decompose: false,
46 max_hop_depth: 10,
47 max_rule_depth: 10,
48 idf_clip_factor: 3.0,
49 }
50 }
51}
52
53#[derive(Clone, Debug, Default)]
54pub struct SecurityConfig {
55 pub signing_enabled: bool,
56 pub key_path: Option<String>,
57 pub encryption_enabled: bool,
58 pub encryption_passphrase: Option<String>,
59 pub encryption_passphrase_env: Option<String>,
62 pub audit_enabled: bool,
63}
64
65#[derive(Clone, Debug, Default)]
66pub struct ProvenanceConfig {
67 pub enabled: bool,
68 pub key_path: Option<String>,
69 pub auto_sign: bool,
70 #[cfg(feature = "provenance-scitt")]
71 pub scitt_endpoint: Option<String>,
72}
73
74#[derive(Clone, Debug)]
75pub struct PrivacyConfig {
76 pub dp_enabled: bool,
78 pub epsilon: f64,
81}
82
83impl Default for PrivacyConfig {
84 fn default() -> Self {
85 Self {
86 dp_enabled: false,
87 epsilon: 1.0,
88 }
89 }
90}
91
92#[derive(Clone, Debug)]
93pub struct QueryConfig {
94 pub component_similarity_threshold: f64,
95 pub component_max_neighbors: u32,
96}
97
98impl Default for QueryConfig {
99 fn default() -> Self {
100 Self {
101 component_similarity_threshold: 0.05,
102 component_max_neighbors: 20,
103 }
104 }
105}
106
107#[derive(Clone, Debug)]
108pub struct ConceptsConfig {
109 pub similarity_threshold: f64,
110 pub min_cluster_size: usize,
111}
112
113impl Default for ConceptsConfig {
114 fn default() -> Self {
115 Self {
116 similarity_threshold: 0.3,
117 min_cluster_size: 3,
118 }
119 }
120}
121
122#[derive(Clone, Debug)]
123pub struct DiffusionConfig {
124 pub steps: usize,
125 pub sigma_max: f64,
126 pub sigma_min: f64,
127 pub step_size: f64,
128 pub n_langevin: usize,
129}
130
131impl Default for DiffusionConfig {
132 fn default() -> Self {
133 Self {
134 steps: 10,
135 sigma_max: 0.5,
136 sigma_min: 0.01,
137 step_size: 0.1,
138 n_langevin: 5,
139 }
140 }
141}
142
143#[derive(Clone, Debug)]
144pub struct ShardConfig {
145 pub enabled: bool,
146 pub shard_count: usize,
147 pub auto_threshold: usize,
148 pub target_shard_size: usize,
149}
150
151impl Default for ShardConfig {
152 fn default() -> Self {
153 Self {
154 enabled: false,
155 shard_count: 0,
156 auto_threshold: 1_000_000,
157 target_shard_size: 250_000,
158 }
159 }
160}
161
162#[derive(Clone, Debug, Serialize, Deserialize)]
163pub struct NSGConfig {
164 pub max_degree: usize,
165 pub ef_construction: usize,
166 pub auto_threshold: usize,
167 pub seed: u64,
168}
169
170impl Default for NSGConfig {
171 fn default() -> Self {
172 Self {
173 max_degree: 32,
174 ef_construction: 128,
175 auto_threshold: 10_000,
176 seed: 42,
177 }
178 }
179}
180
181#[derive(Clone, Debug)]
182pub struct IVFConfig {
183 pub enabled: bool,
185 pub n_clusters: usize,
186 pub n_landmarks: usize,
187 pub d_reduced: usize,
188 pub n_probe: usize,
189 pub auto_threshold: usize,
190}
191
192impl Default for IVFConfig {
193 fn default() -> Self {
194 Self {
195 enabled: false,
196 n_clusters: 256,
197 n_landmarks: 1024,
198 d_reduced: 128,
199 n_probe: 8,
200 auto_threshold: 10_000,
201 }
202 }
203}
204
205#[derive(Clone, Debug)]
206pub struct CognitionConfig {
207 pub enabled: bool,
208 pub interval_secs: u64,
209 pub min_pattern_freq: usize,
210 pub min_abstraction_members: usize,
211 pub min_shared_relations: usize,
212 pub min_peer_coverage: f64,
213 pub hypothesis_beta: f64,
214 pub min_hypothesis_confidence: f64,
215 pub min_analogy_relations: usize,
216 pub governor_duplicate_threshold: f64,
217 pub governor_max_scan_size: usize,
218 pub governor_forget_unreferenced: bool,
219 pub refine_atoms: bool,
220}
221
222impl Default for CognitionConfig {
223 fn default() -> Self {
224 Self {
225 enabled: false,
226 interval_secs: 60,
227 min_pattern_freq: 3,
228 min_abstraction_members: 3,
229 min_shared_relations: 2,
230 min_peer_coverage: 0.5,
231 hypothesis_beta: 24.0,
232 min_hypothesis_confidence: 0.3,
233 min_analogy_relations: 2,
234 governor_duplicate_threshold: 0.95,
235 governor_max_scan_size: 1000,
236 governor_forget_unreferenced: false,
237 refine_atoms: false,
238 }
239 }
240}
241
242impl HmsConfig {
243 pub(crate) fn validate(&self) -> anyhow::Result<()> {
244 anyhow::ensure!(
245 self.text_encoder != super::encoding::TextEncoder::MultiscaleCappedUnionV1
246 || cfg!(feature = "experimental"),
247 "capped-union text encoding requires a build with the experimental feature"
248 );
249 anyhow::ensure!(
250 self.text_encoder != super::encoding::TextEncoder::LocalEmbedV1
251 || cfg!(feature = "local-embed"),
252 "the local-embed-v1 text encoder requires a build with the local-embed feature"
253 );
254 anyhow::ensure!(
255 cfg!(feature = "security")
256 || !(self.security.signing_enabled || self.security.encryption_enabled),
257 "signing/encryption requires a build with the security feature"
258 );
259 anyhow::ensure!(
260 cfg!(feature = "provenance") || !self.provenance.enabled,
261 "provenance requires a build with the provenance feature"
262 );
263 anyhow::ensure!(
264 self.privacy.epsilon.is_finite() && self.privacy.epsilon > 0.0,
265 "privacy epsilon must be finite and positive"
266 );
267 anyhow::ensure!(
268 self.shard.target_shard_size > 0 && self.shard.shard_count <= 1024,
269 "invalid shard configuration"
270 );
271 anyhow::ensure!(
272 self.meaning.beta.is_finite() && self.meaning.beta > 0.0,
273 "meaning beta must be finite and positive"
274 );
275 if let Some(space) = &self.embedding_space {
276 anyhow::ensure!(!space.model.is_empty() && !space.revision.is_empty() && (1..=65536).contains(&space.dimensions)
277 && space.metric == "cosine" && space.normalization == "l2",
278 "embedding space requires model, revision, dimensions 1..=65536, normalization l2 and metric cosine");
279 }
280 Ok(())
281 }
282}