1#![cfg_attr(test, allow(clippy::expect_used))]
2use chrono::{DateTime, FixedOffset};
3use constraint_compiler::{compile_batch, CompileOutput, CompilerPolicy};
4use forge_memory_bridge::ProjectionImportBatchV3;
5use kernel_execution::{execute_delta_propagation, execute_message_passing_baseline};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use stack_ids::{ContentDigest, OracleSliceId, RegionId};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
11#[serde(rename_all = "snake_case")]
12pub enum OracleMode {
13 ExactBounded,
14 ConservativeFallback,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18pub struct OracleSliceSelectionPolicy {
19 pub policy_name: String,
20 pub prefers_bounded_regions: bool,
21 pub max_exact_nodes: usize,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
25pub struct OracleAssessment {
26 pub slice_id: OracleSliceId,
27 pub mode: OracleMode,
28 pub supported: bool,
29 pub satisfied_constraint_count: usize,
30 pub selected_region_ids: Vec<RegionId>,
31 pub selection_policy: OracleSliceSelectionPolicy,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
35pub struct OracleParityCertificate {
36 pub slice_id: OracleSliceId,
37 pub region_ids: Vec<RegionId>,
38 pub parity_match: bool,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42pub struct DeltaParityAssessment {
43 pub changed_node_ids: Vec<String>,
44 pub recomputed_node_ids: Vec<String>,
45 pub recomputed_region_ids: Vec<RegionId>,
46 pub parity_match: bool,
47 pub certificate: OracleParityCertificate,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
51pub struct TemporalReplaySnapshot {
52 pub recorded_at: String,
53 pub batch: ProjectionImportBatchV3,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
57pub struct TemporalReplayAssessment {
58 pub cutoff_recorded_at: String,
59 pub selected_recorded_at: String,
60 pub source_envelope_id: String,
61 pub graph_hash: ContentDigest,
62 pub matched_expected_hash: bool,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
66#[serde(rename_all = "snake_case")]
67pub enum RefutationMode {
68 CausalRefuter,
69 MinimalPerturbation,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73#[serde(tag = "result", rename_all = "snake_case")]
74pub enum OracleRefutationOutcome {
75 FlipWitness { removed_node_ids: Vec<String> },
76 NoFlipFound { searched_budget: usize },
77 NotApplicable { reason: String },
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
81pub struct OracleRefutationResult {
82 pub mode: RefutationMode,
83 pub target_node_id: String,
84 pub baseline_supported: bool,
85 pub searched_budget: usize,
86 pub outcome: OracleRefutationOutcome,
87}
88
89const MAX_REMOVAL_SET_SIZE: usize = 4;
90const MAX_REMOVAL_COMBINATION_BUDGET: usize = 256;
91
92pub fn evaluate_exact_bounded(compiled: &CompileOutput) -> Option<OracleAssessment> {
94 let slice = compiled.oracle_candidates.first()?;
95 let selected_region_ids = compiled
96 .regions
97 .iter()
98 .filter(|region| {
99 region
100 .node_ids
101 .iter()
102 .any(|node_id| slice.node_ids.contains(node_id))
103 })
104 .map(|region| region.region_id.clone())
105 .collect::<Vec<_>>();
106 Some(OracleAssessment {
107 slice_id: slice.oracle_slice_id.clone(),
108 mode: OracleMode::ExactBounded,
109 supported: compiled.degradations.is_empty() && compiled.nodes.len() <= 8,
110 satisfied_constraint_count: compiled.constraints.len(),
111 selected_region_ids,
112 selection_policy: OracleSliceSelectionPolicy {
113 policy_name: "bounded_region_exact_first".into(),
114 prefers_bounded_regions: true,
115 max_exact_nodes: 8,
116 },
117 })
118}
119
120pub fn evaluate_conservative(compiled: &CompileOutput) -> OracleAssessment {
122 let slice_id = compiled
123 .oracle_candidates
124 .first()
125 .map(|slice| slice.oracle_slice_id.clone())
126 .unwrap_or_else(|| OracleSliceId::new("oracle:unsupported"));
127 OracleAssessment {
128 slice_id,
129 mode: OracleMode::ConservativeFallback,
130 supported: true,
131 satisfied_constraint_count: compiled.constraints.len(),
132 selected_region_ids: compiled
133 .regions
134 .iter()
135 .map(|region| region.region_id.clone())
136 .collect(),
137 selection_policy: OracleSliceSelectionPolicy {
138 policy_name: "conservative_fallback".into(),
139 prefers_bounded_regions: true,
140 max_exact_nodes: 8,
141 },
142 }
143}
144
145pub fn evaluate_delta_parity(
147 compiled: &CompileOutput,
148 changed_node_ids: &[String],
149 max_iterations: u32,
150) -> DeltaParityAssessment {
151 let delta = execute_delta_propagation(compiled, changed_node_ids, max_iterations);
152 let bounded = execute_message_passing_baseline(compiled, max_iterations);
153 let recomputed_nodes = delta.recomputed_node_ids.clone();
154 let recomputed_region_ids = delta.recomputed_region_ids.clone();
155 let delta_focus = delta
156 .execution
157 .node_beliefs
158 .iter()
159 .filter(|belief| recomputed_nodes.contains(&belief.node_id))
160 .map(|belief| (belief.node_id.clone(), belief.belief_micros))
161 .collect::<Vec<_>>();
162 let bounded_focus = bounded
163 .node_beliefs
164 .iter()
165 .filter(|belief| recomputed_nodes.contains(&belief.node_id))
166 .map(|belief| (belief.node_id.clone(), belief.belief_micros))
167 .collect::<Vec<_>>();
168
169 DeltaParityAssessment {
170 changed_node_ids: changed_node_ids.to_vec(),
171 recomputed_node_ids: recomputed_nodes.clone(),
172 recomputed_region_ids: recomputed_region_ids.clone(),
173 parity_match: delta_focus == bounded_focus,
174 certificate: OracleParityCertificate {
175 slice_id: compiled
176 .oracle_candidates
177 .first()
178 .map(|slice| slice.oracle_slice_id.clone())
179 .unwrap_or_else(|| OracleSliceId::new("oracle:unsupported")),
180 region_ids: recomputed_region_ids,
181 parity_match: delta_focus == bounded_focus,
182 },
183 }
184}
185
186pub fn replay_as_of(
188 snapshots: &[TemporalReplaySnapshot],
189 cutoff_recorded_at: &str,
190 policy: &CompilerPolicy,
191) -> Option<CompileOutput> {
192 let cutoff = parsed_utc_timestamp(cutoff_recorded_at)?;
193 let snapshot = snapshots
194 .iter()
195 .filter_map(|snapshot| {
196 Some((
197 parsed_utc_timestamp(&snapshot.recorded_at)?,
198 snapshot.recorded_at.as_str(),
199 snapshot,
200 ))
201 })
202 .filter(|(recorded_at, _, _)| *recorded_at <= cutoff)
203 .max_by_key(|(recorded_at, ..)| *recorded_at)
204 .map(|(_, _, snapshot)| snapshot)?;
205 Some(compile_batch(&snapshot.batch, policy))
206}
207
208pub fn evaluate_temporal_replay(
210 snapshots: &[TemporalReplaySnapshot],
211 cutoff_recorded_at: &str,
212 policy: &CompilerPolicy,
213 expected_hash: &ContentDigest,
214) -> Option<TemporalReplayAssessment> {
215 let cutoff = parsed_utc_timestamp(cutoff_recorded_at)?;
216 let snapshot = snapshots
217 .iter()
218 .filter_map(|snapshot| {
219 Some((
220 parsed_utc_timestamp(&snapshot.recorded_at)?,
221 snapshot.recorded_at.as_str(),
222 snapshot,
223 ))
224 })
225 .filter(|(recorded_at, _, _)| *recorded_at <= cutoff)
226 .max_by_key(|(recorded_at, ..)| *recorded_at)
227 .map(|(_, _, snapshot)| snapshot)?;
228 let compiled = compile_batch(&snapshot.batch, policy);
229 Some(TemporalReplayAssessment {
230 cutoff_recorded_at: cutoff_recorded_at.to_string(),
231 selected_recorded_at: snapshot.recorded_at.clone(),
232 source_envelope_id: snapshot.batch.source_envelope_id.to_string(),
233 matched_expected_hash: &compiled.graph_hash == expected_hash,
234 graph_hash: compiled.graph_hash,
235 })
236}
237
238pub fn evaluate_causal_refuter(
240 compiled: &CompileOutput,
241 target_node_id: &str,
242 max_removed_nodes: usize,
243) -> OracleRefutationResult {
244 refute_internal(
245 RefutationMode::CausalRefuter,
246 compiled,
247 target_node_id,
248 max_removed_nodes,
249 )
250}
251
252pub fn minimal_perturbation_witness(
254 compiled: &CompileOutput,
255 target_node_id: &str,
256 max_removed_nodes: usize,
257) -> OracleRefutationResult {
258 refute_internal(
259 RefutationMode::MinimalPerturbation,
260 compiled,
261 target_node_id,
262 max_removed_nodes,
263 )
264}
265
266pub fn evaluate_minimal_perturbation(
268 compiled: &CompileOutput,
269 target_node_id: &str,
270 max_removed_nodes: usize,
271) -> OracleRefutationResult {
272 minimal_perturbation_witness(compiled, target_node_id, max_removed_nodes)
273}
274
275fn refute_internal(
276 mode: RefutationMode,
277 compiled: &CompileOutput,
278 target_node_id: &str,
279 max_removed_nodes: usize,
280) -> OracleRefutationResult {
281 let baseline_supported = node_is_supported(compiled, target_node_id, &[]);
282 if !baseline_supported {
283 return OracleRefutationResult {
284 mode,
285 target_node_id: target_node_id.to_string(),
286 baseline_supported,
287 searched_budget: 0,
288 outcome: OracleRefutationOutcome::NotApplicable {
289 reason: "target node is not supported in the compiled graph".into(),
290 },
291 };
292 }
293
294 let candidates = perturbation_candidates(compiled, target_node_id);
295 if candidates.is_empty() {
296 return OracleRefutationResult {
297 mode,
298 target_node_id: target_node_id.to_string(),
299 baseline_supported,
300 searched_budget: 0,
301 outcome: OracleRefutationOutcome::NotApplicable {
302 reason: "no admissible perturbation candidates are connected to the target".into(),
303 },
304 };
305 }
306
307 let mut searched_budget = 0usize;
308 let max_size = max_removed_nodes
309 .max(1)
310 .min(candidates.len())
311 .min(MAX_REMOVAL_SET_SIZE);
312
313 for size in 1..=max_size {
314 let (witness, budget_exhausted) = find_witness_by_size(
315 &candidates,
316 compiled,
317 target_node_id,
318 size,
319 MAX_REMOVAL_COMBINATION_BUDGET,
320 &mut searched_budget,
321 );
322 if let Some(removed_node_ids) = witness {
323 return OracleRefutationResult {
324 mode,
325 target_node_id: target_node_id.to_string(),
326 baseline_supported,
327 searched_budget,
328 outcome: OracleRefutationOutcome::FlipWitness { removed_node_ids },
329 };
330 }
331 if budget_exhausted {
332 return OracleRefutationResult {
333 mode,
334 target_node_id: target_node_id.to_string(),
335 baseline_supported,
336 searched_budget,
337 outcome: OracleRefutationOutcome::NoFlipFound { searched_budget },
338 };
339 }
340 }
341
342 OracleRefutationResult {
343 mode,
344 target_node_id: target_node_id.to_string(),
345 baseline_supported,
346 searched_budget,
347 outcome: OracleRefutationOutcome::NoFlipFound { searched_budget },
348 }
349}
350
351fn perturbation_candidates(compiled: &CompileOutput, target_node_id: &str) -> Vec<String> {
352 let mut candidates = compiled
353 .hyperedges
354 .iter()
355 .filter(|edge| {
356 edge.member_node_ids
357 .iter()
358 .any(|node| node == target_node_id)
359 })
360 .flat_map(|edge| edge.member_node_ids.iter())
361 .filter(|node_id| node_id.as_str() != target_node_id)
362 .filter(|node_id| !node_id.starts_with("nuisance:"))
363 .cloned()
364 .collect::<Vec<_>>();
365 candidates.sort();
366 candidates.dedup();
367 candidates
368}
369
370fn parsed_utc_timestamp(value: &str) -> Option<DateTime<FixedOffset>> {
371 DateTime::parse_from_rfc3339(value).ok()
372}
373
374fn node_is_supported(compiled: &CompileOutput, target_node_id: &str, removed: &[String]) -> bool {
375 compiled.hyperedges.iter().any(|edge| {
376 if !edge
377 .member_node_ids
378 .iter()
379 .any(|node| node == target_node_id)
380 {
381 return false;
382 }
383 edge.member_node_ids
384 .iter()
385 .filter(|node_id| node_id.as_str() != target_node_id)
386 .filter(|node_id| !removed.contains(node_id))
387 .any(|node_id| !node_id.starts_with("nuisance:"))
388 })
389}
390
391fn find_witness_by_size(
392 candidates: &[String],
393 compiled: &CompileOutput,
394 target_node_id: &str,
395 size: usize,
396 max_budget: usize,
397 searched_budget: &mut usize,
398) -> (Option<Vec<String>>, bool) {
399 let mut current = Vec::with_capacity(size);
400 search_combinations_for_witness(
401 candidates,
402 size,
403 0,
404 &mut current,
405 compiled,
406 target_node_id,
407 max_budget,
408 searched_budget,
409 )
410}
411
412#[allow(clippy::too_many_arguments)]
413fn search_combinations_for_witness(
414 items: &[String],
415 size: usize,
416 index: usize,
417 current: &mut Vec<String>,
418 compiled: &CompileOutput,
419 target_node_id: &str,
420 max_budget: usize,
421 searched_budget: &mut usize,
422) -> (Option<Vec<String>>, bool) {
423 if current.len() == size {
424 if *searched_budget >= max_budget {
425 return (None, true);
426 }
427
428 *searched_budget += 1;
429 if !node_is_supported(compiled, target_node_id, current) {
430 return (Some(current.clone()), false);
431 }
432 return (None, false);
433 }
434 if index >= items.len() {
435 return (None, false);
436 }
437
438 let mut idx = index;
439 while idx < items.len() {
440 current.push(items[idx].clone());
441 let (witness, budget_exhausted) = search_combinations_for_witness(
442 items,
443 size,
444 idx + 1,
445 current,
446 compiled,
447 target_node_id,
448 max_budget,
449 searched_budget,
450 );
451 current.pop();
452
453 if let Some(witness) = witness {
454 return (Some(witness), false);
455 }
456 if budget_exhausted {
457 return (None, true);
458 }
459
460 idx += 1;
461 }
462 (None, false)
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use constraint_compiler::{
469 CompilationBoundary, CompiledRegion, GraphGeometryManifest, GraphSurfaceKind,
470 InferenceHyperedge, InferenceNode,
471 };
472 use forge_memory_bridge::transform_envelope_v3;
473 use proptest::prelude::*;
474 use recursive_kernel_core::ConstraintUnit;
475 use semantic_memory_forge::{
476 ConstraintSeedKind, ExportAuthority, ExportClaim, ExportConfidenceClass, ExportEnvelopeV3,
477 ExportRecord, ExportRecordSemanticsV3, ExportRecordV3, ForgeExportMeta,
478 ProjectionVisibilityClass, EXPORT_ENVELOPE_V3_SCHEMA,
479 };
480 use stack_ids::{
481 AssertionGroupId, ClaimFamilyId, ConstraintId, EntityId, EnvelopeId, OperatorId,
482 RegionDigestId, RegionId, ScopeKey,
483 };
484
485 fn rich_batch(envelope_id: &str, claim_suffix: &str) -> ProjectionImportBatchV3 {
486 let scope = ScopeKey::namespace_only("oracle");
487 let records = vec![
488 ExportRecordV3 {
489 record: ExportRecord::Claim(ExportClaim {
490 claim_id: None,
491 claim_version_id: Some(format!("claim-version-{claim_suffix}-1").into()),
492 subject_entity_id: EntityId::new(format!("entity-{claim_suffix}-1")),
493 predicate: "supports".into(),
494 object_anchor: serde_json::json!("result"),
495 valid_from: None,
496 valid_to: None,
497 confidence: 1.0,
498 content: "entity supports result".into(),
499 projection_family: "forge_verification".into(),
500 supersedes_claim_id: None,
501 supersedes_claim_version_id: None,
502 metadata: None,
503 }),
504 semantics: Some(ExportRecordSemanticsV3 {
505 claim_family_id: Some(ClaimFamilyId::new(format!("family-{claim_suffix}"))),
506 assertion_group_id: Some(AssertionGroupId::new(format!(
507 "group-{claim_suffix}"
508 ))),
509 relation_group_id: None,
510 joint_evidence_group_id: None,
511 constraint_seed_kind: Some(ConstraintSeedKind::Hyperedge),
512 treatment_hint: None,
513 outcome_hint: None,
514 confounder_hint: None,
515 instrument_hint: None,
516 effect_modifier_hint: None,
517 contradiction_candidate_group_id: None,
518 mutual_exclusion_group_id: None,
519 comparability_snapshot_version: None,
520 nuisance_snapshot: None,
521 projection_visibility_class: ProjectionVisibilityClass::Standard,
522 export_confidence_class: ExportConfidenceClass::Verified,
523 derivation_seed_ids: vec![],
524 review_priority_hint: None,
525 }),
526 },
527 ExportRecordV3 {
528 record: ExportRecord::Claim(ExportClaim {
529 claim_id: None,
530 claim_version_id: Some(format!("claim-version-{claim_suffix}-2").into()),
531 subject_entity_id: EntityId::new(format!("entity-{claim_suffix}-2")),
532 predicate: "supports".into(),
533 object_anchor: serde_json::json!("result"),
534 valid_from: None,
535 valid_to: None,
536 confidence: 0.95,
537 content: "entity 2 supports result".into(),
538 projection_family: "forge_verification".into(),
539 supersedes_claim_id: None,
540 supersedes_claim_version_id: None,
541 metadata: None,
542 }),
543 semantics: Some(ExportRecordSemanticsV3 {
544 claim_family_id: Some(ClaimFamilyId::new(format!("family-{claim_suffix}"))),
545 assertion_group_id: Some(AssertionGroupId::new(format!(
546 "group-{claim_suffix}"
547 ))),
548 relation_group_id: None,
549 joint_evidence_group_id: None,
550 constraint_seed_kind: Some(ConstraintSeedKind::Hyperedge),
551 treatment_hint: None,
552 outcome_hint: None,
553 confounder_hint: None,
554 instrument_hint: None,
555 effect_modifier_hint: None,
556 contradiction_candidate_group_id: None,
557 mutual_exclusion_group_id: None,
558 comparability_snapshot_version: None,
559 nuisance_snapshot: None,
560 projection_visibility_class: ProjectionVisibilityClass::Standard,
561 export_confidence_class: ExportConfidenceClass::Verified,
562 derivation_seed_ids: vec![],
563 review_priority_hint: None,
564 }),
565 },
566 ];
567 let export_meta = ForgeExportMeta {
568 authority: ExportAuthority::Forge,
569 run_id: Some("run-1".into()),
570 direct_write: false,
571 comparability_snapshot_version: None,
572 exported_at: "2026-03-10T00:00:00Z".into(),
573 };
574 let digest =
575 ExportEnvelopeV3::compute_digest("forge", &scope, &records, Some(&export_meta), None)
576 .unwrap();
577 let envelope = ExportEnvelopeV3 {
578 envelope_id: EnvelopeId::new(envelope_id),
579 schema_version: EXPORT_ENVELOPE_V3_SCHEMA.into(),
580 content_digest: digest,
581 source_authority: "forge".into(),
582 scope_key: scope,
583 trace_ctx: None,
584 exported_at: "2026-03-10T00:00:00Z".into(),
585 export_meta: Some(export_meta),
586 evidence_bundle: None,
587 support_sets: vec![],
588 contradiction_witnesses: vec![],
589 retraction_records: vec![],
590 claim_states_v13: vec![],
591 intervention_bundles_v14: vec![],
592 outcome_schemas_v14: vec![],
593 cohort_contracts_v14: vec![],
594 counterfactual_slices_v14: vec![],
595 experiment_cases_v14: vec![],
596 comparability_matrices_v14: vec![],
597 decision_traces_v14: vec![],
598 refuter_suites_v14: vec![],
599 refuter_results_v14: vec![],
600 experiment_budgets_v14: vec![],
601 rollout_decisions_v14: vec![],
602 rollback_decisions_v14: vec![],
603 attestation_envelopes_v15: vec![],
604 trust_root_sets_v15: vec![],
605 artifact_admission_policies_v15: vec![],
606 transparency_receipts_v15: vec![],
607 attestation_revocations_v15: vec![],
608 attestation_supersessions_v15: vec![],
609 remote_oracle_leases_v15: vec![],
610 remote_slice_requests_v15: vec![],
611 remote_slice_results_v15: vec![],
612 cross_runtime_replay_tickets_v15: vec![],
613 dispute_bundles_v15: vec![],
614 disclosure_policies_v15: vec![],
615 disclosure_budgets_v15: vec![],
616 records,
617 };
618 transform_envelope_v3(&envelope).unwrap()
619 }
620
621 fn explicit_refute_fixture() -> CompileOutput {
622 CompileOutput {
623 graph_hash: ContentDigest::compute(b"oracle-causal-cost-fixture"),
624 scope_key: ScopeKey::namespace_only("oracle"),
625 geometry_manifest: GraphGeometryManifest {
626 surfaces: vec![
627 GraphSurfaceKind::Storage,
628 GraphSurfaceKind::Retrieval,
629 GraphSurfaceKind::Inference,
630 GraphSurfaceKind::Repair,
631 GraphSurfaceKind::Control,
632 ],
633 compilation_boundaries: vec![CompilationBoundary {
634 from_surface: GraphSurfaceKind::Inference,
635 to_surface: GraphSurfaceKind::Repair,
636 artifact_families: vec!["syndrome".into()],
637 deterministic: true,
638 }],
639 no_silent_collapse: true,
640 },
641 nodes: vec![
642 InferenceNode {
643 node_id: "target".into(),
644 kind: "claim_version".into(),
645 },
646 InferenceNode {
647 node_id: "peer-b".into(),
648 kind: "claim_version".into(),
649 },
650 InferenceNode {
651 node_id: "peer-c".into(),
652 kind: "claim_version".into(),
653 },
654 ],
655 hyperedges: vec![InferenceHyperedge {
656 edge_id: "edge:triad".into(),
657 member_node_ids: vec!["target".into(), "peer-b".into(), "peer-c".into()],
658 }],
659 constraints: vec![ConstraintUnit {
660 constraint_id: ConstraintId::new("constraint:edge:triad"),
661 kind: "hyperedge".into(),
662 variable_ids: vec!["target".into(), "peer-b".into(), "peer-c".into()],
663 operator_id: OperatorId::new("kernel-oracles:tests"),
664 }],
665 regions: vec![CompiledRegion {
666 region_id: RegionId::new("region:oracle-triad"),
667 region_digest_id: RegionDigestId::new("region-digest:oracle-triad"),
668 node_ids: vec!["target".into(), "peer-b".into(), "peer-c".into()],
669 hyperedge_ids: vec!["edge:triad".into()],
670 constraint_ids: vec![ConstraintId::new("constraint:edge:triad")],
671 bounded_default_unit_of_work: true,
672 }],
673 invalidation_cones: vec![],
674 degradations: vec![],
675 oracle_candidates: vec![],
676 }
677 }
678
679 fn compiled_fixture() -> CompileOutput {
680 compile_batch(
681 &rich_batch("env-1", "alpha"),
682 &CompilerPolicy {
683 policy_version: "policy-1".into(),
684 include_hyperedges: true,
685 },
686 )
687 }
688
689 fn compile_many_claim_fixture(claim_count: usize) -> (CompileOutput, Vec<String>) {
690 let scope = ScopeKey::namespace_only("oracle");
691 let claims: Vec<String> = (0..claim_count)
692 .map(|index| format!("claim-large-{index}"))
693 .collect();
694 let records: Vec<ExportRecordV3> = claims
695 .iter()
696 .map(|claim_id| ExportRecordV3 {
697 record: ExportRecord::Claim(ExportClaim {
698 claim_id: None,
699 claim_version_id: Some(claim_id.clone().into()),
700 subject_entity_id: EntityId::new(format!("entity-{claim_id}")),
701 predicate: "supports".into(),
702 object_anchor: serde_json::json!("result"),
703 valid_from: None,
704 valid_to: None,
705 confidence: 1.0,
706 content: format!("{claim_id} supports result"),
707 projection_family: "forge_verification".into(),
708 supersedes_claim_id: None,
709 supersedes_claim_version_id: None,
710 metadata: None,
711 }),
712 semantics: Some(ExportRecordSemanticsV3 {
713 claim_family_id: Some(ClaimFamilyId::new("family-large")),
714 assertion_group_id: Some(AssertionGroupId::new("group-large")),
715 relation_group_id: None,
716 joint_evidence_group_id: None,
717 constraint_seed_kind: Some(ConstraintSeedKind::Hyperedge),
718 treatment_hint: None,
719 outcome_hint: None,
720 confounder_hint: None,
721 instrument_hint: None,
722 effect_modifier_hint: None,
723 contradiction_candidate_group_id: None,
724 mutual_exclusion_group_id: None,
725 comparability_snapshot_version: None,
726 nuisance_snapshot: None,
727 projection_visibility_class: ProjectionVisibilityClass::Standard,
728 export_confidence_class: ExportConfidenceClass::Verified,
729 derivation_seed_ids: vec![],
730 review_priority_hint: None,
731 }),
732 })
733 .collect();
734 let export_meta = ForgeExportMeta {
735 authority: ExportAuthority::Forge,
736 run_id: Some("run-large".into()),
737 direct_write: false,
738 comparability_snapshot_version: None,
739 exported_at: "2026-03-10T00:00:00Z".into(),
740 };
741 let digest =
742 ExportEnvelopeV3::compute_digest("forge", &scope, &records, Some(&export_meta), None)
743 .unwrap();
744 let envelope = ExportEnvelopeV3 {
745 envelope_id: EnvelopeId::new("env-large"),
746 schema_version: EXPORT_ENVELOPE_V3_SCHEMA.into(),
747 content_digest: digest,
748 source_authority: "forge".into(),
749 scope_key: scope,
750 trace_ctx: None,
751 exported_at: "2026-03-10T00:00:00Z".into(),
752 export_meta: Some(export_meta),
753 evidence_bundle: None,
754 support_sets: vec![],
755 contradiction_witnesses: vec![],
756 retraction_records: vec![],
757 claim_states_v13: vec![],
758 intervention_bundles_v14: vec![],
759 outcome_schemas_v14: vec![],
760 cohort_contracts_v14: vec![],
761 counterfactual_slices_v14: vec![],
762 experiment_cases_v14: vec![],
763 comparability_matrices_v14: vec![],
764 decision_traces_v14: vec![],
765 refuter_suites_v14: vec![],
766 refuter_results_v14: vec![],
767 experiment_budgets_v14: vec![],
768 rollout_decisions_v14: vec![],
769 rollback_decisions_v14: vec![],
770 attestation_envelopes_v15: vec![],
771 trust_root_sets_v15: vec![],
772 artifact_admission_policies_v15: vec![],
773 transparency_receipts_v15: vec![],
774 attestation_revocations_v15: vec![],
775 attestation_supersessions_v15: vec![],
776 remote_oracle_leases_v15: vec![],
777 remote_slice_requests_v15: vec![],
778 remote_slice_results_v15: vec![],
779 cross_runtime_replay_tickets_v15: vec![],
780 dispute_bundles_v15: vec![],
781 disclosure_policies_v15: vec![],
782 disclosure_budgets_v15: vec![],
783 records,
784 };
785 let batch = transform_envelope_v3(&envelope).unwrap();
786 let compiled = compile_batch(
787 &batch,
788 &CompilerPolicy {
789 policy_version: "policy-1".into(),
790 include_hyperedges: true,
791 },
792 );
793 (compiled, claims)
794 }
795
796 #[test]
797 fn exact_bounded_path_exists_for_small_supported_slice() {
798 let compiled = compiled_fixture();
799 let exact = evaluate_exact_bounded(&compiled).expect("expected exact oracle slice");
800 let conservative = evaluate_conservative(&compiled);
801
802 assert!(exact.supported);
803 assert_eq!(
804 exact.satisfied_constraint_count,
805 conservative.satisfied_constraint_count
806 );
807 assert!(!compiled.oracle_candidates[0]
808 .node_ids
809 .iter()
810 .any(|node_id| node_id.starts_with("nuisance:")));
811 assert!(exact.selection_policy.prefers_bounded_regions);
812 assert!(!exact.selected_region_ids.is_empty());
813 assert_eq!(exact.selected_region_ids, conservative.selected_region_ids);
814 }
815
816 #[test]
817 fn temporal_replay_selects_latest_snapshot_before_cutoff() {
818 let policy = CompilerPolicy {
819 policy_version: "policy-1".into(),
820 include_hyperedges: true,
821 };
822 let older = rich_batch("env-old", "old");
823 let newer = rich_batch("env-new", "new");
824 let expected = compile_batch(&older, &policy);
825 let replay = evaluate_temporal_replay(
826 &[
827 TemporalReplaySnapshot {
828 recorded_at: "2026-03-08T00:00:00Z".into(),
829 batch: older,
830 },
831 TemporalReplaySnapshot {
832 recorded_at: "2026-03-10T00:00:00Z".into(),
833 batch: newer,
834 },
835 ],
836 "2026-03-09T00:00:00Z",
837 &policy,
838 &expected.graph_hash,
839 )
840 .expect("replay result expected");
841
842 assert!(replay.matched_expected_hash);
843 assert_eq!(replay.selected_recorded_at, "2026-03-08T00:00:00Z");
844 assert_eq!(replay.source_envelope_id, "env-old");
845 }
846
847 #[test]
848 fn temporal_replay_handles_timezone_offsets_without_string_ordering_bugs() {
849 let policy = CompilerPolicy {
850 policy_version: "policy-1".into(),
851 include_hyperedges: true,
852 };
853 let earlier = rich_batch("env-offset-earlier", "older");
854 let newer = rich_batch("env-offset-newer", "newer");
855 let expected = compile_batch(&newer, &policy);
856 let replay = evaluate_temporal_replay(
857 &[
858 TemporalReplaySnapshot {
859 recorded_at: "2026-03-08T15:00:00-05:00".into(),
860 batch: earlier,
861 },
862 TemporalReplaySnapshot {
863 recorded_at: "2026-03-08T21:00:00+02:00".into(),
864 batch: newer,
865 },
866 ],
867 "2026-03-08T19:30:00+00:00",
868 &policy,
869 &expected.graph_hash,
870 )
871 .expect("replay result expected");
872
873 assert_eq!(replay.selected_recorded_at, "2026-03-08T21:00:00+02:00");
874 }
875
876 #[test]
877 fn temporal_replay_returns_none_for_malformed_cutoff_timestamp() {
878 let policy = CompilerPolicy {
879 policy_version: "policy-1".into(),
880 include_hyperedges: true,
881 };
882 let replay = replay_as_of(
883 &[TemporalReplaySnapshot {
884 recorded_at: "2026-03-08T00:00:00Z".into(),
885 batch: rich_batch("env-old", "old"),
886 }],
887 "not-a-real-timestamp",
888 &policy,
889 );
890
891 assert!(replay.is_none());
892 }
893
894 #[test]
895 fn temporal_replay_returns_none_for_malformed_snapshot_timestamp() {
896 let policy = CompilerPolicy {
897 policy_version: "policy-1".into(),
898 include_hyperedges: true,
899 };
900 let replay = replay_as_of(
901 &[TemporalReplaySnapshot {
902 recorded_at: "not-a-real-timestamp".into(),
903 batch: rich_batch("env-old", "old"),
904 }],
905 "2026-03-09T00:00:00Z",
906 &policy,
907 );
908
909 assert!(replay.is_none());
910 }
911
912 #[test]
913 fn causal_refuter_emits_flip_witness_for_single_peer_removal() {
914 let compiled = compiled_fixture();
915 let result = evaluate_causal_refuter(&compiled, "claim-version-alpha-1", 1);
916
917 assert_eq!(result.mode, RefutationMode::CausalRefuter);
918 assert!(matches!(
919 result.outcome,
920 OracleRefutationOutcome::FlipWitness { .. }
921 ));
922 }
923
924 #[test]
925 fn minimal_perturbation_search_defaults_to_one_step_budget() {
926 let compiled = compiled_fixture();
927 let result = minimal_perturbation_witness(&compiled, "claim-version-alpha-1", 0);
928
929 assert!(matches!(
930 result.outcome,
931 OracleRefutationOutcome::FlipWitness { .. }
932 ));
933 assert_eq!(result.searched_budget, 1);
934 }
935
936 #[test]
937 fn causal_refuter_budget_is_exhausted_before_flip_is_possible() {
938 let (compiled, claims) = compile_many_claim_fixture(11);
939 let target = &claims[0];
940 let result = evaluate_causal_refuter(&compiled, target, 10);
941
942 match result.outcome {
943 OracleRefutationOutcome::NoFlipFound { searched_budget } => {
944 assert_eq!(
945 searched_budget, MAX_REMOVAL_COMBINATION_BUDGET,
946 "combinatorial search should stop at bounded budget"
947 );
948 }
949 other => unreachable!("OracleRefutationOutcome::NoFlipFound expected, got {other:?}"),
950 }
951 assert_eq!(
952 result.mode,
953 RefutationMode::CausalRefuter,
954 "mode should remain causal refuter"
955 );
956 assert!(result.searched_budget >= MAX_REMOVAL_COMBINATION_BUDGET);
957 }
958
959 #[test]
960 fn causal_refuter_reports_actual_witness_search_budget() {
961 let compiled = explicit_refute_fixture();
962 let result = evaluate_causal_refuter(&compiled, "target", 2);
963
964 match result.outcome {
965 OracleRefutationOutcome::FlipWitness { removed_node_ids } => {
966 assert_eq!(
967 removed_node_ids.len(),
968 2,
969 "expected two-node witness to break support"
970 );
971 }
972 other => unreachable!("OracleRefutationOutcome::FlipWitness expected, got {other:?}"),
973 }
974 assert_eq!(result.searched_budget, 3);
975 }
976
977 #[test]
978 fn minimal_perturbation_preserves_small_graph_flip_witness() {
979 let compiled = compiled_fixture();
980 let result = minimal_perturbation_witness(&compiled, "claim-version-alpha-1", 4);
981
982 assert!(matches!(
983 result.outcome,
984 OracleRefutationOutcome::FlipWitness { .. }
985 ));
986 assert_eq!(result.searched_budget, 1);
987 }
988
989 #[test]
990 fn delta_parity_matches_full_recompute_on_recomputed_nodes() {
991 let compiled = compiled_fixture();
992 let parity = evaluate_delta_parity(&compiled, &["claim-version-alpha-2".into()], 4);
993
994 assert!(parity.parity_match);
995 assert!(!parity.recomputed_node_ids.is_empty());
996 assert!(!parity.recomputed_region_ids.is_empty());
997 assert!(parity.certificate.parity_match);
998 }
999
1000 proptest! {
1001 #[test]
1002 fn malformed_rfc3339_timestamps_are_rejected(value in ".{1,64}") {
1003 prop_assume!(chrono::DateTime::parse_from_rfc3339(&value).is_err());
1004 prop_assert!(parsed_utc_timestamp(&value).is_none());
1005 }
1006 }
1007}