Skip to main content

microscope_memory/
types.rs

1//! Shared types used across native and WASM targets.
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::fmt;
5use std::str::FromStr;
6
7/// 16-byte project identifier. BLAKE3-derived from a project root path,
8/// or the all-zero `GLOBAL` context shared across projects.
9#[repr(C)]
10#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
11pub struct ProjectId(pub [u8; 16]);
12
13impl ProjectId {
14    /// Global context — visible to every project when included.
15    pub const GLOBAL: Self = ProjectId([0u8; 16]);
16
17    /// Derive a project id from a filesystem path.
18    pub fn from_path(path: &str) -> Self {
19        let hash = blake3::hash(path.as_bytes());
20        let mut bytes = [0u8; 16];
21        bytes.copy_from_slice(&hash.as_bytes()[..16]);
22        ProjectId(bytes)
23    }
24
25    /// True for the zeroed global project id.
26    pub fn is_global(&self) -> bool {
27        self.0 == [0u8; 16]
28    }
29}
30
31impl fmt::Display for ProjectId {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        for b in &self.0 {
34            write!(f, "{:02x}", b)?;
35        }
36        Ok(())
37    }
38}
39
40impl FromStr for ProjectId {
41    type Err = String;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        if s.eq_ignore_ascii_case("global") {
45            return Ok(ProjectId::GLOBAL);
46        }
47        let s = s.trim_start_matches("0x");
48        if s.len() != 32 {
49            return Err(format!("ProjectId must be 32 hex chars, got {}", s.len()));
50        }
51        let mut bytes = [0u8; 16];
52        for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
53            let chunk =
54                std::str::from_utf8(chunk).map_err(|_| "invalid utf-8 in hex".to_string())?;
55            bytes[i] = u8::from_str_radix(chunk, 16).map_err(|e| format!("invalid hex: {}", e))?;
56        }
57        Ok(ProjectId(bytes))
58    }
59}
60
61impl Serialize for ProjectId {
62    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
63        serializer.serialize_str(&self.to_string())
64    }
65}
66
67impl<'de> Deserialize<'de> for ProjectId {
68    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
69        let s = String::deserialize(deserializer)?;
70        ProjectId::from_str(&s).map_err(serde::de::Error::custom)
71    }
72}
73
74/// Block header: packed, mmap-ready.
75///
76/// v4 (MSC4) layout appends `project_id`, `importance` and `flags` to the
77/// legacy 32-byte spatial header. The first 32 bytes keep their old offsets so
78/// existing fixed-offset reads remain valid.
79#[repr(C, packed)]
80#[derive(Clone, Copy, Debug)]
81pub struct BlockHeader {
82    pub x: f32,
83    pub y: f32,
84    pub z: f32,
85    pub zoom: f32,
86    pub depth: u8,
87    pub layer_id: u8,
88    pub data_offset: u32,
89    pub data_len: u16,
90    pub parent_idx: u32,
91    pub child_count: u16,
92    pub crc16: [u8; 2],
93    pub project_id: ProjectId,
94    pub importance: u8,
95    pub flags: u8,
96}
97
98/// Alias used by the project-isolation protocol.
99pub type MemoryBlockHeader = BlockHeader;
100
101impl BlockHeader {
102    /// Zero-copy visibility check for project isolation.
103    #[inline(always)]
104    pub fn is_visible_to(&self, active: ProjectId, include_global: bool) -> bool {
105        self.project_id == active || (include_global && self.project_id.is_global())
106    }
107}
108
109/// Query options used by project-scoped search and Hebbian propagation.
110#[derive(Clone)]
111pub struct MemoryQueryOptions {
112    pub active_project: ProjectId,
113    pub include_global: bool,
114    pub min_importance: u8,
115    pub max_results: usize,
116}
117
118impl Default for MemoryQueryOptions {
119    fn default() -> Self {
120        Self {
121            active_project: ProjectId::GLOBAL,
122            include_global: true,
123            min_importance: 0,
124            max_results: usize::MAX,
125        }
126    }
127}
128
129/// Meta header: 48 bytes at start of meta.bin
130#[repr(C, packed)]
131#[derive(Clone, Copy)]
132#[allow(dead_code)]
133pub struct MetaHeader {
134    pub magic: [u8; 4],
135    pub version: u32,
136    pub block_count: u32,
137    pub depth_count: u32,
138}
139
140/// Append entry for the in-memory append log
141pub struct AppendEntry {
142    pub text: String,
143    pub layer_id: u8,
144    pub importance: u8,
145    pub depth: u8,
146    pub x: f32,
147    pub y: f32,
148    pub z: f32,
149    pub emotion: [f32; 21],
150    pub project_id: ProjectId,
151}