1use std::collections::{BTreeMap, BTreeSet};
5
6use anyhow::{ensure, Context, Result};
7
8use super::{Mutation, WarmCore};
9use crate::core::atom_memory::AtomMemory;
10use crate::core::cognition::governor::{
11 GovernanceConflict, GovernanceReport, GovernorConfig, MemoryGovernor,
12};
13use crate::core::composite_memory::CompositeMemory;
14use crate::core::entangled::EntangledHVec;
15use crate::core::query_limits::{QueryBudget, QueryLimits};
16use crate::core::triple_store::{TripleRecord, TripleStore};
17
18const MAX_OPERATIONS: usize = 4096;
19const MAX_PAYLOAD_BYTES: usize = 32 * 1024 * 1024;
20
21struct Snapshot {
22 revision: u64,
23 atoms: BTreeMap<String, EntangledHVec>,
24 composites: BTreeMap<String, EntangledHVec>,
25 triples: Vec<TripleRecord>,
26 mirrored: BTreeSet<String>,
27}
28
29struct Plan {
30 revision: u64,
31 mutations: Vec<Mutation>,
32 payload: Vec<u8>,
33 report: GovernanceReport,
34}
35
36fn same(left: &EntangledHVec, right: &EntangledHVec) -> bool {
37 left.dim == right.dim && left.indices() == right.indices()
38}
39
40fn append(
41 mutations: &mut Vec<Mutation>,
42 budget: &mut QueryBudget,
43 mutation: Mutation,
44) -> Result<()> {
45 ensure!(
46 mutations.len() < MAX_OPERATIONS,
47 "governance exceeds the 4096-operation atomic transaction limit"
48 );
49 budget.frontier(mutations.len() + 1)?;
50 mutations.push(mutation);
51 Ok(())
52}
53
54impl WarmCore {
55 pub fn govern_memory_with_limits(&self, limits: QueryLimits) -> Result<GovernanceReport> {
58 self.govern_with_hook(limits, || {})
59 }
60
61 fn govern_with_hook(
62 &self,
63 limits: QueryLimits,
64 after_plan: impl FnOnce(),
65 ) -> Result<GovernanceReport> {
66 if !self.meaning_enabled() {
67 return Ok(GovernanceReport::default());
68 }
69 ensure!(
70 self.schema >= crate::core::format::MEANING_MAINTENANCE_SCHEMA_VERSION,
71 "durable governance requires store schema 5; use warm-format upgrade to create an upgraded store"
72 );
73 let mut budget = QueryBudget::new(limits);
74 budget.reserve(self.dimensions.saturating_mul(96))?;
76 budget.work(self.dimensions.saturating_mul(12))?;
77 let snapshot = self.governance_snapshot(&mut budget)?;
78 let plan = self.governance_plan(snapshot, &mut budget)?;
79 after_plan();
80 ensure!(
81 self.commit_serialized_if_revision(&plan.mutations, &plan.payload, plan.revision)?,
82 GovernanceConflict
83 );
84 Ok(plan.report)
85 }
86
87 fn governance_snapshot(&self, budget: &mut QueryBudget) -> Result<Snapshot> {
88 let _transaction = self.mutation_gate.read();
89 let revision = self.revision();
90 let atoms = self
91 .atom_memory
92 .as_ref()
93 .context("meaning atom store is missing")?
94 .inner()
95 .bounded_snapshot(budget)?
96 .into_iter()
97 .collect();
98 let composites: BTreeMap<_, _> = self
99 .composite_memory
100 .as_ref()
101 .context("meaning composite store is missing")?
102 .inner()
103 .bounded_snapshot(budget)?
104 .into_iter()
105 .collect();
106 let triples = self
107 .triple_store
108 .as_ref()
109 .context("meaning triple store is missing")?
110 .bounded_snapshot(budget)?;
111 let mut mirrored = BTreeSet::new();
112 self.shards.read().try_for_each_shard(|shard| {
113 for (id, vector) in shard.vectors.read().iter() {
114 budget.work(1)?;
115 if composites
116 .get(id)
117 .is_some_and(|composite| same(composite, vector))
118 {
119 budget.reserve(id.len().saturating_mul(2).saturating_add(64))?;
120 mirrored.insert(id.clone());
121 }
122 }
123 Ok(())
124 })?;
125 Ok(Snapshot {
126 revision,
127 atoms,
128 composites,
129 triples,
130 mirrored,
131 })
132 }
133
134 fn governance_plan(&self, snapshot: Snapshot, budget: &mut QueryBudget) -> Result<Plan> {
135 let config = GovernorConfig {
136 duplicate_threshold: self.config.cognition.governor_duplicate_threshold,
137 max_scan_size: self.config.cognition.governor_max_scan_size,
138 forget_unreferenced_atoms: self.config.cognition.governor_forget_unreferenced,
139 refine_atoms: self.config.cognition.refine_atoms,
140 ..Default::default()
141 };
142 let scan = config.max_scan_size.min(snapshot.composites.len());
143 let owned: BTreeSet<_> = snapshot
144 .triples
145 .iter()
146 .map(|triple| triple.composite_id.as_str())
147 .collect();
148 let eligible = snapshot
149 .composites
150 .keys()
151 .take(scan)
152 .filter(|id| !owned.contains(id.as_str()))
153 .count();
154 let composite_width = snapshot
155 .composites
156 .values()
157 .take(scan)
158 .map(|vector| vector.indices().len())
159 .max()
160 .unwrap_or(0)
161 .max(1);
162 let activations = snapshot
163 .atoms
164 .values()
165 .chain(snapshot.composites.values())
166 .fold(0usize, |total, vector| {
167 total.saturating_add(vector.indices().len())
168 });
169 let refinement = if config.refine_atoms {
170 let width = snapshot
171 .atoms
172 .values()
173 .map(|vector| vector.indices().len())
174 .max()
175 .unwrap_or(0)
176 .max((self.dimensions / 256).max(1));
177 snapshot
178 .atoms
179 .len()
180 .saturating_mul(snapshot.triples.len())
181 .saturating_mul(width)
182 .saturating_mul(3)
183 } else {
184 0
185 };
186 budget.work(
187 eligible
188 .saturating_mul(scan)
189 .saturating_mul(composite_width)
190 .saturating_mul(3)
191 .saturating_add(activations.saturating_mul(4))
192 .saturating_add(snapshot.atoms.len())
193 .saturating_add(snapshot.composites.len())
194 .saturating_add(snapshot.triples.len().saturating_mul(4))
195 .saturating_add(refinement),
196 )?;
197 let atoms = AtomMemory::new(self.dimensions, self.config.meaning.idf_clip_factor);
198 let composites = CompositeMemory::new(self.dimensions, self.config.meaning.idf_clip_factor);
199 let triples = TripleStore::new();
200 for (id, vector) in &snapshot.atoms {
201 atoms.insert_with_vec(id, vector);
202 }
203 for (id, vector) in &snapshot.composites {
204 composites.insert(id.clone(), vector.clone());
205 }
206 for triple in &snapshot.triples {
207 ensure!(
208 snapshot.composites.contains_key(&triple.composite_id),
209 "governance found a live triple without its composite; repair the store first"
210 );
211 triples.load_triple(triple.clone());
212 }
213 let report = MemoryGovernor::govern(&atoms, &composites, &triples, &config);
214 let mut mutations = Vec::new();
215 let mut changed = BTreeSet::new();
216 for (id, old) in &snapshot.atoms {
217 match atoms.get(id) {
218 None => append(
219 &mut mutations,
220 budget,
221 Mutation::DeleteAtom { id: id.clone() },
222 )?,
223 Some(vector) if !same(old, &vector) => {
224 changed.insert(id.as_str());
225 append(
226 &mut mutations,
227 budget,
228 Mutation::Atom {
229 id: id.clone(),
230 vector,
231 },
232 )?;
233 }
234 Some(_) => {}
235 }
236 }
237 for id in snapshot.composites.keys() {
238 if composites.inner().get(id).is_none() {
239 ensure!(
240 !triples.has_composite(id),
241 "governance cannot discard an owned composite"
242 );
243 append(
244 &mut mutations,
245 budget,
246 Mutation::DeleteComposite { id: id.clone() },
247 )?;
248 }
249 }
250 let mut owners: BTreeMap<&str, Vec<&TripleRecord>> = BTreeMap::new();
251 for triple in &snapshot.triples {
252 owners.entry(&triple.composite_id).or_default().push(triple);
253 }
254 let roles = self
255 .role_registry
256 .as_ref()
257 .context("meaning roles are missing")?;
258 for (id, owners) in owners {
259 if !owners.iter().any(|owner| {
260 [&owner.subject_id, &owner.relation_id, &owner.object_id]
261 .iter()
262 .any(|atom| changed.contains(atom.as_str()))
263 }) {
264 continue;
265 }
266 ensure!(
267 owners.len() == 1 && snapshot.mirrored.contains(id),
268 "governance cannot refine an ambiguous composite/vector owner"
269 );
270 budget.work(1)?;
271 let owner = owners[0];
272 let subject = atoms
273 .get(&owner.subject_id)
274 .context("refinement lost a referenced subject")?;
275 let relation = atoms
276 .get(&owner.relation_id)
277 .context("refinement lost a referenced relation")?;
278 let object = atoms
279 .get(&owner.object_id)
280 .context("refinement lost a referenced object")?;
281 let vector = roles.compose_triple(&subject, &relation, &object);
282 append(
283 &mut mutations,
284 budget,
285 Mutation::Composite {
286 id: id.to_owned(),
287 vector,
288 },
289 )?;
290 }
291 append(&mut mutations, budget, Mutation::RefreshMeaningIndices {})?;
292 let json_bytes = crate::core::documents::serialized_size(&mutations, MAX_PAYLOAD_BYTES)
293 .context("governance transaction exceeds 32 MiB")?;
294 budget.reserve(
295 json_bytes
296 .saturating_add(mutations.len().saturating_mul(8))
297 .saturating_add(64),
298 )?;
299 let payload = self.transaction_payload(&mutations)?;
300 ensure!(
301 payload.len() <= MAX_PAYLOAD_BYTES,
302 "governance transaction exceeds 32 MiB"
303 );
304 Ok(Plan {
305 revision: snapshot.revision,
306 mutations,
307 payload,
308 report,
309 })
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::core::{entangled::EntangledHVec, WarmConfig};
317
318 fn config() -> WarmConfig {
319 let mut config = WarmConfig::default();
320 config.meaning.enabled = true;
321 config.cognition.governor_forget_unreferenced = true;
322 config
323 }
324
325 #[test]
326 fn governance_keeps_distinct_live_triple_owners() {
327 let directory = tempfile::tempdir().unwrap();
328 let core = WarmCore::new(
329 16384,
330 Some(directory.path().display().to_string()),
331 Some(config()),
332 )
333 .unwrap();
334 for index in 0..512 {
335 core.memorize_triplet(
336 format!("triple-{index:03}"),
337 "alice".into(),
338 "knows".into(),
339 "bob".into(),
340 )
341 .unwrap();
342 }
343 core.govern_memory().unwrap();
344 assert_eq!(core.meaning_composite_count(), 512);
345 assert_eq!(core.meaning_triple_count(), 512);
346 for triple in core.triple_store.as_ref().unwrap().snapshot() {
347 assert!(core
348 .composite_memory
349 .as_ref()
350 .unwrap()
351 .inner()
352 .get(&triple.composite_id)
353 .is_some());
354 }
355 }
356
357 #[test]
358 fn governance_atom_alias_deletion_is_durable_without_deleting_the_raw_vector() {
359 let directory = tempfile::tempdir().unwrap();
360 let path = directory.path().display().to_string();
361 let core = WarmCore::new(1024, Some(path.clone()), Some(config())).unwrap();
362 let vector = EntangledHVec::from_indices(vec![7, 9], 1024);
363 core.memorize("alias".into(), vector.clone()).unwrap();
364 core.govern_memory().unwrap();
365 assert!(core.atom_memory.as_ref().unwrap().get("alias").is_none());
366 assert_eq!(
367 core.shards.read().get_vector("alias").unwrap().indices(),
368 vector.indices()
369 );
370 drop(core);
371 let core = WarmCore::new(1024, Some(path.clone()), Some(config())).unwrap();
372 assert!(core.atom_memory.as_ref().unwrap().get("alias").is_none());
373 assert_eq!(
374 core.shards.read().get_vector("alias").unwrap().indices(),
375 vector.indices()
376 );
377 core.compact().unwrap();
378 drop(core);
379 let core = WarmCore::new(1024, Some(path), Some(config())).unwrap();
380 assert!(core.atom_memory.as_ref().unwrap().get("alias").is_none());
381 assert_eq!(
382 core.shards.read().get_vector("alias").unwrap().indices(),
383 vector.indices()
384 );
385 }
386
387 fn fingerprint(core: &WarmCore) -> serde_json::Value {
388 let entries = |memory: &crate::core::indexed_memory::IndexedMemory| {
389 let mut entries: Vec<_> = memory
390 .all_vectors()
391 .into_iter()
392 .map(|(_, id, vector)| (id, vector.indices().to_vec()))
393 .collect();
394 entries.sort_unstable();
395 entries
396 };
397 let mut vectors: Vec<_> = core
398 .shards
399 .read()
400 .collect_all_patterns()
401 .into_iter()
402 .map(|(id, vector)| (id, vector.indices().to_vec()))
403 .collect();
404 vectors.sort_unstable();
405 let mut triples: Vec<_> = core
406 .triple_store
407 .as_ref()
408 .unwrap()
409 .snapshot()
410 .into_iter()
411 .map(|t| (t.subject_id, t.relation_id, t.object_id, t.composite_id))
412 .collect();
413 triples.sort_unstable();
414 serde_json::json!({"atoms": entries(core.atom_memory.as_ref().unwrap().inner()), "composites": entries(core.composite_memory.as_ref().unwrap().inner()), "vectors": vectors, "triples": triples})
415 }
416
417 #[test]
418 fn governance_refinement_rebinds_owned_composites_and_survives_replay_and_compaction() {
419 let directory = tempfile::tempdir().unwrap();
420 let path = directory.path().display().to_string();
421 let mut config = config();
422 config.cognition.refine_atoms = true;
423 let core = WarmCore::new(16384, Some(path.clone()), Some(config.clone())).unwrap();
424 for (city, country) in [
425 ("paris", "france"),
426 ("berlin", "germany"),
427 ("tokyo", "japan"),
428 ] {
429 core.memorize_triplet(
430 format!("{city}:capital"),
431 city.into(),
432 "capital_of".into(),
433 country.into(),
434 )
435 .unwrap();
436 core.memorize_triplet(
437 format!("{city}:located"),
438 city.into(),
439 "located_in".into(),
440 "continent".into(),
441 )
442 .unwrap();
443 }
444 let before = fingerprint(&core);
445 let report = core.govern_memory().unwrap();
446 assert!(report.refinement.atoms_refined > 0);
447 let after = fingerprint(&core);
448 assert_ne!(before["atoms"], after["atoms"]);
449 for triple in core.triple_store.as_ref().unwrap().snapshot() {
450 let atoms = core.atom_memory.as_ref().unwrap();
451 let vector = core.role_registry.as_ref().unwrap().compose_triple(
452 &atoms.get(&triple.subject_id).unwrap(),
453 &atoms.get(&triple.relation_id).unwrap(),
454 &atoms.get(&triple.object_id).unwrap(),
455 );
456 assert!(same(
457 &vector,
458 &core
459 .composite_memory
460 .as_ref()
461 .unwrap()
462 .inner()
463 .get(&triple.composite_id)
464 .unwrap()
465 ));
466 assert!(same(
467 &vector,
468 &core.shards.read().get_vector(&triple.composite_id).unwrap()
469 ));
470 }
471 drop(core);
472 let core = WarmCore::new(16384, Some(path.clone()), Some(config.clone())).unwrap();
473 assert_eq!(fingerprint(&core), after);
474 core.compact().unwrap();
475 drop(core);
476 let core = WarmCore::new(16384, Some(path), Some(config)).unwrap();
477 assert_eq!(fingerprint(&core), after);
478 }
479
480 #[test]
481 fn governance_cas_rejects_a_concurrent_writer_without_publishing_the_plan() {
482 let directory = tempfile::tempdir().unwrap();
483 let core = std::sync::Arc::new(
484 WarmCore::new(
485 1024,
486 Some(directory.path().display().to_string()),
487 Some(config()),
488 )
489 .unwrap(),
490 );
491 core.memorize("alias".into(), EntangledHVec::from_indices(vec![1], 1024))
492 .unwrap();
493 let gate = std::sync::Arc::new(std::sync::Barrier::new(2));
494 let (writer_core, writer_gate) = (core.clone(), gate.clone());
495 let writer = std::thread::spawn(move || {
496 writer_gate.wait();
497 writer_core
498 .memorize("writer".into(), EntangledHVec::from_indices(vec![2], 1024))
499 .unwrap();
500 writer_gate.wait();
501 });
502 let error = core
503 .govern_with_hook(QueryLimits::default(), || {
504 gate.wait();
505 gate.wait();
506 })
507 .unwrap_err();
508 writer.join().unwrap();
509 assert!(error.downcast_ref::<GovernanceConflict>().is_some());
510 assert!(core.atom_memory.as_ref().unwrap().get("alias").is_some());
511 assert!(core.shards.read().get_vector("writer").is_some());
512 core.govern_memory().unwrap();
513 assert!(core.atom_memory.as_ref().unwrap().get("alias").is_none());
514 }
515
516 #[test]
517 fn governance_transaction_limit_is_atomic_at_4096_operations() {
518 for atoms in [4094, 4095, 4096] {
519 let directory = tempfile::tempdir().unwrap();
520 let core = WarmCore::new(
521 1024,
522 Some(directory.path().display().to_string()),
523 Some(config()),
524 )
525 .unwrap();
526 let mutations: Vec<_> = (0..atoms)
527 .map(|index| Mutation::Atom {
528 id: format!("a{index:04}"),
529 vector: EntangledHVec::from_indices(vec![1], 1024),
530 })
531 .collect();
532 core.commit(&mutations).unwrap();
533 let revision = core.revision();
534 let result = core.govern_memory();
535 if atoms == 4096 {
536 assert!(result.unwrap_err().to_string().contains("4096-operation"));
537 assert_eq!(core.meaning_atom_count(), atoms);
538 assert_eq!(core.revision(), revision);
539 } else {
540 result.unwrap();
541 assert_eq!(core.meaning_atom_count(), 0);
542 }
543 }
544 }
545
546 #[test]
547 fn governance_budget_errors_leave_authoritative_state_unchanged() {
548 let directory = tempfile::tempdir().unwrap();
549 let core = WarmCore::new(
550 1024,
551 Some(directory.path().display().to_string()),
552 Some(config()),
553 )
554 .unwrap();
555 core.memorize("alias".into(), EntangledHVec::from_indices(vec![1], 1024))
556 .unwrap();
557 let before = fingerprint(&core);
558 for limits in [
559 QueryLimits {
560 max_work: 0,
561 ..Default::default()
562 },
563 QueryLimits {
564 max_bytes: 0,
565 ..Default::default()
566 },
567 QueryLimits {
568 max_bytes: 4096,
569 ..Default::default()
570 },
571 ] {
572 assert!(core.govern_memory_with_limits(limits).is_err());
573 assert_eq!(fingerprint(&core), before);
574 }
575 }
576}