Skip to main content

koprogo_api/application/ports/
syndic_response_repository.rs

1//! Port for persisting [`SyndicResponse`] entities (Story 3.7 — FR32 INV-23).
2//!
3//! All methods return `Result<_, AppError>` natively — no legacy `String`
4//! error debt to migrate later (CRITICAL.md #4 / #555).
5//!
6//! The repository also exposes the SLA-escalation tracking primitives
7//! ([`SyndicResponseRepository::find_overdue_tickets`],
8//! [`SyndicResponseRepository::mark_ticket_escalated`]) used by the cron job
9//! [`crate::infrastructure::jobs::sla_escalation_job::SlaEscalationJob`].
10//! These primitives are intentionally on this repository (and not on
11//! `TicketRepository`) because they are part of the SyndicResponse / SLA
12//! bounded responsibility and the existing `TicketRepository` still carries
13//! the legacy `Result<_, String>` surface (cluster #555 migration WIP).
14
15use crate::application::error::AppError;
16use crate::domain::entities::SyndicResponse;
17use async_trait::async_trait;
18use chrono::{DateTime, Utc};
19use uuid::Uuid;
20
21#[async_trait]
22pub trait SyndicResponseRepository: Send + Sync {
23    /// Persist a freshly minted response. Implementation MUST NOT attempt
24    /// `UPDATE ... ON CONFLICT` — the table is append-only and the DB
25    /// trigger guards against any subsequent mutation (INV-23).
26    async fn save(&self, response: &SyndicResponse) -> Result<(), AppError>;
27
28    /// List every response attached to a ticket, oldest first (audit order).
29    async fn list_for_ticket(&self, ticket_id: Uuid) -> Result<Vec<SyndicResponse>, AppError>;
30
31    /// Return the ids of tickets whose SLA deadline (`sla_due_at`) is past
32    /// `now` AND that have not been escalated yet
33    /// (`sla_escalated_at IS NULL`). Cron entry point.
34    async fn find_overdue_tickets(&self, now: DateTime<Utc>) -> Result<Vec<Uuid>, AppError>;
35
36    /// Mark a ticket as SLA-escalated. Idempotent: if the column already
37    /// carries a timestamp, the implementation SHOULD leave it untouched
38    /// (audit fidelity — first event wins).
39    async fn mark_ticket_escalated(
40        &self,
41        ticket_id: Uuid,
42        escalated_at: DateTime<Utc>,
43    ) -> Result<(), AppError>;
44}