agent-team-mail-core 1.3.1

Core library for local agent team mail workflows.
Documentation
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::boundary::{ConfigDoctorReport, MailStoreDoctorReport, RosterStoreDoctorReport};
use crate::error_codes::AtmErrorCode;
use crate::observability::AtmObservabilityHealth;
use crate::protocol::{ReleaseVersion, RuntimeStatusSnapshot};
use crate::team_admin::MembersList;
use crate::types::{AgentName, TeamName};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DoctorSeverity {
    Info,
    Warning,
    Error,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DoctorStatus {
    Healthy,
    Warning,
    Error,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DoctorFinding {
    pub severity: DoctorSeverity,
    pub code: AtmErrorCode,
    pub message: String,
    pub remediation: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DoctorSummary {
    pub status: DoctorStatus,
    pub message: String,
    pub info_count: usize,
    pub warning_count: usize,
    pub error_count: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DoctorEnvironmentVisibility {
    pub atm_home: Option<PathBuf>,
    pub atm_team: Option<TeamName>,
    pub atm_identity: Option<AgentName>,
    pub team_override: Option<TeamName>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct DoctorExecutionContext {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub team: Option<TeamName>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub identity: Option<AgentName>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub version: Option<ReleaseVersion>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BootstrapConnectOutcome {
    Connected,
    NotFound,
    Timeout,
    Failed,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BootstrapLaunchGateOutcome {
    Launched,
    Failed,
    Skipped,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BootstrapAutoStartOutcome {
    AutoStarted,
    Failed,
    Skipped,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BootstrapTraceReport {
    pub daemon_connect: BootstrapConnectOutcome,
    pub daemon_launch_gate: BootstrapLaunchGateOutcome,
    pub daemon_auto_start: BootstrapAutoStartOutcome,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub connect_detail: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub launch_gate_detail: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub auto_start_detail: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct DaemonRuntimeDoctorReport {
    pub findings: Vec<DoctorFinding>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PostSendHookRuleReport {
    pub recipient_matcher: String,
    pub executable: PathBuf,
    pub argv: Vec<String>,
    pub config_root: PathBuf,
}

/// Stable, zero-based position of an external post-send rule in the active
/// configuration.  Keeping this distinct from arbitrary integers prevents a
/// consumer from treating a recipient index as a hook-rule reference.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct PostSendHookRuleIndex(pub u32);

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RecipientDeliveryPath {
    BuiltIn,
    ExternalOverride { rule: PostSendHookRuleIndex },
    Disabled,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RecipientDeliveryPathReport {
    pub recipient: AgentName,
    pub path: RecipientDeliveryPath,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct PostSendDoctorReport {
    pub config_root: PathBuf,
    pub external_rules: Vec<PostSendHookRuleReport>,
    pub recipient_paths: Vec<RecipientDeliveryPathReport>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DoctorReport {
    pub summary: DoctorSummary,
    pub findings: Vec<DoctorFinding>,
    pub recommendations: Vec<String>,
    pub environment: DoctorEnvironmentVisibility,
    #[serde(default)]
    pub client_context: DoctorExecutionContext,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub daemon_context: Option<DoctorExecutionContext>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub member_roster: Option<MembersList>,
    pub observability: AtmObservabilityHealth,
    #[serde(default)]
    pub post_send: PostSendDoctorReport,
    pub config: ConfigDoctorReport,
    pub mail_store: MailStoreDoctorReport,
    pub roster_store: RosterStoreDoctorReport,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub daemon_runtime: Option<DaemonRuntimeDoctorReport>,
    #[serde(default)]
    pub drift_findings: Vec<DoctorFinding>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub runtime_status: Option<RuntimeStatusSnapshot>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub bootstrap_trace: Option<BootstrapTraceReport>,
}

impl DoctorReport {
    pub fn has_errors(&self) -> bool {
        self.summary.status == DoctorStatus::Error
    }
}