Skip to main content

made_core/ports/
statistics.rs

1//! [`StatisticsPort`] — operational counter surface.
2//!
3//! Every [`DeliberateUseCase`](super) invocation that completes
4//! records its duration and specialty through this port; every
5//! [`OrchestrateUseCase`](super) invocation records the orchestration
6//! duration. Adapters decide where the numbers live — in-memory for
7//! a single replica, external store for multi-replica setups — and
8//! whatever Prometheus / gRPC exposer the composition root wires
9//! reads [`Statistics`] through `snapshot`.
10
11use async_trait::async_trait;
12
13use crate::entities::Statistics;
14use crate::error::DomainError;
15use crate::value_objects::{AuthorizationEvidence, DurationMs, Specialty};
16
17#[async_trait]
18pub trait StatisticsPort: Send + Sync {
19    /// Record that a deliberation for `specialty` completed in
20    /// `duration`.
21    async fn record_deliberation(
22        &self,
23        specialty: &Specialty,
24        duration: DurationMs,
25    ) -> Result<(), DomainError>;
26    async fn record_deliberation_authorized(
27        &self,
28        specialty: &Specialty,
29        duration: DurationMs,
30        authorization: Option<AuthorizationEvidence>,
31    ) -> Result<(), DomainError> {
32        reject_unsupported(authorization.as_ref())?;
33        self.record_deliberation(specialty, duration).await
34    }
35
36    /// Record that an orchestration (deliberate + execute) completed
37    /// in `duration`.
38    async fn record_orchestration(&self, duration: DurationMs) -> Result<(), DomainError>;
39    async fn record_orchestration_authorized(
40        &self,
41        duration: DurationMs,
42        authorization: Option<AuthorizationEvidence>,
43    ) -> Result<(), DomainError> {
44        reject_unsupported(authorization.as_ref())?;
45        self.record_orchestration(duration).await
46    }
47
48    /// Read-only snapshot. Callers receive a clone so the returned
49    /// value is safe to serialise without holding any lock.
50    async fn snapshot(&self) -> Result<Statistics, DomainError>;
51}
52
53fn reject_unsupported(authorization: Option<&AuthorizationEvidence>) -> Result<(), DomainError> {
54    if authorization.is_some() {
55        return Err(DomainError::InvariantViolated {
56            reason: "statistics adapter cannot persist authorization evidence",
57        });
58    }
59    Ok(())
60}