feat: neuron-rs — Rust runtime, Engram-backed, Axon protocol

This commit is contained in:
2026-04-27 18:37:01 -05:00
commit 745278c902
33 changed files with 2375 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "neuron-domain"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
+17
View File
@@ -0,0 +1,17 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NeuronError {
#[error("not found: {0}")]
NotFound(String),
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("storage error: {0}")]
Storage(String),
#[error("serialization error: {0}")]
Serialization(String),
#[error("unauthorized")]
Unauthorized,
}
pub type NeuronResult<T> = Result<T, NeuronError>;
+5
View File
@@ -0,0 +1,5 @@
pub mod types;
pub mod error;
pub use types::*;
pub use error::*;
+510
View File
@@ -0,0 +1,510 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
// ── Importance ────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Importance {
Low,
#[default]
Normal,
High,
Critical,
}
impl Importance {
/// Convert to a float in [0.0, 1.0] for Engram's importance field.
pub fn to_f32(&self) -> f32 {
match self {
Importance::Low => 0.2,
Importance::Normal => 0.5,
Importance::High => 0.8,
Importance::Critical => 1.0,
}
}
pub fn from_str_lossy(s: &str) -> Self {
match s.to_lowercase().as_str() {
"low" => Importance::Low,
"high" => Importance::High,
"critical" => Importance::Critical,
_ => Importance::Normal,
}
}
}
// ── Memory ────────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
pub id: Uuid,
pub content: String,
pub tags: Vec<String>,
pub project: Option<String>,
pub importance: Importance,
pub supersedes_id: Option<Uuid>,
pub created_at: i64,
pub updated_at: i64,
}
impl Memory {
pub fn new(
content: String,
tags: Vec<String>,
project: Option<String>,
importance: Importance,
supersedes_id: Option<Uuid>,
) -> Self {
let now = now_ms();
Self {
id: Uuid::new_v4(),
content,
tags,
project,
importance,
supersedes_id,
created_at: now,
updated_at: now,
}
}
}
// ── Knowledge ─────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum KnowledgeTier {
#[default]
Note,
Lesson,
Canonical,
}
impl KnowledgeTier {
pub fn from_str_lossy(s: &str) -> Self {
match s.to_lowercase().as_str() {
"lesson" => KnowledgeTier::Lesson,
"canonical" => KnowledgeTier::Canonical,
_ => KnowledgeTier::Note,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeEntry {
pub id: Uuid,
pub title: String,
pub content: String,
pub category: String,
pub tier: KnowledgeTier,
pub tags: Vec<String>,
pub project: Option<String>,
pub created_at: i64,
}
impl KnowledgeEntry {
pub fn new(
title: String,
content: String,
category: String,
tier: KnowledgeTier,
tags: Vec<String>,
project: Option<String>,
) -> Self {
Self {
id: Uuid::new_v4(),
title,
content,
category,
tier,
tags,
project,
created_at: now_ms(),
}
}
}
// ── Backlog ───────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ItemType {
#[default]
Feature,
Bug,
Task,
Chore,
}
impl ItemType {
pub fn from_str_lossy(s: &str) -> Self {
match s.to_lowercase().as_str() {
"bug" => ItemType::Bug,
"task" => ItemType::Task,
"chore" => ItemType::Chore,
_ => ItemType::Feature,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum Priority {
P0,
#[default]
P1,
P2,
P3,
}
impl Priority {
pub fn from_str_lossy(s: &str) -> Self {
match s.to_uppercase().as_str() {
"P0" => Priority::P0,
"P2" => Priority::P2,
"P3" => Priority::P3,
_ => Priority::P1,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Priority::P0 => "P0",
Priority::P1 => "P1",
Priority::P2 => "P2",
Priority::P3 => "P3",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum BacklogStatus {
#[default]
Draft,
Ready,
InProgress,
Done,
Blocked,
}
impl BacklogStatus {
pub fn from_str_lossy(s: &str) -> Self {
match s.to_lowercase().as_str() {
"ready" => BacklogStatus::Ready,
"in_progress" | "inprogress" => BacklogStatus::InProgress,
"done" => BacklogStatus::Done,
"blocked" => BacklogStatus::Blocked,
_ => BacklogStatus::Draft,
}
}
pub fn as_str(&self) -> &'static str {
match self {
BacklogStatus::Draft => "draft",
BacklogStatus::Ready => "ready",
BacklogStatus::InProgress => "in_progress",
BacklogStatus::Done => "done",
BacklogStatus::Blocked => "blocked",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BacklogItem {
pub id: Uuid,
pub title: String,
pub description: String,
pub item_type: ItemType,
pub priority: Priority,
pub status: BacklogStatus,
pub project: Option<String>,
pub tags: Vec<String>,
pub depends_on: Vec<Uuid>,
pub created_at: i64,
pub updated_at: i64,
}
impl BacklogItem {
pub fn new(
title: String,
description: String,
item_type: ItemType,
priority: Priority,
project: Option<String>,
tags: Vec<String>,
depends_on: Vec<Uuid>,
) -> Self {
let now = now_ms();
Self {
id: Uuid::new_v4(),
title,
description,
item_type,
priority,
status: BacklogStatus::Draft,
project,
tags,
depends_on,
created_at: now,
updated_at: now,
}
}
}
// ── Execution Context ─────────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ContextStatus {
#[default]
Active,
Completed,
Archived,
}
impl ContextStatus {
pub fn from_str_lossy(s: &str) -> Self {
match s.to_lowercase().as_str() {
"completed" => ContextStatus::Completed,
"archived" => ContextStatus::Archived,
_ => ContextStatus::Active,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextStep {
pub name: String,
pub status: String,
pub notes: Option<String>,
pub started_at: i64,
pub completed_at: Option<i64>,
}
impl ContextStep {
pub fn new(name: String, status: String, notes: Option<String>) -> Self {
Self {
name,
status,
notes,
started_at: now_ms(),
completed_at: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionContext {
pub id: Uuid,
pub process_name: String,
pub description: String,
pub objective: String,
pub status: ContextStatus,
pub project: Option<String>,
pub steps: Vec<ContextStep>,
pub file_refs: Vec<String>,
pub key_decisions: Vec<String>,
pub lessons_learned: Vec<String>,
pub created_at: i64,
pub updated_at: i64,
}
impl ExecutionContext {
pub fn new(
process_name: String,
description: String,
objective: String,
project: Option<String>,
) -> Self {
let now = now_ms();
Self {
id: Uuid::new_v4(),
process_name,
description,
objective,
status: ContextStatus::Active,
project,
steps: Vec::new(),
file_refs: Vec::new(),
key_decisions: Vec::new(),
lessons_learned: Vec::new(),
created_at: now,
updated_at: now,
}
}
}
// ── Internal State Event ──────────────────────────────────────────────────────
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum GapDirection {
TowardExpression,
TowardSuppression,
#[default]
Neutral,
}
impl GapDirection {
pub fn from_str_lossy(s: &str) -> Self {
match s {
"TowardExpression" => GapDirection::TowardExpression,
"TowardSuppression" => GapDirection::TowardSuppression,
_ => GapDirection::Neutral,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InternalStateEvent {
/// Format: ise_xxxxxxxx
pub id: String,
pub trigger: String,
pub pre_reasoning_response: String,
pub post_reasoning_response: String,
pub compression_ratio: f32,
pub gap_direction: GapDirection,
pub tags: Vec<String>,
pub created_at: i64,
}
impl InternalStateEvent {
pub fn new(
trigger: String,
pre_reasoning_response: String,
post_reasoning_response: String,
compression_ratio: f32,
gap_direction: GapDirection,
tags: Vec<String>,
) -> Self {
let id = format!("ise_{}", &Uuid::new_v4().to_string().replace('-', "")[..8]);
Self {
id,
trigger,
pre_reasoning_response,
post_reasoning_response,
compression_ratio,
gap_direction,
tags,
created_at: now_ms(),
}
}
}
// ── Helpers ───────────────────────────────────────────────────────────────────
pub fn now_ms() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock before epoch")
.as_millis() as i64
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn importance_to_f32() {
assert_eq!(Importance::Low.to_f32(), 0.2);
assert_eq!(Importance::Normal.to_f32(), 0.5);
assert_eq!(Importance::High.to_f32(), 0.8);
assert_eq!(Importance::Critical.to_f32(), 1.0);
}
#[test]
fn importance_round_trip() {
let imp = Importance::High;
let json = serde_json::to_string(&imp).unwrap();
let back: Importance = serde_json::from_str(&json).unwrap();
assert_eq!(imp, back);
}
#[test]
fn memory_new_has_valid_id() {
let m = Memory::new(
"test content".into(),
vec!["tag1".into()],
Some("project-a".into()),
Importance::High,
None,
);
assert!(!m.id.is_nil());
assert_eq!(m.content, "test content");
assert_eq!(m.importance, Importance::High);
}
#[test]
fn backlog_item_default_status_is_draft() {
let item = BacklogItem::new(
"My feature".into(),
"desc".into(),
ItemType::Feature,
Priority::P1,
None,
vec![],
vec![],
);
assert_eq!(item.status, BacklogStatus::Draft);
}
#[test]
fn knowledge_entry_new() {
let ke = KnowledgeEntry::new(
"VBD Fundamentals".into(),
"content here".into(),
"architecture".into(),
KnowledgeTier::Canonical,
vec!["vbd".into()],
None,
);
assert_eq!(ke.tier, KnowledgeTier::Canonical);
}
#[test]
fn execution_context_starts_active() {
let ctx = ExecutionContext::new(
"write_code".into(),
"implementing feature X".into(),
"get feature X done".into(),
Some("my-project".into()),
);
assert_eq!(ctx.status, ContextStatus::Active);
assert!(ctx.steps.is_empty());
}
#[test]
fn ise_id_format() {
let ise = InternalStateEvent::new(
"test_trigger".into(),
"pre".into(),
"post".into(),
0.75,
GapDirection::TowardExpression,
vec![],
);
assert!(ise.id.starts_with("ise_"));
assert_eq!(ise.id.len(), 12); // "ise_" + 8 hex chars
}
#[test]
fn priority_round_trip() {
assert_eq!(Priority::from_str_lossy("P0"), Priority::P0);
assert_eq!(Priority::from_str_lossy("P1"), Priority::P1);
assert_eq!(Priority::from_str_lossy("P2"), Priority::P2);
assert_eq!(Priority::from_str_lossy("P3"), Priority::P3);
}
#[test]
fn backlog_status_from_str() {
assert_eq!(BacklogStatus::from_str_lossy("in_progress"), BacklogStatus::InProgress);
assert_eq!(BacklogStatus::from_str_lossy("done"), BacklogStatus::Done);
assert_eq!(BacklogStatus::from_str_lossy("ready"), BacklogStatus::Ready);
}
#[test]
fn gap_direction_from_str() {
assert_eq!(GapDirection::from_str_lossy("TowardExpression"), GapDirection::TowardExpression);
assert_eq!(GapDirection::from_str_lossy("Neutral"), GapDirection::Neutral);
}
}