Skip to main content

forge_pilot/
bundle_builder.rs

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
29/// Builds a Forge experiment-evidence bundle from an oracle execution result.
30pub 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
128/// Builds a Forge experiment-evidence bundle from a patch execution result.
129pub 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}