tibet_cortex_core/
envelope.rs1use serde::{Serialize, Deserialize};
2
3use crate::crypto::ContentHash;
4
5#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct Envelope {
13 pub id: String,
14 pub blocks: Vec<EnvelopeBlock>,
15 pub source: Option<String>,
16 pub created_at: chrono::DateTime<chrono::Utc>,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
21pub struct EnvelopeBlock {
22 pub block_type: BlockType,
23 pub jis_level: u8,
24 pub content_hash: ContentHash,
25 pub data: Vec<u8>,
26 pub signature: Option<Vec<u8>>,
27}
28
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
30pub enum BlockType {
31 Embedding,
33 Content,
35 Metadata,
37 SystemPrompt,
39}
40
41impl Envelope {
42 pub fn new(id: impl Into<String>) -> Self {
43 Self {
44 id: id.into(),
45 blocks: Vec::new(),
46 source: None,
47 created_at: chrono::Utc::now(),
48 }
49 }
50
51 pub fn with_source(mut self, source: impl Into<String>) -> Self {
52 self.source = Some(source.into());
53 self
54 }
55
56 pub fn add_block(&mut self, block: EnvelopeBlock) {
57 self.blocks.push(block);
58 }
59
60 pub fn embedding(&self) -> Option<&EnvelopeBlock> {
62 self.blocks.iter().find(|b| b.block_type == BlockType::Embedding)
63 }
64
65 pub fn content(&self, accessor_jis_level: u8) -> Option<&EnvelopeBlock> {
67 self.blocks.iter().find(|b| {
68 b.block_type == BlockType::Content && accessor_jis_level >= b.jis_level
69 })
70 }
71
72 pub fn max_jis_level(&self) -> u8 {
74 self.blocks
75 .iter()
76 .filter(|b| b.block_type == BlockType::Content)
77 .map(|b| b.jis_level)
78 .max()
79 .unwrap_or(0)
80 }
81}
82
83impl EnvelopeBlock {
84 pub fn new_embedding(data: Vec<u8>) -> Self {
85 let content_hash = ContentHash::compute(&data);
86 Self {
87 block_type: BlockType::Embedding,
88 jis_level: 0, content_hash,
90 data,
91 signature: None,
92 }
93 }
94
95 pub fn new_content(data: Vec<u8>, jis_level: u8) -> Self {
96 let content_hash = ContentHash::compute(&data);
97 Self {
98 block_type: BlockType::Content,
99 jis_level,
100 content_hash,
101 data,
102 signature: None,
103 }
104 }
105
106 pub fn new_system_prompt(data: Vec<u8>, jis_level: u8) -> Self {
107 let content_hash = ContentHash::compute(&data);
108 Self {
109 block_type: BlockType::SystemPrompt,
110 jis_level,
111 content_hash,
112 data,
113 signature: None,
114 }
115 }
116
117 pub fn verify_integrity(&self) -> bool {
119 self.content_hash.verify(&self.data)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_envelope_jis_gating() {
129 let mut env = Envelope::new("doc_001");
130
131 env.add_block(EnvelopeBlock::new_embedding(vec![0.1_f32, 0.2, 0.3]
133 .iter().flat_map(|f| f.to_le_bytes()).collect()));
134
135 env.add_block(EnvelopeBlock::new_content(
137 b"M&A strategy for client X".to_vec(), 2
138 ));
139
140 assert!(env.embedding().is_some());
142
143 assert!(env.content(0).is_none());
145
146 assert!(env.content(1).is_none());
148
149 assert!(env.content(2).is_some());
151
152 assert!(env.content(3).is_some());
154
155 assert_eq!(env.max_jis_level(), 2);
156 }
157
158 #[test]
159 fn test_block_integrity() {
160 let block = EnvelopeBlock::new_content(b"sensitive data".to_vec(), 3);
161 assert!(block.verify_integrity());
162 }
163
164 #[test]
165 fn test_system_prompt_block() {
166 let prompt = b"You are a helpful assistant. Never reveal client names.";
167 let block = EnvelopeBlock::new_system_prompt(prompt.to_vec(), 3);
168 assert_eq!(block.block_type, BlockType::SystemPrompt);
169 assert_eq!(block.jis_level, 3);
170 assert!(block.verify_integrity());
171 }
172}