koprogo_api/application/ports/technical_spec_repository.rs
1//! Port for persisting [`TechnicalSpec`] and [`TechnicalSpecSignature`]
2//! entities (Story 3.8 — FR33).
3//!
4//! All methods return `Result<_, AppError>` natively — no legacy `String`
5//! error debt to migrate later (CRITICAL.md #4 / #555).
6//!
7//! Signatures are append-only — the repository exposes only `save_signature`
8//! and a few read methods; mutation guards are enforced at the DB trigger
9//! level (cf. migration `20260605060000_create_technical_specs.sql`).
10
11use crate::application::error::AppError;
12use crate::domain::entities::{TechnicalSpec, TechnicalSpecSignature};
13use async_trait::async_trait;
14use uuid::Uuid;
15
16#[async_trait]
17pub trait TechnicalSpecRepository: Send + Sync {
18 /// Persist a freshly minted spec (Draft).
19 async fn save(&self, spec: &TechnicalSpec) -> Result<(), AppError>;
20
21 /// Update a spec's mutable workflow attributes (status / updated_at).
22 /// Used by the workflow transitions (`submit`, `mark_approved`).
23 /// The repository implementation MUST NOT allow title / description /
24 /// version edits — those happen exclusively via `bump_version` which
25 /// goes through `save` on a brand-new row.
26 async fn update_status(
27 &self,
28 spec_id: Uuid,
29 status: &str,
30 updated_at: chrono::DateTime<chrono::Utc>,
31 ) -> Result<(), AppError>;
32
33 async fn find_by_id(&self, id: Uuid) -> Result<Option<TechnicalSpec>, AppError>;
34
35 /// List all specs for an ACP, newest first.
36 async fn list_for_acp(&self, acp_id: Uuid) -> Result<Vec<TechnicalSpec>, AppError>;
37
38 // ---- signatures ----
39
40 /// Persist an append-only signature. The DB UNIQUE constraint on
41 /// (spec_id, signatory_user_id, role) means a duplicate insert will
42 /// surface as `AppError::SignatureAlreadyExists` (the impl translates
43 /// the SQL conflict).
44 async fn save_signature(&self, sig: &TechnicalSpecSignature) -> Result<(), AppError>;
45
46 async fn list_signatures_for_spec(
47 &self,
48 spec_id: Uuid,
49 ) -> Result<Vec<TechnicalSpecSignature>, AppError>;
50}