1use crate::act::{OracleExecution, PlanKind};
2use forge_engine::lab::evidence::{BaselineOrPatch, PairComparability, VerificationTrial};
3use forge_engine::{
4 BundleScope, CausalHypothesis, ClaimStrength, EffectKind, ExperimentEvidenceBundle,
5 ExperimentResult, HypothesisStatus, ReceiptKind, ReceiptRef, ReceiptStorage, ScoreVector,
6 Treatment, TypedLocatedEffect,
7};
8use stack_ids::{AttemptId, ContentDigest, TrialId};
9use uuid::Uuid;
10
11pub struct OracleBundleInput<'a> {
12 pub plan: &'a PlanKind,
13 pub target_key: &'a str,
14 pub trace_id: Option<String>,
15 pub scope_namespace: &'a str,
16 pub oracle_execution: &'a OracleExecution,
17 pub known_threats: Vec<String>,
18}
19
20pub struct PatchBundleInput<'a> {
21 pub plan: &'a PlanKind,
22 pub target_key: &'a str,
23 pub trace_id: Option<String>,
24 pub scope_namespace: &'a str,
25 pub experiment_result: &'a ExperimentResult,
26 pub known_threats: Vec<String>,
27}
28
29pub fn build_bundle_from_oracle(input: OracleBundleInput<'_>) -> ExperimentEvidenceBundle {
31 let bundle_id = Uuid::new_v4().to_string();
32 let eval_id = format!("oracle-eval:{}", Uuid::new_v4());
33 let outcome = input
34 .oracle_execution
35 .outcome_summary()
36 .unwrap_or_else(|| "oracle evaluation completed".into());
37 let receipt_payload = serde_json::to_string(&input.oracle_execution.summary_json())
38 .unwrap_or_else(|_| "{}".into());
39
40 ExperimentEvidenceBundle {
41 bundle_id: bundle_id.clone(),
42 candidate_id: input.target_key.to_string(),
43 eval_id,
44 version_id: "forge-pilot.v1".into(),
45 supersedes_claim_version_id: input.plan.supersedes_claim_version_id(),
46 relation_lineage_hints: Default::default(),
47 scores: ScoreVector {
48 correctness: 0.85,
49 novelty: 0.10,
50 stability: 0.80,
51 weighted_total: 0.75,
52 cea_confidence: None,
53 cea_predicted_correctness: None,
54 },
55 hypotheses: vec![CausalHypothesis {
56 hypothesis_id: format!("hypothesis:{bundle_id}"),
57 cause_signature: input.target_key.to_string(),
58 effect_signature: outcome.clone(),
59 confidence: 0.60,
60 status: HypothesisStatus::Supported,
61 support_count: 1,
62 contradiction_count: 0,
63 }],
64 verification: None,
65 trace_id: input.trace_id,
66 experiment_diff: None,
67 attribution_json: Some(receipt_payload.clone()),
68 assessment: None,
69 warnings: input.known_threats.clone(),
70 created_at: chrono::Utc::now().to_rfc3339(),
71 run_id: None,
72 attempt_id: Some(AttemptId::generate().to_string()),
73 causal_question: Some(format!("Should {} remain supported?", input.target_key)),
74 unit_definition: Some("kernel oracle evaluation".into()),
75 bundle_scope: Some(BundleScope {
76 workload_id: input.target_key.to_string(),
77 backend_family: "kernel_oracle".into(),
78 selected_checks: input.plan.check_names(),
79 timeout_class: "bounded".into(),
80 config_flags: vec!["canonical_v3".into()],
81 }),
82 pair_comparability: Some(PairComparability {
83 valid: true,
84 violations: vec![],
85 }),
86 claim_strength: ClaimStrength::ProvisionalSinglePair,
87 identification_rationale: Some(
88 "bounded kernel-oracle evaluation over imported payload".into(),
89 ),
90 known_threats: input.known_threats,
91 patch_hash: None,
92 treatment: Some(Treatment {
93 kind: "oracle_check".into(),
94 patch_hash: ContentDigest::compute_str(input.target_key)
95 .hex()
96 .to_string(),
97 patch_summary: format!("{:?}", input.plan),
98 }),
99 outcome: Some(outcome),
100 covariates: None,
101 promotion_state: None,
102 primary_effect: Some(TypedLocatedEffect {
103 kind: EffectKind::TestFailure,
104 file: None,
105 line: None,
106 message: input
107 .oracle_execution
108 .outcome_summary()
109 .unwrap_or_else(|| "oracle evaluation completed".into()),
110 in_baseline: true,
111 in_patched: false,
112 }),
113 all_effects: vec![],
114 hypothesis_edges: vec![],
115 receipts: vec![inline_receipt("oracle-summary", &receipt_payload)],
116 verification_trials: vec![VerificationTrial {
117 trial_id: TrialId::generate(),
118 attempt_id: AttemptId::generate(),
119 baseline_or_patch: BaselineOrPatch::Baseline,
120 completed: true,
121 receipts: vec!["oracle-summary".into()],
122 }],
123 refutation_artifacts: input.oracle_execution.refutation_artifacts(),
124 sealed: false,
125 }
126}
127
128pub fn build_bundle_from_patch(input: PatchBundleInput<'_>) -> ExperimentEvidenceBundle {
130 let bundle_id = Uuid::new_v4().to_string();
131 let eval_id = format!("patch-eval:{}", Uuid::new_v4());
132 let attempt_id = AttemptId::generate();
133 let receipt_payload = serde_json::json!({
134 "run_id": input.experiment_result.run_id,
135 "diff": input.experiment_result.diff,
136 })
137 .to_string();
138 let patch_hash = ContentDigest::compute_str(&format!("{:?}", input.plan))
139 .hex()
140 .to_string();
141
142 ExperimentEvidenceBundle {
143 bundle_id: bundle_id.clone(),
144 candidate_id: input.target_key.to_string(),
145 eval_id,
146 version_id: "forge-pilot.v1".into(),
147 supersedes_claim_version_id: input.plan.supersedes_claim_version_id(),
148 relation_lineage_hints: Default::default(),
149 scores: ScoreVector {
150 correctness: if input.experiment_result.diff.improvements
151 >= input.experiment_result.diff.regressions
152 {
153 0.90
154 } else {
155 0.40
156 },
157 novelty: 0.25,
158 stability: 0.70,
159 weighted_total: 0.70,
160 cea_confidence: None,
161 cea_predicted_correctness: None,
162 },
163 hypotheses: vec![CausalHypothesis {
164 hypothesis_id: format!("hypothesis:{bundle_id}"),
165 cause_signature: input.target_key.to_string(),
166 effect_signature: format!(
167 "improvements={} regressions={}",
168 input.experiment_result.diff.improvements, input.experiment_result.diff.regressions
169 ),
170 confidence: 0.65,
171 status: HypothesisStatus::Supported,
172 support_count: input.experiment_result.diff.improvements as u64,
173 contradiction_count: input.experiment_result.diff.regressions as u64,
174 }],
175 verification: None,
176 trace_id: input.trace_id,
177 experiment_diff: Some(input.experiment_result.diff.clone()),
178 attribution_json: None,
179 assessment: None,
180 warnings: input.known_threats.clone(),
181 created_at: chrono::Utc::now().to_rfc3339(),
182 run_id: Some(input.experiment_result.run_id.clone()),
183 attempt_id: Some(attempt_id.to_string()),
184 causal_question: Some(format!(
185 "Does {} improve the selected fixture?",
186 input.target_key
187 )),
188 unit_definition: Some("paired patch experiment".into()),
189 bundle_scope: Some(BundleScope {
190 workload_id: input.target_key.to_string(),
191 backend_family: format!("{:?}", input.experiment_result.mode),
192 selected_checks: vec!["fmt".into(), "clippy".into(), "test".into()],
193 timeout_class: "forge".into(),
194 config_flags: vec!["canonical_v3".into()],
195 }),
196 pair_comparability: Some(PairComparability {
197 valid: true,
198 violations: vec![],
199 }),
200 claim_strength: ClaimStrength::ProvisionalSinglePair,
201 identification_rationale: Some(
202 "paired baseline/patched experiment over the same fixture".into(),
203 ),
204 known_threats: input.known_threats,
205 patch_hash: Some(patch_hash.clone()),
206 treatment: Some(Treatment {
207 kind: "patch_applied".into(),
208 patch_hash,
209 patch_summary: format!("{:?}", input.plan),
210 }),
211 outcome: Some(format!(
212 "paired patch completed: improvements={} regressions={}",
213 input.experiment_result.diff.improvements, input.experiment_result.diff.regressions
214 )),
215 covariates: None,
216 promotion_state: None,
217 primary_effect: input.experiment_result.diff.effects.first().map(|effect| {
218 TypedLocatedEffect {
219 kind: effect.kind.clone(),
220 file: effect.file.clone(),
221 line: effect.line,
222 message: effect.message.clone(),
223 in_baseline: effect.in_baseline,
224 in_patched: effect.in_patched,
225 }
226 }),
227 all_effects: input
228 .experiment_result
229 .diff
230 .effects
231 .iter()
232 .map(|effect| TypedLocatedEffect {
233 kind: effect.kind.clone(),
234 file: effect.file.clone(),
235 line: effect.line,
236 message: effect.message.clone(),
237 in_baseline: effect.in_baseline,
238 in_patched: effect.in_patched,
239 })
240 .collect(),
241 hypothesis_edges: vec![],
242 receipts: vec![inline_receipt("patch-summary", &receipt_payload)],
243 verification_trials: vec![
244 VerificationTrial {
245 trial_id: TrialId::generate(),
246 attempt_id: attempt_id.clone(),
247 baseline_or_patch: BaselineOrPatch::Baseline,
248 completed: true,
249 receipts: vec!["patch-summary".into()],
250 },
251 VerificationTrial {
252 trial_id: TrialId::generate(),
253 attempt_id,
254 baseline_or_patch: BaselineOrPatch::Patched,
255 completed: true,
256 receipts: vec!["patch-summary".into()],
257 },
258 ],
259 refutation_artifacts: vec![],
260 sealed: false,
261 }
262}
263
264fn inline_receipt(receipt_id: &str, payload: &str) -> ReceiptRef {
265 ReceiptRef {
266 receipt_id: receipt_id.into(),
267 kind: ReceiptKind::CheckResult,
268 storage: ReceiptStorage::Inline(payload.into()),
269 content_hash: ContentDigest::compute_str(payload).hex().to_string(),
270 trace_id: None,
271 replay_handle: None,
272 }
273}