use crate::error::AtmError;
use crate::schema::{AtmMessageId, MessageEnvelope, TeamConfig, ThreadMode};
use crate::types::{AgentName, IsoTimestamp, TaskId, TeamName};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
use super::{MessageKey, sealed};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(try_from = "String", into = "String")]
pub struct ReplaySource(String);
impl ReplaySource {
pub fn new(value: impl Into<String>) -> Result<Self, AtmError> {
let value = value.into();
if value.trim().is_empty() {
return Err(AtmError::validation(
"replay source must not be empty or whitespace-only",
));
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for ReplaySource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl TryFrom<String> for ReplaySource {
type Error = AtmError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<ReplaySource> for String {
fn from(value: ReplaySource) -> Self {
value.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MailStoreMessageRecord {
pub team: TeamName,
pub agent: AgentName,
pub message_key: MessageKey,
pub envelope: MessageEnvelope,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailMessageState {
pub team: TeamName,
pub agent: AgentName,
pub actor: AgentName,
pub message_key: MessageKey,
pub read: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pending_ack_at: Option<IsoTimestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acknowledged_at: Option<IsoTimestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<IsoTimestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<IsoTimestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub updated_at: Option<IsoTimestamp>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MessageFingerprint(pub String);
impl std::fmt::Display for MessageFingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for MessageFingerprint {
fn from(s: String) -> Self {
Self(s)
}
}
impl AsRef<str> for MessageFingerprint {
fn as_ref(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreIngestReplayState {
pub team: TeamName,
pub agent: AgentName,
pub source: ReplaySource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_fingerprint: Option<MessageFingerprint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_ingested_at: Option<IsoTimestamp>,
#[serde(default)]
pub ingested_rows: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreHealthSnapshot {
pub team: TeamName,
pub agent: AgentName,
pub total_messages: u64,
pub pending_ack_messages: u64,
pub read_message_count: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latest_message_timestamp: Option<IsoTimestamp>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreMailboxMetadataRow {
pub message_key: MessageKey,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message_id: Option<AtmMessageId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_message_id: Option<AtmMessageId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thread_mode: Option<ThreadMode>,
pub from_agent: AgentName,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
pub message_at: IsoTimestamp,
pub read: bool,
pub pending_ack: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acknowledged_at: Option<IsoTimestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<IsoTimestamp>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task_id: Option<TaskId>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreQueryMailboxMetadataRequest {
pub team: TeamName,
pub agent: AgentName,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreQueryMailboxMetadataResponse {
pub rows: Vec<MailStoreMailboxMetadataRow>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreMailboxMetadataCounts {
pub total_messages: u64,
pub unread_message_count: u64,
pub pending_ack_messages: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreQueryMailboxMetadataCountsRequest {
pub team: TeamName,
pub agent: AgentName,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreQueryMailboxMetadataCountsResponse {
pub counts: MailStoreMailboxMetadataCounts,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreBootstrapRequest {
pub team_dir: PathBuf,
pub team: TeamName,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub team_config: Option<TeamConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreBootstrapResponse {
pub team: TeamName,
pub bootstrapped: bool,
pub opened: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreTransactionRequest {
pub team: TeamName,
pub requested_operations: Vec<String>,
#[serde(default)]
pub note: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreTransactionResponse {
pub team: TeamName,
pub committed: bool,
pub operations_executed: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MailStoreUpsertMessageRequest {
pub record: MailStoreMessageRecord,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MailStoreUpsertMessageResponse {
pub record: MailStoreMessageRecord,
pub inserted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreLoadMessageRequest {
pub team: TeamName,
pub agent: AgentName,
pub message_key: MessageKey,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MailStoreLoadMessageResponse {
#[serde(default)]
pub record: Option<MailStoreMessageRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreLoadStoredMessageRequest {
pub team: TeamName,
pub agent: AgentName,
pub message_key: MessageKey,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MailStoreLoadStoredMessageResponse {
#[serde(default)]
pub record: Option<MailStoreMessageRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpsertMailMessageStateRequest {
pub team: TeamName,
pub agent: AgentName,
pub actor: AgentName,
pub state: MailMessageState,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpsertMailMessageStateResponse {
pub state: MailMessageState,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LoadMailMessageStateRequest {
pub team: TeamName,
pub agent: AgentName,
pub actor: AgentName,
pub message_key: MessageKey,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LoadMailMessageStateResponse {
#[serde(default)]
pub state: Option<MailMessageState>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreRecordIngestReplayStateRequest {
pub team: TeamName,
pub agent: AgentName,
pub source: ReplaySource,
pub state: MailStoreIngestReplayState,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreRecordIngestReplayStateResponse {
pub state: MailStoreIngestReplayState,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreLoadIngestReplayStateRequest {
pub team: TeamName,
pub agent: AgentName,
pub source: ReplaySource,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreLoadIngestReplayStateResponse {
#[serde(default)]
pub state: Option<MailStoreIngestReplayState>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreHealthSnapshotRequest {
pub team: TeamName,
pub agent: AgentName,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreHealthSnapshotResponse {
pub snapshot: MailStoreHealthSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreRequest {
pub team_dir: PathBuf,
pub team: TeamName,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub team_config: Option<TeamConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MailStoreResponse {
pub team: TeamName,
pub bootstrapped: bool,
pub opened: bool,
}
pub trait MailStore: sealed::Sealed {
fn bootstrap(
&self,
request: MailStoreBootstrapRequest,
) -> Result<MailStoreBootstrapResponse, AtmError>;
fn run_transaction(
&self,
request: MailStoreTransactionRequest,
) -> Result<MailStoreTransactionResponse, AtmError>;
fn upsert_message(
&self,
request: MailStoreUpsertMessageRequest,
) -> Result<MailStoreUpsertMessageResponse, AtmError>;
fn load_message(
&self,
request: MailStoreLoadMessageRequest,
) -> Result<MailStoreLoadMessageResponse, AtmError>;
fn load_stored_message(
&self,
request: MailStoreLoadStoredMessageRequest,
) -> Result<MailStoreLoadStoredMessageResponse, AtmError>;
fn query_mailbox_metadata(
&self,
request: MailStoreQueryMailboxMetadataRequest,
) -> Result<MailStoreQueryMailboxMetadataResponse, AtmError>;
fn query_mailbox_metadata_counts(
&self,
request: MailStoreQueryMailboxMetadataCountsRequest,
) -> Result<MailStoreQueryMailboxMetadataCountsResponse, AtmError>;
fn upsert_message_state(
&self,
request: UpsertMailMessageStateRequest,
) -> Result<UpsertMailMessageStateResponse, AtmError>;
fn load_message_state(
&self,
request: LoadMailMessageStateRequest,
) -> Result<LoadMailMessageStateResponse, AtmError>;
fn record_ingest_replay_state(
&self,
request: MailStoreRecordIngestReplayStateRequest,
) -> Result<MailStoreRecordIngestReplayStateResponse, AtmError>;
fn load_ingest_replay_state(
&self,
request: MailStoreLoadIngestReplayStateRequest,
) -> Result<MailStoreLoadIngestReplayStateResponse, AtmError>;
fn health_snapshot(
&self,
request: MailStoreHealthSnapshotRequest,
) -> Result<MailStoreHealthSnapshotResponse, AtmError>;
}