Skip to main content

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}