gglib_core/ports/
council_repository.rs

1//! Port trait for persisting orchestrator runs and events.
2//!
3//! Adapters implement this trait (e.g. [`gglib_db::SqliteCouncilRepository`])
4//! and inject it into the executor via [`crate::domain::council::executor::CouncilConfig`].
5//! The executor uses it to:
6//!
7//! - Create a run record at start-up.
8//! - Update run status on every state transition.
9//! - Append every emitted event to the event log.
10//! - Update the serialised graph whenever its state changes.
11
12use async_trait::async_trait;
13
14use crate::domain::council::run::{CouncilRun, CouncilRunEvent, CouncilRunStatus};
15use crate::ports::RepositoryError;
16
17// =============================================================================
18// CouncilRepositoryPort
19// =============================================================================
20
21/// Persistence operations for orchestrator runs.
22///
23/// All methods return a [`RepositoryError`] on failure.  The `append_event`
24/// method is best-effort in the executor: a storage failure is logged but
25/// does NOT abort the run.  All other methods that change run status propagate
26/// errors to the executor.
27#[async_trait]
28pub trait CouncilRepositoryPort: Send + Sync + 'static {
29    /// Persist a new run record.
30    ///
31    /// The run's `id` MUST be unique; callers generate a UUID v4 before
32    /// calling this method.
33    async fn create_run(&self, run: CouncilRun) -> Result<(), RepositoryError>;
34
35    /// Update the lifecycle status of an existing run.
36    async fn update_run_status(
37        &self,
38        run_id: &str,
39        status: CouncilRunStatus,
40    ) -> Result<(), RepositoryError>;
41
42    /// Replace the serialised task graph for an existing run.
43    ///
44    /// Called after plan approval and after each node completes so that the
45    /// persisted graph stays up to date for resume purposes.
46    async fn update_graph(&self, run_id: &str, graph_json: &str) -> Result<(), RepositoryError>;
47
48    /// Append a single event record to the run's event log.
49    async fn append_event(&self, event: CouncilRunEvent) -> Result<(), RepositoryError>;
50
51    /// Retrieve a single run by id.
52    ///
53    /// Returns `Ok(None)` if the run does not exist.
54    async fn get_run(&self, run_id: &str) -> Result<Option<CouncilRun>, RepositoryError>;
55
56    /// List runs optionally filtered by status.
57    ///
58    /// Results are ordered by `created_at` descending (most recent first).
59    async fn list_runs(
60        &self,
61        status_filter: Option<CouncilRunStatus>,
62    ) -> Result<Vec<CouncilRun>, RepositoryError>;
63
64    /// Return all events for a run in sequence order.
65    async fn list_events(&self, run_id: &str) -> Result<Vec<CouncilRunEvent>, RepositoryError>;
66
67    /// Mark all runs currently in [`CouncilRunStatus::Running`] as
68    /// [`CouncilRunStatus::Interrupted`].
69    ///
70    /// Called once on application boot to handle the case where a process
71    /// was killed mid-execution.
72    async fn mark_interrupted_runs(&self) -> Result<u64, RepositoryError>;
73
74    /// Delete all events for a run whose `wave_index` is **strictly greater
75    /// than** `wave_index`.
76    ///
77    /// Used by the Phase M rewind endpoint to truncate the event log back to
78    /// the state it was in at the end of the specified wave so the run can be
79    /// re-executed from that point.
80    async fn truncate_events_after_wave(
81        &self,
82        run_id: &str,
83        wave_index: u32,
84    ) -> Result<(), RepositoryError>;
85}