Skip to main content

oris_evolution/
port.rs

1//! Port traits for Detect and Execute stage integration.
2//!
3//! These traits let `StandardEvolutionPipeline` accept injected implementations
4//! without directly depending on `oris-evokernel` or `oris-sandbox` (which
5//! already depend on `oris-evolution`, so backward imports would be circular).
6//!
7//! Canonical implementations live in `oris-evokernel`:
8//! - `RuntimeSignalExtractorAdapter` implements `SignalExtractorPort`
9//! - `LocalSandboxAdapter` implements `SandboxPort`
10
11use serde_json::Value;
12
13use crate::core::PreparedMutation;
14use crate::evolver::EvolutionSignal;
15
16/// Input passed to the signal extractor during the Detect stage.
17#[derive(Clone, Debug, Default)]
18pub struct SignalExtractorInput {
19    /// Raw compiler output (rustc / cargo build stderr)
20    pub compiler_output: Option<String>,
21    /// Stack trace text
22    pub stack_trace: Option<String>,
23    /// Execution log lines
24    pub logs: Option<String>,
25    /// Arbitrary extra context (serialised JSON)
26    pub extra: Value,
27}
28
29/// Outcome of a sandbox execution during the Execute stage.
30#[derive(Clone, Debug)]
31pub struct SandboxExecutionResult {
32    /// Whether the sandbox apply succeeded
33    pub success: bool,
34    /// Standard output captured during execution
35    pub stdout: String,
36    /// Standard error captured during execution
37    pub stderr: String,
38    /// Wall-clock duration in milliseconds
39    pub duration_ms: u64,
40    /// Human-readable message
41    pub message: String,
42}
43
44impl SandboxExecutionResult {
45    /// Convenience constructor for a successful result.
46    pub fn success(stdout: impl Into<String>, duration_ms: u64) -> Self {
47        Self {
48            success: true,
49            stdout: stdout.into(),
50            stderr: String::new(),
51            duration_ms,
52            message: "Mutation executed successfully".into(),
53        }
54    }
55
56    /// Convenience constructor for a failed result.
57    pub fn failure(
58        stderr: impl Into<String>,
59        message: impl Into<String>,
60        duration_ms: u64,
61    ) -> Self {
62        Self {
63            success: false,
64            stdout: String::new(),
65            stderr: stderr.into(),
66            duration_ms,
67            message: message.into(),
68        }
69    }
70
71    /// Serialise to a `serde_json::Value` suitable for `PipelineContext.execution_result`.
72    pub fn to_json(&self) -> Value {
73        serde_json::json!({
74            "success": self.success,
75            "stdout": self.stdout,
76            "stderr": self.stderr,
77            "duration_ms": self.duration_ms,
78            "message": self.message,
79        })
80    }
81}
82
83/// Trait for signal extractors that can be injected into the Detect stage.
84///
85/// Implement this in `oris-evokernel` (or any crate that does not form
86/// a circular dependency with `oris-evolution`) and pass an
87/// `Arc<dyn SignalExtractorPort>` when constructing `StandardEvolutionPipeline`.
88pub trait SignalExtractorPort: Send + Sync {
89    /// Extract evolution signals from the given runtime input.
90    ///
91    /// Returns a (possibly empty) list of signals. The implementation is
92    /// responsible for deduplication and confidence scoring.
93    fn extract(&self, input: &SignalExtractorInput) -> Vec<EvolutionSignal>;
94}
95
96/// Trait for sandboxes that can be injected into the Execute stage.
97///
98/// Implement this in `oris-evokernel` and pass an `Arc<dyn SandboxPort>`
99/// when constructing `StandardEvolutionPipeline`. The trait uses a
100/// synchronous contract so that `EvolutionPipeline::execute` remains
101/// synchronous at the pipeline level; async adapters should block internally.
102pub trait SandboxPort: Send + Sync {
103    /// Apply the first mutation proposal (represented as a `PreparedMutation`)
104    /// inside a sandbox and return the execution result.
105    fn execute(&self, mutation: &PreparedMutation) -> SandboxExecutionResult;
106}
107
108/// Trait for persisting gene/capsule data during Solidify and Reuse stages.
109///
110/// Implement this in `oris-evokernel` (or any crate with access to
111/// `oris-genestore`) and inject via `StandardEvolutionPipeline::with_gene_store`.
112/// The trait is synchronous so the pipeline itself remains sync; async store
113/// calls should block internally (same pattern as `SandboxPort`).
114pub trait GeneStorePersistPort: Send + Sync {
115    /// Persist a candidate gene during the Solidify stage.
116    ///
117    /// * `gene_id` – opaque string ID from `oris-evolution::Gene`
118    /// * `signals`  – signal descriptions driving this gene
119    /// * `strategy` – strategy steps for solving the class of problem
120    /// * `validation` – validation criteria for the gene
121    ///
122    /// Returns `true` on success, `false` on a non-fatal error (the pipeline
123    /// records the outcome but does not abort).
124    fn persist_gene(
125        &self,
126        gene_id: &str,
127        signals: &[String],
128        strategy: &[String],
129        validation: &[String],
130    ) -> bool;
131
132    /// Record that a capsule was successfully reused during the Reuse stage.
133    ///
134    /// * `gene_id`    – the parent gene
135    /// * `capsule_ids` – the capsule IDs that were reused
136    ///
137    /// Returns `true` on success.
138    fn mark_reused(&self, gene_id: &str, capsule_ids: &[String]) -> bool;
139}
140
141// ── Validate stage port ───────────────────────────────────────────────────────
142
143/// Input passed to a `ValidatePort` implementation during the Validate stage.
144#[derive(Clone, Debug, Default)]
145pub struct ValidateInput {
146    /// Opaque proposal identifier from `oris-evolution`.
147    pub proposal_id: String,
148    /// Whether the prior Execute (sandbox) stage succeeded.
149    pub execution_success: bool,
150    /// Captured standard output from the sandbox execution.
151    pub stdout: String,
152    /// Captured standard error from the sandbox execution.
153    pub stderr: String,
154}
155
156/// Trait for validators injected into the Validate stage.
157///
158/// Implement this in `oris-evokernel` (or another crate that does not form
159/// a circular dependency with `oris-evolution`) and pass an
160/// `Arc<dyn ValidatePort>` via `StandardEvolutionPipeline::with_validate_port`.
161/// The trait is synchronous; async implementations must block internally
162/// (same pattern as `SandboxPort`).
163pub trait ValidatePort: Send + Sync {
164    /// Validate the mutation result and return a `ValidationResult`.
165    ///
166    /// The pipeline falls back to a stub result when no validator is injected.
167    fn validate(&self, input: &ValidateInput) -> crate::evolver::ValidationResult;
168}
169
170// ── Evaluate stage port ───────────────────────────────────────────────────────
171
172/// Input passed to an `EvaluatePort` implementation during the Evaluate stage.
173#[derive(Clone, Debug, Default)]
174pub struct EvaluateInput {
175    /// Opaque proposal identifier from `oris-evolution`.
176    pub proposal_id: String,
177    /// Human-readable intent description of the mutation.
178    pub intent: String,
179    /// The original source content / context before the mutation.
180    pub original: String,
181    /// The proposed replacement (diff payload or full proposed content).
182    pub proposed: String,
183    /// Signal tokens extracted by `oris-evokernel` that drove this mutation.
184    pub signals: Vec<String>,
185}
186
187/// Trait for mutation evaluators injected into the Evaluate stage.
188///
189/// Implement this in `oris-evokernel` using `oris-mutation-evaluator` as the
190/// backend and inject via `StandardEvolutionPipeline::with_evaluate_port`.
191/// The trait is synchronous; async implementations must block internally.
192pub trait EvaluatePort: Send + Sync {
193    /// Score / evaluate the mutation and return an `EvaluationResult`.
194    ///
195    /// The pipeline falls back to a stub result (score 0.8, Accept) when no
196    /// evaluator is injected.
197    fn evaluate(&self, input: &EvaluateInput) -> crate::pipeline::EvaluationResult;
198}
199
200// ── Orchestrator pipeline port ───────────────────────────────────────────────
201
202/// Request passed from higher-level orchestrators into the evolution pipeline.
203///
204/// This keeps `oris-evolution` decoupled from `oris-orchestrator` while still
205/// exposing a stable contract for running execute/validate/evaluate steps
206/// against a generated mutation proposal.
207#[derive(Clone, Debug, Default)]
208pub struct EvolutionPipelineRequest {
209    /// Upstream issue or run identifier.
210    pub issue_id: String,
211    /// Human-readable intent for the proposed mutation.
212    pub intent: String,
213    /// Input signal tokens that motivated the mutation.
214    pub signals: Vec<String>,
215    /// File paths targeted by the mutation.
216    pub files: Vec<String>,
217    /// Expected user-visible outcome after the mutation is applied.
218    pub expected_effect: String,
219    /// Unified diff payload or equivalent serialized mutation content.
220    pub diff_payload: String,
221}
222
223/// Port for orchestrators that want to run an evolution pipeline before taking
224/// an external side effect such as PR creation.
225pub trait EvolutionPipelinePort: Send + Sync {
226    /// Execute the relevant pipeline stages for the provided request.
227    ///
228    /// Implementations should fail closed and report that failure through the
229    /// returned `PipelineResult`.
230    fn run_pipeline(&self, request: &EvolutionPipelineRequest) -> crate::pipeline::PipelineResult;
231}