1#![cfg_attr(test, allow(deprecated))]
2#![cfg_attr(test, allow(clippy::expect_used))]
3
4pub mod reference_interpreters;
5
6pub fn phase_1_gates() -> &'static [&'static str] {
8 &["CF-A1", "CF-C1", "CF-C2", "CF-O1", "CF-R1"]
9}
10
11pub fn phase_2_gates() -> &'static [&'static str] {
13 &["CF-A3", "CF-C3", "CF-C4", "CF-O2"]
14}
15
16pub fn phase_3_plus_gates() -> &'static [&'static str] {
18 &[
19 "CF-O3", "CF-O4", "CF-R2", "CF-R3", "CF-R4", "CF-B1", "CF-B2", "CF-B3", "CF-P1", "CF-P2",
20 ]
21}
22
23pub fn v16_v20_gates() -> &'static [&'static str] {
25 &[
26 "FED-001", "FED-004", "THY-001", "POR-001", "GOV-001", "SFX-001",
27 ]
28}
29
30pub fn v9_constitutional_gates() -> &'static [&'static str] {
41 &[
42 "CF-V9-C001",
43 "CF-V9-C002",
44 "CF-V9-C004",
45 "CF-V9-C005",
46 "CF-V9-C008",
47 "CF-V9-C009",
48 "CF-V9-C017",
49 ]
50}
51
52#[cfg(test)]
53mod tests {
54 use constraint_compiler::{
55 compile_batch, CompilerPolicy, ConstraintDegradation, GraphSurfaceKind,
56 };
57 use forge_memory_bridge::{
58 transform_envelope_v2, transform_envelope_v3, BridgeImportFailureArtifact,
59 ImportProjectionRecord,
60 };
61 use kernel_execution::{
62 affected_nodes_for_delta, execute_delta_propagation, execute_message_passing_baseline,
63 execute_residual_correction, schedule_execution, ExecutionBudget, ExecutionStopReason,
64 WorkloadClass,
65 };
66 use kernel_oracles::{
67 evaluate_causal_refuter, evaluate_conservative, evaluate_exact_bounded,
68 evaluate_temporal_replay, minimal_perturbation_witness, OracleRefutationOutcome,
69 TemporalReplaySnapshot,
70 };
71 use knowledge_runtime::{
72 adapters::semantic_memory::SemanticMemoryAdapter, config::ProjectionConfig,
73 KnowledgeRuntime, RuntimeConfig, Scope,
74 };
75 use recursive_kernel_core::{ArtifactAuthorityClass, KernelRun};
76 use semantic_memory::{MemoryConfig, MemoryStore, MockEmbedder, ProjectionQuery};
77 use semantic_memory_forge::{
78 ConstraintSeedKind, ExportAuthority, ExportClaim, ExportConfidenceClass,
79 ExportEnvelopeError, ExportEnvelopeV2, ExportEnvelopeV3, ExportRecord,
80 ExportRecordSemanticsV3, ExportRecordV3, ForgeExportMeta, NuisanceSnapshot,
81 ProjectionVisibilityClass, EXPORT_ENVELOPE_V2_SCHEMA, EXPORT_ENVELOPE_V3_SCHEMA,
82 };
83 use stack_ids::{
84 AssertionGroupId, ClaimFamilyId, ClaimId, ClaimVersionId, ContentDigest, EntityId,
85 EnvelopeId, KernelRunId, OperatorId, ScopeKey,
86 };
87 use std::{convert::TryFrom, future::Future, path::PathBuf};
88 use tempfile::TempDir;
89 use verification_calibration::NuisanceStateArtifact;
90 use verification_control::{RegionArtifactKind, RegionArtifactTransport, SyndromeRouteRecord};
91
92 fn block_on<F: Future>(future: F) -> F::Output {
93 tokio::runtime::Runtime::new().unwrap().block_on(future)
94 }
95
96 fn make_batch(
97 namespace: &str,
98 claim_ids: &[&str],
99 thin: bool,
100 nuisance: bool,
101 ) -> forge_memory_bridge::ProjectionImportBatchV3 {
102 let scope = ScopeKey::namespace_only(namespace);
103 let records = claim_ids
104 .iter()
105 .enumerate()
106 .map(|(index, claim_id)| ExportRecordV3 {
107 record: ExportRecord::Claim(ExportClaim {
108 claim_id: None,
109 claim_version_id: Some((*claim_id).into()),
110 subject_entity_id: EntityId::new(format!("entity-{index}")),
111 predicate: "supports".into(),
112 object_anchor: serde_json::json!("result"),
113 valid_from: None,
114 valid_to: None,
115 confidence: 1.0,
116 content: format!("{claim_id} supports result"),
117 projection_family: "forge_verification".into(),
118 supersedes_claim_id: None,
119 supersedes_claim_version_id: None,
120 metadata: None,
121 }),
122 semantics: (!thin).then(|| ExportRecordSemanticsV3 {
123 claim_family_id: Some(ClaimFamilyId::new("family-1")),
124 assertion_group_id: Some(AssertionGroupId::new("group-1")),
125 relation_group_id: None,
126 joint_evidence_group_id: None,
127 constraint_seed_kind: Some(ConstraintSeedKind::Hyperedge),
128 treatment_hint: None,
129 outcome_hint: None,
130 confounder_hint: None,
131 instrument_hint: None,
132 effect_modifier_hint: None,
133 contradiction_candidate_group_id: None,
134 mutual_exclusion_group_id: None,
135 comparability_snapshot_version: nuisance.then(|| "cmp-1".into()),
136 nuisance_snapshot: nuisance.then(|| NuisanceSnapshot {
137 environment_fingerprint: Some("linux".into()),
138 toolchain_version: Some("rust-1.85".into()),
139 dependency_set_hash: Some("deps-1".into()),
140 scope_mismatch_markers: vec![],
141 measurement_notes: vec!["measurement drift".into()],
142 selection_bias_markers: vec!["selection bias".into()],
143 }),
144 projection_visibility_class: ProjectionVisibilityClass::Standard,
145 export_confidence_class: ExportConfidenceClass::Verified,
146 derivation_seed_ids: vec![],
147 review_priority_hint: None,
148 }),
149 })
150 .collect::<Vec<_>>();
151 let export_meta = ForgeExportMeta {
152 authority: ExportAuthority::Forge,
153 run_id: Some("run-1".into()),
154 direct_write: false,
155 comparability_snapshot_version: nuisance.then(|| "cmp-1".into()),
156 exported_at: "2026-03-10T00:00:00Z".into(),
157 };
158 let digest =
159 ExportEnvelopeV3::compute_digest("forge", &scope, &records, Some(&export_meta), None)
160 .unwrap();
161 let envelope = ExportEnvelopeV3 {
162 envelope_id: EnvelopeId::new(format!("env-{namespace}")),
163 schema_version: EXPORT_ENVELOPE_V3_SCHEMA.into(),
164 content_digest: digest,
165 source_authority: "forge".into(),
166 scope_key: scope,
167 trace_ctx: None,
168 exported_at: "2026-03-10T00:00:00Z".into(),
169 export_meta: Some(export_meta),
170 evidence_bundle: None,
171 support_sets: vec![],
172 contradiction_witnesses: vec![],
173 retraction_records: vec![],
174 claim_states_v13: vec![],
175 intervention_bundles_v14: vec![],
176 outcome_schemas_v14: vec![],
177 cohort_contracts_v14: vec![],
178 counterfactual_slices_v14: vec![],
179 experiment_cases_v14: vec![],
180 comparability_matrices_v14: vec![],
181 decision_traces_v14: vec![],
182 refuter_suites_v14: vec![],
183 refuter_results_v14: vec![],
184 experiment_budgets_v14: vec![],
185 rollout_decisions_v14: vec![],
186 rollback_decisions_v14: vec![],
187 attestation_envelopes_v15: vec![],
188 trust_root_sets_v15: vec![],
189 artifact_admission_policies_v15: vec![],
190 transparency_receipts_v15: vec![],
191 attestation_revocations_v15: vec![],
192 attestation_supersessions_v15: vec![],
193 remote_oracle_leases_v15: vec![],
194 remote_slice_requests_v15: vec![],
195 remote_slice_results_v15: vec![],
196 cross_runtime_replay_tickets_v15: vec![],
197 dispute_bundles_v15: vec![],
198 disclosure_policies_v15: vec![],
199 disclosure_budgets_v15: vec![],
200 records,
201 };
202 transform_envelope_v3(&envelope).unwrap()
203 }
204
205 fn make_v2_envelope(namespace: &str, claim_ids: &[&str]) -> ExportEnvelopeV2 {
206 let scope = ScopeKey::namespace_only(namespace);
207 let records = claim_ids
208 .iter()
209 .enumerate()
210 .map(|(index, claim_version_id)| {
211 ExportRecord::Claim(ExportClaim {
212 claim_id: Some(ClaimId::new(format!("claim-{index}"))),
213 claim_version_id: Some(ClaimVersionId::new(*claim_version_id)),
214 subject_entity_id: EntityId::new(format!("entity-{index}")),
215 predicate: "supports".into(),
216 object_anchor: serde_json::json!("result"),
217 valid_from: None,
218 valid_to: None,
219 confidence: 1.0,
220 content: format!("{claim_version_id} supports result"),
221 projection_family: "forge_verification".into(),
222 supersedes_claim_id: None,
223 supersedes_claim_version_id: None,
224 metadata: None,
225 })
226 })
227 .collect::<Vec<_>>();
228 let export_meta = ForgeExportMeta {
229 authority: ExportAuthority::Forge,
230 run_id: Some("run-v2".into()),
231 direct_write: false,
232 comparability_snapshot_version: Some("cmp-v2".into()),
233 exported_at: "2026-03-10T00:00:00Z".into(),
234 };
235 let digest =
236 ExportEnvelopeV2::compute_digest("forge", &scope, &records, Some(&export_meta), None)
237 .unwrap();
238
239 ExportEnvelopeV2 {
240 envelope_id: EnvelopeId::new(format!("env-v2-{namespace}")),
241 schema_version: EXPORT_ENVELOPE_V2_SCHEMA.into(),
242 content_digest: digest,
243 source_authority: "forge".into(),
244 scope_key: scope,
245 trace_ctx: None,
246 exported_at: "2026-03-10T00:00:00Z".into(),
247 export_meta: Some(export_meta),
248 evidence_bundle: None,
249 records,
250 }
251 }
252
253 fn compile_fixture(
254 namespace: &str,
255 claim_ids: &[&str],
256 thin: bool,
257 nuisance: bool,
258 ) -> constraint_compiler::CompileOutput {
259 compile_batch(
260 &make_batch(namespace, claim_ids, thin, nuisance),
261 &CompilerPolicy {
262 policy_version: "policy-1".into(),
263 include_hyperedges: true,
264 },
265 )
266 }
267
268 fn open_runtime(
269 batch: &forge_memory_bridge::ProjectionImportBatchV3,
270 namespace: &str,
271 ) -> KnowledgeRuntime {
272 let dir = TempDir::new().unwrap();
273 let base_dir = dir.path().to_path_buf();
274 std::mem::forget(dir);
275 let config = MemoryConfig {
276 base_dir,
277 ..Default::default()
278 };
279 let embedder = Box::new(MockEmbedder::new(config.embedding.dimensions));
280 let store = MemoryStore::open_with_embedder(config, embedder).unwrap();
281 block_on(store.import_projection_batch(batch)).unwrap();
282 let adapter = SemanticMemoryAdapter::new(store);
283 KnowledgeRuntime::new(
284 RuntimeConfig {
285 default_scope: Scope::new(namespace),
286 query: Default::default(),
287 entity: Default::default(),
288 projection: ProjectionConfig {
289 staleness_threshold_secs: 3600,
290 import_staleness_threshold_secs: 0,
291 persist: false,
292 },
293 strict_temporal: false,
294 strict_scope: false,
295 },
296 adapter,
297 )
298 .unwrap()
299 }
300
301 fn open_store() -> MemoryStore {
302 let dir = TempDir::new().unwrap();
303 let base_dir = dir.path().to_path_buf();
304 std::mem::forget(dir);
305 open_store_with_path(base_dir)
306 }
307
308 fn open_store_with_path(base_dir: PathBuf) -> MemoryStore {
309 let config = MemoryConfig {
310 base_dir,
311 ..Default::default()
312 };
313 let embedder = Box::new(MockEmbedder::new(config.embedding.dimensions));
314 MemoryStore::open_with_embedder(config, embedder).unwrap()
315 }
316
317 #[test]
318 fn cf_a1_kernel_artifacts_are_non_authoritative() {
319 let run = KernelRun {
320 run_id: KernelRunId::new("run-1"),
321 policy_version: "policy-1".into(),
322 scope_key: ScopeKey::namespace_only("ns"),
323 operator_ids: vec![OperatorId::new(
324 recursive_kernel_core::CONSTRAINT_COMPILER_OPERATOR_ID,
325 )],
326 oracle_slice_id: None,
327 degradation_active: false,
328 };
329
330 assert_eq!(
331 run.authority_class(),
332 ArtifactAuthorityClass::NonAuthoritativeDerived
333 );
334 }
335
336 #[test]
337 fn cf_a3_advisory_outputs_do_not_self_promote() {
338 let runtime = open_runtime(
339 &make_batch("cf-a3", &["claim-a", "claim-b", "claim-c"], false, false),
340 "cf-a3",
341 );
342 let advisory = block_on(runtime.latest_inference_advisory(Some(&Scope::new("cf-a3"))))
343 .unwrap()
344 .expect("advisory expected");
345 let gate = block_on(runtime.latest_risk_gate(Some(&Scope::new("cf-a3"))))
346 .unwrap()
347 .expect("gate expected");
348
349 assert!(advisory.advisory_only);
350 assert!(gate.advisory_only);
351 assert_ne!(gate.status, "authoritative");
352 }
353
354 #[test]
355 fn cf_c1_deterministic_compile() {
356 let a = compile_fixture("cf-c1", &["claim-a", "claim-b"], false, false);
357 let b = compile_fixture("cf-c1", &["claim-a", "claim-b"], false, false);
358 assert_eq!(a.graph_hash, b.graph_hash);
359 }
360
361 #[test]
362 fn cf_c2_thin_export_degrades_explicitly() {
363 let compiled = compile_fixture("cf-c2", &["claim-a"], true, false);
364 assert!(compiled
365 .degradations
366 .contains(&ConstraintDegradation::ThinExport));
367 }
368
369 #[test]
370 fn cf_c2_mixed_semantics_do_not_hallucinate_hyperedge_membership() {
371 let mut batch = make_batch("cf-c2-mixed", &["claim-a", "claim-b"], false, false);
372 batch.records[0].semantics = None;
373 let compiled = compile_batch(
374 &batch,
375 &CompilerPolicy {
376 policy_version: "policy-1".into(),
377 include_hyperedges: true,
378 },
379 );
380
381 assert!(compiled
382 .degradations
383 .contains(&ConstraintDegradation::ThinExport));
384 assert!(compiled.oracle_candidates.is_empty());
385
386 let group_hyperedge = compiled
387 .hyperedges
388 .iter()
389 .find(|edge| edge.edge_id == "assertion_group:group-1")
390 .expect("thin export should keep only explicitly provided grouping");
391 assert_eq!(group_hyperedge.member_node_ids, vec!["claim-b".to_string()]);
392 }
393
394 #[test]
395 fn cf_c2a_thin_export_forces_conservative_oracle_mode() {
396 let compiled = compile_fixture("cf-c2a", &["claim-a"], true, false);
397
398 assert!(compiled
399 .degradations
400 .contains(&ConstraintDegradation::ThinExport));
401 assert!(compiled.oracle_candidates.is_empty());
402 assert!(evaluate_exact_bounded(&compiled).is_none());
403 let conservative = evaluate_conservative(&compiled);
404 assert!(conservative.supported);
405 assert_eq!(
406 conservative.mode,
407 kernel_oracles::OracleMode::ConservativeFallback
408 );
409 }
410
411 #[test]
412 fn cf_c3_nuisance_state_is_included() {
413 let compiled = compile_fixture("cf-c3", &["claim-a", "claim-b"], false, true);
414 assert!(compiled
415 .nodes
416 .iter()
417 .any(|node| node.kind == "nuisance_state"));
418 }
419
420 #[test]
421 fn cf_c4_hyperedge_correctness_preserves_group_membership() {
422 let compiled = compile_fixture("cf-c4", &["claim-a", "claim-b"], false, false);
423 let hyperedge = compiled
424 .hyperedges
425 .iter()
426 .find(|edge| edge.edge_id == "assertion_group:group-1")
427 .expect("group hyperedge expected");
428 assert_eq!(hyperedge.member_node_ids.len(), 2);
429 }
430
431 #[test]
432 fn cf_g1_v2_to_v3_upgrade_preserves_bridgeable_projection_content() {
433 let v2 = make_v2_envelope("cf-g1", &["claim-g1-a", "claim-g1-b"]);
434 let v3 = ExportEnvelopeV3::try_from(v2.clone()).unwrap();
435 let v2_batch = transform_envelope_v2(&v2).unwrap();
436 let v3_batch = transform_envelope_v3(&v3).unwrap();
437 let v3_records = v3_batch
438 .records
439 .iter()
440 .map(|record| record.record.clone())
441 .collect::<Vec<_>>();
442
443 assert_eq!(v2_batch.source_envelope_id, v3_batch.source_envelope_id);
444 assert_eq!(v2_batch.scope_key, v3_batch.scope_key);
445 assert_eq!(v2_batch.source_authority, v3_batch.source_authority);
446 assert_eq!(v2_batch.records.len(), v3_batch.records.len());
447 assert_eq!(
448 serde_json::to_value(&v2_batch.records).unwrap(),
449 serde_json::to_value(&v3_records).unwrap()
450 );
451 }
452
453 #[test]
454 fn cf_g4_imported_projection_data_survives_drop_and_reopen() {
455 let dir = TempDir::new().unwrap();
456 let base_dir = dir.path().to_path_buf();
457 std::mem::forget(dir);
458
459 let batch = make_batch("cf-g4", &["claim-g4-a", "claim-g4-b"], false, false);
460 let imported_count = {
461 let store = open_store_with_path(base_dir.clone());
462 let imported = block_on(store.import_projection_batch(&batch)).unwrap();
463 assert_eq!(imported.status, "complete");
464 let claims = block_on(
465 store.query_claim_versions(ProjectionQuery::new(ScopeKey::namespace_only("cf-g4"))),
466 )
467 .unwrap();
468 assert!(
469 claims
470 .iter()
471 .any(|claim| claim.claim_version_id == ClaimVersionId::new("claim-g4-a")),
472 "imported data should be queryable before drop"
473 );
474 claims.len()
475 };
476
477 let reopened_count = {
478 let store = open_store_with_path(base_dir);
479 let claims = block_on(
480 store.query_claim_versions(ProjectionQuery::new(ScopeKey::namespace_only("cf-g4"))),
481 )
482 .unwrap();
483 claims.len()
484 };
485
486 assert_eq!(reopened_count, imported_count);
487 }
488
489 #[test]
490 fn cf_g2_invalid_upgrade_fails_explicitly() {
491 let mut v2 = make_v2_envelope("cf-g2", &["claim-g2-a"]);
492 v2.content_digest = ContentDigest::compute(b"tampered");
493
494 let err = ExportEnvelopeV3::try_from(v2).unwrap_err();
495 assert!(matches!(err, ExportEnvelopeError::DigestMismatch { .. }));
496 }
497
498 #[test]
499 fn cf_g3_retry_import_of_upgraded_batch_is_idempotent() {
500 let v2 = make_v2_envelope("cf-g3", &["claim-g3-a"]);
501 let v3 = ExportEnvelopeV3::try_from(v2).unwrap();
502 let batch = transform_envelope_v3(&v3).unwrap();
503 let store = open_store();
504
505 let first = block_on(store.import_projection_batch(&batch)).unwrap();
506 let second = block_on(store.import_projection_batch(&batch)).unwrap();
507 let claims = block_on(
508 store.query_claim_versions(ProjectionQuery::new(ScopeKey::namespace_only("cf-g3"))),
509 )
510 .unwrap();
511
512 assert_eq!(first.status, "complete");
513 assert_eq!(second.status, "already_imported");
514 assert_eq!(claims.len(), 1);
515 assert!(matches!(
516 batch.records.first().map(|record| &record.record),
517 Some(ImportProjectionRecord::ClaimVersion(_))
518 ));
519 }
520
521 #[test]
522 fn cf_o1_oracle_parity_on_supported_slice() {
523 let compiled = compile_fixture("cf-o1", &["claim-a", "claim-b"], false, false);
524 let exact = evaluate_exact_bounded(&compiled).expect("exact oracle slice expected");
525 let conservative = evaluate_conservative(&compiled);
526 assert_eq!(
527 exact.satisfied_constraint_count,
528 conservative.satisfied_constraint_count
529 );
530 }
531
532 #[test]
533 fn cf_o1_exact_oracle_mode_is_reproducible() {
534 let compiled = compile_fixture("cf-o1-repro", &["claim-a", "claim-b"], false, false);
535 let first = evaluate_exact_bounded(&compiled).expect("exact oracle slice expected");
536 let second = evaluate_exact_bounded(&compiled).expect("exact oracle slice expected");
537
538 assert_eq!(first, second);
539 }
540
541 #[test]
542 fn cf_o2_delta_parity_matches_bounded_recompute() {
543 let compiled = compile_fixture("cf-o2", &["claim-a", "claim-b"], false, false);
544 let delta = execute_delta_propagation(&compiled, &["claim-a".into()], 3);
545 let full = execute_message_passing_baseline(&compiled, 3);
546 let affected = affected_nodes_for_delta(&compiled.invalidation_cones, &["claim-a".into()]);
547
548 for node_id in affected {
549 let delta_belief = delta
550 .execution
551 .node_beliefs
552 .iter()
553 .find(|belief| belief.node_id == node_id)
554 .expect("delta belief expected")
555 .belief_micros;
556 let full_belief = full
557 .node_beliefs
558 .iter()
559 .find(|belief| belief.node_id == node_id)
560 .expect("full belief expected")
561 .belief_micros;
562 assert_eq!(delta_belief, full_belief);
563 }
564 assert!(!delta.recomputed_region_ids.is_empty());
565 assert!(!delta.region_traces.is_empty());
566 assert!(!delta.invalidation_manifest.explicit_global_rebuild);
567 }
568
569 #[test]
570 fn cf_v10_geometry_and_regions_are_explicit() {
571 let compiled = compile_fixture("cf-v10-geometry", &["claim-a", "claim-b"], false, false);
572
573 assert_eq!(
574 compiled.geometry_manifest.surfaces,
575 vec![
576 GraphSurfaceKind::Storage,
577 GraphSurfaceKind::Retrieval,
578 GraphSurfaceKind::Inference,
579 GraphSurfaceKind::Repair,
580 GraphSurfaceKind::Control,
581 ]
582 );
583 assert!(compiled.geometry_manifest.no_silent_collapse);
584 assert!(compiled
585 .regions
586 .iter()
587 .all(|region| region.bounded_default_unit_of_work));
588 }
589
590 #[test]
591 fn cf_o3_temporal_replay_matches_expected_snapshot() {
592 let older = make_batch("cf-o3-old", &["claim-old-a", "claim-old-b"], false, false);
593 let newer = make_batch("cf-o3-new", &["claim-new-a", "claim-new-b"], false, false);
594 let policy = CompilerPolicy {
595 policy_version: "policy-1".into(),
596 include_hyperedges: true,
597 };
598 let expected = compile_batch(&older, &policy);
599 let replay = evaluate_temporal_replay(
600 &[
601 TemporalReplaySnapshot {
602 recorded_at: "2026-03-08T00:00:00Z".into(),
603 batch: older,
604 },
605 TemporalReplaySnapshot {
606 recorded_at: "2026-03-10T00:00:00Z".into(),
607 batch: newer,
608 },
609 ],
610 "2026-03-09T00:00:00Z",
611 &policy,
612 &expected.graph_hash,
613 )
614 .expect("replay result expected");
615
616 assert!(replay.matched_expected_hash);
617 }
618
619 #[test]
620 fn cf_o4_refuter_integration_emits_structured_result() {
621 let compiled = compile_fixture("cf-o4", &["claim-a", "claim-b"], false, false);
622 let result = evaluate_causal_refuter(&compiled, "claim-a", 1);
623 assert!(matches!(
624 result.outcome,
625 OracleRefutationOutcome::FlipWitness { .. }
626 | OracleRefutationOutcome::NoFlipFound { .. }
627 | OracleRefutationOutcome::NotApplicable { .. }
628 ));
629 }
630
631 #[test]
632 fn cf_r1_operator_requires_explicit_stop_rule() {
633 let operator = recursive_kernel_core::constraint_compiler_operator();
634 operator.validate().unwrap();
635 }
636
637 #[test]
638 fn cf_r2_instability_terminates_explicitly() {
639 let compiled = compile_fixture("cf-r2", &["claim-a", "claim-b"], false, true);
640 let report = execute_message_passing_baseline(&compiled, 1);
641 assert_eq!(report.stop_reason, ExecutionStopReason::MaxIterations);
642 }
643
644 #[test]
645 fn cf_r3_residual_repair_is_monotone() {
646 let compiled = compile_fixture("cf-r3", &["claim-a", "claim-b"], false, false);
647 let report = execute_residual_correction(&compiled, 3);
648 let mut previous = u64::MAX;
649 for sample in &report.residuals {
650 assert!(sample.total_residual_micros <= previous);
651 previous = sample.total_residual_micros;
652 }
653 assert!(report.convergence_report.residual_monotone_nonincreasing);
654 }
655
656 #[test]
657 fn cf_r4_syndromes_are_reproducible() {
658 let compiled = compile_fixture("cf-r4", &["claim-a"], true, false);
659 let a = execute_message_passing_baseline(&compiled, 2);
660 let b = execute_message_passing_baseline(&compiled, 2);
661 assert_eq!(a.syndromes, b.syndromes);
662 }
663
664 #[test]
665 fn cf_b1_minimal_perturbation_returns_structured_witness() {
666 let compiled = compile_fixture("cf-b1", &["claim-a", "claim-b"], false, false);
667 let result = minimal_perturbation_witness(&compiled, "claim-a", 1);
668 assert!(matches!(
669 result.outcome,
670 OracleRefutationOutcome::FlipWitness { .. }
671 | OracleRefutationOutcome::NoFlipFound { .. }
672 ));
673 }
674
675 #[test]
676 fn cf_b2_witness_and_certificate_emission_exist() {
677 let compiled = compile_fixture("cf-b2", &["claim-a", "claim-b", "claim-c"], false, false);
678 let scheduled = schedule_execution(&compiled, &ExecutionBudget::default());
679 assert!(!scheduled.execution.witnesses.is_empty());
680 assert!(!scheduled.execution.certificates.is_empty());
681 }
682
683 #[test]
684 fn cf_b3_calibration_disclosure_surfaces_on_nuisance_runs() {
685 let runtime = open_runtime(
686 &make_batch("cf-b3", &["claim-a", "claim-b"], false, true),
687 "cf-b3",
688 );
689 let explanation =
690 block_on(runtime.latest_inference_explanation(Some(&Scope::new("cf-b3"))))
691 .unwrap()
692 .expect("explanation expected");
693
694 assert!(explanation
695 .calibration_caveats
696 .iter()
697 .any(|caveat| caveat.contains("nuisance state")));
698 }
699
700 #[test]
701 fn cf_v10_syndrome_transport_and_nuisance_artifacts_are_typed() {
702 let compiled = compile_fixture("cf-v10-artifacts", &["claim-a", "claim-b"], false, true);
703 let report = execute_message_passing_baseline(&compiled, 2);
704 let first_region = compiled.regions.first().expect("region expected");
705 let first_syndrome = report.syndromes.first().expect("syndrome expected");
706
707 let route = SyndromeRouteRecord::new(
708 first_syndrome.syndrome_id.clone(),
709 first_region.region_id.clone(),
710 first_region.node_ids.clone(),
711 vec!["witness:claim-a".into()],
712 );
713 let transport_payload = serde_json::to_string(first_syndrome).unwrap();
714 let transport = RegionArtifactTransport::new(
715 RegionArtifactKind::Syndrome,
716 first_syndrome.syndrome_id.to_string(),
717 transport_payload.as_bytes(),
718 first_region.region_id.clone(),
719 first_region.region_id.clone(),
720 "inference",
721 "repair",
722 "replay:syndrome-route",
723 );
724 let nuisance = NuisanceStateArtifact::new(
725 "2026-03-13T00:00:00Z",
726 Some(first_region.region_id.clone()),
727 "provider_instability",
728 vec!["retry_storm".into(), "comparability_shift".into()],
729 true,
730 vec!["kernel_execution".into(), "verification_control".into()],
731 );
732
733 assert!(route.routed_before_global_invalidation);
734 assert_eq!(transport.from_surface, "inference");
735 assert!(nuisance.semantic_change_confounded);
736 }
737
738 #[test]
739 fn cf_p1_workload_classes_remain_separate() {
740 let small = schedule_execution(
741 &compile_fixture("cf-p1-small", &["claim-a", "claim-b"], false, false),
742 &ExecutionBudget::default(),
743 );
744 let large = schedule_execution(
745 &compile_fixture(
746 "cf-p1-large",
747 &[
748 "claim-a", "claim-b", "claim-c", "claim-d", "claim-e", "claim-f", "claim-g",
749 "claim-h", "claim-i",
750 ],
751 false,
752 false,
753 ),
754 &ExecutionBudget {
755 max_nodes: 4,
756 ..ExecutionBudget::default()
757 },
758 );
759
760 assert_eq!(small.workload_class, WorkloadClass::Background);
761 assert_eq!(large.workload_class, WorkloadClass::Heavy);
762 }
763
764 #[test]
765 fn cf_p2_budgeted_degradation_is_explicit() {
766 let scheduled = schedule_execution(
767 &compile_fixture(
768 "cf-p2",
769 &["claim-a", "claim-b", "claim-c", "claim-d", "claim-e"],
770 false,
771 false,
772 ),
773 &ExecutionBudget {
774 max_nodes: 2,
775 ..ExecutionBudget::default()
776 },
777 );
778 assert_eq!(
779 scheduled.execution.stop_reason,
780 ExecutionStopReason::BudgetExhausted
781 );
782 assert_eq!(
783 scheduled.degraded_reason.as_deref(),
784 Some("budget_exhausted")
785 );
786 }
787
788 #[test]
796 fn cf_v9_c001_episode_bundle_round_trips() {
797 use semantic_memory_forge::{
799 DispatchOutcomeV1, EpisodeBundleV1, ExecutionContextV1, VerificationSummary,
800 EPISODE_BUNDLE_V1_SCHEMA, EXECUTION_CONTEXT_V1_SCHEMA,
801 };
802 use stack_ids::{EpisodeId, TraceCtx};
803
804 let trace = TraceCtx::generate();
805 let ctx = ExecutionContextV1 {
806 schema_version: EXECUTION_CONTEXT_V1_SCHEMA.into(),
807 trace_ctx: trace.clone(),
808 attempt_id: Some(stack_ids::AttemptId::new("attempt-c001")),
809 trial_id: Some(stack_ids::TrialId::new("trial-c001")),
810 replay_link: Some("replay://link".into()),
811 workload_class: Some("forge_export".into()),
812 queue_hops: vec!["hop-1".into(), "hop-2".into()],
813 deadline: Some("2026-04-01T00:00:00Z".into()),
814 cost_budget_units: Some(1000),
815 degradation_markers: vec!["marker-a".into()],
816 dispatch_outcome: DispatchOutcomeV1::Succeeded,
817 environment_fingerprint: Some("linux-amd64".into()),
818 provider_route: Some("forge".into()),
819 cancellation_reason: None,
820 tool_receipt_ref: Some("receipt://ref".into()),
821 provider_call_ref: Some("call://ref".into()),
822 replay_parent_ref: Some("parent://ref".into()),
823 };
824 let bundle = EpisodeBundleV1 {
825 schema_version: EPISODE_BUNDLE_V1_SCHEMA.into(),
826 bundle_id: "bundle-c001".into(),
827 episode_id: EpisodeId::new("ep-c001"),
828 primary_document_id: "doc-c001".into(),
829 namespace: "ns-c001".into(),
830 scope_key: ScopeKey::namespace_only("ns-c001"),
831 valid_from: Some("2026-01-01T00:00:00Z".into()),
832 valid_to: Some("2026-12-31T23:59:59Z".into()),
833 exported_at: "2026-03-15T00:00:00Z".into(),
834 recorded_at: Some("2026-03-15T00:00:01Z".into()),
835 source_envelope_id: EnvelopeId::new("env-c001"),
836 content_digest: ContentDigest::compute(b"c001-content"),
837 source_evidence_pointers: vec!["evidence://ptr-1".into()],
838 source_receipt_digests: vec!["digest-1".into()],
839 claim_version_ids: vec!["claim-v1".into(), "claim-v2".into()],
840 relation_version_ids: vec!["rel-v1".into()],
841 verification_summary: Some(VerificationSummary {
842 lifecycle_state: semantic_memory_forge::VerificationLifecycleState::Verified,
843 promotion_state: semantic_memory_forge::PromotionState::Promoted {
844 version_id: Some("v1".into()),
845 promoted_at: Some("2026-03-15T00:00:00Z".into()),
846 },
847 completed_trial_count: 3,
848 passed_refutation_count: 2,
849 failed_refutation_count: 0,
850 comparability_snapshot_version: None,
851 notes: vec!["clean".into()],
852 }),
853 refutation_artifact_ids: vec!["refutation-1".into()],
854 control_plane_refs: vec!["forge_run:run-c001".into()],
855 execution_context: ctx,
856 thin_export: false,
857 supersedes_bundle_id: Some("bundle-prev".into()),
858 evidence_bundle_id: Some("ev-bundle-1".into()),
859 };
860
861 let serialized = serde_json::to_string(&bundle).unwrap();
862 let deserialized: EpisodeBundleV1 = serde_json::from_str(&serialized).unwrap();
863
864 assert_eq!(deserialized.bundle_id, bundle.bundle_id);
865 assert_eq!(deserialized.episode_id, bundle.episode_id);
866 assert_eq!(deserialized.claim_version_ids.len(), 2);
867 assert_eq!(deserialized.relation_version_ids.len(), 1);
868 assert!(deserialized.verification_summary.is_some());
869 assert_eq!(
870 deserialized.execution_context.attempt_id,
871 bundle.execution_context.attempt_id
872 );
873 assert_eq!(
874 deserialized.execution_context.queue_hops.len(),
875 bundle.execution_context.queue_hops.len()
876 );
877 assert_eq!(
878 deserialized.supersedes_bundle_id,
879 bundle.supersedes_bundle_id
880 );
881 assert_eq!(deserialized.evidence_bundle_id, bundle.evidence_bundle_id);
882 }
883
884 #[test]
885 fn cf_v9_c001_execution_context_round_trips() {
886 use semantic_memory_forge::{
888 DispatchOutcomeV1, ExecutionContextV1, EXECUTION_CONTEXT_V1_SCHEMA,
889 };
890 use stack_ids::TraceCtx;
891
892 let ctx = ExecutionContextV1 {
893 schema_version: EXECUTION_CONTEXT_V1_SCHEMA.into(),
894 trace_ctx: TraceCtx::generate(),
895 attempt_id: Some(stack_ids::AttemptId::new("a-1")),
896 trial_id: Some(stack_ids::TrialId::new("t-1")),
897 replay_link: Some("replay://x".into()),
898 workload_class: Some("heavy".into()),
899 queue_hops: vec!["q1".into(), "q2".into(), "q3".into()],
900 deadline: Some("2026-04-01T12:00:00Z".into()),
901 cost_budget_units: Some(500),
902 degradation_markers: vec!["thin_export".into(), "missing_nuisance".into()],
903 dispatch_outcome: DispatchOutcomeV1::Degraded,
904 environment_fingerprint: Some("env-fp".into()),
905 provider_route: Some("openrouter".into()),
906 cancellation_reason: None,
907 tool_receipt_ref: Some("receipt://1".into()),
908 provider_call_ref: Some("call://1".into()),
909 replay_parent_ref: Some("parent://1".into()),
910 };
911
912 let json = serde_json::to_value(&ctx).unwrap();
913 let restored: ExecutionContextV1 = serde_json::from_value(json).unwrap();
914
915 assert_eq!(restored.attempt_id, ctx.attempt_id);
916 assert_eq!(restored.trial_id, ctx.trial_id);
917 assert_eq!(restored.queue_hops.len(), 3);
918 assert_eq!(restored.degradation_markers.len(), 2);
919 assert_eq!(restored.dispatch_outcome, DispatchOutcomeV1::Degraded);
920 assert_eq!(restored.tool_receipt_ref, ctx.tool_receipt_ref);
921 assert_eq!(restored.provider_call_ref, ctx.provider_call_ref);
922 assert_eq!(restored.replay_parent_ref, ctx.replay_parent_ref);
923 }
924
925 #[test]
928 fn cf_v9_c005_tampered_digest_produces_typed_error() {
929 let mut envelope = make_v2_envelope("cf-c005-tamper", &["claim-c005"]);
932 envelope.content_digest = ContentDigest::compute(b"tampered-data");
933
934 let err = transform_envelope_v2(&envelope).unwrap_err();
935 assert!(
936 matches!(err, forge_memory_bridge::BridgeError::DigestMismatch { .. }),
937 "LIB-C005: tampered digest must produce BridgeError::DigestMismatch, got: {err:?}"
938 );
939 assert_eq!(err.kind(), "digest_mismatch");
941 }
942
943 #[test]
944 fn cf_v9_c005_v3_tampered_digest_produces_typed_error() {
945 let scope = ScopeKey::namespace_only("c005-v3");
947 let records = vec![ExportRecordV3 {
948 record: ExportRecord::Claim(ExportClaim {
949 claim_id: None,
950 claim_version_id: Some(ClaimVersionId::new("claim-c005-v3")),
951 subject_entity_id: EntityId::new("entity-c005"),
952 predicate: "supports".into(),
953 object_anchor: serde_json::json!("result"),
954 valid_from: None,
955 valid_to: None,
956 confidence: 1.0,
957 content: "claim supports result".into(),
958 projection_family: "forge_verification".into(),
959 supersedes_claim_id: None,
960 supersedes_claim_version_id: None,
961 metadata: None,
962 }),
963 semantics: None,
964 }];
965 let envelope = ExportEnvelopeV3 {
966 envelope_id: EnvelopeId::new("env-c005-v3"),
967 schema_version: EXPORT_ENVELOPE_V3_SCHEMA.into(),
968 content_digest: ContentDigest::compute(b"wrong-digest"),
969 source_authority: "forge".into(),
970 scope_key: scope,
971 trace_ctx: None,
972 exported_at: "2026-04-01T00:00:00Z".into(),
973 export_meta: None,
974 evidence_bundle: None,
975 support_sets: vec![],
976 contradiction_witnesses: vec![],
977 retraction_records: vec![],
978 claim_states_v13: vec![],
979 intervention_bundles_v14: vec![],
980 outcome_schemas_v14: vec![],
981 cohort_contracts_v14: vec![],
982 counterfactual_slices_v14: vec![],
983 experiment_cases_v14: vec![],
984 comparability_matrices_v14: vec![],
985 decision_traces_v14: vec![],
986 refuter_suites_v14: vec![],
987 refuter_results_v14: vec![],
988 experiment_budgets_v14: vec![],
989 rollout_decisions_v14: vec![],
990 rollback_decisions_v14: vec![],
991 attestation_envelopes_v15: vec![],
992 trust_root_sets_v15: vec![],
993 artifact_admission_policies_v15: vec![],
994 transparency_receipts_v15: vec![],
995 attestation_revocations_v15: vec![],
996 attestation_supersessions_v15: vec![],
997 remote_oracle_leases_v15: vec![],
998 remote_slice_requests_v15: vec![],
999 remote_slice_results_v15: vec![],
1000 cross_runtime_replay_tickets_v15: vec![],
1001 dispute_bundles_v15: vec![],
1002 disclosure_policies_v15: vec![],
1003 disclosure_budgets_v15: vec![],
1004 records,
1005 };
1006
1007 let err = transform_envelope_v3(&envelope).unwrap_err();
1008 assert!(
1009 matches!(err, forge_memory_bridge::BridgeError::DigestMismatch { .. }),
1010 "LIB-C005: V3 tampered digest must produce BridgeError::DigestMismatch, got: {err:?}"
1011 );
1012 assert_eq!(err.kind(), "digest_mismatch");
1013 }
1014
1015 #[test]
1016 fn cf_v9_c005_bridge_preserves_digest_through_v3_transform() {
1017 let batch = make_batch(
1019 "c005-digest",
1020 &["claim-c005-a", "claim-c005-b"],
1021 false,
1022 false,
1023 );
1024 assert!(
1026 !batch.content_digest.0.is_empty(),
1027 "LIB-C005: content_digest must be carried through from source envelope"
1028 );
1029 assert_eq!(
1031 batch.content_digest.0.len(),
1032 64,
1033 "LIB-C005: content_digest must be a 64-char hex string (BLAKE3)"
1034 );
1035 }
1036
1037 #[test]
1038 fn cf_v9_c005_bridge_error_kinds_are_stable() {
1039 use forge_memory_bridge::BridgeError;
1041 let cases: Vec<BridgeError> = vec![
1042 BridgeError::InvalidEnvelope {
1043 reason: "test".into(),
1044 },
1045 BridgeError::IncompatibleVersion {
1046 expected: "a".into(),
1047 actual: "b".into(),
1048 },
1049 BridgeError::DigestMismatch {
1050 expected: "a".into(),
1051 actual: "b".into(),
1052 },
1053 BridgeError::DigestComputationFailed {
1054 reason: "test".into(),
1055 },
1056 BridgeError::InvalidRecord {
1057 reason: "test".into(),
1058 },
1059 BridgeError::TransformFailed {
1060 reason: "test".into(),
1061 },
1062 BridgeError::MissingEpisodeIdentity {
1063 record_context: "test".into(),
1064 },
1065 ];
1066 let expected_kinds = [
1067 "invalid_envelope",
1068 "incompatible_version",
1069 "digest_mismatch",
1070 "digest_computation_failed",
1071 "invalid_record",
1072 "transform_failed",
1073 "missing_episode_identity",
1074 ];
1075 for (error, expected_kind) in cases.iter().zip(expected_kinds.iter()) {
1076 assert_eq!(
1077 error.kind(),
1078 *expected_kind,
1079 "LIB-C005: BridgeError kind must be stable"
1080 );
1081 }
1082 }
1083
1084 #[test]
1085 fn cf_v9_c005_bridge_failure_produces_replayable_artifact() {
1086 let mut envelope = make_v2_envelope("cf-c005-artifact", &["claim-c005"]);
1088 envelope.content_digest = ContentDigest::compute(b"tampered");
1089
1090 let err = transform_envelope_v2(&envelope).unwrap_err();
1091 let artifact = BridgeImportFailureArtifact::from_error(
1092 &err,
1093 envelope.envelope_id.as_str(),
1094 &envelope.source_authority,
1095 &envelope.scope_key.namespace,
1096 );
1097
1098 assert_eq!(
1099 artifact.schema_version,
1100 forge_memory_bridge::BRIDGE_IMPORT_FAILURE_ARTIFACT_V1_SCHEMA
1101 );
1102 assert_eq!(artifact.error_kind, "digest_mismatch");
1103 assert!(!artifact.error_message.is_empty());
1104 assert_eq!(artifact.source_envelope_id, envelope.envelope_id.as_str());
1105 assert!(!artifact.failed_at.is_empty());
1106
1107 let json = serde_json::to_value(&artifact).unwrap();
1109 let restored: BridgeImportFailureArtifact = serde_json::from_value(json).unwrap();
1110 assert_eq!(restored.error_kind, artifact.error_kind);
1111 assert_eq!(restored.source_envelope_id, artifact.source_envelope_id);
1112 }
1113
1114 #[test]
1117 fn cf_v9_c008_repair_record_has_required_fields() {
1118 use forge_pilot::{RepairClassV1, RepairRecordV1};
1121 use semantic_memory_forge::{
1122 DispatchOutcomeV1, ExecutionContextV1, EXECUTION_CONTEXT_V1_SCHEMA,
1123 };
1124 use stack_ids::TraceCtx;
1125
1126 let record = RepairRecordV1 {
1127 schema_version: "repair_record_v1".into(),
1128 repair_record_id: "repair-c008".into(),
1129 affected_identities: vec!["claim-1".into(), "claim-2".into()],
1130 repair_class: RepairClassV1::SupersessionRepair,
1131 trigger_artifacts: vec!["contradiction-witness-1".into()],
1132 blast_radius: "scoped:ns-c008".into(),
1133 reversibility: "reversible_scoped".into(),
1134 action: "supersede claim-1 with claim-2".into(),
1135 execution_context: ExecutionContextV1 {
1136 schema_version: EXECUTION_CONTEXT_V1_SCHEMA.into(),
1137 trace_ctx: TraceCtx::generate(),
1138 attempt_id: None,
1139 trial_id: None,
1140 replay_link: None,
1141 workload_class: Some("repair".into()),
1142 queue_hops: vec![],
1143 deadline: None,
1144 cost_budget_units: None,
1145 degradation_markers: vec![],
1146 dispatch_outcome: DispatchOutcomeV1::Succeeded,
1147 environment_fingerprint: None,
1148 provider_route: None,
1149 cancellation_reason: None,
1150 tool_receipt_ref: None,
1151 provider_call_ref: None,
1152 replay_parent_ref: None,
1153 },
1154 opened_at: "2026-04-01T00:00:00Z".into(),
1155 resolved_at: Some("2026-04-01T00:00:01Z".into()),
1156 unchanged_statement: "no semantic widening applied; supersession only".into(),
1157 };
1158
1159 assert!(
1161 !record.blast_radius.is_empty(),
1162 "LIB-C008: blast_radius required"
1163 );
1164 assert!(
1165 !record.reversibility.is_empty(),
1166 "LIB-C008: reversibility required"
1167 );
1168 assert!(
1169 !record.unchanged_statement.is_empty(),
1170 "LIB-C008: unchanged_statement required"
1171 );
1172 assert!(!record.action.is_empty(), "LIB-C008: action required");
1173 assert_eq!(
1174 record.execution_context.schema_version, EXECUTION_CONTEXT_V1_SCHEMA,
1175 "LIB-C008: repair record must carry a valid execution context"
1176 );
1177
1178 let json = serde_json::to_value(&record).unwrap();
1180 let restored: RepairRecordV1 = serde_json::from_value(json).unwrap();
1181 assert_eq!(restored.repair_record_id, record.repair_record_id);
1182 assert_eq!(restored.repair_class, record.repair_class);
1183 assert_eq!(restored.blast_radius, record.blast_radius);
1184 assert_eq!(restored.reversibility, record.reversibility);
1185 assert_eq!(restored.unchanged_statement, record.unchanged_statement);
1186 assert_eq!(restored.affected_identities.len(), 2);
1187 }
1188
1189 #[test]
1190 fn cf_v9_c008_repair_classes_are_exhaustive() {
1191 use forge_pilot::RepairClassV1;
1193
1194 let classes = [
1195 RepairClassV1::IdentityRepair,
1196 RepairClassV1::TemporalRepair,
1197 RepairClassV1::SupersessionRepair,
1198 RepairClassV1::RollbackRepair,
1199 RepairClassV1::BundleImportRepair,
1200 RepairClassV1::ScopeWideningRepair,
1201 RepairClassV1::VerificationStateRepair,
1202 ];
1203 let mut seen = std::collections::HashSet::new();
1205 for class in &classes {
1206 let wire = serde_json::to_value(class).unwrap();
1207 let label = wire.as_str().unwrap().to_string();
1208 assert!(
1209 seen.insert(label.clone()),
1210 "LIB-C008: duplicate repair class wire label: {label}"
1211 );
1212 }
1213 assert_eq!(
1214 seen.len(),
1215 7,
1216 "LIB-C008: expected 7 distinct repair classes"
1217 );
1218 }
1219
1220 #[test]
1223 fn cf_v9_c009_query_trace_carries_view_model_and_widening() {
1224 let runtime = open_runtime(
1226 &make_batch("cf-c009", &["claim-a", "claim-b"], false, false),
1227 "cf-c009",
1228 );
1229 let (_results, trace) =
1230 block_on(runtime.query("claim-a supports result", Some(&Scope::new("cf-c009"))))
1231 .unwrap();
1232
1233 assert!(
1237 !trace.requested_views.is_empty()
1238 || !trace.executed_views.is_empty()
1239 || !trace.plan.legs.is_empty(),
1240 "LIB-C009: query trace must declare requested/executed views or have at least one leg"
1241 );
1242
1243 let _ = &trace.widenings;
1246
1247 assert!(
1250 trace.temporal_mode.is_some() || !trace.has_temporal_downgrade(),
1251 "LIB-C009: temporal_mode must be explicitly set or temporal downgrade absent"
1252 );
1253
1254 let provenance = trace.runtime_query_provenance();
1256 assert_eq!(
1257 provenance.schema_version, "runtime_query_provenance_v1",
1258 "LIB-C009: provenance schema version must be runtime_query_provenance_v1"
1259 );
1260 assert!(!provenance.scope.namespace.is_empty());
1261 assert!(!provenance.classification_mode.is_empty());
1262 }
1263
1264 #[test]
1265 fn cf_v9_c009_runtime_query_provenance_round_trips() {
1266 use knowledge_runtime::RuntimeQueryProvenanceV1;
1268
1269 let runtime = open_runtime(
1270 &make_batch("cf-c009-rt", &["claim-a"], false, false),
1271 "cf-c009-rt",
1272 );
1273 let (_results, trace) =
1274 block_on(runtime.query("claim-a supports result", Some(&Scope::new("cf-c009-rt"))))
1275 .unwrap();
1276 let provenance = trace.runtime_query_provenance();
1277
1278 let json = serde_json::to_value(&provenance).unwrap();
1279 let restored: RuntimeQueryProvenanceV1 = serde_json::from_value(json).unwrap();
1280
1281 assert_eq!(restored.schema_version, provenance.schema_version);
1282 assert_eq!(restored.scope, provenance.scope);
1283 assert_eq!(restored.classification_mode, provenance.classification_mode);
1284 assert_eq!(restored.raw_result_count, provenance.raw_result_count);
1285 assert_eq!(restored.merged_result_count, provenance.merged_result_count);
1286 assert_eq!(restored.total_duration_ms, provenance.total_duration_ms);
1287 }
1288
1289 #[test]
1290 fn cf_v9_c009_degraded_query_surfaces_widening() {
1291 let runtime = open_runtime(
1294 &make_batch("cf-c009-deg", &["claim-a", "claim-b"], false, false),
1295 "cf-c009-deg",
1296 );
1297 let (_results, trace) = block_on(runtime.query(
1299 "what happened last week with claim-a",
1300 Some(&Scope::new("cf-c009-deg")),
1301 ))
1302 .unwrap();
1303
1304 if trace.has_temporal_downgrade() {
1305 assert!(
1307 !trace.widenings.is_empty(),
1308 "LIB-C009: temporal downgrade must produce an explicit widening disclosure"
1309 );
1310 let provenance = trace.runtime_query_provenance();
1311 assert!(
1312 !provenance.widenings.is_empty(),
1313 "LIB-C009: provenance must carry widening disclosures"
1314 );
1315 }
1316 }
1319
1320 #[test]
1323 fn cf_v9_c017_conformance_gate_registry_is_exhaustive() {
1324 let all_gates: Vec<&str> = [
1327 super::phase_1_gates(),
1328 super::phase_2_gates(),
1329 super::phase_3_plus_gates(),
1330 super::v16_v20_gates(),
1331 ]
1332 .into_iter()
1333 .flat_map(|gates| gates.iter().copied())
1334 .collect();
1335
1336 let mut seen = std::collections::HashSet::new();
1338 for gate in &all_gates {
1339 assert!(
1340 seen.insert(*gate),
1341 "LIB-C017: duplicate conformance gate: {gate}"
1342 );
1343 }
1344
1345 assert!(
1347 all_gates.len() >= 19,
1348 "LIB-C017: expected at least 19 conformance gates, got {}",
1349 all_gates.len()
1350 );
1351 }
1352
1353 #[test]
1354 fn cf_v9_c017_v3_envelope_digest_is_deterministic() {
1355 let batch_a = make_batch("c017-det", &["claim-a", "claim-b"], false, false);
1357 let batch_b = make_batch("c017-det", &["claim-a", "claim-b"], false, false);
1358 assert_eq!(
1359 batch_a.content_digest, batch_b.content_digest,
1360 "LIB-C017: identical envelopes must produce identical digests"
1361 );
1362 }
1363
1364 #[test]
1365 fn cf_v9_c017_bridge_transform_preserves_execution_context() {
1366 let batch = make_batch("c017-ctx", &["claim-a"], false, false);
1368 assert!(
1369 batch.execution_context.is_some(),
1370 "LIB-C017: V3 bridge must always emit an execution context"
1371 );
1372 let ctx = batch.execution_context.as_ref().unwrap();
1373 assert_eq!(
1374 ctx.schema_version, "execution_context_v1",
1375 "LIB-C017: execution context schema version must be execution_context_v1"
1376 );
1377 assert!(
1378 ctx.workload_class.is_some(),
1379 "LIB-C017: execution context must declare workload class"
1380 );
1381 }
1382
1383 #[test]
1384 fn cf_v9_c017_v2_to_v3_upgrade_preserves_record_count() {
1385 let v2 = make_v2_envelope("c017-upgrade", &["claim-a", "claim-b", "claim-c"]);
1387 let v3 = ExportEnvelopeV3::try_from(v2.clone()).unwrap();
1388 assert_eq!(
1389 v2.records.len(),
1390 v3.records.len(),
1391 "LIB-C017: V2->V3 upgrade must preserve record count"
1392 );
1393 }
1394}