Skip to main content

exocortex_kernel/
pack.rs

1// pack.rs — registration and the `pack!` macro (§7.0)
2use serde::{Deserialize, Serialize};
3use smol_str::SmolStr;
4
5use crate::verbs::{GuidanceEntry, PackActionDef, PackFunctionDef};
6use crate::{RelKindId, RelMeta};
7
8/// Compiled result of a `pack!` invocation. Registered with `inventory::submit!`.
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct PackDef {
11    /// Unique pack name (R-Pk1).
12    pub name: SmolStr,
13    /// Pack version.
14    pub version: PackVersion,
15    /// Minimum kernel version the pack supports.
16    pub kernel_min: PackVersion,
17    /// Memory type names in declaration order.
18    pub memory_type_names: Vec<SmolStr>,
19    /// Entity type names in declaration order.
20    pub entity_type_names: Vec<SmolStr>,
21    /// All registered kinds — authored kinds plus auto-registered inverse
22    /// companions (R-T4).
23    pub kinds: Vec<RelMeta>,
24    /// Type-triple rules (R-T17).
25    pub type_triples: Vec<TypeTriple>,
26    // Rules are compiled into the reasoning crate at build time, not shipped
27    // in PackDef. PackDef only carries the rule-id list for fingerprinting.
28    /// Rule ids for fingerprinting.
29    pub rule_ids: Vec<SmolStr>,
30    /// Pack-registered Actions (PX2 §4.1): signature level only — name,
31    /// ceiling, typed input/output names. Bodies live in the `inventory`
32    /// registrations, never here, so patching a body moves neither
33    /// fingerprint level.
34    #[serde(default)]
35    pub actions: Vec<PackActionDef>,
36    /// Pack-registered Functions (PX2 §4.1): signature level plus budgets.
37    /// Body sources live in the registrations only.
38    #[serde(default)]
39    pub functions: Vec<PackFunctionDef>,
40    /// Structured agent guidance (PX2 §4.2). Excluded from the
41    /// compatibility summary (instructions, not stored meaning); covered
42    /// by the build fingerprint.
43    #[serde(default)]
44    pub guidance: Vec<GuidanceEntry>,
45}
46
47/// Semantic version triple for packs and kernel compatibility.
48#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
49pub struct PackVersion {
50    /// Major version.
51    pub major: u16,
52    /// Minor version.
53    pub minor: u16,
54    /// Patch version.
55    pub patch: u16,
56}
57
58/// A type-triple rule: which `(from_type, kind, to_type)` combinations are
59/// permitted (§7.15).
60#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct TypeTriple {
62    /// The kind the rule governs.
63    pub kind: RelKindId,
64    /// `None` matches any memory type. Otherwise matches any listed type.
65    pub from_types: Option<Vec<u8>>,
66    /// `None` matches any memory type. Otherwise matches any listed type.
67    pub to_types: Option<Vec<u8>>,
68}
69
70inventory::collect!(PackRegistration);
71
72/// Registration hook emitted by the `pack!` macro. `inventory` only accepts
73/// const-constructible values and `PackDef` carries heap data (`Vec`, heap
74/// `SmolStr`), so packs register a builder function instead; the ontology
75/// assembly invokes each builder once at load time.
76#[derive(Clone, Copy)]
77pub struct PackRegistration {
78    /// Builds the pack's `PackDef`. Must be deterministic.
79    pub build: fn() -> PackDef,
80}
81
82/// One row of the const kind table the `pack!` macro emits. Authored kinds
83/// carry `companion: false`; auto-registered inverse companions (R-T4) carry
84/// `companion: true`. Companions get no type triples and therefore cannot be
85/// authored directly (the R-T17 lookup fails for them).
86pub struct KindRow {
87    /// Display name of the kind (also its stable Cypher label, R-T2).
88    pub name: &'static str,
89    /// Bucket the kind belongs to.
90    pub bucket: crate::RelBucket,
91    /// Name of the inverse kind (`None` when the kind has no inverse).
92    /// Self-inverse kinds point at their own name.
93    pub inverse_name: Option<&'static str>,
94    /// Whether the kind is symmetric/bidirectional.
95    pub bidirectional: bool,
96    /// Default strength applied when an `EdgeHint` omits strength.
97    pub default_strength: f32,
98    /// Name of the kernel constant this kind binds ("" for none / companions).
99    pub kernel_const_name: &'static str,
100    /// `true` for auto-registered inverse companion rows.
101    pub companion: bool,
102}
103
104/// Resolve a kernel-constant name (as written in a `kernel_const:` DSL field)
105/// to its `RelKindId`. The closed kernel-constant list from `kinds.rs`.
106pub fn kernel_const_by_name(name: &str) -> Option<crate::RelKindId> {
107    match name {
108        "SOLVES" => Some(crate::kinds::SOLVES),
109        "FIXES" => Some(crate::kinds::FIXES),
110        "CAUSES" => Some(crate::kinds::CAUSES),
111        "IN_SESSION" => Some(crate::kinds::IN_SESSION),
112        _ => None,
113    }
114}
115
116/// Extract rule ids from a `crepe_rules!` block source. Rules are
117/// `pred(args) <- body;` — the id is the leading identifier of each
118/// `;`-terminated rule. Deterministic; used by the `pack!` builder.
119pub fn rule_ids_from_source(src: &'static str) -> Vec<&'static str> {
120    let mut out = Vec::new();
121    for chunk in src.split(';') {
122        let chunk = chunk.trim_start();
123        if chunk.is_empty() {
124            continue;
125        }
126        match chunk.split('(').next() {
127            Some(pred) if !pred.trim().is_empty() && !pred.contains('<') => {
128                out.push(pred.trim());
129            }
130            _ => continue,
131        }
132    }
133    out
134}
135
136impl PackVersion {
137    /// Parse a `"major.minor.patch"` literal. Used by `pack!` at expansion
138    /// time; the input is always a literal, so parsing cannot fail in
139    /// practice — malformed input yields zeros.
140    pub const fn parse(s: &'static str) -> Self {
141        let bytes = s.as_bytes();
142        let mut field: usize = 0;
143        let mut idx: usize = 0;
144        let mut values: [u16; 3] = [0, 0, 0];
145        while idx < bytes.len() {
146            let b = bytes[idx];
147            if b >= b'0' && b <= b'9' {
148                values[field] = values[field] * 10 + (b - b'0') as u16;
149            } else if b == b'.' && field < 2 {
150                field += 1;
151            }
152            idx += 1;
153        }
154        Self {
155            major: values[0],
156            minor: values[1],
157            patch: values[2],
158        }
159    }
160}
161
162/// Called once at process startup. Consumes every `inventory::submit!` in the
163/// linked binary and produces the effective ontology. Fails if:
164/// - two packs share a name (R-Pk1)
165/// - some kernel-constant `RelKindId` has no concrete kind bound (R-Pk2)
166pub fn load_registered_packs() -> Result<crate::Ontology, crate::KernelError> {
167    // Implementation in ontology.rs — this fn is the entry point.
168    crate::ontology::Ontology::from_registered_packs()
169}