Skip to main content

ferro_ai/confirmation/
mod.rs

1pub mod events;
2pub mod store;
3
4use crate::error::Error;
5use async_trait::async_trait;
6use chrono::{DateTime, Utc};
7use serde::Serialize;
8use std::time::Duration;
9
10pub use events::ConfirmationExpired;
11pub use store::InMemoryConfirmationStore;
12
13/// Metadata about a pending action waiting for confirmation.
14#[derive(Debug, Clone, Serialize)]
15pub struct PendingActionInfo {
16    /// The confirmation key.
17    pub key: String,
18    /// When the action was first queued for confirmation.
19    pub created_at: DateTime<Utc>,
20}
21
22/// Store for managing pending confirmation actions.
23///
24/// Stores type-erased [`serde_json::Value`] payloads keyed by a string.
25/// Each entry has a TTL; when it expires, a [`ConfirmationExpired`] event is
26/// dispatched via [`ferro_events::dispatch_sync`].
27///
28/// # Example
29///
30/// ```rust,ignore
31/// use ferro_ai::{InMemoryConfirmationStore, ConfirmationStore};
32/// use std::time::Duration;
33///
34/// let store = InMemoryConfirmationStore::new(Duration::from_secs(60));
35/// let payload = serde_json::json!({"action": "delete_user", "user_id": 42});
36///
37/// store.request_confirmation("confirm-delete-42", payload, Duration::from_secs(60)).await?;
38/// let confirmed = store.confirm("confirm-delete-42").await?;
39/// // confirmed = Some(serde_json::Value)
40/// ```
41#[async_trait]
42pub trait ConfirmationStore: Send + Sync {
43    /// Store a payload under the given key, replacing any existing entry.
44    ///
45    /// Starts a TTL timer. When the timer expires, the entry is removed and
46    /// a [`ConfirmationExpired`] event is dispatched.
47    async fn request_confirmation(
48        &self,
49        key: &str,
50        payload: serde_json::Value,
51        ttl: Duration,
52    ) -> Result<(), Error>;
53
54    /// Confirm the action identified by `key`.
55    ///
56    /// Removes the entry and aborts its TTL timer.
57    /// Returns the stored payload, or `None` if the key does not exist.
58    async fn confirm(&self, key: &str) -> Result<Option<serde_json::Value>, Error>;
59
60    /// Reject the action identified by `key`.
61    ///
62    /// Removes the entry and aborts its TTL timer without dispatching an
63    /// expiry event.
64    ///
65    /// Returns `true` if the entry existed, `false` if it was not found.
66    async fn reject(&self, key: &str) -> Result<bool, Error>;
67
68    /// Retrieve the stored payload without removing the entry.
69    ///
70    /// Returns `None` if the key does not exist or has already expired.
71    async fn get(&self, key: &str) -> Result<Option<serde_json::Value>, Error>;
72
73    /// List all pending (not yet confirmed, rejected, or expired) entries.
74    async fn list_pending(&self) -> Result<Vec<PendingActionInfo>, Error>;
75}