From 372463bfd48ab40299349618296c85e0b86f344f Mon Sep 17 00:00:00 2001 From: It Apilium Date: Sat, 14 Mar 2026 08:26:09 +0100 Subject: [PATCH 1/4] feat: DAG integration with triple writes and custom actions Triple mutations (create/delete) now automatically generate DagActions in both cluster and non-cluster mode, enabling complete audit trails. - Add Custom variant to DagPayload for explicit user actions - Add subjects field to TripleDelete for subject-based indexing - Add subject_index to DagStore with full lifecycle (put/ingest/rebuild/prune) - Add history_by_subject() for subject-scoped DAG queries - Add POST /api/v1/dag/actions endpoint for custom DagActions - Fix GET /api/v1/dag/history?subject=X to use dedicated subject index - Non-cluster triple writes now record DagActions via dag_store.put() - Handle Custom payload in Raft state machine and export/timetravel --- Cargo.lock | 2 +- crates/aingle_cortex/src/rest/dag.rs | 175 +++++++++++++++++----- crates/aingle_cortex/src/rest/triples.rs | 101 ++++++++++++- crates/aingle_graph/src/dag/action.rs | 18 +++ crates/aingle_graph/src/dag/export.rs | 5 +- crates/aingle_graph/src/dag/store.rs | 101 ++++++++++++- crates/aingle_graph/src/dag/timetravel.rs | 3 +- crates/aingle_graph/src/lib.rs | 14 ++ crates/aingle_raft/src/state_machine.rs | 10 +- 9 files changed, 383 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e611f5..31f16b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,7 +138,7 @@ dependencies = [ [[package]] name = "aingle_cortex" -version = "0.6.0" +version = "0.6.1" dependencies = [ "aingle_graph", "aingle_logic", diff --git a/crates/aingle_cortex/src/rest/dag.rs b/crates/aingle_cortex/src/rest/dag.rs index e0c28ac..6675f1f 100644 --- a/crates/aingle_cortex/src/rest/dag.rs +++ b/crates/aingle_cortex/src/rest/dag.rs @@ -144,6 +144,32 @@ pub struct VerifyQuery { pub public_key: String, } +/// Request body for POST /api/v1/dag/actions. +#[derive(Debug, Deserialize)] +pub struct CreateDagActionRequest { + /// Author identity. Defaults to the node's configured DAG author. + pub author: Option, + /// A descriptive type tag (e.g., "checkpoint", "decision", "annotation"). + pub payload_type: String, + /// A human-readable summary. + pub payload_summary: String, + /// Optional arbitrary payload data. + pub payload: Option, + /// Optional subject for indexing in DAG history. + pub subject: Option, + /// Whether to sign the action. Defaults to true if a signing key is configured. + pub sign: Option, +} + +/// Response for POST /api/v1/dag/actions. +#[derive(Debug, Serialize)] +pub struct CreateDagActionResponse { + pub hash: String, + pub seq: u64, + pub timestamp: String, + pub signed: bool, +} + fn default_limit() -> usize { 50 } @@ -196,12 +222,17 @@ pub async fn get_dag_history( Query(query): Query, ) -> Result>> { let graph = state.graph.read().await; - let dag_store = graph - .dag_store() - .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; - // If triple_id is provided directly, use it - let triple_id_bytes: [u8; 32] = if let Some(ref tid_hex) = query.triple_id { + // Subject-based lookup uses the dedicated subject index + if let Some(ref subject) = query.subject { + let actions = graph + .dag_history_by_subject(subject, query.limit) + .map_err(|e| Error::Internal(e.to_string()))?; + return Ok(Json(actions.iter().map(action_to_dto).collect())); + } + + // Triple-ID-based lookup uses the affected index + if let Some(ref tid_hex) = query.triple_id { let mut bytes = [0u8; 32]; if tid_hex.len() != 64 { return Err(Error::InvalidInput("triple_id must be 64 hex chars".into())); @@ -210,23 +241,16 @@ pub async fn get_dag_history( bytes[i] = u8::from_str_radix(&tid_hex[i * 2..i * 2 + 2], 16) .map_err(|_| Error::InvalidInput("Invalid hex in triple_id".into()))?; } - bytes - } else if let Some(ref subject) = query.subject { - // Compute a lookup key from subject — returns actions mentioning this subject - let mut hasher = blake3::Hasher::new(); - hasher.update(subject.as_bytes()); - *hasher.finalize().as_bytes() - } else { - return Err(Error::InvalidInput( - "Either 'subject' or 'triple_id' query parameter is required".into(), - )); - }; - let actions = dag_store - .history(&triple_id_bytes, query.limit) - .map_err(|e| Error::Internal(e.to_string()))?; + let actions = graph + .dag_history(&bytes, query.limit) + .map_err(|e| Error::Internal(e.to_string()))?; + return Ok(Json(actions.iter().map(action_to_dto).collect())); + } - Ok(Json(actions.iter().map(action_to_dto).collect())) + Err(Error::InvalidInput( + "Either 'subject' or 'triple_id' query parameter is required".into(), + )) } /// GET /api/v1/dag/chain?author=X&limit=N @@ -518,6 +542,74 @@ pub async fn get_dag_diff( })) } +/// POST /api/v1/dag/actions — create an explicit DAG action with arbitrary payload +pub async fn post_create_dag_action( + State(state): State, + Json(req): Json, +) -> Result<(axum::http::StatusCode, Json)> { + if req.payload_type.is_empty() { + return Err(Error::InvalidInput("payload_type cannot be empty".into())); + } + + let dag_author = if let Some(ref author) = req.author { + aingle_graph::NodeId::named(author) + } else { + state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")) + }; + + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + let graph = state.graph.read().await; + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + let parents = dag_store.tips().map_err(|e| Error::Internal(e.to_string()))?; + + let timestamp = chrono::Utc::now(); + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp, + payload: aingle_graph::dag::DagPayload::Custom { + payload_type: req.payload_type, + payload_summary: req.payload_summary, + payload: req.payload, + subject: req.subject, + }, + signature: None, + }; + + // Sign unless explicitly disabled + let should_sign = req.sign.unwrap_or(true); + if should_sign { + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + } + + let signed = action.signature.is_some(); + let hash = dag_store + .put(&action) + .map_err(|e| Error::Internal(e.to_string()))?; + + Ok(( + axum::http::StatusCode::CREATED, + Json(CreateDagActionResponse { + hash: hash.to_hex(), + seq: dag_seq, + timestamp: timestamp.to_rfc3339(), + signed, + }), + )) +} + // ============================================================================ // Router // ============================================================================ @@ -534,7 +626,8 @@ pub fn dag_router() -> Router { .route("/api/v1/dag/diff", get(get_dag_diff)) .route("/api/v1/dag/export", get(get_dag_export)) .route("/api/v1/dag/sync", post(post_dag_sync)) - .route("/api/v1/dag/sync/pull", post(post_dag_pull)); + .route("/api/v1/dag/sync/pull", post(post_dag_pull)) + .route("/api/v1/dag/actions", post(post_create_dag_action)); #[cfg(feature = "dag")] let router = router.route("/api/v1/dag/verify/{hash}", get(get_dag_verify)); @@ -551,14 +644,23 @@ fn action_to_dto(action: &aingle_graph::dag::DagAction) -> DagActionDto { let parents: Vec = action.parents.iter().map(|h| h.to_hex()).collect(); let (payload_type, payload_summary) = match &action.payload { - aingle_graph::dag::DagPayload::TripleInsert { triples } => ( - "TripleInsert".to_string(), - format!("{} triple(s)", triples.len()), - ), - aingle_graph::dag::DagPayload::TripleDelete { triple_ids } => ( - "TripleDelete".to_string(), - format!("{} triple(s)", triple_ids.len()), - ), + aingle_graph::dag::DagPayload::TripleInsert { triples } => { + let summary = if triples.len() == 1 { + let t = &triples[0]; + format!("{} -> {} -> {}", t.subject, t.predicate, t.object) + } else { + format!("{} triple(s)", triples.len()) + }; + ("triple:create".to_string(), summary) + } + aingle_graph::dag::DagPayload::TripleDelete { triple_ids, subjects } => { + let summary = if !subjects.is_empty() { + format!("{} triple(s) [{}]", triple_ids.len(), subjects.join(", ")) + } else { + format!("{} triple(s)", triple_ids.len()) + }; + ("triple:delete".to_string(), summary) + } aingle_graph::dag::DagPayload::MemoryOp { kind } => { let summary = match kind { aingle_graph::dag::MemoryOpKind::Store { entry_type, .. } => { @@ -569,17 +671,17 @@ fn action_to_dto(action: &aingle_graph::dag::DagAction) -> DagActionDto { } aingle_graph::dag::MemoryOpKind::Consolidate => "Consolidate".to_string(), }; - ("MemoryOp".to_string(), summary) + ("memory:op".to_string(), summary) } aingle_graph::dag::DagPayload::Batch { ops } => ( - "Batch".to_string(), + "batch".to_string(), format!("{} ops", ops.len()), ), aingle_graph::dag::DagPayload::Genesis { triple_count, description, } => ( - "Genesis".to_string(), + "genesis".to_string(), format!("{} triples: {}", triple_count, description), ), aingle_graph::dag::DagPayload::Compact { @@ -587,10 +689,15 @@ fn action_to_dto(action: &aingle_graph::dag::DagAction) -> DagActionDto { retained_count, ref policy, } => ( - "Compact".to_string(), + "compact".to_string(), format!("pruned {} / retained {} ({})", pruned_count, retained_count, policy), ), - aingle_graph::dag::DagPayload::Noop => ("Noop".to_string(), String::new()), + aingle_graph::dag::DagPayload::Noop => ("noop".to_string(), String::new()), + aingle_graph::dag::DagPayload::Custom { + payload_type, + payload_summary, + .. + } => (payload_type.clone(), payload_summary.clone()), }; DagActionDto { diff --git a/crates/aingle_cortex/src/rest/triples.rs b/crates/aingle_cortex/src/rest/triples.rs index 92336d7..a36b20f 100644 --- a/crates/aingle_cortex/src/rest/triples.rs +++ b/crates/aingle_cortex/src/rest/triples.rs @@ -334,10 +334,48 @@ pub async fn create_triple( object, ); - // Add triple to graph + // Add triple to graph (and record DAG action if enabled) let triple_id = { let graph = state.graph.read().await; - graph.insert(triple.clone())? + let id = graph.insert(triple.clone())?; + + // Record in DAG if enabled + #[cfg(feature = "dag")] + if let Some(dag_store) = graph.dag_store() { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let parents = dag_store.tips().unwrap_or_default(); + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleInsert { + triples: vec![aingle_graph::dag::TripleInsertPayload { + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: serde_json::to_value(&req.object).unwrap_or_default(), + }], + }, + signature: None, + }; + + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + if let Err(e) = dag_store.put(&action) { + tracing::warn!("Failed to record DAG action for triple insert: {e}"); + } + } + + id }; // Append to WAL (cluster mode without Raft — legacy path) @@ -467,9 +505,15 @@ pub async fn delete_triple( .dag_seq_counter .fetch_add(1, std::sync::atomic::Ordering::SeqCst); - let parents = { + let (parents, subject_for_dag) = { let graph = state.graph.read().await; - graph.dag_tips().unwrap_or_default() + let tips = graph.dag_tips().unwrap_or_default(); + let subj = graph + .get(&triple_id) + .ok() + .flatten() + .map(|t| t.subject.to_string()); + (tips, subj) }; let mut action = aingle_graph::dag::DagAction { @@ -479,6 +523,7 @@ pub async fn delete_triple( timestamp: chrono::Utc::now(), payload: aingle_graph::dag::DagPayload::TripleDelete { triple_ids: vec![*triple_id.as_bytes()], + subjects: subject_for_dag.into_iter().collect(), }, signature: None, }; @@ -550,7 +595,53 @@ pub async fn delete_triple( // Non-cluster mode: direct delete let deleted = { let graph = state.graph.read().await; - graph.delete(&triple_id)? + + // Look up subject before deleting (for DAG indexing) + #[cfg(feature = "dag")] + let subject_for_dag = graph + .get(&triple_id) + .ok() + .flatten() + .map(|t| t.subject.to_string()); + + let deleted = graph.delete(&triple_id)?; + + // Record in DAG if enabled and deletion succeeded + #[cfg(feature = "dag")] + if deleted { + if let Some(dag_store) = graph.dag_store() { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let parents = dag_store.tips().unwrap_or_default(); + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleDelete { + triple_ids: vec![*triple_id.as_bytes()], + subjects: subject_for_dag.into_iter().collect(), + }, + signature: None, + }; + + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + if let Err(e) = dag_store.put(&action) { + tracing::warn!("Failed to record DAG action for triple delete: {e}"); + } + } + } + + deleted }; if deleted { diff --git a/crates/aingle_graph/src/dag/action.rs b/crates/aingle_graph/src/dag/action.rs index 5c08c9f..af9c237 100644 --- a/crates/aingle_graph/src/dag/action.rs +++ b/crates/aingle_graph/src/dag/action.rs @@ -63,6 +63,10 @@ pub enum DagPayload { TripleDelete { /// Content-addressable IDs of the deleted triples. triple_ids: Vec<[u8; 32]>, + /// Subjects of the deleted triples (for subject-based indexing). + /// Empty for actions created before v0.6.2. + #[serde(default)] + subjects: Vec, }, /// A memory subsystem operation. MemoryOp { @@ -92,6 +96,19 @@ pub enum DagPayload { }, /// No-op action (e.g., for linearizable reads). Noop, + /// Custom user-defined action (audit annotations, checkpoints, decisions). + Custom { + /// A descriptive type tag (e.g., "checkpoint", "decision", "annotation"). + payload_type: String, + /// A human-readable summary of the action. + payload_summary: String, + /// Optional arbitrary payload data. + #[serde(default)] + payload: Option, + /// Optional subject for indexing in the DAG history. + #[serde(default)] + subject: Option, + }, } /// Wire format for a triple insert within a DAG action. @@ -300,6 +317,7 @@ mod tests { }, DagPayload::TripleDelete { triple_ids: vec![[0u8; 32]], + subjects: vec![], }, ], }, diff --git a/crates/aingle_graph/src/dag/export.rs b/crates/aingle_graph/src/dag/export.rs index 60adde6..7245255 100644 --- a/crates/aingle_graph/src/dag/export.rs +++ b/crates/aingle_graph/src/dag/export.rs @@ -69,7 +69,7 @@ impl DagGraph { DagPayload::TripleInsert { triples } => { format!("Insert({})", triples.len()) } - DagPayload::TripleDelete { triple_ids } => { + DagPayload::TripleDelete { triple_ids, .. } => { format!("Delete({})", triple_ids.len()) } DagPayload::MemoryOp { .. } => "MemoryOp".into(), @@ -77,6 +77,9 @@ impl DagGraph { DagPayload::Genesis { .. } => "Genesis".into(), DagPayload::Compact { .. } => "Compact".into(), DagPayload::Noop => "Noop".into(), + DagPayload::Custom { ref payload_type, .. } => { + format!("Custom({})", payload_type) + } }; let label = format!("{}\\nseq={} {}", short_id, action.seq, payload_type); diff --git a/crates/aingle_graph/src/dag/store.rs b/crates/aingle_graph/src/dag/store.rs index bcf993f..b30b578 100644 --- a/crates/aingle_graph/src/dag/store.rs +++ b/crates/aingle_graph/src/dag/store.rs @@ -66,6 +66,8 @@ pub struct DagStore { author_index: RwLock>, /// Affected triple index: triple_id → list of action hashes. affected_index: RwLock>>, + /// Subject index: blake3(subject_string) → list of action hashes. + subject_index: RwLock>>, /// Current DAG tips. tips: RwLock, /// Total action count (cached for fast stats). @@ -88,6 +90,7 @@ impl DagStore { backend, author_index: RwLock::new(HashMap::new()), affected_index: RwLock::new(HashMap::new()), + subject_index: RwLock::new(HashMap::new()), tips: RwLock::new(DagTipSet::new()), count: RwLock::new(0), }; @@ -140,6 +143,10 @@ impl DagStore { .affected_index .write() .map_err(|_| crate::Error::Storage("DagStore affected index lock poisoned".into()))?; + let mut subject_idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; let mut count = self .count .write() @@ -147,6 +154,7 @@ impl DagStore { author_idx.clear(); affected_idx.clear(); + subject_idx.clear(); let mut action_count = 0usize; for (key, value) in &entries { @@ -163,6 +171,9 @@ impl DagStore { for triple_id in extract_affected_triple_ids(&action.payload) { affected_idx.entry(triple_id).or_default().push(hash_bytes); } + for subject_hash in extract_subject_hashes(&action.payload) { + subject_idx.entry(subject_hash).or_default().push(hash_bytes); + } action_count += 1; } } @@ -170,6 +181,7 @@ impl DagStore { *count = action_count; drop(author_idx); drop(affected_idx); + drop(subject_idx); drop(count); // Restore tips from backend @@ -236,6 +248,17 @@ impl DagStore { } } + // Update subject index + { + let mut idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + for subject_hash in extract_subject_hashes(&action.payload) { + idx.entry(subject_hash).or_default().push(hash.0); + } + } + // Update tip set and persist { let mut tips = self @@ -369,6 +392,39 @@ impl DagStore { Ok(result) } + /// Get the history of mutations affecting a specific subject. + /// + /// Looks up all DAG actions that contain the given subject string + /// (in TripleInsert, TripleDelete, or Custom payloads). + pub fn history_by_subject(&self, subject: &str, limit: usize) -> crate::Result> { + let subject_hash = *blake3::hash(subject.as_bytes()).as_bytes(); + + let idx = self + .subject_index + .read() + .map_err(|_| crate::Error::Storage("DagStore lock poisoned".into()))?; + + let hashes = match idx.get(&subject_hash) { + Some(h) => h.clone(), + None => return Ok(vec![]), + }; + drop(idx); + + let mut result: Vec = Vec::new(); + for hash in hashes.iter().rev().take(limit) { + if let Some(bytes) = self.backend.get(&action_key(hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + result.push(action); + } + } + } + + result.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + result.truncate(limit); + + Ok(result) + } + /// Total number of stored actions. pub fn action_count(&self) -> usize { self.count.read().map(|c| *c).unwrap_or(0) @@ -464,6 +520,17 @@ impl DagStore { } } + // Update subject index + { + let mut idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + for subject_hash in extract_subject_hashes(&action.payload) { + idx.entry(subject_hash).or_default().push(hash.0); + } + } + // Update count (but NOT tips) { let mut c = self @@ -778,6 +845,16 @@ impl DagStore { !hashes.is_empty() }); + // Clean subject index + let mut subject_idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + subject_idx.retain(|_, hashes| { + hashes.retain(|h| !to_remove.contains(h)); + !hashes.is_empty() + }); + // Update count let mut count = self .count @@ -947,12 +1024,34 @@ fn extract_affected_triple_ids(payload: &DagPayload) -> Vec<[u8; 32]> { .iter() .map(|t| compute_triple_id_from_payload(t)) .collect(), - DagPayload::TripleDelete { triple_ids } => triple_ids.clone(), + DagPayload::TripleDelete { triple_ids, .. } => triple_ids.clone(), DagPayload::Batch { ops } => ops.iter().flat_map(extract_affected_triple_ids).collect(), _ => vec![], } } +/// Extract subject hashes from a payload (for the subject index). +/// +/// Returns `blake3(subject_string)` for each subject mentioned in the payload. +fn extract_subject_hashes(payload: &DagPayload) -> Vec<[u8; 32]> { + match payload { + DagPayload::TripleInsert { triples } => triples + .iter() + .map(|t| *blake3::hash(t.subject.as_bytes()).as_bytes()) + .collect(), + DagPayload::TripleDelete { subjects, .. } => subjects + .iter() + .map(|s| *blake3::hash(s.as_bytes()).as_bytes()) + .collect(), + DagPayload::Custom { subject, .. } => subject + .iter() + .map(|s| *blake3::hash(s.as_bytes()).as_bytes()) + .collect(), + DagPayload::Batch { ops } => ops.iter().flat_map(extract_subject_hashes).collect(), + _ => vec![], + } +} + /// Compute a triple ID from a TripleInsertPayload. /// /// Must match `TripleId::from_triple()` exactly: blake3(subject.to_bytes() || predicate.to_bytes() || object.to_bytes()). diff --git a/crates/aingle_graph/src/dag/timetravel.rs b/crates/aingle_graph/src/dag/timetravel.rs index 966a172..46df01d 100644 --- a/crates/aingle_graph/src/dag/timetravel.rs +++ b/crates/aingle_graph/src/dag/timetravel.rs @@ -63,7 +63,7 @@ pub(crate) fn replay_payload(db: &crate::GraphDB, payload: &DagPayload) -> crate let _ = db.insert(triple); } } - DagPayload::TripleDelete { triple_ids } => { + DagPayload::TripleDelete { triple_ids, .. } => { for tid_bytes in triple_ids { let tid = crate::TripleId::new(*tid_bytes); // Delete of nonexistent triple returns Err(NotFound) — expected after pruning. @@ -139,6 +139,7 @@ mod tests { let payload = DagPayload::TripleDelete { triple_ids: vec![*tid.as_bytes()], + subjects: vec![], }; replay_payload(&db, &payload).unwrap(); assert_eq!(db.count(), 0); diff --git a/crates/aingle_graph/src/lib.rs b/crates/aingle_graph/src/lib.rs index daafdbf..f4b25b3 100644 --- a/crates/aingle_graph/src/lib.rs +++ b/crates/aingle_graph/src/lib.rs @@ -439,6 +439,7 @@ impl GraphDB { timestamp: chrono::Utc::now(), payload: dag::DagPayload::TripleDelete { triple_ids: vec![*triple_id.as_bytes()], + subjects: vec![], }, signature: None, }; @@ -477,6 +478,19 @@ impl GraphDB { .history(triple_id, limit) } + /// Get mutation history for a specific subject string. + #[cfg(feature = "dag")] + pub fn dag_history_by_subject( + &self, + subject: &str, + limit: usize, + ) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .history_by_subject(subject, limit) + } + /// Get an author's action chain in sequence order. #[cfg(feature = "dag")] pub fn dag_chain(&self, author: &NodeId, limit: usize) -> Result> { diff --git a/crates/aingle_raft/src/state_machine.rs b/crates/aingle_raft/src/state_machine.rs index 1ee64c4..cd9bf57 100644 --- a/crates/aingle_raft/src/state_machine.rs +++ b/crates/aingle_raft/src/state_machine.rs @@ -290,7 +290,7 @@ impl CortexStateMachine { } } } - DagPayload::TripleDelete { triple_ids } => { + DagPayload::TripleDelete { triple_ids, .. } => { let graph = self.graph.read().await; for tid in triple_ids { let _ = graph.delete(&aingle_graph::TripleId::new(*tid)); @@ -339,7 +339,7 @@ impl CortexStateMachine { let _ = graph.insert(triple); } } - DagPayload::TripleDelete { triple_ids } => { + DagPayload::TripleDelete { triple_ids, .. } => { for tid in triple_ids { let _ = graph.delete(&aingle_graph::TripleId::new(*tid)); } @@ -347,7 +347,8 @@ impl CortexStateMachine { DagPayload::MemoryOp { .. } | DagPayload::Genesis { .. } | DagPayload::Compact { .. } - | DagPayload::Noop => { + | DagPayload::Noop + | DagPayload::Custom { .. } => { // Audit-only: no graph mutation needed } DagPayload::Batch { .. } => { @@ -367,6 +368,9 @@ impl CortexStateMachine { tracing::info!(pruned_count, retained_count, policy, "Applied DagAction::Compact"); } DagPayload::Noop => {} + DagPayload::Custom { ref payload_type, ref payload_summary, .. } => { + tracing::info!(payload_type, payload_summary, "Applied DagAction::Custom (audit only)"); + } } tracing::debug!(hash = %action_hash, "Applied DagAction"); From 7d2ec054dae98b87c94ea1a511fa1f0fe0e2c77b Mon Sep 17 00:00:00 2001 From: It Apilium Date: Sat, 14 Mar 2026 08:26:22 +0100 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20proof=20verification=20422=20?= =?UTF-8?q?=E2=80=94=20reconstruct=20ZkProof=20envelope=20from=20stored=20?= =?UTF-8?q?data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProofStore.submit() persists only request.proof_data (the raw proof bytes) but ProofVerifier.verify() was deserializing as a full ZkProof which requires proof_type, proof_data, timestamp, and metadata fields. This caused 422 "missing field proof_type" on GET /api/v1/proofs/{id}/verify. Fix: when direct ZkProof deserialization fails, reconstruct the envelope from StoredProof.proof_type metadata + the stored raw proof data. Also injects the ProofData serde type tag when the client omitted it. --- .../aingle_cortex/src/proofs/verification.rs | 111 +++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/crates/aingle_cortex/src/proofs/verification.rs b/crates/aingle_cortex/src/proofs/verification.rs index 227c745..24842c9 100644 --- a/crates/aingle_cortex/src/proofs/verification.rs +++ b/crates/aingle_cortex/src/proofs/verification.rs @@ -89,6 +89,61 @@ impl VerificationResult { } } +/// Reconstruct a `ZkProof` from a `StoredProof` whose `data` field contains +/// only the raw `proof_data` JSON (without the ZkProof envelope). +fn reconstruct_zk_proof(proof: &StoredProof) -> Result { + let mut proof_data: serde_json::Value = serde_json::from_slice(&proof.data) + .map_err(|e| VerificationError::DeserializationError(e.to_string()))?; + + // Inject the ProofData serde tag ("type") if the client omitted it. + // ProofData uses #[serde(tag = "type")] so deserialisation requires it. + if let serde_json::Value::Object(ref mut map) = proof_data { + if !map.contains_key("type") { + let tag = cortex_to_proof_data_tag(&proof.proof_type); + map.insert("type".into(), serde_json::Value::String(tag.into())); + } + } + + let zk_type = cortex_to_zk_proof_type(&proof.proof_type); + + let envelope = serde_json::json!({ + "proof_type": serde_json::to_value(&zk_type) + .map_err(|e| VerificationError::DeserializationError(e.to_string()))?, + "proof_data": proof_data, + "timestamp": proof.created_at.timestamp() as u64, + "metadata": null + }); + + serde_json::from_value(envelope) + .map_err(|e| VerificationError::DeserializationError( + format!("Failed to reconstruct ZkProof envelope: {e}") + )) +} + +/// Map Cortex `ProofType` → `aingle_zk::ProofType`. +fn cortex_to_zk_proof_type(pt: &ProofType) -> aingle_zk::ProofType { + match pt { + ProofType::Schnorr | ProofType::Knowledge | ProofType::HashOpening => { + aingle_zk::ProofType::KnowledgeProof + } + ProofType::Equality => aingle_zk::ProofType::EqualityProof, + ProofType::Membership => aingle_zk::ProofType::MembershipProof, + ProofType::NonMembership => aingle_zk::ProofType::NonMembershipProof, + ProofType::Range => aingle_zk::ProofType::RangeProof, + } +} + +/// Map Cortex `ProofType` → `ProofData` serde tag value. +fn cortex_to_proof_data_tag(pt: &ProofType) -> &'static str { + match pt { + ProofType::Schnorr | ProofType::Knowledge => "Knowledge", + ProofType::HashOpening => "HashOpening", + ProofType::Equality => "Equality", + ProofType::Membership | ProofType::NonMembership => "Membership", + ProofType::Range => "Knowledge", + } +} + /// Proof verifier that integrates with aingle_zk pub struct ProofVerifier { /// Configuration for verification @@ -143,9 +198,13 @@ impl ProofVerifier { ))); } - // Deserialize the proof data into aingle_zk::ZkProof + // Deserialize the proof data into aingle_zk::ZkProof. + // The stored data may be just the raw proof_data (without the ZkProof + // envelope) when submitted via the REST API, since submit() only + // persists request.proof_data. Try full envelope first, then + // reconstruct from StoredProof.proof_type + raw proof data. let zk_proof: aingle_zk::ZkProof = serde_json::from_slice(&proof.data) - .map_err(|e| VerificationError::DeserializationError(e.to_string()))?; + .or_else(|_| reconstruct_zk_proof(proof))?; // Verify based on proof type let valid = match proof.proof_type { @@ -433,6 +492,54 @@ mod tests { assert!(verifier.config.strict_mode); } + /// Simulates the real REST API path: submit() stores only proof_data + /// (without the ZkProof envelope), and verify() must reconstruct it. + #[tokio::test] + async fn test_verify_proof_stored_without_envelope() { + let verifier = ProofVerifier::new(); + + // Create a valid hash-opening proof via aingle_zk + let commitment = aingle_zk::HashCommitment::commit(b"test data"); + let zk_proof = aingle_zk::ZkProof::hash_opening(&commitment); + + // Store ONLY the proof_data portion (what submit() actually does) + let proof_data_only = serde_json::to_vec(&zk_proof.proof_data).unwrap(); + let stored = StoredProof::new( + ProofType::HashOpening, + proof_data_only, + ProofMetadata::default(), + ); + + // This is the exact path that was failing with 422 + let result = verifier.verify(&stored).await; + assert!(result.is_ok(), "verify() should reconstruct ZkProof envelope: {:?}", result.err()); + assert!(result.unwrap().valid); + } + + /// Verify reconstruction works when client sends raw fields without serde tag. + #[tokio::test] + async fn test_verify_proof_without_type_tag() { + let verifier = ProofVerifier::new(); + + // Client sends proof_data as raw JSON without the ProofData "type" tag + let commitment = aingle_zk::HashCommitment::commit(b"test data"); + let raw = serde_json::json!({ + "commitment": commitment.hash, + "salt": commitment.salt, + }); + let raw_bytes = serde_json::to_vec(&raw).unwrap(); + + let stored = StoredProof::new( + ProofType::HashOpening, + raw_bytes, + ProofMetadata::default(), + ); + + let result = verifier.verify(&stored).await; + assert!(result.is_ok(), "verify() should inject type tag: {:?}", result.err()); + assert!(result.unwrap().valid); + } + #[tokio::test] async fn test_proof_size_limit() { let config = VerifierConfig { From 9a40772a1800dbb3551b655d7270e640bd03ba48 Mon Sep 17 00:00:00 2001 From: It Apilium Date: Sat, 14 Mar 2026 08:55:10 +0100 Subject: [PATCH 3/4] fix: change default Cortex port from 8080 to 19090 --- crates/aingle_cortex/src/client.rs | 6 +++--- crates/aingle_cortex/src/lib.rs | 10 +++++----- crates/aingle_cortex/src/main.rs | 4 ++-- crates/aingle_cortex/src/server.rs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/aingle_cortex/src/client.rs b/crates/aingle_cortex/src/client.rs index 26434ff..eebfdb7 100644 --- a/crates/aingle_cortex/src/client.rs +++ b/crates/aingle_cortex/src/client.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; /// Configuration for the Cortex internal client. #[derive(Debug, Clone)] pub struct CortexClientConfig { - /// Base URL of the Cortex REST API (e.g., "http://127.0.0.1:8080"). + /// Base URL of the Cortex REST API (e.g., "http://127.0.0.1:19090"). pub base_url: String, /// Optional authentication token. pub auth_token: Option, @@ -28,7 +28,7 @@ pub struct CortexClientConfig { impl Default for CortexClientConfig { fn default() -> Self { Self { - base_url: "http://127.0.0.1:8080".to_string(), + base_url: "http://127.0.0.1:19090".to_string(), auth_token: None, timeout_ms: 5000, } @@ -332,7 +332,7 @@ mod tests { #[test] fn test_default_config() { let config = CortexClientConfig::default(); - assert_eq!(config.base_url, "http://127.0.0.1:8080"); + assert_eq!(config.base_url, "http://127.0.0.1:19090"); assert!(config.auth_token.is_none()); assert_eq!(config.timeout_ms, 5000); } diff --git a/crates/aingle_cortex/src/lib.rs b/crates/aingle_cortex/src/lib.rs index 1875b7c..dd2b99b 100644 --- a/crates/aingle_cortex/src/lib.rs +++ b/crates/aingle_cortex/src/lib.rs @@ -57,7 +57,7 @@ //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! // Start server on localhost:8080 +//! // Start server on localhost:19090 //! let config = CortexConfig::default(); //! let server = CortexServer::new(config)?; //! server.run().await?; @@ -80,7 +80,7 @@ //! ### Add a Triple //! //! ```bash -//! curl -X POST http://localhost:8080/api/v1/triples \ +//! curl -X POST http://localhost:19090/api/v1/triples \ //! -H "Content-Type: application/json" \ //! -H "Authorization: Bearer YOUR_TOKEN" \ //! -d '{ @@ -93,13 +93,13 @@ //! ### Query Triples //! //! ```bash -//! curl "http://localhost:8080/api/v1/triples?subject=alice" +//! curl "http://localhost:19090/api/v1/triples?subject=alice" //! ``` //! //! ### Validate Proof //! //! ```bash -//! curl -X POST http://localhost:8080/api/v1/proofs/validate \ +//! curl -X POST http://localhost:19090/api/v1/proofs/validate \ //! -H "Content-Type: application/json" \ //! -d '{ //! "proof_type": "schnorr", @@ -110,7 +110,7 @@ //! //! ## GraphQL Examples //! -//! Access the GraphQL playground at `http://localhost:8080/graphql`. +//! Access the GraphQL playground at `http://localhost:19090/graphql`. //! //! ### Query //! diff --git a/crates/aingle_cortex/src/main.rs b/crates/aingle_cortex/src/main.rs index 56f6737..04d4cab 100644 --- a/crates/aingle_cortex/src/main.rs +++ b/crates/aingle_cortex/src/main.rs @@ -45,7 +45,7 @@ async fn main() -> Result<(), Box> { } "--port" | "-p" => { if i + 1 < args.len() { - config.port = args[i + 1].parse().unwrap_or(8080); + config.port = args[i + 1].parse().unwrap_or(19090); i += 1; } } @@ -344,7 +344,7 @@ fn print_help() { println!(); println!("OPTIONS:"); println!(" -h, --host Host to bind to (default: 127.0.0.1)"); - println!(" -p, --port Port to listen on (default: 8080)"); + println!(" -p, --port Port to listen on (default: 19090)"); println!(" --public Bind to all interfaces (0.0.0.0)"); println!(" --db Path to graph database (default: ~/.aingle/cortex/graph.sled)"); println!(" --memory Use volatile in-memory storage (no persistence)"); diff --git a/crates/aingle_cortex/src/server.rs b/crates/aingle_cortex/src/server.rs index 4fcfd23..c923228 100644 --- a/crates/aingle_cortex/src/server.rs +++ b/crates/aingle_cortex/src/server.rs @@ -50,7 +50,7 @@ impl Default for CortexConfig { fn default() -> Self { Self { host: "127.0.0.1".to_string(), - port: 8080, + port: 19090, cors_allowed_origins: vec![], // CORS disabled by default graphql_playground: false, // Disabled by default for security tracing: true, @@ -364,7 +364,7 @@ mod tests { fn test_config_default() { let config = CortexConfig::default(); assert_eq!(config.host, "127.0.0.1"); - assert_eq!(config.port, 8080); + assert_eq!(config.port, 19090); assert!(config.cors_allowed_origins.is_empty()); } From 3195a21f58f152059210f5b0fd952d47ab6f0aee Mon Sep 17 00:00:00 2001 From: It Apilium Date: Sat, 14 Mar 2026 20:36:19 +0100 Subject: [PATCH 4/4] =?UTF-8?q?release:=20bump=20all=20crates=20to=20v0.6.?= =?UTF-8?q?2=20=E2=80=94=20fix=20crash=20scenarios,=20standardize=2019xxx?= =?UTF-8?q?=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminate 5 potential crash scenarios: - Replace nested unwrap in Raft peer fallback with infallible SocketAddr::from - Replace HeaderValue::from_str().unwrap() with infallible From in rate limiter - Change DagGraph::to_json/export to return Result instead of expect/panic - Propagate errors from GraphDB::memory() in AppState::new/with_audit_path - Add safe short_id() helper to prevent string slice out-of-bounds in DAG export Standardize all components to 19xxx port range: - Cortex REST: 19090, P2P: 19091 (already correct) - Minimal REST/WebRTC: 8080 → 19080, QUIC: 8443 → 19081 - Fix OpenAPI spec, docker-compose, tutorials, and examples --- Cargo.lock | 24 ++++++------ README.md | 4 +- crates/aingle_ai/Cargo.toml | 2 +- crates/aingle_contracts/Cargo.toml | 2 +- crates/aingle_cortex/COMPLETION_REPORT.md | 2 +- crates/aingle_cortex/Cargo.toml | 2 +- crates/aingle_cortex/openapi.yaml | 2 +- .../src/middleware/rate_limit.rs | 10 ++--- crates/aingle_cortex/src/p2p/manager.rs | 2 +- crates/aingle_cortex/src/rest/dag.rs | 6 ++- crates/aingle_cortex/src/rest/proof_api.rs | 6 +-- crates/aingle_cortex/src/state.rs | 18 ++++----- .../aingle_cortex/tests/proof_system_test.rs | 2 +- crates/aingle_graph/Cargo.toml | 2 +- crates/aingle_graph/src/dag/export.rs | 37 +++++++++++-------- crates/aingle_logic/Cargo.toml | 2 +- crates/aingle_minimal/Cargo.toml | 2 +- crates/aingle_minimal/README.md | 4 +- crates/aingle_minimal/src/config.rs | 4 +- crates/aingle_minimal/src/network.rs | 20 +++++----- crates/aingle_minimal/src/quic.rs | 10 ++--- crates/aingle_minimal/src/rest.rs | 8 ++-- crates/aingle_minimal/src/sync.rs | 20 +++++----- crates/aingle_minimal/src/webrtc.rs | 10 ++--- .../aingle_minimal/tests/p2p_network_tests.rs | 2 +- crates/aingle_raft/Cargo.toml | 2 +- crates/aingle_raft/src/network.rs | 23 ++++++++---- crates/aingle_viz/Cargo.toml | 2 +- crates/aingle_viz/FEATURES.md | 2 +- crates/aingle_viz/INTEGRATION.md | 8 ++-- crates/aingle_wal/Cargo.toml | 2 +- crates/aingle_zk/Cargo.toml | 2 +- crates/ineru/Cargo.toml | 2 +- crates/kaneru/Cargo.toml | 2 +- docker-compose.yml | 6 +-- docs/tutorials/es/getting-started.md | 8 ++-- docs/tutorials/es/semantic-queries.md | 32 ++++++++-------- docs/tutorials/getting-started.md | 8 ++-- docs/tutorials/semantic-queries.md | 32 ++++++++-------- examples/semantic_compliance/README.md | 2 +- 40 files changed, 177 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31f16b7..e9acac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,7 +97,7 @@ dependencies = [ [[package]] name = "aingle_ai" -version = "0.6.0" +version = "0.6.2" dependencies = [ "blake2", "candle-core 0.9.2", @@ -119,7 +119,7 @@ dependencies = [ [[package]] name = "aingle_contracts" -version = "0.6.0" +version = "0.6.2" dependencies = [ "blake3", "dashmap 6.1.0", @@ -138,7 +138,7 @@ dependencies = [ [[package]] name = "aingle_cortex" -version = "0.6.1" +version = "0.6.2" dependencies = [ "aingle_graph", "aingle_logic", @@ -190,7 +190,7 @@ dependencies = [ [[package]] name = "aingle_graph" -version = "0.6.0" +version = "0.6.2" dependencies = [ "bincode", "blake3", @@ -214,7 +214,7 @@ dependencies = [ [[package]] name = "aingle_logic" -version = "0.6.0" +version = "0.6.2" dependencies = [ "aingle_graph", "chrono", @@ -230,7 +230,7 @@ dependencies = [ [[package]] name = "aingle_minimal" -version = "0.6.0" +version = "0.6.2" dependencies = [ "async-io", "async-tungstenite", @@ -272,7 +272,7 @@ dependencies = [ [[package]] name = "aingle_raft" -version = "0.6.0" +version = "0.6.2" dependencies = [ "aingle_graph", "aingle_wal", @@ -293,7 +293,7 @@ dependencies = [ [[package]] name = "aingle_viz" -version = "0.6.0" +version = "0.6.2" dependencies = [ "aingle_graph", "aingle_minimal", @@ -315,7 +315,7 @@ dependencies = [ [[package]] name = "aingle_wal" -version = "0.6.0" +version = "0.6.2" dependencies = [ "bincode", "blake3", @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "aingle_zk" -version = "0.6.0" +version = "0.6.2" dependencies = [ "blake3", "bulletproofs", @@ -4129,7 +4129,7 @@ dependencies = [ [[package]] name = "ineru" -version = "0.6.0" +version = "0.6.2" dependencies = [ "bincode", "blake3", @@ -4388,7 +4388,7 @@ dependencies = [ [[package]] name = "kaneru" -version = "0.6.0" +version = "0.6.2" dependencies = [ "chrono", "criterion", diff --git a/README.md b/README.md index 3941129..f635ccc 100644 --- a/README.md +++ b/README.md @@ -375,7 +375,7 @@ Official SDKs for integrating AIngle into your applications: ```javascript import { AIngleClient } from '@apilium/aingle-sdk'; -const client = new AIngleClient('http://localhost:8080'); +const client = new AIngleClient('http://localhost:19090'); // Create an entry const hash = await client.createEntry({ sensor: 'temp', value: 23.5 }); @@ -390,7 +390,7 @@ client.subscribe((entry) => { ```bash # Start node with REST API enabled -aingle-minimal run --rest-port 8080 +aingle-minimal run --rest-port 19080 ``` --- diff --git a/crates/aingle_ai/Cargo.toml b/crates/aingle_ai/Cargo.toml index b2bdc16..6c9ddc8 100644 --- a/crates/aingle_ai/Cargo.toml +++ b/crates/aingle_ai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_ai" -version = "0.6.0" +version = "0.6.2" description = "AI integration layer for AIngle - Ineru, Nested Learning, Kaneru" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_contracts/Cargo.toml b/crates/aingle_contracts/Cargo.toml index 8a387e6..e00318d 100644 --- a/crates/aingle_contracts/Cargo.toml +++ b/crates/aingle_contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_contracts" -version = "0.6.0" +version = "0.6.2" description = "Smart Contracts DSL and WASM Runtime for AIngle" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_cortex/COMPLETION_REPORT.md b/crates/aingle_cortex/COMPLETION_REPORT.md index 0623f10..2e3b9de 100644 --- a/crates/aingle_cortex/COMPLETION_REPORT.md +++ b/crates/aingle_cortex/COMPLETION_REPORT.md @@ -284,7 +284,7 @@ use aingle_cortex::{CortexServer, CortexConfig}; #[tokio::main] async fn main() -> Result<(), Box> { let config = CortexConfig::default() - .with_port(8080) + .with_port(19090) .with_rate_limit_rpm(100); let server = CortexServer::new(config)?; diff --git a/crates/aingle_cortex/Cargo.toml b/crates/aingle_cortex/Cargo.toml index 79ec37f..3f400d0 100644 --- a/crates/aingle_cortex/Cargo.toml +++ b/crates/aingle_cortex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_cortex" -version = "0.6.1" +version = "0.6.2" description = "Córtex API - REST/GraphQL/SPARQL interface for AIngle semantic graphs" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_cortex/openapi.yaml b/crates/aingle_cortex/openapi.yaml index 9f67f12..6eab772 100644 --- a/crates/aingle_cortex/openapi.yaml +++ b/crates/aingle_cortex/openapi.yaml @@ -40,7 +40,7 @@ info: url: https://www.apache.org/licenses/LICENSE-2.0.html servers: - - url: http://localhost:8080 + - url: http://localhost:19090 description: Local development server - url: https://api.aingle.apilium.com description: Production server diff --git a/crates/aingle_cortex/src/middleware/rate_limit.rs b/crates/aingle_cortex/src/middleware/rate_limit.rs index dc2ea90..dcf490b 100644 --- a/crates/aingle_cortex/src/middleware/rate_limit.rs +++ b/crates/aingle_cortex/src/middleware/rate_limit.rs @@ -65,10 +65,10 @@ impl IntoResponse for RateLimitError { ) .into_response(); - // Add Retry-After header + // Add Retry-After header (infallible: From for HeaderValue) response.headers_mut().insert( "Retry-After", - HeaderValue::from_str(&secs.to_string()).unwrap(), + HeaderValue::from(*secs), ); // Add rate limit headers @@ -303,15 +303,15 @@ where // Call inner service let mut response = inner.call(req).await?; - // Add rate limit headers + // Add rate limit headers (infallible: From/From) let headers = response.headers_mut(); headers.insert( "X-RateLimit-Limit", - HeaderValue::from_str(&limiter.requests_per_minute.to_string()).unwrap(), + HeaderValue::from(limiter.requests_per_minute), ); headers.insert( "X-RateLimit-Remaining", - HeaderValue::from_str(&remaining.to_string()).unwrap(), + HeaderValue::from(remaining), ); Ok(response) diff --git a/crates/aingle_cortex/src/p2p/manager.rs b/crates/aingle_cortex/src/p2p/manager.rs index 93bbf14..763e27e 100644 --- a/crates/aingle_cortex/src/p2p/manager.rs +++ b/crates/aingle_cortex/src/p2p/manager.rs @@ -998,7 +998,7 @@ mod tests { config.port = 0; // OS-assigned config.data_dir = tempfile::TempDir::new().unwrap().into_path(); - let state = AppState::new(); + let state = AppState::new().unwrap(); let manager = P2pManager::start(config, state).await.unwrap(); assert!(!manager.node_id().is_empty()); diff --git a/crates/aingle_cortex/src/rest/dag.rs b/crates/aingle_cortex/src/rest/dag.rs index 6675f1f..8d7ec16 100644 --- a/crates/aingle_cortex/src/rest/dag.rs +++ b/crates/aingle_cortex/src/rest/dag.rs @@ -107,7 +107,7 @@ pub struct DiffQuery { #[derive(Debug, Deserialize)] pub struct PullRequest { - /// The peer URL to pull from (e.g. "http://node2:8080"). + /// The peer URL to pull from (e.g. "http://node2:19090"). pub peer_url: String, } @@ -332,7 +332,9 @@ pub async fn get_dag_export( .dag_export() .map_err(|e| Error::Internal(e.to_string()))?; - let body = dag_graph.export(format); + let body = dag_graph + .export(format) + .map_err(|e| Error::Internal(e.to_string()))?; let content_type = match format { aingle_graph::dag::ExportFormat::Dot => "text/vnd.graphviz", diff --git a/crates/aingle_cortex/src/rest/proof_api.rs b/crates/aingle_cortex/src/rest/proof_api.rs index 9c44dff..2ca718f 100644 --- a/crates/aingle_cortex/src/rest/proof_api.rs +++ b/crates/aingle_cortex/src/rest/proof_api.rs @@ -380,7 +380,7 @@ mod tests { #[tokio::test] async fn test_submit_and_get_proof() { - let state = AppState::new(); + let state = AppState::new().unwrap(); let request = SubmitProofRequest { proof_type: ProofType::Knowledge, @@ -406,7 +406,7 @@ mod tests { #[tokio::test] async fn test_list_proofs() { - let state = AppState::new(); + let state = AppState::new().unwrap(); // Submit multiple proofs for _ in 0..3 { @@ -433,7 +433,7 @@ mod tests { #[tokio::test] async fn test_proof_stats() { - let state = AppState::new(); + let state = AppState::new().unwrap(); let request = SubmitProofRequest { proof_type: ProofType::Equality, diff --git a/crates/aingle_cortex/src/state.rs b/crates/aingle_cortex/src/state.rs index 6fabe5c..98109fa 100644 --- a/crates/aingle_cortex/src/state.rs +++ b/crates/aingle_cortex/src/state.rs @@ -72,8 +72,8 @@ pub struct AppState { impl AppState { /// Creates a new `AppState` with an in-memory graph database. /// This is useful for testing or development environments. - pub fn new() -> Self { - let graph = GraphDB::memory().expect("Failed to create in-memory graph"); + pub fn new() -> crate::error::Result { + let graph = GraphDB::memory()?; let logic = RuleEngine::new(); let memory = IneruMemory::agent_mode(); @@ -85,7 +85,7 @@ impl AppState { store }; - Self { + Ok(Self { graph: Arc::new(RwLock::new(graph)), logic: Arc::new(RwLock::new(logic)), memory: Arc::new(RwLock::new(memory)), @@ -113,7 +113,7 @@ impl AppState { dag_seq_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)), #[cfg(feature = "dag")] dag_signing_key: None, - } + }) } /// Creates a new `AppState` with a pre-configured `GraphDB` instance. @@ -161,8 +161,8 @@ impl AppState { } /// Creates a new `AppState` with a file-backed audit log. - pub fn with_audit_path(path: std::path::PathBuf) -> Self { - let graph = GraphDB::memory().expect("Failed to create in-memory graph"); + pub fn with_audit_path(path: std::path::PathBuf) -> crate::error::Result { + let graph = GraphDB::memory()?; let logic = RuleEngine::new(); let memory = IneruMemory::agent_mode(); @@ -173,7 +173,7 @@ impl AppState { store }; - Self { + Ok(Self { graph: Arc::new(RwLock::new(graph)), logic: Arc::new(RwLock::new(logic)), memory: Arc::new(RwLock::new(memory)), @@ -201,7 +201,7 @@ impl AppState { dag_seq_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)), #[cfg(feature = "dag")] dag_signing_key: None, - } + }) } /// Creates a new `AppState` with a configurable database path and optional audit log. @@ -342,7 +342,7 @@ impl AppState { impl Default for AppState { fn default() -> Self { - Self::new() + Self::new().expect("Failed to create default AppState with in-memory graph") } } diff --git a/crates/aingle_cortex/tests/proof_system_test.rs b/crates/aingle_cortex/tests/proof_system_test.rs index 826fa45..a0e451c 100644 --- a/crates/aingle_cortex/tests/proof_system_test.rs +++ b/crates/aingle_cortex/tests/proof_system_test.rs @@ -307,7 +307,7 @@ async fn test_schnorr_proof_verification() { #[tokio::test] async fn test_app_state_integration() { - let state = AppState::new(); + let state = AppState::new().unwrap(); // Test that proof store is accessible let count = state.proof_store.count().await; diff --git a/crates/aingle_graph/Cargo.toml b/crates/aingle_graph/Cargo.toml index 4667a05..3639122 100644 --- a/crates/aingle_graph/Cargo.toml +++ b/crates/aingle_graph/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_graph" -version = "0.6.0" +version = "0.6.2" description = "Native GraphDB for AIngle - Semantic triple store with SPO indexes" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_graph/src/dag/export.rs b/crates/aingle_graph/src/dag/export.rs index 7245255..a6c8bba 100644 --- a/crates/aingle_graph/src/dag/export.rs +++ b/crates/aingle_graph/src/dag/export.rs @@ -52,6 +52,13 @@ impl ExportFormat { } } +/// Shorten a hex ID to at most 12 characters for display. +/// Returns the full string if shorter than 12 characters. +fn short_id(id: &str) -> &str { + let end = id.len().min(12); + &id[..end] +} + impl DagGraph { /// Build a graph from a list of actions and their tip status. pub fn from_actions(actions: &[DagAction], tips: &[DagActionHash]) -> Self { @@ -63,7 +70,8 @@ impl DagGraph { for action in actions { let hash = action.compute_hash(); - let short_id = hash.to_hex()[..12].to_string(); + let hex = hash.to_hex(); + let short_label = short_id(&hex).to_string(); let payload_type = match &action.payload { DagPayload::TripleInsert { triples } => { @@ -82,7 +90,7 @@ impl DagGraph { } }; - let label = format!("{}\\nseq={} {}", short_id, action.seq, payload_type); + let label = format!("{}\\nseq={} {}", short_label, action.seq, payload_type); nodes.push(DagNode { id: hash.to_hex(), @@ -119,7 +127,7 @@ impl DagGraph { _ => "#2196F3", } }; - let short = &node.id[..12]; + let short = short_id(&node.id); out.push_str(&format!( " \"{}\" [label=\"{}\\nseq={} {}\", fillcolor=\"{}\", fontcolor=white];\n", short, short, node.seq, node.payload_type, color @@ -131,8 +139,8 @@ impl DagGraph { for edge in &self.edges { out.push_str(&format!( " \"{}\" -> \"{}\";\n", - &edge.from[..12], - &edge.to[..12] + short_id(&edge.from), + short_id(&edge.to) )); } @@ -145,7 +153,7 @@ impl DagGraph { let mut out = String::from("graph BT\n"); for node in &self.nodes { - let short = &node.id[..12]; + let short = short_id(&node.id); let shape = if node.is_tip { format!("{}([\"{} seq={}\"])", short, node.payload_type, node.seq) } else { @@ -157,15 +165,15 @@ impl DagGraph { for edge in &self.edges { out.push_str(&format!( " {} --> {}\n", - &edge.from[..12], - &edge.to[..12] + short_id(&edge.from), + short_id(&edge.to) )); } // Style tips for node in &self.nodes { if node.is_tip { - out.push_str(&format!(" style {} fill:#4CAF50,color:white\n", &node.id[..12])); + out.push_str(&format!(" style {} fill:#4CAF50,color:white\n", short_id(&node.id))); } } @@ -173,16 +181,15 @@ impl DagGraph { } /// Export as JSON string. - pub fn to_json(&self) -> String { + pub fn to_json(&self) -> Result { serde_json::to_string_pretty(self) - .expect("DagGraph serialization must not fail") } /// Export in the given format. - pub fn export(&self, format: ExportFormat) -> String { + pub fn export(&self, format: ExportFormat) -> Result { match format { - ExportFormat::Dot => self.to_dot(), - ExportFormat::Mermaid => self.to_mermaid(), + ExportFormat::Dot => Ok(self.to_dot()), + ExportFormat::Mermaid => Ok(self.to_mermaid()), ExportFormat::Json => self.to_json(), } } @@ -262,7 +269,7 @@ mod tests { fn test_json_roundtrip() { let (actions, tips) = build_linear_chain(); let graph = DagGraph::from_actions(&actions, &tips); - let json = graph.to_json(); + let json = graph.to_json().unwrap(); let back: DagGraph = serde_json::from_str(&json).unwrap(); assert_eq!(back.nodes.len(), 3); diff --git a/crates/aingle_logic/Cargo.toml b/crates/aingle_logic/Cargo.toml index e7d0465..d404501 100644 --- a/crates/aingle_logic/Cargo.toml +++ b/crates/aingle_logic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_logic" -version = "0.6.0" +version = "0.6.2" description = "Proof-of-Logic validation engine for AIngle semantic graphs" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_minimal/Cargo.toml b/crates/aingle_minimal/Cargo.toml index de87b5d..d5fef44 100644 --- a/crates/aingle_minimal/Cargo.toml +++ b/crates/aingle_minimal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_minimal" -version = "0.6.0" +version = "0.6.2" description = "Ultra-light AIngle node for IoT devices (<1MB RAM)" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_minimal/README.md b/crates/aingle_minimal/README.md index 11f05b1..edcc083 100644 --- a/crates/aingle_minimal/README.md +++ b/crates/aingle_minimal/README.md @@ -68,8 +68,8 @@ fn main() -> Result<(), Box> { Enable the `rest` feature to expose an HTTP API for SDK integration: ```bash -# Run with REST API on port 8080 -aingle-minimal run --rest-port 8080 +# Run with REST API on port 19080 +aingle-minimal run --rest-port 19080 ``` ### Endpoints diff --git a/crates/aingle_minimal/src/config.rs b/crates/aingle_minimal/src/config.rs index 13714e5..94459c3 100644 --- a/crates/aingle_minimal/src/config.rs +++ b/crates/aingle_minimal/src/config.rs @@ -90,7 +90,7 @@ impl Default for PowerMode { /// // Use QUIC for production /// let quic = TransportConfig::Quic { /// bind_addr: "0.0.0.0".to_string(), -/// port: 8443, +/// port: 19081, /// }; /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] @@ -704,7 +704,7 @@ impl Config { power_mode: PowerMode::Full, transport: TransportConfig::Quic { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, }, gossip: GossipConfig::default(), storage: StorageConfig::rocksdb(db_path), diff --git a/crates/aingle_minimal/src/network.rs b/crates/aingle_minimal/src/network.rs index 62faa22..a2d3167 100644 --- a/crates/aingle_minimal/src/network.rs +++ b/crates/aingle_minimal/src/network.rs @@ -895,7 +895,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); assert_eq!(network.peer_count(), 1); @@ -1095,7 +1095,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Update peer with new seq @@ -1114,7 +1114,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); // Update peer that doesn't exist - should not panic network.update_peer(addr, 100); assert_eq!(network.peer_count(), 0); @@ -1126,7 +1126,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); let initial_quality = network.active_peers()[0].quality; @@ -1144,7 +1144,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); // Mark peer that doesn't exist - should not panic network.mark_peer_failed(&addr); } @@ -1155,7 +1155,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Active peers should include the just-added peer @@ -1279,7 +1279,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Update many times to try to exceed 100 @@ -1297,7 +1297,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Mark failed many times to try to go below 0 @@ -1316,7 +1316,7 @@ mod tests { let mut network = Network::new(config, gossip, "test-node".to_string()); let addrs: Vec = vec![ - "127.0.0.1:8080".parse().unwrap(), + "127.0.0.1:19080".parse().unwrap(), "127.0.0.1:8081".parse().unwrap(), "127.0.0.1:8082".parse().unwrap(), ]; @@ -1509,7 +1509,7 @@ mod tests { let mut network = Network::new(config, gossip, "test-node".to_string()); // Add some peers - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); network.add_peer(addr1); network.add_peer(addr2); diff --git a/crates/aingle_minimal/src/quic.rs b/crates/aingle_minimal/src/quic.rs index 57202f7..61ff2a4 100644 --- a/crates/aingle_minimal/src/quic.rs +++ b/crates/aingle_minimal/src/quic.rs @@ -53,7 +53,7 @@ impl Default for QuicConfig { fn default() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, keep_alive: Duration::from_secs(10), idle_timeout: Duration::from_secs(30), max_concurrent_streams: 100, @@ -67,7 +67,7 @@ impl QuicConfig { pub fn iot_mode() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, keep_alive: Duration::from_secs(30), idle_timeout: Duration::from_secs(60), max_concurrent_streams: 10, @@ -79,7 +79,7 @@ impl QuicConfig { pub fn production() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, keep_alive: Duration::from_secs(5), idle_timeout: Duration::from_secs(30), max_concurrent_streams: 1000, @@ -424,7 +424,7 @@ mod tests { #[test] fn test_quic_config_default() { let config = QuicConfig::default(); - assert_eq!(config.port, 8443); + assert_eq!(config.port, 19081); assert_eq!(config.max_concurrent_streams, 100); } @@ -454,7 +454,7 @@ mod tests { fn test_quic_server_is_connected() { let config = QuicConfig::default(); let server = QuicServer::new(config, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8443".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19081".parse().unwrap(); assert!(!server.is_connected(&addr)); } } diff --git a/crates/aingle_minimal/src/rest.rs b/crates/aingle_minimal/src/rest.rs index d266f99..19af433 100644 --- a/crates/aingle_minimal/src/rest.rs +++ b/crates/aingle_minimal/src/rest.rs @@ -24,7 +24,7 @@ //! # fn main() -> Result<(), Box> { //! let mut node = MinimalNode::new(Config::iot_mode())?; //! -//! // Start REST server on port 8080 +//! // Start REST server on port 19080 //! let rest_config = RestConfig::default(); //! let server = RestServer::start(rest_config, &mut node)?; //! @@ -47,7 +47,7 @@ use tiny_http::{Header, Method, Request, Response, Server}; pub struct RestConfig { /// Address to bind the server to (default: "0.0.0.0") pub bind_addr: String, - /// Port to listen on (default: 8080) + /// Port to listen on (default: 19080) pub port: u16, /// Enable CORS headers for browser access (default: true) pub enable_cors: bool, @@ -57,7 +57,7 @@ impl Default for RestConfig { fn default() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8080, + port: 19080, enable_cors: true, } } @@ -696,7 +696,7 @@ mod tests { fn test_rest_config_default() { let config = RestConfig::default(); assert_eq!(config.bind_addr, "0.0.0.0"); - assert_eq!(config.port, 8080); + assert_eq!(config.port, 19080); assert!(config.enable_cors); } diff --git a/crates/aingle_minimal/src/sync.rs b/crates/aingle_minimal/src/sync.rs index e5084fc..9ac5935 100644 --- a/crates/aingle_minimal/src/sync.rs +++ b/crates/aingle_minimal/src/sync.rs @@ -424,7 +424,7 @@ mod tests { #[test] fn test_sync_manager_get_peer_state() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let state = manager.get_peer_state(&addr); assert_eq!(state.successful_syncs, 0); @@ -438,7 +438,7 @@ mod tests { #[test] fn test_sync_stats() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); manager.add_local_hash(Hash::from_bytes(&[1; 32])); manager.get_peer_state(&addr).record_success(); @@ -503,7 +503,7 @@ mod tests { #[test] fn test_sync_manager_peers_needing_sync_with_interval() { let mut manager = SyncManager::new(Duration::from_millis(10)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); manager.get_peer_state(&addr); // Immediately after creation, should not need sync (just saw it) @@ -517,7 +517,7 @@ mod tests { #[test] fn test_sync_manager_cleanup_inactive() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1); @@ -535,7 +535,7 @@ mod tests { #[test] fn test_sync_manager_cleanup_inactive_keeps_recent() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); manager.get_peer_state(&addr).record_success(); @@ -590,20 +590,20 @@ mod tests { #[test] fn test_sync_result_debug() { let result = SyncResult { - peer: "127.0.0.1:8080".parse().unwrap(), + peer: "127.0.0.1:19080".parse().unwrap(), sent_filter: true, records_sent: 5, records_received: 3, }; let debug_str = format!("{:?}", result); assert!(debug_str.contains("SyncResult")); - assert!(debug_str.contains("127.0.0.1:8080")); + assert!(debug_str.contains("127.0.0.1:19080")); } #[test] fn test_sync_manager_stats_with_failures() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1).record_success(); @@ -771,7 +771,7 @@ mod tests { let mut manager = SyncManager::new(Duration::from_secs(60)); assert_eq!(manager.peer_states.len(), 0); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1); @@ -918,7 +918,7 @@ mod tests { fn test_sync_manager_cleanup_all_inactive() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1); diff --git a/crates/aingle_minimal/src/webrtc.rs b/crates/aingle_minimal/src/webrtc.rs index 60a213e..33d646d 100644 --- a/crates/aingle_minimal/src/webrtc.rs +++ b/crates/aingle_minimal/src/webrtc.rs @@ -107,7 +107,7 @@ impl Default for WebRtcConfig { turn_server: None, turn_username: None, turn_credential: None, - signaling_port: 8080, + signaling_port: 19080, ice_timeout: Duration::from_secs(30), channel_label: "aingle".to_string(), } @@ -641,7 +641,7 @@ pub struct SignalingConfig { impl Default for SignalingConfig { fn default() -> Self { Self { - bind_addr: "0.0.0.0:8080".to_string(), + bind_addr: "0.0.0.0:19080".to_string(), max_connections: 100, heartbeat_interval: Duration::from_secs(30), connection_timeout: Duration::from_secs(60), @@ -975,7 +975,7 @@ impl SignalingServer { /// ```rust,ignore /// use aingle_minimal::SignalingClient; /// -/// let mut client = SignalingClient::new("ws://localhost:8080", "my-peer-id"); +/// let mut client = SignalingClient::new("ws://localhost:19080", "my-peer-id"); /// /// smol::block_on(async { /// client.connect().await.unwrap(); @@ -1131,7 +1131,7 @@ mod tests { let config = WebRtcConfig::default(); assert!(config.stun_server.contains("stun")); assert!(config.turn_server.is_none()); - assert_eq!(config.signaling_port, 8080); + assert_eq!(config.signaling_port, 19080); } #[test] @@ -1190,7 +1190,7 @@ mod tests { #[test] fn test_signaling_config_default() { let config = SignalingConfig::default(); - assert_eq!(config.bind_addr, "0.0.0.0:8080"); + assert_eq!(config.bind_addr, "0.0.0.0:19080"); assert_eq!(config.max_connections, 100); assert_eq!(config.heartbeat_interval, Duration::from_secs(30)); } diff --git a/crates/aingle_minimal/tests/p2p_network_tests.rs b/crates/aingle_minimal/tests/p2p_network_tests.rs index 4bbe98f..528bb5f 100644 --- a/crates/aingle_minimal/tests/p2p_network_tests.rs +++ b/crates/aingle_minimal/tests/p2p_network_tests.rs @@ -316,7 +316,7 @@ fn test_production_p2p_config() { // Should use QUIC transport match config.transport { aingle_minimal::config::TransportConfig::Quic { port, .. } => { - assert_eq!(port, 8443); + assert_eq!(port, 19081); } _ => panic!("Expected QUIC transport for production mode"), } diff --git a/crates/aingle_raft/Cargo.toml b/crates/aingle_raft/Cargo.toml index accea2a..f4b66a8 100644 --- a/crates/aingle_raft/Cargo.toml +++ b/crates/aingle_raft/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_raft" -version = "0.6.0" +version = "0.6.2" description = "Raft consensus for AIngle clustering" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_raft/src/network.rs b/crates/aingle_raft/src/network.rs index 3dc2dfc..4fcb62a 100644 --- a/crates/aingle_raft/src/network.rs +++ b/crates/aingle_raft/src/network.rs @@ -165,10 +165,19 @@ impl RaftNetworkFactory for CortexNetworkFactory { async fn new_client(&mut self, target: NodeId, node: &CortexNode) -> Self::Network { // Use REST address for HTTP-based Raft RPC routing. + // Fallback is constructed infallibly (no parse) to avoid panics. let addr: SocketAddr = node .rest_addr .parse() - .unwrap_or_else(|_| "127.0.0.1:8080".parse().unwrap()); + .unwrap_or_else(|e| { + tracing::warn!( + target_node = target, + addr = %node.rest_addr, + error = %e, + "Invalid REST address for Raft peer, falling back to localhost:19090" + ); + SocketAddr::from(([127, 0, 0, 1], 19090)) + }); CortexNetworkConnection { target, @@ -442,7 +451,7 @@ mod tests { fn test_cluster_join_roundtrip() { let msg = RaftMessage::ClusterJoin { node_id: 42, - rest_addr: "127.0.0.1:8080".into(), + rest_addr: "127.0.0.1:19090".into(), p2p_addr: "127.0.0.1:19091".into(), }; let json = serde_json::to_string(&msg).unwrap(); @@ -454,7 +463,7 @@ mod tests { p2p_addr, } => { assert_eq!(node_id, 42); - assert_eq!(rest_addr, "127.0.0.1:8080"); + assert_eq!(rest_addr, "127.0.0.1:19090"); assert_eq!(p2p_addr, "127.0.0.1:19091"); } _ => panic!("wrong variant"), @@ -466,7 +475,7 @@ mod tests { let msg = RaftMessage::ClusterJoinAck { accepted: true, leader_id: Some(1), - leader_addr: Some("127.0.0.1:8080".into()), + leader_addr: Some("127.0.0.1:19090".into()), }; let json = serde_json::to_string(&msg).unwrap(); let back: RaftMessage = serde_json::from_str(&json).unwrap(); @@ -491,7 +500,7 @@ mod tests { .register( 1, CortexNode { - rest_addr: "127.0.0.1:8080".into(), + rest_addr: "127.0.0.1:19090".into(), p2p_addr: "127.0.0.1:19091".into(), }, ) @@ -501,7 +510,7 @@ mod tests { .register( 2, CortexNode { - rest_addr: "127.0.0.1:8081".into(), + rest_addr: "127.0.0.1:19092".into(), p2p_addr: "127.0.0.1:19092".into(), }, ) @@ -511,7 +520,7 @@ mod tests { let node = resolver.resolve(&1).await; assert!(node.is_some()); - assert_eq!(node.unwrap().rest_addr, "127.0.0.1:8080"); + assert_eq!(node.unwrap().rest_addr, "127.0.0.1:19090"); resolver.unregister(&1).await; assert_eq!(resolver.node_count().await, 1); diff --git a/crates/aingle_viz/Cargo.toml b/crates/aingle_viz/Cargo.toml index c05cf2c..d4688f1 100644 --- a/crates/aingle_viz/Cargo.toml +++ b/crates/aingle_viz/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_viz" -version = "0.6.0" +version = "0.6.2" description = "DAG Visualization for AIngle - Web-based graph explorer" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_viz/FEATURES.md b/crates/aingle_viz/FEATURES.md index c3e81e4..9198743 100644 --- a/crates/aingle_viz/FEATURES.md +++ b/crates/aingle_viz/FEATURES.md @@ -480,7 +480,7 @@ cargo run --release ``` ### 2. Open Browser -Navigate to: `http://localhost:8080` +Navigate to: `http://localhost:3000` ### 3. Try Features - **Click nodes** to see details diff --git a/crates/aingle_viz/INTEGRATION.md b/crates/aingle_viz/INTEGRATION.md index 824991b..4056331 100644 --- a/crates/aingle_viz/INTEGRATION.md +++ b/crates/aingle_viz/INTEGRATION.md @@ -377,7 +377,7 @@ cargo build --release --bin aingle_viz ```bash export VIZ_HOST=0.0.0.0 # Allow external connections -export VIZ_PORT=8080 # Port number +export VIZ_PORT=3000 # Port number ``` ### HTTPS Setup @@ -393,7 +393,7 @@ server { ssl_certificate_key /path/to/key.pem; location / { - proxy_pass http://localhost:8080; + proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; @@ -414,14 +414,14 @@ RUN cargo build --release --bin aingle_viz FROM debian:bookworm-slim COPY --from=builder /app/target/release/aingle_viz /usr/local/bin/ -EXPOSE 8080 +EXPOSE 3000 CMD ["aingle_viz"] ``` Build and run: ```bash docker build -t aingle-viz . -docker run -p 8080:8080 aingle-viz +docker run -p 3000:3000 aingle-viz ``` ## Support and Troubleshooting diff --git a/crates/aingle_wal/Cargo.toml b/crates/aingle_wal/Cargo.toml index 65310b9..5327a03 100644 --- a/crates/aingle_wal/Cargo.toml +++ b/crates/aingle_wal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_wal" -version = "0.6.0" +version = "0.6.2" description = "Write-Ahead Log for AIngle clustering and replication" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/aingle_zk/Cargo.toml b/crates/aingle_zk/Cargo.toml index 2551177..b864f56 100644 --- a/crates/aingle_zk/Cargo.toml +++ b/crates/aingle_zk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aingle_zk" -version = "0.6.0" +version = "0.6.2" description = "Zero-Knowledge Proofs for AIngle - privacy-preserving cryptographic primitives" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/ineru/Cargo.toml b/crates/ineru/Cargo.toml index af9f4e0..af3c9ee 100644 --- a/crates/ineru/Cargo.toml +++ b/crates/ineru/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ineru" -version = "0.6.0" +version = "0.6.2" description = "Ineru: Neural-inspired memory system for AIngle AI agents" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/crates/kaneru/Cargo.toml b/crates/kaneru/Cargo.toml index fc7901a..a6ed882 100644 --- a/crates/kaneru/Cargo.toml +++ b/crates/kaneru/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kaneru" -version = "0.6.0" +version = "0.6.2" description = "Kaneru: Unified Multi-Agent Execution System for AIngle AI agents" license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" diff --git a/docker-compose.yml b/docker-compose.yml index 58c13e2..87b964a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,16 +67,16 @@ services: container_name: aingle-cortex restart: unless-stopped ports: - - "4000:4000" # REST/GraphQL API + - "19090:19090" # REST/GraphQL API environment: - RUST_LOG=info - - CORTEX_PORT=4000 + - CORTEX_PORT=19090 - AINGLE_ADMIN_URL=ws://aingle:8888 depends_on: - aingle networks: - aingle-network - command: ["aingle-cortex", "--port", "4000"] + command: ["aingle-cortex", "--port", "19090"] # ========================================================================== # Development Environment diff --git a/docs/tutorials/es/getting-started.md b/docs/tutorials/es/getting-started.md index fe7153c..7538639 100644 --- a/docs/tutorials/es/getting-started.md +++ b/docs/tutorials/es/getting-started.md @@ -182,7 +182,7 @@ let config = Config { node_id: Some("node-1".to_string()), transport: TransportConfig::Quic { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, }, enable_mdns: true, // Habilitar descubrimiento automático // ... resto de configuración @@ -205,8 +205,8 @@ use aingle_p2p::NetworkConfig; // Conectar a peers conocidos let network_config = NetworkConfig { bootstrap_nodes: vec![ - "quic://192.168.1.100:8443".to_string(), - "quic://192.168.1.101:8443".to_string(), + "quic://192.168.1.100:19081".to_string(), + "quic://192.168.1.101:19081".to_string(), ], ..Default::default() }; @@ -383,7 +383,7 @@ config.transport = TransportConfig::Quic { config.enable_mdns = true; // Habilitar mDNS // O configurar peers manualmente -let bootstrap_nodes = vec!["quic://192.168.1.100:8443"]; +let bootstrap_nodes = vec!["quic://192.168.1.100:19081"]; ``` --- diff --git a/docs/tutorials/es/semantic-queries.md b/docs/tutorials/es/semantic-queries.md index 10b3270..cb67317 100644 --- a/docs/tutorials/es/semantic-queries.md +++ b/docs/tutorials/es/semantic-queries.md @@ -56,7 +56,7 @@ pub async fn start_cortex_server() -> anyhow::Result<()> { // Configurar servidor let config = CortexConfig { host: "127.0.0.1".to_string(), - port: 8080, + port: 19090, cors_enabled: true, graphql_playground: true, tracing: true, @@ -100,16 +100,16 @@ cargo run **Resultado esperado:** ``` 🚀 Iniciando Córtex API Server... - Host: 127.0.0.1:8080 - REST API: http://127.0.0.1:8080/api/v1 - GraphQL: http://127.0.0.1:8080/graphql - SPARQL: http://127.0.0.1:8080/sparql + Host: 127.0.0.1:19090 + REST API: http://127.0.0.1:19090/api/v1 + GraphQL: http://127.0.0.1:19090/graphql + SPARQL: http://127.0.0.1:19090/sparql -[INFO] Córtex API server listening on 127.0.0.1:8080 +[INFO] Córtex API server listening on 127.0.0.1:19090 ``` **Explicación:** -- **Puerto 8080**: API REST, GraphQL y SPARQL +- **Puerto 19090**: API REST, GraphQL y SPARQL - **CORS enabled**: Permite llamadas desde navegador - **Rate limiting**: Máximo 100 requests/minuto por IP - **GraphQL Playground**: UI interactiva en `/graphql` @@ -189,7 +189,7 @@ Uso: ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { - let client = CortexClient::new("http://127.0.0.1:8080"); + let client = CortexClient::new("http://127.0.0.1:19090"); // Health check client.health_check().await?; @@ -414,7 +414,7 @@ impl GraphQLClient { Uso: ```rust -let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); +let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); // Consultar sensores de temperatura let entries = graphql @@ -509,7 +509,7 @@ impl SparqlClient { ### Query 1: Listar todos los sensores de temperatura ```rust -let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); +let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let query = r#" PREFIX aingle: @@ -781,7 +781,7 @@ impl WebSocketClient { Uso: ```rust -let ws_client = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); +let ws_client = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); // Subscribirse a nuevas entries de sensores IoT ws_client.subscribe_entries(Some("iot_sensors".to_string())).await?; @@ -836,14 +836,14 @@ async fn main() -> anyhow::Result<()> { // 1. REST API println!("═══ REST API ═══"); - let rest = CortexClient::new("http://127.0.0.1:8080"); + let rest = CortexClient::new("http://127.0.0.1:19090"); rest.health_check().await?; let entries = rest.list_entries(5).await?; println!(); // 2. GraphQL println!("═══ GraphQL ═══"); - let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); + let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); let gql_entries = graphql .query_entries("iot_sensors", "temperature", 5) .await?; @@ -851,7 +851,7 @@ async fn main() -> anyhow::Result<()> { // 3. SPARQL println!("═══ SPARQL ═══"); - let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); + let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let sparql_query = r#" PREFIX aingle: SELECT ?entry ?timestamp @@ -866,7 +866,7 @@ async fn main() -> anyhow::Result<()> { // 4. WebSocket (en background) tokio::spawn(async move { - let ws = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); + let ws = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); ws.subscribe_entries(None).await }); @@ -891,7 +891,7 @@ async fn main() -> anyhow::Result<()> { **Solución:** ```bash # Verificar que el servidor Córtex esté ejecutando -curl http://127.0.0.1:8080/api/v1/health +curl http://127.0.0.1:19090/api/v1/health ``` ### Rate limit excedido diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 5f9d483..69f39e4 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -182,7 +182,7 @@ let config = Config { node_id: Some("node-1".to_string()), transport: TransportConfig::Quic { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, }, enable_mdns: true, // Enable automatic discovery // ... rest of configuration @@ -205,8 +205,8 @@ use aingle_p2p::NetworkConfig; // Connect to known peers let network_config = NetworkConfig { bootstrap_nodes: vec![ - "quic://192.168.1.100:8443".to_string(), - "quic://192.168.1.101:8443".to_string(), + "quic://192.168.1.100:19081".to_string(), + "quic://192.168.1.101:19081".to_string(), ], ..Default::default() }; @@ -383,7 +383,7 @@ config.transport = TransportConfig::Quic { config.enable_mdns = true; // Enable mDNS // Or configure peers manually -let bootstrap_nodes = vec!["quic://192.168.1.100:8443"]; +let bootstrap_nodes = vec!["quic://192.168.1.100:19081"]; ``` --- diff --git a/docs/tutorials/semantic-queries.md b/docs/tutorials/semantic-queries.md index 3414f5a..2a029c9 100644 --- a/docs/tutorials/semantic-queries.md +++ b/docs/tutorials/semantic-queries.md @@ -56,7 +56,7 @@ pub async fn start_cortex_server() -> anyhow::Result<()> { // Configure server let config = CortexConfig { host: "127.0.0.1".to_string(), - port: 8080, + port: 19090, cors_enabled: true, graphql_playground: true, tracing: true, @@ -100,16 +100,16 @@ cargo run **Expected output:** ``` 🚀 Starting Cortex API Server... - Host: 127.0.0.1:8080 - REST API: http://127.0.0.1:8080/api/v1 - GraphQL: http://127.0.0.1:8080/graphql - SPARQL: http://127.0.0.1:8080/sparql + Host: 127.0.0.1:19090 + REST API: http://127.0.0.1:19090/api/v1 + GraphQL: http://127.0.0.1:19090/graphql + SPARQL: http://127.0.0.1:19090/sparql -[INFO] Cortex API server listening on 127.0.0.1:8080 +[INFO] Cortex API server listening on 127.0.0.1:19090 ``` **Explanation:** -- **Port 8080**: REST API, GraphQL and SPARQL +- **Port 19090**: REST API, GraphQL and SPARQL - **CORS enabled**: Allows calls from browser - **Rate limiting**: Maximum 100 requests/minute per IP - **GraphQL Playground**: Interactive UI at `/graphql` @@ -189,7 +189,7 @@ Usage: ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { - let client = CortexClient::new("http://127.0.0.1:8080"); + let client = CortexClient::new("http://127.0.0.1:19090"); // Health check client.health_check().await?; @@ -414,7 +414,7 @@ impl GraphQLClient { Usage: ```rust -let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); +let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); // Query temperature sensors let entries = graphql @@ -509,7 +509,7 @@ impl SparqlClient { ### Query 1: List all temperature sensors ```rust -let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); +let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let query = r#" PREFIX aingle: @@ -781,7 +781,7 @@ impl WebSocketClient { Usage: ```rust -let ws_client = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); +let ws_client = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); // Subscribe to new IoT sensor entries ws_client.subscribe_entries(Some("iot_sensors".to_string())).await?; @@ -836,14 +836,14 @@ async fn main() -> anyhow::Result<()> { // 1. REST API println!("═══ REST API ═══"); - let rest = CortexClient::new("http://127.0.0.1:8080"); + let rest = CortexClient::new("http://127.0.0.1:19090"); rest.health_check().await?; let entries = rest.list_entries(5).await?; println!(); // 2. GraphQL println!("═══ GraphQL ═══"); - let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); + let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); let gql_entries = graphql .query_entries("iot_sensors", "temperature", 5) .await?; @@ -851,7 +851,7 @@ async fn main() -> anyhow::Result<()> { // 3. SPARQL println!("═══ SPARQL ═══"); - let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); + let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let sparql_query = r#" PREFIX aingle: SELECT ?entry ?timestamp @@ -866,7 +866,7 @@ async fn main() -> anyhow::Result<()> { // 4. WebSocket (in background) tokio::spawn(async move { - let ws = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); + let ws = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); ws.subscribe_entries(None).await }); @@ -891,7 +891,7 @@ async fn main() -> anyhow::Result<()> { **Solution:** ```bash # Verify that the Cortex server is running -curl http://127.0.0.1:8080/api/v1/health +curl http://127.0.0.1:19090/api/v1/health ``` ### Rate limit exceeded diff --git a/examples/semantic_compliance/README.md b/examples/semantic_compliance/README.md index 39be478..8ea1093 100644 --- a/examples/semantic_compliance/README.md +++ b/examples/semantic_compliance/README.md @@ -601,7 +601,7 @@ docker build -t semantic-compliance . # Run container docker run -d \ - -p 8080:8080 \ + -p 19090:19090 \ -v $(pwd)/config:/app/config \ -v $(pwd)/data:/app/data \ semantic-compliance