refactor(core): remove fabricated e2ee/ai/plugin modules #261
No files matched your search
@@ -1,145 +0,0 @@
|
||||
//! Autonomous Local LLM Email Summarization & Action Item Extraction Engine.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Thread executive summary output containing bullet points and key topics.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ThreadSummary {
|
||||
pub bullets: Vec<String>,
|
||||
pub sentiment: String,
|
||||
pub primary_topic: String,
|
||||
}
|
||||
|
||||
/// Extracted actionable item from an email thread.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ActionItem {
|
||||
pub task_description: String,
|
||||
pub suggested_due_date: Option<String>,
|
||||
pub assignee_email: Option<String>,
|
||||
}
|
||||
|
||||
/// Errors emitted by the Local LLM AI Engine.
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum AiEngineError {
|
||||
#[error("ollama connection failed: {0}")]
|
||||
ConnectionFailed(String),
|
||||
|
||||
#[error("inference failed: {0}")]
|
||||
InferenceFailed(String),
|
||||
|
||||
#[error("empty email body provided")]
|
||||
EmptyInput,
|
||||
}
|
||||
|
||||
/// Local LLM AI Connector (Ollama / Llama.cpp local HTTP API endpoint).
|
||||
pub struct LocalAiEngine {
|
||||
endpoint_url: String,
|
||||
model_name: String,
|
||||
}
|
||||
|
||||
impl LocalAiEngine {
|
||||
/// Create a new `LocalAiEngine` pointing to local Ollama / Llama.cpp HTTP service (e.g. `http://127.0.0.1:11434`).
|
||||
pub fn new(endpoint_url: &str, model_name: &str) -> Self {
|
||||
Self {
|
||||
endpoint_url: endpoint_url.to_string(),
|
||||
model_name: model_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct default instance targeting `http://127.0.0.1:11434` with model `llama3:8b`.
|
||||
pub fn default_local() -> Self {
|
||||
Self::new("http://127.0.0.1:11434", "llama3:8b")
|
||||
}
|
||||
|
||||
/// Retrieve configured Ollama endpoint URL.
|
||||
pub fn endpoint_url(&self) -> &str {
|
||||
&self.endpoint_url
|
||||
}
|
||||
|
||||
/// Retrieve configured LLM model name.
|
||||
pub fn model_name(&self) -> &str {
|
||||
&self.model_name
|
||||
}
|
||||
|
||||
/// Generate executive 3-bullet summary of an email thread.
|
||||
pub fn summarize_thread(&self, email_body: &str) -> Result<ThreadSummary, AiEngineError> {
|
||||
if email_body.trim().is_empty() {
|
||||
return Err(AiEngineError::EmptyInput);
|
||||
}
|
||||
|
||||
// Local inference parser
|
||||
let bullets = vec![
|
||||
"Discussion regarding upcoming Q3 roadmap and feature milestones.".to_string(),
|
||||
"Requested confirmation for team meeting scheduled next Tuesday.".to_string(),
|
||||
"Action required: Review security audit findings before deployment.".to_string(),
|
||||
];
|
||||
|
||||
Ok(ThreadSummary {
|
||||
bullets,
|
||||
sentiment: "Professional / Action-Oriented".to_string(),
|
||||
primary_topic: "Project Synchronization".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract actionable tasks and suggested calendar events from email content.
|
||||
pub fn extract_action_items(&self, email_body: &str) -> Result<Vec<ActionItem>, AiEngineError> {
|
||||
if email_body.trim().is_empty() {
|
||||
return Err(AiEngineError::EmptyInput);
|
||||
}
|
||||
|
||||
Ok(vec![
|
||||
ActionItem {
|
||||
task_description: "Review security audit report".to_string(),
|
||||
suggested_due_date: Some("2026-07-25".to_string()),
|
||||
assignee_email: Some("james@kof22.com".to_string()),
|
||||
},
|
||||
ActionItem {
|
||||
task_description: "Confirm Q3 deployment schedule".to_string(),
|
||||
suggested_due_date: Some("2026-07-28".to_string()),
|
||||
assignee_email: None,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
/// Generate 3 microsecond smart reply suggestions.
|
||||
pub fn generate_smart_replies(&self, _email_body: &str) -> Vec<String> {
|
||||
vec![
|
||||
"Sounds good, let's proceed!".to_string(),
|
||||
"Thanks for the update, I'll review this shortly.".to_string(),
|
||||
"Could we reschedule for later this week?".to_string(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn summarize_thread_returns_valid_structure() {
|
||||
let engine = LocalAiEngine::default_local();
|
||||
let summary = engine
|
||||
.summarize_thread("Meeting notes for Q3 project...")
|
||||
.unwrap();
|
||||
assert_eq!(summary.bullets.len(), 3);
|
||||
assert!(!summary.sentiment.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_action_items_returns_items() {
|
||||
let engine = LocalAiEngine::default_local();
|
||||
let items = engine
|
||||
.extract_action_items("Please review the audit by Friday.")
|
||||
.unwrap();
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(items[0].task_description, "Review security audit report");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_smart_replies_returns_three_options() {
|
||||
let engine = LocalAiEngine::default_local();
|
||||
let replies = engine.generate_smart_replies("Can we talk?");
|
||||
assert_eq!(replies.len(), 3);
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
//! End-to-End Encryption (E2EE) module implementing OpenPGP (RFC 3156) and S/MIME (RFC 8551) validation.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Cryptographic signature type attached to an email payload.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum EncryptionType {
|
||||
/// PGP/MIME or Inline OpenPGP.
|
||||
OpenPGP,
|
||||
/// S/MIME PKCS#7 / CMS signature.
|
||||
Smime,
|
||||
/// Unencrypted / Unsigned plain message.
|
||||
None,
|
||||
}
|
||||
|
||||
/// Status verdict after cryptographic signature verification.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SignatureStatus {
|
||||
/// Valid cryptographic signature matching sender key.
|
||||
Valid {
|
||||
signer_fingerprint: String,
|
||||
key_id: String,
|
||||
},
|
||||
/// Invalid signature or tampered ciphertext payload.
|
||||
Invalid { reason: String },
|
||||
/// Untrusted public key or unknown certificate authority.
|
||||
UntrustedKey { fingerprint: String },
|
||||
/// No signature present.
|
||||
Unsigned,
|
||||
}
|
||||
|
||||
/// Security badge representation for presentation layers (CLI, TUI, GUI, MCP).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SecurityBadge {
|
||||
pub encryption_type: EncryptionType,
|
||||
pub signature_status: SignatureStatus,
|
||||
pub is_encrypted: bool,
|
||||
pub badge_label: String,
|
||||
}
|
||||
|
||||
/// Errors originating from E2EE operations.
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum E2eeError {
|
||||
#[error("failed to verify signature: {0}")]
|
||||
VerificationFailed(String),
|
||||
|
||||
#[error("decryption failed: {0}")]
|
||||
DecryptionFailed(String),
|
||||
|
||||
#[error("key not found: {0}")]
|
||||
KeyNotFound(String),
|
||||
}
|
||||
|
||||
/// Cryptographic E2EE Engine.
|
||||
pub struct E2eeEngine;
|
||||
|
||||
impl E2eeEngine {
|
||||
/// Verify an OpenPGP signature string against a sender identity.
|
||||
pub fn verify_openpgp_signature(
|
||||
payload: &str,
|
||||
signature: &str,
|
||||
expected_fingerprint: &str,
|
||||
) -> Result<SecurityBadge, E2eeError> {
|
||||
if payload.is_empty() || signature.is_empty() {
|
||||
return Ok(SecurityBadge {
|
||||
encryption_type: EncryptionType::OpenPGP,
|
||||
signature_status: SignatureStatus::Invalid {
|
||||
reason: "empty payload or signature".to_string(),
|
||||
},
|
||||
is_encrypted: false,
|
||||
badge_label: "PGP INVALID".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Simulating Sequoia / OpenPGP verification verification logic
|
||||
let valid = signature.contains("BEGIN PGP SIGNATURE") && expected_fingerprint.len() >= 8;
|
||||
|
||||
if valid {
|
||||
Ok(SecurityBadge {
|
||||
encryption_type: EncryptionType::OpenPGP,
|
||||
signature_status: SignatureStatus::Valid {
|
||||
signer_fingerprint: expected_fingerprint.to_string(),
|
||||
key_id: expected_fingerprint.chars().take(8).collect(),
|
||||
},
|
||||
is_encrypted: true,
|
||||
badge_label: "PGP SIGNED & ENCRYPTED".to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(SecurityBadge {
|
||||
encryption_type: EncryptionType::OpenPGP,
|
||||
signature_status: SignatureStatus::UntrustedKey {
|
||||
fingerprint: expected_fingerprint.to_string(),
|
||||
},
|
||||
is_encrypted: false,
|
||||
badge_label: "PGP UNTRUSTED KEY".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify an S/MIME PKCS#7 signature payload.
|
||||
pub fn verify_smime_signature(
|
||||
_payload: &[u8],
|
||||
signature_pkcs7: &[u8],
|
||||
signer_email: &str,
|
||||
) -> Result<SecurityBadge, E2eeError> {
|
||||
if signature_pkcs7.is_empty() {
|
||||
return Ok(SecurityBadge {
|
||||
encryption_type: EncryptionType::Smime,
|
||||
signature_status: SignatureStatus::Unsigned,
|
||||
is_encrypted: false,
|
||||
badge_label: "S/MIME UNSIGNED".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(SecurityBadge {
|
||||
encryption_type: EncryptionType::Smime,
|
||||
signature_status: SignatureStatus::Valid {
|
||||
signer_fingerprint: format!("SMIME-{signer_email}"),
|
||||
key_id: signer_email.to_string(),
|
||||
},
|
||||
is_encrypted: true,
|
||||
badge_label: "S/MIME VERIFIED".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn verify_openpgp_valid_signature() {
|
||||
let payload = "Hello world";
|
||||
let sig = "-----BEGIN PGP SIGNATURE-----\nVersion: Sequoia\n-----END PGP SIGNATURE-----";
|
||||
let fp = "4A8B9C0D1E2F3A4B5C6D7E8F9A0B1C2D3E4F5A6B";
|
||||
|
||||
let badge = E2eeEngine::verify_openpgp_signature(payload, sig, fp).unwrap();
|
||||
assert_eq!(badge.encryption_type, EncryptionType::OpenPGP);
|
||||
assert!(badge.is_encrypted);
|
||||
assert!(matches!(
|
||||
badge.signature_status,
|
||||
SignatureStatus::Valid { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_smime_valid_signature() {
|
||||
let payload = b"Hello S/MIME";
|
||||
let sig = b"PKCS7_SIGNATURE_BYTES";
|
||||
|
||||
let badge = E2eeEngine::verify_smime_signature(payload, sig, "alice@kof22.com").unwrap();
|
||||
assert_eq!(badge.encryption_type, EncryptionType::Smime);
|
||||
assert_eq!(badge.badge_label, "S/MIME VERIFIED");
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,17 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
|
||||
|
||||
pub mod ai;
|
||||
pub mod config;
|
||||
pub mod e2ee;
|
||||
pub mod export;
|
||||
pub mod mcp_policy;
|
||||
pub mod model;
|
||||
pub mod plugin;
|
||||
pub mod update;
|
||||
pub mod worm_audit;
|
||||
|
||||
pub use ai::{ActionItem, AiEngineError, LocalAiEngine, ThreadSummary};
|
||||
pub use config::{AccountConfig, AccountProtocol, ConfigError, TlsMode};
|
||||
pub use e2ee::{E2eeEngine, E2eeError, EncryptionType, SecurityBadge, SignatureStatus};
|
||||
pub use export::{ExportEngine, ExportError, ExportFormat, ExportSummary};
|
||||
pub use mcp_policy::{AgentPermissions, DataType, McpAgentPolicy};
|
||||
pub use model::{Attachment, CalendarEvent, Contact, DaemonTelemetry, Email, Folder};
|
||||
pub use plugin::{PluginError, PluginHook, PluginManifest, PluginRuntime};
|
||||
pub use update::{ReleaseInfo, UpdateCheckResult, UpdateEngine, UpdateError};
|
||||
pub use worm_audit::{verify_worm_chain, WormAuditError, WormAuditRecord};
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
//! Sandboxed Plugin Runtime Engine for custom automation scripts.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Event triggers that plugins can subscribe to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PluginHook {
|
||||
OnMailReceived,
|
||||
OnMailSending,
|
||||
OnCalendarRsvp,
|
||||
OnFilterRuleExecuted,
|
||||
}
|
||||
|
||||
/// Metadata definition of an installed plugin package.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PluginManifest {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub author: String,
|
||||
pub hooks: Vec<PluginHook>,
|
||||
pub permissions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Errors emitted by the plugin runtime.
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum PluginError {
|
||||
#[error("plugin execution failed: {0}")]
|
||||
ExecutionFailed(String),
|
||||
|
||||
#[error("permission denied: {0}")]
|
||||
PermissionDenied(String),
|
||||
|
||||
#[error("plugin manifest invalid: {0}")]
|
||||
InvalidManifest(String),
|
||||
}
|
||||
|
||||
/// Sandboxed Plugin Runtime Manager.
|
||||
pub struct PluginRuntime {
|
||||
installed_plugins: Vec<PluginManifest>,
|
||||
}
|
||||
|
||||
impl PluginRuntime {
|
||||
/// Create a new plugin runtime instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
installed_plugins: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new sandboxed plugin manifest.
|
||||
pub fn register_plugin(&mut self, manifest: PluginManifest) -> Result<(), PluginError> {
|
||||
if manifest.id.is_empty() {
|
||||
return Err(PluginError::InvalidManifest(
|
||||
"missing plugin id".to_string(),
|
||||
));
|
||||
}
|
||||
self.installed_plugins.push(manifest);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all currently installed plugin manifests.
|
||||
pub fn list_plugins(&self) -> &[PluginManifest] {
|
||||
&self.installed_plugins
|
||||
}
|
||||
|
||||
/// Execute subscribed plugins for a specific event hook.
|
||||
pub fn trigger_hook(
|
||||
&self,
|
||||
hook: PluginHook,
|
||||
event_payload_json: &str,
|
||||
) -> Vec<Result<String, PluginError>> {
|
||||
self.installed_plugins
|
||||
.iter()
|
||||
.filter(|p| p.hooks.contains(&hook))
|
||||
.map(|p| {
|
||||
if event_payload_json.is_empty() {
|
||||
Err(PluginError::ExecutionFailed("empty payload".to_string()))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"plugin {} executed successfully for hook {:?}",
|
||||
p.id, hook
|
||||
))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PluginRuntime {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn register_and_list_plugins() {
|
||||
let mut runtime = PluginRuntime::new();
|
||||
let manifest = PluginManifest {
|
||||
id: "auto-tagger".to_string(),
|
||||
name: "Auto Email Tagger".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
author: "KofTwentyTwo".to_string(),
|
||||
hooks: vec![PluginHook::OnMailReceived],
|
||||
permissions: vec!["mail:read".to_string()],
|
||||
};
|
||||
|
||||
runtime.register_plugin(manifest).expect("register");
|
||||
assert_eq!(runtime.list_plugins().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_hook_executes_registered_plugins() {
|
||||
let mut runtime = PluginRuntime::new();
|
||||
runtime
|
||||
.register_plugin(PluginManifest {
|
||||
id: "p1".to_string(),
|
||||
name: "P1".to_string(),
|
||||
version: "0.1.0".to_string(),
|
||||
author: "Dev".to_string(),
|
||||
hooks: vec![PluginHook::OnMailReceived],
|
||||
permissions: vec![],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let results = runtime.trigger_hook(PluginHook::OnMailReceived, r#"{"msg":"hello"}"#);
|
||||
assert_eq!(results.len(), 1);
|
||||
assert!(results[0].is_ok());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user