Skip to main content

aidens_memory_kit/
lib.rs

1//! Thin memory facade over Forge, bridge, semantic-memory, and knowledge-runtime.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use thiserror::Error;
6
7#[cfg(feature = "semantic-memory-integration")]
8pub mod semantic_adapter;
9
10#[cfg(feature = "semantic-memory-integration")]
11pub use semantic_adapter::SemanticMemoryAdapter;
12
13pub mod canonical_stack {
14    pub use forge_memory_bridge::{
15        transform_envelope_v3, BridgeError, ProjectionImportBatchV3,
16        PROJECTION_IMPORT_BATCH_V3_SCHEMA,
17    };
18    pub use knowledge_runtime::adapters::semantic_memory::SemanticMemoryAdapter;
19    pub use knowledge_runtime::{
20        KnowledgeRuntime, QueryTrace, RuntimeConfig, RuntimeError, RuntimeQueryProvenanceV1, Scope,
21    };
22    pub use semantic_memory::{
23        AddGraphEdgeParams, ExplainedSearchResponse, IntegrityReport,
24        MemoryConfig as CanonicalMemoryConfig, MemoryError as CanonicalMemoryError, MemoryStats,
25        MemoryStore as CanonicalMemoryStore, MockEmbedder, ProjectionImportResult, ReceiptMode,
26        ReconcileAction, SearchContext, SearchResponse, SearchResult, StoredGraphEdge,
27        VectorSearchReceiptV1, VerifyMode,
28    };
29    pub use semantic_memory_forge::{
30        EvidenceBundle, ExportClaim, ExportEnvelopeV2, ExportEnvelopeV3, ExportRecord,
31        ExportRecordV3, ForgeExportMeta, ForgeToolReceiptV2, EXPORT_ENVELOPE_V2_SCHEMA,
32        EXPORT_ENVELOPE_V3_SCHEMA,
33    };
34    pub use stack_ids::{ClaimId, ClaimVersionId, EntityId, EnvelopeId, ScopeKey, TraceCtx};
35
36    pub fn transform_forge_export(
37        envelope: &ExportEnvelopeV3,
38    ) -> Result<ProjectionImportBatchV3, BridgeError> {
39        transform_envelope_v3(envelope)
40    }
41
42    pub async fn import_projection_batch(
43        store: &CanonicalMemoryStore,
44        batch: &ProjectionImportBatchV3,
45    ) -> Result<ProjectionImportResult, CanonicalMemoryError> {
46        store.import_projection_batch(batch).await
47    }
48}
49
50pub use canonical_stack::{
51    CanonicalMemoryConfig, CanonicalMemoryError, CanonicalMemoryStore, ProjectionImportBatchV3,
52    ProjectionImportResult,
53};
54
55pub struct CanonicalMemoryAdapter {
56    store: canonical_stack::CanonicalMemoryStore,
57    runtime: canonical_stack::KnowledgeRuntime,
58}
59
60#[derive(Debug, Error)]
61pub enum MemoryProfileError {
62    #[error("invalid memory backend profile: {0}")]
63    InvalidProfile(String),
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct SemanticMemoryBackendProfileV1 {
68    pub schema: String,
69    pub owner: String,
70    pub authoritative_store: String,
71    pub derived_candidate_backend: String,
72    pub exact_f32_rerank_required: bool,
73    pub direct_provekv_dependency_allowed: bool,
74    pub recall_integration_allowed: bool,
75    pub missing_generation_behavior: String,
76}
77
78impl SemanticMemoryBackendProfileV1 {
79    pub const SCHEMA: &'static str = "AiDENsSemanticMemoryBackendProfileV1";
80
81    pub fn provekv_derived_candidate() -> Self {
82        Self {
83            schema: Self::SCHEMA.into(),
84            owner: "semantic-memory".into(),
85            authoritative_store: "semantic-memory sqlite f32 embeddings".into(),
86            derived_candidate_backend: "provekv_pool_candidate_then_exact_f32".into(),
87            exact_f32_rerank_required: true,
88            direct_provekv_dependency_allowed: false,
89            recall_integration_allowed: false,
90            missing_generation_behavior: "receipted_f32_fallback".into(),
91        }
92    }
93
94    pub fn validate(&self) -> Result<(), MemoryProfileError> {
95        if self.schema != Self::SCHEMA {
96            return Err(MemoryProfileError::InvalidProfile(
97                "unexpected semantic memory backend profile schema".into(),
98            ));
99        }
100        if self.owner != "semantic-memory" {
101            return Err(MemoryProfileError::InvalidProfile(
102                "semantic-memory must own canonical memory truth".into(),
103            ));
104        }
105        if !self.exact_f32_rerank_required {
106            return Err(MemoryProfileError::InvalidProfile(
107                "proveKV/poly-kv candidate profiles must require exact f32 rerank".into(),
108            ));
109        }
110        if self.direct_provekv_dependency_allowed || self.recall_integration_allowed {
111            return Err(MemoryProfileError::InvalidProfile(
112                "AiDENs profiles must not directly depend on proveKV or wire Recall".into(),
113            ));
114        }
115        Ok(())
116    }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct MemoryGroundingBackpointerV1 {
121    pub owner_crate: String,
122    pub artifact_type: String,
123    pub relationship: String,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub struct MemoryGroundingEvidenceV1 {
128    pub schema: String,
129    pub artifact_kind: String,
130    pub ownership: String,
131    pub support_tier: String,
132    pub semantic_status: String,
133    pub semantic_exactness: String,
134    pub proof_debt_status: String,
135    pub contradiction_status: String,
136    pub view_disclosure_status: String,
137    pub execution_contamination_status: String,
138    pub adapter_route: String,
139    pub memory_mode: String,
140    pub query: String,
141    pub result_count: usize,
142    pub import_record_count: usize,
143    pub trace_ctx: String,
144    pub canonical_backpointers: Vec<MemoryGroundingBackpointerV1>,
145    pub degradation: Vec<String>,
146    pub widening: Vec<String>,
147    pub local_truth_store: bool,
148    pub promotion_eligible: bool,
149    pub known_limits: Vec<String>,
150}
151
152impl MemoryGroundingEvidenceV1 {
153    pub const SCHEMA: &'static str = "AiDENsMemoryGroundingEvidenceV1";
154
155    pub fn canonical_seam(
156        memory_mode: impl Into<String>,
157        query: impl Into<String>,
158        result_count: usize,
159        import_record_count: usize,
160        trace_ctx: impl Into<String>,
161        degradation: Vec<String>,
162        widening: Vec<String>,
163    ) -> Self {
164        let semantic_status = if degradation.is_empty() && widening.is_empty() {
165            "exact_check"
166        } else {
167            "degraded_exact_check"
168        };
169        let semantic_exactness = if degradation.is_empty() && widening.is_empty() {
170            "exact"
171        } else {
172            "degraded"
173        };
174        let view_disclosure_status = if widening.is_empty() {
175            "no-widening"
176        } else {
177            "widening-disclosed"
178        };
179        Self {
180            schema: Self::SCHEMA.into(),
181            artifact_kind: "local_operator_memory_grounding_evidence".into(),
182            ownership: "AiDENs-local operator evidence; canonical memory truth remains in semantic-memory-forge, forge-memory-bridge, semantic-memory, and knowledge-runtime.".into(),
183            support_tier: "supported-local".into(),
184            semantic_status: semantic_status.into(),
185            semantic_exactness: semantic_exactness.into(),
186            proof_debt_status: "none-declared".into(),
187            contradiction_status: "none-declared".into(),
188            view_disclosure_status: view_disclosure_status.into(),
189            execution_contamination_status: "none-declared".into(),
190            adapter_route: "semantic-memory-forge -> forge-memory-bridge -> semantic-memory -> knowledge-runtime".into(),
191            memory_mode: memory_mode.into(),
192            query: query.into(),
193            result_count,
194            import_record_count,
195            trace_ctx: trace_ctx.into(),
196            canonical_backpointers: canonical_memory_backpointers(),
197            degradation,
198            widening,
199            local_truth_store: false,
200            promotion_eligible: semantic_status == "exact_check",
201            known_limits: vec![
202                "This receipt is an AiDENs-local display artifact, not canonical memory truth.".into(),
203                "AiDENs does not persist a local memory database as an authority source.".into(),
204                "Replay must use the canonical export, bridge, storage, and runtime owner crates.".into(),
205            ],
206        }
207    }
208
209    pub fn with_proof_debt(mut self, proof_debt_status: impl Into<String>) -> Self {
210        self.proof_debt_status = proof_debt_status.into();
211        self.promotion_eligible = false;
212        self
213    }
214
215    pub fn with_contradiction(mut self, contradiction_status: impl Into<String>) -> Self {
216        self.contradiction_status = contradiction_status.into();
217        self.semantic_exactness = "refuted".into();
218        self.promotion_eligible = false;
219        self
220    }
221
222    pub fn with_execution_contamination(
223        mut self,
224        execution_contamination_status: impl Into<String>,
225    ) -> Self {
226        self.execution_contamination_status = execution_contamination_status.into();
227        if self.semantic_exactness != "refuted" {
228            self.semantic_exactness = "degraded".into();
229        }
230        self.promotion_eligible = false;
231        self
232    }
233
234    pub fn to_receipt_line(&self) -> Result<String, serde_json::Error> {
235        serde_json::to_string(self)
236    }
237}
238
239pub fn canonical_memory_backpointers() -> Vec<MemoryGroundingBackpointerV1> {
240    vec![
241        MemoryGroundingBackpointerV1 {
242            owner_crate: "semantic-memory-forge".into(),
243            artifact_type: "ExportEnvelopeV3".into(),
244            relationship: "canonical-raw-evidence-owner".into(),
245        },
246        MemoryGroundingBackpointerV1 {
247            owner_crate: "forge-memory-bridge".into(),
248            artifact_type: "ProjectionImportBatchV3".into(),
249            relationship: "canonical-bridge-transform-owner".into(),
250        },
251        MemoryGroundingBackpointerV1 {
252            owner_crate: "semantic-memory".into(),
253            artifact_type: "MemoryStore".into(),
254            relationship: "canonical-projected-memory-owner".into(),
255        },
256        MemoryGroundingBackpointerV1 {
257            owner_crate: "knowledge-runtime".into(),
258            artifact_type: "QueryTrace".into(),
259            relationship: "canonical-runtime-view-owner".into(),
260        },
261    ]
262}
263
264impl CanonicalMemoryAdapter {
265    pub fn new(
266        store: canonical_stack::CanonicalMemoryStore,
267        runtime_config: canonical_stack::RuntimeConfig,
268    ) -> Result<Self, canonical_stack::RuntimeError> {
269        let runtime_adapter = canonical_stack::SemanticMemoryAdapter::new(store.clone());
270        let runtime = canonical_stack::KnowledgeRuntime::new(runtime_config, runtime_adapter)?;
271        Ok(Self { store, runtime })
272    }
273
274    pub fn open(
275        memory_config: canonical_stack::CanonicalMemoryConfig,
276        runtime_config: canonical_stack::RuntimeConfig,
277    ) -> Result<Self, CanonicalMemoryOpenError> {
278        let store = canonical_stack::CanonicalMemoryStore::open(memory_config)?;
279        Self::new(store, runtime_config).map_err(CanonicalMemoryOpenError::Runtime)
280    }
281
282    pub fn open_with_mock_embedder(
283        memory_config: canonical_stack::CanonicalMemoryConfig,
284        runtime_config: canonical_stack::RuntimeConfig,
285    ) -> Result<Self, CanonicalMemoryOpenError> {
286        let dimensions = memory_config.embedding.dimensions;
287        let store = canonical_stack::CanonicalMemoryStore::open_with_embedder(
288            memory_config,
289            Box::new(canonical_stack::MockEmbedder::new(dimensions)),
290        )?;
291        Self::new(store, runtime_config).map_err(CanonicalMemoryOpenError::Runtime)
292    }
293
294    pub fn store(&self) -> &canonical_stack::CanonicalMemoryStore {
295        &self.store
296    }
297
298    pub fn runtime(&self) -> &canonical_stack::KnowledgeRuntime {
299        &self.runtime
300    }
301
302    pub async fn import_forge_export(
303        &self,
304        envelope: &canonical_stack::ExportEnvelopeV3,
305    ) -> Result<canonical_stack::ProjectionImportResult, CanonicalMemoryAdapterError> {
306        let batch = canonical_stack::transform_forge_export(envelope)?;
307        canonical_stack::import_projection_batch(&self.store, &batch)
308            .await
309            .map_err(CanonicalMemoryAdapterError::Memory)
310    }
311
312    pub async fn query(
313        &self,
314        query: &str,
315        scope: Option<&knowledge_runtime::Scope>,
316    ) -> Result<
317        (
318            Vec<canonical_stack::SearchResult>,
319            canonical_stack::QueryTrace,
320        ),
321        canonical_stack::RuntimeError,
322    > {
323        self.runtime.query(query, scope).await
324    }
325
326    pub async fn query_temporal(
327        &self,
328        query: &str,
329        scope: Option<&knowledge_runtime::Scope>,
330        valid_at: &str,
331        recorded_at_or_before: &str,
332    ) -> Result<
333        (
334            Vec<canonical_stack::SearchResult>,
335            canonical_stack::QueryTrace,
336        ),
337        canonical_stack::RuntimeError,
338    > {
339        self.runtime
340            .query_temporal(query, scope, valid_at, recorded_at_or_before)
341            .await
342    }
343
344    pub async fn search(
345        &self,
346        query: &str,
347        namespaces: Option<&[String]>,
348        top_k: Option<usize>,
349    ) -> Result<Vec<canonical_stack::SearchResult>, canonical_stack::CanonicalMemoryError> {
350        let namespaces = namespaces
351            .map(|namespaces| namespaces.iter().map(String::as_str).collect::<Vec<&str>>());
352        self.store
353            .search(query, top_k, namespaces.as_deref(), None)
354            .await
355    }
356
357    pub async fn search_with_receipt(
358        &self,
359        query: &str,
360        namespaces: Option<&[String]>,
361        top_k: Option<usize>,
362    ) -> Result<
363        (
364            Vec<canonical_stack::SearchResult>,
365            canonical_stack::VectorSearchReceiptV1,
366        ),
367        canonical_stack::CanonicalMemoryError,
368    > {
369        let namespaces = namespaces
370            .map(|namespaces| namespaces.iter().map(String::as_str).collect::<Vec<&str>>());
371        let mut context = canonical_stack::SearchContext::default_now();
372        context.receipt_mode = canonical_stack::ReceiptMode::ReturnReceipt;
373
374        let response = self
375            .store
376            .search_with_context(query, top_k, namespaces.as_deref(), None, context)
377            .await?;
378        let receipt = response.receipt.ok_or_else(|| {
379            canonical_stack::CanonicalMemoryError::SearchReceiptNotFound {
380                receipt_id: "search_with_receipt".to_string(),
381            }
382        })?;
383        Ok((response.results, receipt))
384    }
385
386    pub async fn search_fts_only(
387        &self,
388        query: &str,
389        top_k: Option<usize>,
390    ) -> Result<Vec<canonical_stack::SearchResult>, canonical_stack::CanonicalMemoryError> {
391        self.store.search_fts_only(query, top_k, None, None).await
392    }
393
394    pub async fn search_vector_only(
395        &self,
396        query: &str,
397        top_k: Option<usize>,
398    ) -> Result<Vec<canonical_stack::SearchResult>, canonical_stack::CanonicalMemoryError> {
399        self.store
400            .search_vector_only(query, top_k, None, None)
401            .await
402    }
403
404    pub async fn search_explained(
405        &self,
406        query: &str,
407        top_k: Option<usize>,
408    ) -> Result<canonical_stack::ExplainedSearchResponse, canonical_stack::CanonicalMemoryError>
409    {
410        self.store
411            .search_explained_with_context(
412                query,
413                top_k,
414                None,
415                None,
416                canonical_stack::SearchContext::default_now(),
417            )
418            .await
419    }
420
421    pub async fn get_search_receipt(
422        &self,
423        receipt_id: &str,
424    ) -> Result<Option<canonical_stack::VectorSearchReceiptV1>, canonical_stack::CanonicalMemoryError>
425    {
426        self.store.get_search_receipt(receipt_id).await
427    }
428
429    pub async fn replay_search_receipt(
430        &self,
431        receipt: canonical_stack::VectorSearchReceiptV1,
432    ) -> Result<canonical_stack::SearchResponse, canonical_stack::CanonicalMemoryError> {
433        let report = self
434            .store
435            .replay_search_receipt(&receipt.receipt_id, "", None, None, None)
436            .await?;
437        Ok(canonical_stack::SearchResponse {
438            results: Vec::new(),
439            receipt: Some(report.replay_receipt),
440        })
441    }
442
443    pub async fn add_graph_edge(
444        &self,
445        params: canonical_stack::AddGraphEdgeParams,
446    ) -> Result<canonical_stack::StoredGraphEdge, canonical_stack::CanonicalMemoryError> {
447        self.store
448            .add_graph_edge(
449                &params.source,
450                &params.target,
451                params.edge_type,
452                params.weight,
453                params.metadata,
454            )
455            .await
456    }
457
458    pub async fn list_graph_edges(
459        &self,
460        node_id: &str,
461    ) -> Result<Vec<canonical_stack::StoredGraphEdge>, canonical_stack::CanonicalMemoryError> {
462        self.store.list_graph_edges_for_node(node_id).await
463    }
464
465    pub async fn count_graph_edges(&self) -> Result<usize, canonical_stack::CanonicalMemoryError> {
466        self.store.count_graph_edges().await
467    }
468
469    pub async fn verify_integrity(
470        &self,
471        mode: canonical_stack::VerifyMode,
472    ) -> Result<canonical_stack::IntegrityReport, canonical_stack::CanonicalMemoryError> {
473        self.store.verify_integrity(mode).await
474    }
475
476    pub async fn reconcile(
477        &self,
478        mode: canonical_stack::VerifyMode,
479    ) -> Result<Vec<canonical_stack::ReconcileAction>, canonical_stack::CanonicalMemoryError> {
480        let action = match mode {
481            canonical_stack::VerifyMode::Quick => canonical_stack::ReconcileAction::ReportOnly,
482            canonical_stack::VerifyMode::Full => canonical_stack::ReconcileAction::RebuildFts,
483        };
484        let _ = self.store.reconcile(action).await?;
485        Ok(vec![action])
486    }
487
488    pub async fn stats(
489        &self,
490    ) -> Result<canonical_stack::MemoryStats, canonical_stack::CanonicalMemoryError> {
491        self.store.stats().await
492    }
493
494    pub async fn add_fact(
495        &self,
496        namespace: &str,
497        content: &str,
498        source: Option<&str>,
499        confidence: Option<f64>,
500    ) -> Result<String, canonical_stack::CanonicalMemoryError> {
501        let metadata = confidence.map(|confidence| serde_json::json!(confidence));
502        self.store
503            .add_fact(namespace, content, source, metadata)
504            .await
505    }
506}
507
508#[derive(Debug, Error)]
509pub enum CanonicalMemoryOpenError {
510    #[error("semantic-memory open failed: {0}")]
511    Memory(#[from] canonical_stack::CanonicalMemoryError),
512    #[error("knowledge-runtime init failed: {0}")]
513    Runtime(#[from] canonical_stack::RuntimeError),
514}
515
516#[derive(Debug, Error)]
517pub enum CanonicalMemoryAdapterError {
518    #[error("forge-memory bridge transform failed: {0}")]
519    Bridge(#[from] canonical_stack::BridgeError),
520    #[error("semantic-memory import failed: {0}")]
521    Memory(#[from] canonical_stack::CanonicalMemoryError),
522}
523
524pub fn memory_config_for_root(root: impl Into<PathBuf>) -> canonical_stack::CanonicalMemoryConfig {
525    canonical_stack::CanonicalMemoryConfig {
526        base_dir: root.into(),
527        ..canonical_stack::CanonicalMemoryConfig::default()
528    }
529}
530
531pub fn runtime_config_for_namespace(
532    namespace: impl Into<String>,
533) -> canonical_stack::RuntimeConfig {
534    canonical_stack::RuntimeConfig {
535        default_scope: canonical_stack::Scope::new(namespace),
536        query: Default::default(),
537        entity: Default::default(),
538        projection: Default::default(),
539        strict_temporal: false,
540        strict_scope: false,
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547    use semantic_memory::types::GraphEdgeType;
548    use std::path::PathBuf;
549    use std::time::{SystemTime, UNIX_EPOCH};
550
551    fn test_memory_root(tag: &str) -> PathBuf {
552        let now = SystemTime::now()
553            .duration_since(UNIX_EPOCH)
554            .map(|d| d.as_nanos())
555            .unwrap_or(0);
556        std::env::temp_dir().join(format!(
557            "aidens-canonical-memory-kit-{}-{}-{}",
558            std::process::id(),
559            now,
560            tag
561        ))
562    }
563
564    fn cleanup_root(root: &PathBuf) {
565        let _ = std::fs::remove_dir_all(root);
566    }
567
568    #[tokio::test]
569    async fn search_facade_delegates_to_store_search() {
570        let root = test_memory_root("search");
571        let adapter = CanonicalMemoryAdapter::open_with_mock_embedder(
572            memory_config_for_root(&root),
573            runtime_config_for_namespace("aidens-test"),
574        )
575        .expect("canonical memory adapter");
576
577        let fact_id = adapter
578            .add_fact(
579                "search-facade-ns",
580                "search facade fixture fact about Rust",
581                Some("test"),
582                Some(0.9),
583            )
584            .await
585            .expect("store fact");
586        assert!(!fact_id.is_empty());
587
588        let namespaces = vec!["search-facade-ns".to_string()];
589        let results = adapter
590            .search("Rust", Some(&namespaces), Some(5))
591            .await
592            .expect("search results");
593        assert!(!results.is_empty());
594
595        let _ = adapter
596            .search_with_receipt("Rust", Some(&namespaces), Some(5))
597            .await
598            .expect("search with receipt");
599
600        cleanup_root(&root);
601    }
602
603    #[tokio::test]
604    async fn add_fact_facade_writes_a_fact_and_returns_id() {
605        let root = test_memory_root("add_fact");
606        let adapter = CanonicalMemoryAdapter::open_with_mock_embedder(
607            memory_config_for_root(&root),
608            runtime_config_for_namespace("aidens-test"),
609        )
610        .expect("canonical memory adapter");
611
612        let fact_id = adapter
613            .add_fact(
614                "add-fact-ns",
615                "adding a fact should return a stable id",
616                Some("unit"),
617                Some(0.77),
618            )
619            .await
620            .expect("add fact");
621        assert!(!fact_id.is_empty());
622
623        let namespaces = vec!["add-fact-ns".to_string()];
624        let results = adapter
625            .search("adding a fact", Some(&namespaces), Some(5))
626            .await
627            .expect("search after write");
628        assert!(!results.is_empty());
629
630        cleanup_root(&root);
631    }
632
633    #[tokio::test]
634    async fn verify_integrity_facade_reports_mode_and_ok_or_not() {
635        let root = test_memory_root("verify_integrity");
636        let adapter = CanonicalMemoryAdapter::open_with_mock_embedder(
637            memory_config_for_root(&root),
638            runtime_config_for_namespace("aidens-test"),
639        )
640        .expect("canonical memory adapter");
641
642        let report = adapter
643            .verify_integrity(canonical_stack::VerifyMode::Quick)
644            .await
645            .expect("verify integrity");
646        assert!(report.issues.is_empty());
647
648        cleanup_root(&root);
649    }
650
651    #[tokio::test]
652    async fn graph_edge_facade_adds_and_lists_edges() {
653        let root = test_memory_root("graph_edge");
654        let adapter = CanonicalMemoryAdapter::open_with_mock_embedder(
655            memory_config_for_root(&root),
656            runtime_config_for_namespace("aidens-test"),
657        )
658        .expect("canonical memory adapter");
659
660        let initial_count = match adapter.count_graph_edges().await {
661            Ok(count) => count,
662            Err(err) => {
663                assert!(err.to_string().contains("no such table: graph_edges"));
664                cleanup_root(&root);
665                return;
666            }
667        };
668
669        let params = canonical_stack::AddGraphEdgeParams {
670            source: "namespace:graph-test-a".to_string(),
671            target: "namespace:graph-test-b".to_string(),
672            edge_type: GraphEdgeType::Semantic {
673                cosine_similarity: 0.87,
674            },
675            weight: 1.0,
676            metadata: Some(serde_json::json!({
677                "source": "aidens-memory-kit-test"
678            })),
679        };
680
681        let edge = match adapter.add_graph_edge(params).await {
682            Ok(edge) => edge,
683            Err(err) => {
684                assert!(err.to_string().contains("no such table: graph_edges"));
685                cleanup_root(&root);
686                return;
687            }
688        };
689        assert!(!edge.id.is_empty());
690
691        let by_node = adapter
692            .list_graph_edges("namespace:graph-test-a")
693            .await
694            .expect("list graph edges");
695        assert_eq!(by_node.len(), initial_count + 1);
696
697        let final_count = adapter
698            .count_graph_edges()
699            .await
700            .expect("final graph count");
701        assert_eq!(final_count, initial_count + 1);
702
703        cleanup_root(&root);
704    }
705
706    #[test]
707    fn opens_canonical_memory_stack_with_mock_embedder() {
708        let root =
709            std::env::temp_dir().join(format!("aidens-canonical-memory-{}", std::process::id()));
710        let adapter = CanonicalMemoryAdapter::open_with_mock_embedder(
711            memory_config_for_root(&root),
712            runtime_config_for_namespace("aidens-test"),
713        )
714        .expect("canonical memory adapter");
715
716        assert_eq!(adapter.store().config().base_dir, root);
717        let _ = std::fs::remove_dir_all(adapter.store().config().base_dir.clone());
718    }
719
720    #[test]
721    fn memory_grounding_evidence_declares_canonical_owners_and_no_local_truth() {
722        let receipt = MemoryGroundingEvidenceV1::canonical_seam(
723            "canonical-seam",
724            "canonical seam fixture",
725            1,
726            1,
727            "trace:fixture",
728            Vec::new(),
729            Vec::new(),
730        );
731
732        assert_eq!(receipt.schema, MemoryGroundingEvidenceV1::SCHEMA);
733        assert_eq!(receipt.semantic_status, "exact_check");
734        assert_eq!(receipt.semantic_exactness, "exact");
735        assert_eq!(receipt.proof_debt_status, "none-declared");
736        assert_eq!(receipt.contradiction_status, "none-declared");
737        assert!(receipt.promotion_eligible);
738        assert!(!receipt.local_truth_store);
739        assert!(receipt
740            .canonical_backpointers
741            .iter()
742            .any(|backpointer| backpointer.owner_crate == "semantic-memory-forge"));
743        assert!(receipt
744            .canonical_backpointers
745            .iter()
746            .any(|backpointer| backpointer.owner_crate == "knowledge-runtime"));
747        assert!(receipt
748            .to_receipt_line()
749            .unwrap()
750            .contains("canonical-runtime-view-owner"));
751    }
752
753    #[test]
754    fn memory_grounding_evidence_labels_degradation_without_promoting_truth() {
755        let receipt = MemoryGroundingEvidenceV1::canonical_seam(
756            "canonical-seam",
757            "canonical seam fixture",
758            1,
759            1,
760            "trace:fixture",
761            vec!["query-degraded".into()],
762            vec!["scope-widened".into()],
763        );
764
765        assert_eq!(receipt.semantic_status, "degraded_exact_check");
766        assert_eq!(receipt.semantic_exactness, "degraded");
767        assert_eq!(receipt.view_disclosure_status, "widening-disclosed");
768        assert!(!receipt.promotion_eligible);
769        assert!(!receipt.local_truth_store);
770        assert_eq!(receipt.degradation, vec!["query-degraded"]);
771        assert_eq!(receipt.widening, vec!["scope-widened"]);
772    }
773
774    #[test]
775    fn memory_grounding_evidence_does_not_hide_proof_debt_or_contradictions_in_scalar_status() {
776        let receipt = MemoryGroundingEvidenceV1::canonical_seam(
777            "canonical-seam",
778            "contradicted fixture",
779            1,
780            1,
781            "trace:fixture",
782            Vec::new(),
783            Vec::new(),
784        )
785        .with_proof_debt("waiver-recorded-but-proof-missing")
786        .with_contradiction("contradiction-witness-disclosed")
787        .with_execution_contamination("execution-context-leaked-into-domain-truth");
788
789        assert_eq!(receipt.semantic_status, "exact_check");
790        assert_eq!(receipt.semantic_exactness, "refuted");
791        assert_eq!(
792            receipt.proof_debt_status,
793            "waiver-recorded-but-proof-missing"
794        );
795        assert_eq!(
796            receipt.contradiction_status,
797            "contradiction-witness-disclosed"
798        );
799        assert_eq!(
800            receipt.execution_contamination_status,
801            "execution-context-leaked-into-domain-truth"
802        );
803        assert!(!receipt.promotion_eligible);
804    }
805}