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}