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}