Skip to main content

sim_lib_forge/
library.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use sim_kernel::{ContentId, Error, Result, Symbol};
4
5use crate::{CompiledIntent, IntentStatus};
6
7/// Named index for compiled intent artifacts.
8///
9/// The index stores `CompiledIntent` records, which point at normalized prose
10/// and BRIDGE packet bytes by `ContentId`. It does not duplicate the packet or
11/// prose content store; lookup is by intent name/version or by normalized
12/// source id.
13#[derive(Clone, Debug, Default)]
14pub struct IntentLibrary {
15    intents: BTreeMap<IntentKey, CompiledIntent>,
16    source_index: BTreeMap<ContentId, BTreeSet<IntentKey>>,
17}
18
19impl IntentLibrary {
20    /// Builds an empty intent library index.
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Stores or updates an intent record at its explicit name/version key.
26    ///
27    /// A packet change at an existing key is rejected because packet changes
28    /// must become a new version. A stored golden record is immutable except
29    /// for idempotent re-storage of the same record.
30    pub fn store(&mut self, intent: CompiledIntent) -> Result<()> {
31        let key = IntentKey::new(&intent.name, intent.version);
32        if let Some(existing) = self.intents.get(&key) {
33            if existing.status == IntentStatus::Golden && existing != &intent {
34                return Err(Error::Eval(format!(
35                    "golden intent {} v{} is immutable",
36                    key.name, key.version
37                )));
38            }
39            if existing.packet != intent.packet {
40                return Err(Error::Eval(format!(
41                    "compiled intent {} v{} changes packet; store a new version",
42                    key.name, key.version
43                )));
44            }
45        }
46
47        if let Some(existing) = self.intents.insert(key.clone(), intent.clone()) {
48            self.remove_source_key(&existing.source, &key);
49        }
50        self.source_index
51            .entry(intent.source.clone())
52            .or_default()
53            .insert(key);
54        Ok(())
55    }
56
57    /// Fetches an intent by exact name and version.
58    pub fn fetch(&self, name: &Symbol, version: u32) -> Option<&CompiledIntent> {
59        self.intents.get(&IntentKey::new(name, version))
60    }
61
62    /// Returns all intents whose normalized source id matches `source`.
63    pub fn by_source(&self, source: &ContentId) -> Vec<&CompiledIntent> {
64        self.source_index
65            .get(source)
66            .into_iter()
67            .flat_map(|keys| keys.iter())
68            .filter_map(|key| self.intents.get(key))
69            .collect()
70    }
71
72    /// Returns the highest-version golden intent for `source`, when one exists.
73    pub fn golden_by_source(&self, source: &ContentId) -> Option<&CompiledIntent> {
74        self.by_source(source)
75            .into_iter()
76            .filter(|intent| intent.status == IntentStatus::Golden)
77            .max_by_key(|intent| intent.version)
78    }
79
80    /// Returns the next version number available for `name`.
81    pub fn next_version(&self, name: &Symbol) -> u32 {
82        self.intents
83            .keys()
84            .filter(|key| &key.name == name)
85            .map(|key| key.version)
86            .max()
87            .unwrap_or(0)
88            .saturating_add(1)
89    }
90
91    pub(crate) fn store_resolved(&mut self, mut intent: CompiledIntent) -> Result<CompiledIntent> {
92        intent.version = self
93            .version_for_packet(&intent.name, &intent.source, &intent.packet)
94            .unwrap_or_else(|| self.next_version(&intent.name));
95        self.store(intent.clone())?;
96        Ok(intent)
97    }
98
99    fn version_for_packet(
100        &self,
101        name: &Symbol,
102        source: &ContentId,
103        packet: &ContentId,
104    ) -> Option<u32> {
105        self.intents.iter().find_map(|(key, intent)| {
106            (&key.name == name && &intent.source == source && &intent.packet == packet)
107                .then_some(key.version)
108        })
109    }
110
111    fn remove_source_key(&mut self, source: &ContentId, key: &IntentKey) {
112        if let Some(keys) = self.source_index.get_mut(source) {
113            keys.remove(key);
114            if keys.is_empty() {
115                self.source_index.remove(source);
116            }
117        }
118    }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
122struct IntentKey {
123    name: Symbol,
124    version: u32,
125}
126
127impl IntentKey {
128    fn new(name: &Symbol, version: u32) -> Self {
129        Self {
130            name: name.clone(),
131            version,
132        }
133    }
134}