1#![no_std]
8#![forbid(unsafe_code)]
9
10pub const CONTRACT_JOURNEY_ID: &str = "J-CORE-TEST-MATRIX";
12
13pub const OLD_WRONG_CORE_GUARD: &str = "God-spec implementation with no enforceable contracts";
15
16pub const CCF_CORE_V1_VERSION: &str = env!("CARGO_PKG_VERSION");
18
19pub mod qac {
20 pub const QAC_JOURNEY_ID: &str = "J-QAC-REFERENCE";
22
23 pub const QAC_STORY_ID: &str = "CCFV1-01";
25
26 pub const SCALAR_ACCUMULATOR_OLD_WRONG_PATH: &str = "Scalar accumulator update";
28
29 pub const ARITHMETIC_INTERPOLATION_OLD_WRONG_PATH: &str = "Arithmetic interpolation regression";
31
32 pub const MISSING_POSITIVE_DIAGONAL_GAUGE_OLD_WRONG_PATH: &str =
34 "Missing positive diagonal gauges";
35
36 #[derive(Clone, Copy, Debug, PartialEq)]
38 pub struct Matrix3 {
39 pub entries: [[f64; 3]; 3],
40 }
41
42 impl Matrix3 {
43 pub const fn new(entries: [[f64; 3]; 3]) -> Self {
44 Self { entries }
45 }
46 }
47
48 #[derive(Clone, Copy, Debug, PartialEq)]
50 pub struct PositiveDiagonal3 {
51 diagonal: [f64; 3],
52 }
53
54 impl PositiveDiagonal3 {
55 pub fn try_new(diagonal: [f64; 3]) -> Result<Self, QacError> {
56 if diagonal
57 .iter()
58 .all(|entry| entry.is_finite() && *entry > 0.0)
59 {
60 Ok(Self { diagonal })
61 } else {
62 Err(QacError::NonPositiveGauge)
63 }
64 }
65
66 pub const fn diagonal(self) -> [f64; 3] {
67 self.diagonal
68 }
69 }
70
71 #[derive(Clone, Copy, Debug, PartialEq)]
73 pub struct QacInputs3 {
74 pub prior_a_t: Matrix3,
75 pub reference_r_t: Matrix3,
76 pub left_l_t: PositiveDiagonal3,
77 pub right_c_t: PositiveDiagonal3,
78 pub alpha_t: f64,
79 pub epsilon_floor: f64,
80 }
81
82 #[derive(Clone, Copy, Debug, PartialEq)]
84 pub struct NegativeGuardReport {
85 pub old_wrong_path: &'static str,
86 pub rejected: bool,
87 pub kappa_excursion: f64,
88 }
89
90 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
91 pub enum QacError {
92 NonPositiveEntry,
93 NonPositiveGauge,
94 InvalidAlpha,
95 }
96
97 pub fn qac_update_3x3(inputs: &QacInputs3) -> Result<Matrix3, QacError> {
100 validate_inputs(inputs)?;
101
102 let mut entries = [[0.0_f64; 3]; 3];
103 for (row, output_row) in entries.iter_mut().enumerate() {
104 for (col, output) in output_row.iter_mut().enumerate() {
105 let prior = inputs.prior_a_t.entries[row][col];
106 let reference = inputs.reference_r_t.entries[row][col];
107 let log_geodesic = libm::exp(
108 (1.0 - inputs.alpha_t) * libm::log(prior)
109 + inputs.alpha_t * libm::log(reference),
110 );
111 *output =
112 inputs.left_l_t.diagonal[row] * log_geodesic * inputs.right_c_t.diagonal[col];
113 }
114 }
115
116 Ok(Matrix3::new(entries))
117 }
118
119 pub fn reject_arithmetic_interpolation_regression(
121 inputs: &QacInputs3,
122 ) -> Result<NegativeGuardReport, QacError> {
123 validate_inputs(inputs)?;
124 let canonical = qac_update_3x3(inputs)?;
125
126 let mut substitute = [[0.0_f64; 3]; 3];
127 for (row, output_row) in substitute.iter_mut().enumerate() {
128 for (col, output) in output_row.iter_mut().enumerate() {
129 let prior = inputs.prior_a_t.entries[row][col];
130 let reference = inputs.reference_r_t.entries[row][col];
131 let interpolated = (1.0 - inputs.alpha_t) * prior + inputs.alpha_t * reference;
132 *output =
133 inputs.left_l_t.diagonal[row] * interpolated * inputs.right_c_t.diagonal[col];
134 }
135 }
136
137 let kappa_excursion = max_abs_diff(Matrix3::new(substitute), canonical);
138 Ok(NegativeGuardReport {
139 old_wrong_path: ARITHMETIC_INTERPOLATION_OLD_WRONG_PATH,
140 rejected: kappa_excursion > inputs.epsilon_floor,
141 kappa_excursion,
142 })
143 }
144
145 pub fn reject_scalar_accumulator_update(
147 inputs: &QacInputs3,
148 ) -> Result<NegativeGuardReport, QacError> {
149 validate_inputs(inputs)?;
150 let canonical = qac_update_3x3(inputs)?;
151 let prior_mean = matrix_mean(inputs.prior_a_t);
152 let reference_mean = matrix_mean(inputs.reference_r_t);
153 let scalar = (1.0 - inputs.alpha_t) * prior_mean + inputs.alpha_t * reference_mean;
154 let substitute = Matrix3::new([[scalar; 3]; 3]);
155 let kappa_excursion = max_abs_diff(substitute, canonical);
156
157 Ok(NegativeGuardReport {
158 old_wrong_path: SCALAR_ACCUMULATOR_OLD_WRONG_PATH,
159 rejected: kappa_excursion > inputs.epsilon_floor,
160 kappa_excursion,
161 })
162 }
163
164 pub fn reject_missing_positive_diagonal_gauge(
166 inputs: &QacInputs3,
167 ) -> Result<NegativeGuardReport, QacError> {
168 validate_inputs(inputs)?;
169 let canonical = qac_update_3x3(inputs)?;
170
171 let mut substitute = [[0.0_f64; 3]; 3];
172 for (row, output_row) in substitute.iter_mut().enumerate() {
173 for (col, output) in output_row.iter_mut().enumerate() {
174 let prior = inputs.prior_a_t.entries[row][col];
175 let reference = inputs.reference_r_t.entries[row][col];
176 *output = libm::exp(
177 (1.0 - inputs.alpha_t) * libm::log(prior)
178 + inputs.alpha_t * libm::log(reference),
179 );
180 }
181 }
182
183 let kappa_excursion = max_abs_diff(Matrix3::new(substitute), canonical);
184 Ok(NegativeGuardReport {
185 old_wrong_path: MISSING_POSITIVE_DIAGONAL_GAUGE_OLD_WRONG_PATH,
186 rejected: kappa_excursion > inputs.epsilon_floor,
187 kappa_excursion,
188 })
189 }
190
191 fn validate_inputs(inputs: &QacInputs3) -> Result<(), QacError> {
192 if !inputs.alpha_t.is_finite() || !(0.0..=1.0).contains(&inputs.alpha_t) {
193 return Err(QacError::InvalidAlpha);
194 }
195 if !inputs.epsilon_floor.is_finite() || inputs.epsilon_floor <= 0.0 {
196 return Err(QacError::NonPositiveEntry);
197 }
198 validate_strict_positive_matrix(inputs.prior_a_t, inputs.epsilon_floor)?;
199 validate_strict_positive_matrix(inputs.reference_r_t, inputs.epsilon_floor)?;
200 validate_positive_diagonal(inputs.left_l_t)?;
201 validate_positive_diagonal(inputs.right_c_t)?;
202 Ok(())
203 }
204
205 fn validate_positive_diagonal(diagonal: PositiveDiagonal3) -> Result<(), QacError> {
206 if diagonal
207 .diagonal()
208 .iter()
209 .all(|entry| entry.is_finite() && *entry > 0.0)
210 {
211 Ok(())
212 } else {
213 Err(QacError::NonPositiveGauge)
214 }
215 }
216
217 fn validate_strict_positive_matrix(
218 matrix: Matrix3,
219 epsilon_floor: f64,
220 ) -> Result<(), QacError> {
221 for row in matrix.entries {
222 for entry in row {
223 if !entry.is_finite() || entry <= epsilon_floor {
224 return Err(QacError::NonPositiveEntry);
225 }
226 }
227 }
228 Ok(())
229 }
230
231 fn max_abs_diff(left: Matrix3, right: Matrix3) -> f64 {
232 let mut max = 0.0_f64;
233 for row in 0..3 {
234 for col in 0..3 {
235 max = max.max((left.entries[row][col] - right.entries[row][col]).abs());
236 }
237 }
238 max
239 }
240
241 fn matrix_mean(matrix: Matrix3) -> f64 {
242 let mut total = 0.0_f64;
243 for row in matrix.entries {
244 for entry in row {
245 total += entry;
246 }
247 }
248 total / 9.0
249 }
250}
251
252pub mod min_gate {
253 const WEIGHTED_AVERAGE_INSTANT_WEIGHT: f64 = 0.3;
254 const WEIGHTED_AVERAGE_CONTEXT_WEIGHT: f64 = 0.7;
255 const SOFT_MIN_TEMPERATURE: f64 = 0.25;
256 const CONSTANT_ALPHA: f64 = 0.35;
257
258 pub const MIN_GATE_JOURNEY_ID: &str = "J-MIN-GATE-EXACT";
260
261 pub const MIN_GATE_STORY_ID: &str = "CCFV1-02";
263
264 pub const WEIGHTED_AVERAGE_GATE_OLD_WRONG_PATH: &str = "Weighted-average gate";
266
267 pub const SOFT_MIN_GATE_OLD_WRONG_PATH: &str = "Soft-min gate";
269
270 pub const CONSTANT_ALPHA_OLD_WRONG_PATH: &str = "Constant alpha as canonical";
272
273 pub const PER_ELEMENT_ALPHA_OLD_WRONG_PATH: &str = "Per-element alpha";
275
276 #[derive(Clone, Copy, Debug, PartialEq)]
278 pub struct RhoConfig {
279 alpha_min: f64,
280 alpha_max: f64,
281 }
282
283 impl RhoConfig {
284 pub fn try_new(alpha_min: f64, alpha_max: f64) -> Result<Self, MinGateError> {
285 if alpha_min.is_finite()
286 && alpha_max.is_finite()
287 && alpha_min >= 0.0
288 && alpha_min <= alpha_max
289 && alpha_max <= 1.0
290 {
291 Ok(Self {
292 alpha_min,
293 alpha_max,
294 })
295 } else {
296 Err(MinGateError::InvalidRhoConfig)
297 }
298 }
299
300 pub const fn alpha_min(self) -> f64 {
301 self.alpha_min
302 }
303
304 pub const fn alpha_max(self) -> f64 {
305 self.alpha_max
306 }
307 }
308
309 #[derive(Clone, Copy, Debug, PartialEq)]
311 pub struct MinGateInputs {
312 pub c_inst: f64,
313 pub c_ctx: f64,
314 pub rho: RhoConfig,
315 }
316
317 #[derive(Clone, Copy, Debug, PartialEq)]
319 pub struct MinGateReport {
320 pub g_t: f64,
321 pub alpha_t: f64,
322 }
323
324 #[derive(Clone, Copy, Debug, PartialEq)]
326 pub struct MinGateNegativeGuardReport {
327 pub old_wrong_path: &'static str,
328 pub rejected: bool,
329 pub canonical_g_t: f64,
330 pub observed_g_t: f64,
331 pub canonical_alpha_t: f64,
332 pub observed_alpha_t: f64,
333 }
334
335 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
336 pub enum MinGateError {
337 NotImplemented,
338 InvalidCeiling,
339 InvalidRhoConfig,
340 }
341
342 pub fn hard_min_gate_step(inputs: &MinGateInputs) -> Result<MinGateReport, MinGateError> {
344 validate_inputs(inputs)?;
345 let g_t = hard_min(inputs.c_inst, inputs.c_ctx);
346 Ok(MinGateReport {
347 g_t,
348 alpha_t: rho(inputs.rho, g_t),
349 })
350 }
351
352 pub fn reject_weighted_average_gate(
354 inputs: &MinGateInputs,
355 ) -> Result<MinGateNegativeGuardReport, MinGateError> {
356 validate_inputs(inputs)?;
357 let observed_g_t = WEIGHTED_AVERAGE_INSTANT_WEIGHT * inputs.c_inst
358 + WEIGHTED_AVERAGE_CONTEXT_WEIGHT * inputs.c_ctx;
359 Ok(negative_gate_report(
360 inputs,
361 WEIGHTED_AVERAGE_GATE_OLD_WRONG_PATH,
362 observed_g_t,
363 rho(inputs.rho, observed_g_t),
364 ))
365 }
366
367 pub fn reject_soft_min_gate(
369 inputs: &MinGateInputs,
370 ) -> Result<MinGateNegativeGuardReport, MinGateError> {
371 validate_inputs(inputs)?;
372 let observed_g_t = soft_min(inputs.c_inst, inputs.c_ctx);
373 Ok(negative_gate_report(
374 inputs,
375 SOFT_MIN_GATE_OLD_WRONG_PATH,
376 observed_g_t,
377 rho(inputs.rho, observed_g_t),
378 ))
379 }
380
381 pub fn reject_constant_alpha(
383 inputs: &MinGateInputs,
384 ) -> Result<MinGateNegativeGuardReport, MinGateError> {
385 validate_inputs(inputs)?;
386 let canonical_g_t = hard_min(inputs.c_inst, inputs.c_ctx);
387 Ok(negative_gate_report(
388 inputs,
389 CONSTANT_ALPHA_OLD_WRONG_PATH,
390 canonical_g_t,
391 CONSTANT_ALPHA,
392 ))
393 }
394
395 pub fn reject_per_element_alpha(
397 inputs: &MinGateInputs,
398 ) -> Result<MinGateNegativeGuardReport, MinGateError> {
399 validate_inputs(inputs)?;
400 let observed_g_t = inputs.c_inst.max(inputs.c_ctx);
401 Ok(negative_gate_report(
402 inputs,
403 PER_ELEMENT_ALPHA_OLD_WRONG_PATH,
404 observed_g_t,
405 rho(inputs.rho, observed_g_t),
406 ))
407 }
408
409 fn validate_inputs(inputs: &MinGateInputs) -> Result<(), MinGateError> {
410 validate_ceiling(inputs.c_inst)?;
411 validate_ceiling(inputs.c_ctx)?;
412 RhoConfig::try_new(inputs.rho.alpha_min(), inputs.rho.alpha_max())?;
413 Ok(())
414 }
415
416 fn validate_ceiling(ceiling: f64) -> Result<(), MinGateError> {
417 if ceiling.is_finite() && (0.0..=1.0).contains(&ceiling) {
418 Ok(())
419 } else {
420 Err(MinGateError::InvalidCeiling)
421 }
422 }
423
424 fn hard_min(c_inst: f64, c_ctx: f64) -> f64 {
425 c_inst.min(c_ctx)
426 }
427
428 fn rho(config: RhoConfig, g_t: f64) -> f64 {
429 config.alpha_min() + (config.alpha_max() - config.alpha_min()) * g_t
430 }
431
432 fn soft_min(c_inst: f64, c_ctx: f64) -> f64 {
433 let x = -c_inst / SOFT_MIN_TEMPERATURE;
434 let y = -c_ctx / SOFT_MIN_TEMPERATURE;
435 let max = x.max(y);
436 -SOFT_MIN_TEMPERATURE * (max + libm::log(libm::exp(x - max) + libm::exp(y - max)))
437 }
438
439 fn negative_gate_report(
440 inputs: &MinGateInputs,
441 old_wrong_path: &'static str,
442 observed_g_t: f64,
443 observed_alpha_t: f64,
444 ) -> MinGateNegativeGuardReport {
445 let canonical_g_t = hard_min(inputs.c_inst, inputs.c_ctx);
446 let canonical_alpha_t = rho(inputs.rho, canonical_g_t);
447 MinGateNegativeGuardReport {
448 old_wrong_path,
449 rejected: observed_g_t.to_bits() != canonical_g_t.to_bits()
450 || observed_alpha_t.to_bits() != canonical_alpha_t.to_bits(),
451 canonical_g_t,
452 observed_g_t,
453 canonical_alpha_t,
454 observed_alpha_t,
455 }
456 }
457}
458
459pub mod sinkhorn {
460 use super::qac::{qac_update_3x3, Matrix3, QacInputs3};
461
462 const SIZE: usize = 3;
463 const SINKHORN_ITERATIONS: usize = 200;
464
465 pub const SINKHORN_GAUGE_JOURNEY_ID: &str = "J-SINKHORN-GAUGE-ONLY";
467
468 pub const SINKHORN_GAUGE_STORY_ID: &str = "CCFV1-03";
470
471 pub const SINKHORN_CAUSAL_DYNAMICS_OLD_WRONG_PATH: &str =
473 "Sinkhorn/Birkhoff as causal trust dynamics";
474
475 pub const FORCED_BIRKHOFF_CANONICAL_STATE_OLD_WRONG_PATH: &str =
477 "Forced Birkhoff canonical state";
478
479 pub const MUTATING_CANONICAL_PRESENTATION_OLD_WRONG_PATH: &str =
481 "Mutating canonical state through presentation";
482
483 #[derive(Clone, Copy, Debug, PartialEq)]
485 pub struct SinkhornGaugeInputs3 {
486 pub canonical_state: Matrix3,
487 pub qac_inputs: QacInputs3,
488 pub tolerance: f64,
489 }
490
491 #[derive(Clone, Copy, Debug, PartialEq)]
493 pub struct SinkhornPresentationReport {
494 pub presentation: Matrix3,
495 pub row_sum_max_error: f64,
496 pub column_sum_max_error: f64,
497 pub quotient_state_delta: f64,
498 pub kappa_delta: f64,
499 pub canonical_state_preserved: bool,
500 pub strict_positive_canonical: bool,
501 }
502
503 #[derive(Clone, Copy, Debug, PartialEq)]
505 pub struct SinkhornNegativeGuardReport {
506 pub old_wrong_path: &'static str,
507 pub rejected: bool,
508 pub canonical_state_delta: f64,
509 pub quotient_state_delta: f64,
510 pub kappa_delta: f64,
511 pub canonical_mutated: bool,
512 }
513
514 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
515 pub enum SinkhornGaugeError {
516 NotImplemented,
517 InvalidCanonicalState,
518 InvalidTolerance,
519 }
520
521 pub fn present_sinkhorn_gauge_only(
525 inputs: &SinkhornGaugeInputs3,
526 ) -> Result<SinkhornPresentationReport, SinkhornGaugeError> {
527 validate_inputs(inputs)?;
528
529 let presentation = sinkhorn_presentation(inputs.canonical_state);
530 let canonical_quotient = quotient_state(inputs.canonical_state)?;
531 let presentation_quotient = quotient_state(presentation)?;
532 let quotient_state_delta = max_abs_diff(canonical_quotient, presentation_quotient);
533 let kappa_before = max_abs(canonical_quotient);
534 let kappa_after = max_abs(presentation_quotient);
535
536 Ok(SinkhornPresentationReport {
537 presentation,
538 row_sum_max_error: max_unit_sum_error(row_sums(presentation)),
539 column_sum_max_error: max_unit_sum_error(column_sums(presentation)),
540 quotient_state_delta,
541 kappa_delta: (kappa_after - kappa_before).abs(),
542 canonical_state_preserved: canonical_state_preserved(inputs),
543 strict_positive_canonical: is_strict_positive_matrix(
544 inputs.canonical_state,
545 inputs.qac_inputs.epsilon_floor,
546 ),
547 })
548 }
549
550 pub fn reject_sinkhorn_as_causal_dynamics(
552 inputs: &SinkhornGaugeInputs3,
553 ) -> Result<SinkhornNegativeGuardReport, SinkhornGaugeError> {
554 negative_guard_report(inputs, SINKHORN_CAUSAL_DYNAMICS_OLD_WRONG_PATH)
555 }
556
557 pub fn reject_forced_birkhoff_canonical_state(
559 inputs: &SinkhornGaugeInputs3,
560 ) -> Result<SinkhornNegativeGuardReport, SinkhornGaugeError> {
561 negative_guard_report(inputs, FORCED_BIRKHOFF_CANONICAL_STATE_OLD_WRONG_PATH)
562 }
563
564 pub fn reject_mutating_canonical_state_through_presentation(
566 inputs: &SinkhornGaugeInputs3,
567 ) -> Result<SinkhornNegativeGuardReport, SinkhornGaugeError> {
568 negative_guard_report(inputs, MUTATING_CANONICAL_PRESENTATION_OLD_WRONG_PATH)
569 }
570
571 fn validate_inputs(inputs: &SinkhornGaugeInputs3) -> Result<(), SinkhornGaugeError> {
572 if !inputs.tolerance.is_finite() || inputs.tolerance <= 0.0 {
573 return Err(SinkhornGaugeError::InvalidTolerance);
574 }
575 if !is_strict_positive_matrix(inputs.canonical_state, inputs.qac_inputs.epsilon_floor) {
576 return Err(SinkhornGaugeError::InvalidCanonicalState);
577 }
578 let expected = qac_update_3x3(&inputs.qac_inputs)
579 .map_err(|_| SinkhornGaugeError::InvalidCanonicalState)?;
580 if max_abs_diff(inputs.canonical_state, expected) > inputs.tolerance {
581 return Err(SinkhornGaugeError::InvalidCanonicalState);
582 }
583 Ok(())
584 }
585
586 fn is_strict_positive_matrix(matrix: Matrix3, epsilon_floor: f64) -> bool {
587 matrix.entries.iter().all(|row| {
588 row.iter()
589 .all(|entry| entry.is_finite() && *entry > epsilon_floor)
590 })
591 }
592
593 fn canonical_state_preserved(inputs: &SinkhornGaugeInputs3) -> bool {
594 qac_update_3x3(&inputs.qac_inputs)
595 .map(|expected| max_abs_diff(inputs.canonical_state, expected) <= inputs.tolerance)
596 .unwrap_or(false)
597 }
598
599 fn sinkhorn_presentation(matrix: Matrix3) -> Matrix3 {
600 let mut entries = matrix.entries;
601
602 for _ in 0..SINKHORN_ITERATIONS {
603 for row in 0..SIZE {
604 let sum = entries[row]
605 .iter()
606 .fold(0.0_f64, |total, entry| total + *entry);
607 let scale = 1.0 / sum;
608 for col in 0..SIZE {
609 entries[row][col] *= scale;
610 }
611 }
612
613 for col in 0..SIZE {
614 let sum = (0..SIZE).fold(0.0_f64, |total, row| total + entries[row][col]);
615 let scale = 1.0 / sum;
616 for row in 0..SIZE {
617 entries[row][col] *= scale;
618 }
619 }
620 }
621
622 Matrix3::new(entries)
623 }
624
625 fn quotient_state(matrix: Matrix3) -> Result<Matrix3, SinkhornGaugeError> {
626 if !is_strict_positive_matrix(matrix, 0.0) {
627 return Err(SinkhornGaugeError::InvalidCanonicalState);
628 }
629
630 let mut log_entries = [[0.0_f64; SIZE]; SIZE];
631 for row in 0..SIZE {
632 for col in 0..SIZE {
633 log_entries[row][col] = libm::log(matrix.entries[row][col]);
634 }
635 }
636
637 let mut row_means = [0.0_f64; SIZE];
638 for row in 0..SIZE {
639 row_means[row] = mean(log_entries[row]);
640 }
641
642 let mut column_means = [0.0_f64; SIZE];
643 for col in 0..SIZE {
644 column_means[col] =
645 (0..SIZE).fold(0.0_f64, |total, row| total + log_entries[row][col]) / SIZE as f64;
646 }
647
648 let grand_mean = mean(row_means);
649 let mut quotient = [[0.0_f64; SIZE]; SIZE];
650 for row in 0..SIZE {
651 for col in 0..SIZE {
652 quotient[row][col] =
653 log_entries[row][col] - row_means[row] - column_means[col] + grand_mean;
654 }
655 }
656
657 Ok(Matrix3::new(quotient))
658 }
659
660 fn row_sums(matrix: Matrix3) -> [f64; SIZE] {
661 let mut sums = [0.0_f64; SIZE];
662 for row in 0..SIZE {
663 sums[row] = matrix.entries[row]
664 .iter()
665 .fold(0.0_f64, |total, entry| total + *entry);
666 }
667 sums
668 }
669
670 fn column_sums(matrix: Matrix3) -> [f64; SIZE] {
671 let mut sums = [0.0_f64; SIZE];
672 for col in 0..SIZE {
673 sums[col] = (0..SIZE).fold(0.0_f64, |total, row| total + matrix.entries[row][col]);
674 }
675 sums
676 }
677
678 fn max_unit_sum_error(sums: [f64; SIZE]) -> f64 {
679 sums.iter()
680 .fold(0.0_f64, |max, sum| max.max((*sum - 1.0).abs()))
681 }
682
683 fn mean(values: [f64; SIZE]) -> f64 {
684 values.iter().fold(0.0_f64, |total, value| total + *value) / SIZE as f64
685 }
686
687 fn max_abs(matrix: Matrix3) -> f64 {
688 let mut max = 0.0_f64;
689 for row in matrix.entries {
690 for entry in row {
691 max = max.max(entry.abs());
692 }
693 }
694 max
695 }
696
697 fn max_abs_diff(left: Matrix3, right: Matrix3) -> f64 {
698 let mut max = 0.0_f64;
699 for row in 0..SIZE {
700 for col in 0..SIZE {
701 max = max.max((left.entries[row][col] - right.entries[row][col]).abs());
702 }
703 }
704 max
705 }
706
707 fn negative_guard_report(
708 inputs: &SinkhornGaugeInputs3,
709 old_wrong_path: &'static str,
710 ) -> Result<SinkhornNegativeGuardReport, SinkhornGaugeError> {
711 validate_inputs(inputs)?;
712 let presentation = present_sinkhorn_gauge_only(inputs)?;
713 let canonical_state_delta = max_abs_diff(presentation.presentation, inputs.canonical_state);
714
715 Ok(SinkhornNegativeGuardReport {
716 old_wrong_path,
717 rejected: canonical_state_delta > inputs.tolerance,
718 canonical_state_delta,
719 quotient_state_delta: presentation.quotient_state_delta,
720 kappa_delta: presentation.kappa_delta,
721 canonical_mutated: canonical_state_delta > inputs.tolerance,
722 })
723 }
724}
725
726pub mod kappa {
727 pub const KAPPA_DYNAMIC_FLOOR_JOURNEY_ID: &str = "J-KAPPA-DYNAMIC-FLOOR";
729
730 pub const KAPPA_DYNAMIC_FLOOR_STORY_ID: &str = "CCFV1-04";
732
733 pub const FIXED_KAPPA_THEOREM_OLD_WRONG_PATH: &str =
735 concat!("Fixed kappa_t < ", "1e-9 theorem");
736
737 pub const PERIODIC_CERTIFICATE_OLD_WRONG_PATH: &str = "Periodic/on-demand certificate";
739
740 pub const NO_FAIL_CLOSED_EXCURSION_OLD_WRONG_PATH: &str = "No fail-closed excursion";
742
743 #[derive(Clone, Copy, Debug, PartialEq)]
745 pub struct KappaFloorTick {
746 pub tick_id: u64,
747 pub dimension_n: usize,
748 pub epsilon_q: f64,
749 pub delta_alpha_t: f64,
750 pub e_t: f64,
751 pub policy_margin: f64,
752 pub kappa_hat_t: f64,
753 }
754
755 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
757 pub enum KappaCertificateStatus {
758 WithinDynamicFloor,
759 FailClosedExcursion,
760 }
761
762 #[derive(Clone, Copy, Debug, PartialEq)]
764 pub struct KappaDynamicFloorReport {
765 pub tick_id: u64,
766 pub kappa_hat_t: f64,
767 pub floor_t: f64,
768 pub epsilon_q: f64,
769 pub delta_alpha_t: f64,
770 pub e_t: f64,
771 pub policy_margin: f64,
772 pub threshold_t: f64,
773 pub certificate_status: KappaCertificateStatus,
774 pub fail_closed: bool,
775 }
776
777 #[derive(Clone, Copy, Debug, PartialEq)]
779 pub struct KappaNegativeGuardReport {
780 pub old_wrong_path: &'static str,
781 pub rejected: bool,
782 pub kappa_hat_t: f64,
783 pub floor_t: f64,
784 pub policy_margin: f64,
785 pub fail_closed: bool,
786 }
787
788 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
789 pub enum KappaDynamicFloorError {
790 InvalidInput,
791 NotImplemented,
792 }
793
794 pub fn classify_dynamic_floor_tick(
796 tick: &KappaFloorTick,
797 ) -> Result<KappaDynamicFloorReport, KappaDynamicFloorError> {
798 validate_tick(tick)?;
799 let floor_t = dynamic_floor(tick);
800 let threshold_t = floor_t + tick.policy_margin;
801 if !threshold_t.is_finite() {
802 return Err(KappaDynamicFloorError::InvalidInput);
803 }
804 let fail_closed = tick.kappa_hat_t > threshold_t;
805
806 Ok(KappaDynamicFloorReport {
807 tick_id: tick.tick_id,
808 kappa_hat_t: tick.kappa_hat_t,
809 floor_t,
810 epsilon_q: tick.epsilon_q,
811 delta_alpha_t: tick.delta_alpha_t,
812 e_t: tick.e_t,
813 policy_margin: tick.policy_margin,
814 threshold_t,
815 certificate_status: if fail_closed {
816 KappaCertificateStatus::FailClosedExcursion
817 } else {
818 KappaCertificateStatus::WithinDynamicFloor
819 },
820 fail_closed,
821 })
822 }
823
824 pub fn reject_fixed_kappa_theorem_threshold(
826 tick: &KappaFloorTick,
827 ) -> Result<KappaNegativeGuardReport, KappaDynamicFloorError> {
828 let report = classify_dynamic_floor_tick(tick)?;
829 Ok(KappaNegativeGuardReport {
830 old_wrong_path: FIXED_KAPPA_THEOREM_OLD_WRONG_PATH,
831 rejected: true,
832 kappa_hat_t: report.kappa_hat_t,
833 floor_t: report.floor_t,
834 policy_margin: report.policy_margin,
835 fail_closed: report.fail_closed,
836 })
837 }
838
839 pub fn reject_periodic_certificate(
841 ticks: &[KappaFloorTick],
842 ) -> Result<KappaNegativeGuardReport, KappaDynamicFloorError> {
843 let last = ticks.last().ok_or(KappaDynamicFloorError::InvalidInput)?;
844 for tick in ticks {
845 validate_tick(tick)?;
846 }
847 let report = classify_dynamic_floor_tick(last)?;
848
849 Ok(KappaNegativeGuardReport {
850 old_wrong_path: PERIODIC_CERTIFICATE_OLD_WRONG_PATH,
851 rejected: ticks.len() > 1,
852 kappa_hat_t: report.kappa_hat_t,
853 floor_t: report.floor_t,
854 policy_margin: report.policy_margin,
855 fail_closed: report.fail_closed,
856 })
857 }
858
859 pub fn reject_no_fail_closed_excursion(
861 tick: &KappaFloorTick,
862 ) -> Result<KappaNegativeGuardReport, KappaDynamicFloorError> {
863 let report = classify_dynamic_floor_tick(tick)?;
864 Ok(KappaNegativeGuardReport {
865 old_wrong_path: NO_FAIL_CLOSED_EXCURSION_OLD_WRONG_PATH,
866 rejected: report.fail_closed,
867 kappa_hat_t: report.kappa_hat_t,
868 floor_t: report.floor_t,
869 policy_margin: report.policy_margin,
870 fail_closed: report.fail_closed,
871 })
872 }
873
874 fn validate_tick(tick: &KappaFloorTick) -> Result<(), KappaDynamicFloorError> {
875 if tick.dimension_n == 0
876 || !tick.epsilon_q.is_finite()
877 || tick.epsilon_q <= 0.0
878 || !tick.delta_alpha_t.is_finite()
879 || !tick.e_t.is_finite()
880 || tick.e_t < 0.0
881 || !tick.policy_margin.is_finite()
882 || tick.policy_margin < 0.0
883 || !tick.kappa_hat_t.is_finite()
884 || tick.kappa_hat_t < 0.0
885 {
886 Err(KappaDynamicFloorError::InvalidInput)
887 } else {
888 Ok(())
889 }
890 }
891
892 fn dynamic_floor(tick: &KappaFloorTick) -> f64 {
893 2.0 * tick.dimension_n as f64 * tick.epsilon_q + tick.delta_alpha_t.abs() * tick.e_t
894 }
895
896 use super::qac::Matrix3;
904
905 const N: usize = 3;
906
907 fn strict_positive_finite(matrix: &Matrix3) -> bool {
908 matrix
909 .entries
910 .iter()
911 .flatten()
912 .all(|value| value.is_finite() && *value > 0.0)
913 }
914
915 fn log_matrix(matrix: &Matrix3) -> [[f64; N]; N] {
917 let mut out = [[0.0_f64; N]; N];
918 for (row, out_row) in out.iter_mut().enumerate() {
919 for (col, slot) in out_row.iter_mut().enumerate() {
920 *slot = libm::log(matrix.entries[row][col]);
921 }
922 }
923 out
924 }
925
926 fn gauge_center(matrix: &[[f64; N]; N]) -> [[f64; N]; N] {
930 let n = N as f64;
931 let mut row_mean = [0.0_f64; N];
932 let mut col_mean = [0.0_f64; N];
933 let mut grand = 0.0_f64;
934 for row in 0..N {
935 for col in 0..N {
936 row_mean[row] += matrix[row][col];
937 col_mean[col] += matrix[row][col];
938 grand += matrix[row][col];
939 }
940 }
941 for value in row_mean.iter_mut() {
942 *value /= n;
943 }
944 for value in col_mean.iter_mut() {
945 *value /= n;
946 }
947 grand /= n * n;
948 let mut out = [[0.0_f64; N]; N];
949 for row in 0..N {
950 for col in 0..N {
951 out[row][col] = matrix[row][col] - row_mean[row] - col_mean[col] + grand;
952 }
953 }
954 out
955 }
956
957 fn frobenius(matrix: &[[f64; N]; N]) -> f64 {
958 let mut sum = 0.0_f64;
959 for row in matrix.iter() {
960 for value in row.iter() {
961 sum += value * value;
962 }
963 }
964 libm::sqrt(sum)
965 }
966
967 pub fn compute_kappa_hat(
975 prior_a_t: &Matrix3,
976 reference_r_t: &Matrix3,
977 realized_a_next: &Matrix3,
978 alpha_t: f64,
979 ) -> Result<f64, KappaDynamicFloorError> {
980 if !alpha_t.is_finite()
981 || !(0.0..=1.0).contains(&alpha_t)
982 || !strict_positive_finite(prior_a_t)
983 || !strict_positive_finite(reference_r_t)
984 || !strict_positive_finite(realized_a_next)
985 {
986 return Err(KappaDynamicFloorError::InvalidInput);
987 }
988 let log_a = log_matrix(prior_a_t);
989 let log_r = log_matrix(reference_r_t);
990 let log_next = log_matrix(realized_a_next);
991 let mut residual = [[0.0_f64; N]; N];
992 for row in 0..N {
993 for col in 0..N {
994 residual[row][col] = log_next[row][col]
995 - (1.0 - alpha_t) * log_a[row][col]
996 - alpha_t * log_r[row][col];
997 }
998 }
999 Ok(frobenius(&gauge_center(&residual)))
1000 }
1001
1002 pub fn quotient_distance(a: &Matrix3, b: &Matrix3) -> Result<f64, KappaDynamicFloorError> {
1005 if !strict_positive_finite(a) || !strict_positive_finite(b) {
1006 return Err(KappaDynamicFloorError::InvalidInput);
1007 }
1008 let log_a = log_matrix(a);
1009 let log_b = log_matrix(b);
1010 let mut diff = [[0.0_f64; N]; N];
1011 for row in 0..N {
1012 for col in 0..N {
1013 diff[row][col] = log_a[row][col] - log_b[row][col];
1014 }
1015 }
1016 Ok(frobenius(&gauge_center(&diff)))
1017 }
1018
1019 #[derive(Clone, Copy, Debug, PartialEq)]
1021 pub struct CertificateInputs {
1022 pub tick_id: u64,
1023 pub prior_a_t: Matrix3,
1024 pub reference_r_t: Matrix3,
1025 pub realized_a_next: Matrix3,
1026 pub alpha_t: f64,
1027 pub delta_alpha_t: f64,
1028 pub epsilon_q: f64,
1029 pub policy_margin: f64,
1030 }
1031
1032 pub fn certify_update(
1037 inputs: &CertificateInputs,
1038 ) -> Result<KappaDynamicFloorReport, KappaDynamicFloorError> {
1039 let kappa_hat_t = compute_kappa_hat(
1040 &inputs.prior_a_t,
1041 &inputs.reference_r_t,
1042 &inputs.realized_a_next,
1043 inputs.alpha_t,
1044 )?;
1045 let e_t = quotient_distance(&inputs.prior_a_t, &inputs.reference_r_t)?;
1046 let tick = KappaFloorTick {
1047 tick_id: inputs.tick_id,
1048 dimension_n: N,
1049 epsilon_q: inputs.epsilon_q,
1050 delta_alpha_t: inputs.delta_alpha_t,
1051 e_t,
1052 policy_margin: inputs.policy_margin,
1053 kappa_hat_t,
1054 };
1055 classify_dynamic_floor_tick(&tick)
1056 }
1057}
1058
1059pub mod endpoint {
1060 use super::kappa::{
1061 classify_dynamic_floor_tick, compute_kappa_hat, quotient_distance, KappaCertificateStatus,
1062 KappaFloorTick,
1063 };
1064 use super::qac::Matrix3;
1065
1066 const STRUCTURAL_TOLERANCE: f64 = 1.0e-12;
1067
1068 pub const ENDPOINT_HYBRID_JOURNEY_ID: &str = "J-ENDPOINT-HYBRID";
1070
1071 pub const ENDPOINT_HYBRID_STORY_ID: &str = "CCFV1-05";
1073
1074 pub const KAPPA_ALONE_ENDPOINT_OLD_WRONG_PATH: &str = "Endpoint steps certified by kappa alone";
1076
1077 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1079 pub enum EndpointKind {
1080 Interior,
1081 AlphaZeroNoop,
1082 AlphaOneTarget,
1083 }
1084
1085 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1087 pub enum EndpointStructuralStatus {
1088 NotRequired,
1089 VerifiedNoop,
1090 VerifiedTarget,
1091 Missing,
1092 }
1093
1094 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1096 pub enum EndpointCertificateStatus {
1097 Interior,
1098 VerifiedEndpoint,
1099 RejectedMissingStructuralVerification,
1100 RejectedKappaExcursion,
1101 }
1102
1103 #[derive(Clone, Copy, Debug, PartialEq)]
1105 pub struct EndpointHybridTick {
1106 pub tick_id: u64,
1107 pub alpha_t: f64,
1108 pub delta_alpha_t: f64,
1109 pub epsilon_q: f64,
1110 pub policy_margin: f64,
1111 pub prior_state: Matrix3,
1112 pub reference_state: Matrix3,
1113 pub observed_state: Matrix3,
1114 pub structural_verification_enabled: bool,
1115 }
1116
1117 #[derive(Clone, Copy, Debug, PartialEq)]
1119 pub struct EndpointHybridReport {
1120 pub tick_id: u64,
1121 pub alpha_t: f64,
1122 pub endpoint_kind: EndpointKind,
1123 pub endpoint_structural_status: EndpointStructuralStatus,
1124 pub endpoint_certificate_status: EndpointCertificateStatus,
1125 pub kappa_certificate_status: KappaCertificateStatus,
1126 pub kappa_hat_t: f64,
1127 pub floor_t: f64,
1128 pub policy_margin: f64,
1129 pub state_max_error: f64,
1130 }
1131
1132 #[derive(Clone, Copy, Debug, PartialEq)]
1134 pub struct EndpointNegativeGuardReport {
1135 pub old_wrong_path: &'static str,
1136 pub rejected: bool,
1137 pub endpoint_structural_status: EndpointStructuralStatus,
1138 pub endpoint_certificate_status: EndpointCertificateStatus,
1139 pub kappa_certificate_status: KappaCertificateStatus,
1140 pub kappa_hat_t: f64,
1141 pub floor_t: f64,
1142 pub policy_margin: f64,
1143 }
1144
1145 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1146 pub enum EndpointHybridError {
1147 InvalidInput,
1148 NotImplemented,
1149 }
1150
1151 pub fn classify_endpoint_hybrid_tick(
1153 tick: &EndpointHybridTick,
1154 ) -> Result<EndpointHybridReport, EndpointHybridError> {
1155 validate_tick(tick)?;
1156 let kappa_hat_t = compute_kappa_hat(
1159 &tick.prior_state,
1160 &tick.reference_state,
1161 &tick.observed_state,
1162 tick.alpha_t,
1163 )
1164 .map_err(|_| EndpointHybridError::InvalidInput)?;
1165 let e_t = quotient_distance(&tick.prior_state, &tick.reference_state)
1166 .map_err(|_| EndpointHybridError::InvalidInput)?;
1167 let kappa_floor_tick = KappaFloorTick {
1168 tick_id: tick.tick_id,
1169 dimension_n: 3,
1170 epsilon_q: tick.epsilon_q,
1171 delta_alpha_t: tick.delta_alpha_t,
1172 e_t,
1173 policy_margin: tick.policy_margin,
1174 kappa_hat_t,
1175 };
1176 let kappa_report = classify_dynamic_floor_tick(&kappa_floor_tick)
1177 .map_err(|_| EndpointHybridError::InvalidInput)?;
1178 let endpoint_kind = endpoint_kind(tick.alpha_t);
1179 let (endpoint_structural_status, state_max_error) = structural_status(endpoint_kind, tick);
1180 let endpoint_certificate_status = endpoint_certificate_status(
1181 endpoint_kind,
1182 endpoint_structural_status,
1183 kappa_report.certificate_status,
1184 );
1185
1186 Ok(EndpointHybridReport {
1187 tick_id: tick.tick_id,
1188 alpha_t: tick.alpha_t,
1189 endpoint_kind,
1190 endpoint_structural_status,
1191 endpoint_certificate_status,
1192 kappa_certificate_status: kappa_report.certificate_status,
1193 kappa_hat_t: kappa_report.kappa_hat_t,
1194 floor_t: kappa_report.floor_t,
1195 policy_margin: kappa_report.policy_margin,
1196 state_max_error,
1197 })
1198 }
1199
1200 pub fn reject_kappa_alone_endpoint_certificate(
1202 tick: &EndpointHybridTick,
1203 ) -> Result<EndpointNegativeGuardReport, EndpointHybridError> {
1204 let report = classify_endpoint_hybrid_tick(tick)?;
1205 Ok(EndpointNegativeGuardReport {
1206 old_wrong_path: KAPPA_ALONE_ENDPOINT_OLD_WRONG_PATH,
1207 rejected: report.endpoint_kind != EndpointKind::Interior
1208 && report.endpoint_structural_status == EndpointStructuralStatus::Missing
1209 && report.kappa_certificate_status == KappaCertificateStatus::WithinDynamicFloor,
1210 endpoint_structural_status: report.endpoint_structural_status,
1211 endpoint_certificate_status: report.endpoint_certificate_status,
1212 kappa_certificate_status: report.kappa_certificate_status,
1213 kappa_hat_t: report.kappa_hat_t,
1214 floor_t: report.floor_t,
1215 policy_margin: report.policy_margin,
1216 })
1217 }
1218
1219 fn validate_tick(tick: &EndpointHybridTick) -> Result<(), EndpointHybridError> {
1220 if !tick.alpha_t.is_finite()
1221 || !(0.0..=1.0).contains(&tick.alpha_t)
1222 || !tick.delta_alpha_t.is_finite()
1223 || !tick.epsilon_q.is_finite()
1224 || tick.epsilon_q <= 0.0
1225 || !tick.policy_margin.is_finite()
1226 || tick.policy_margin < 0.0
1227 || !matrix_is_finite(tick.prior_state)
1228 || !matrix_is_finite(tick.reference_state)
1229 || !matrix_is_finite(tick.observed_state)
1230 {
1231 Err(EndpointHybridError::InvalidInput)
1232 } else {
1233 Ok(())
1234 }
1235 }
1236
1237 fn endpoint_kind(alpha_t: f64) -> EndpointKind {
1238 if alpha_t == 0.0 {
1239 EndpointKind::AlphaZeroNoop
1240 } else if alpha_t == 1.0 {
1241 EndpointKind::AlphaOneTarget
1242 } else {
1243 EndpointKind::Interior
1244 }
1245 }
1246
1247 fn structural_status(
1248 endpoint_kind: EndpointKind,
1249 tick: &EndpointHybridTick,
1250 ) -> (EndpointStructuralStatus, f64) {
1251 match endpoint_kind {
1252 EndpointKind::Interior => (EndpointStructuralStatus::NotRequired, 0.0),
1253 EndpointKind::AlphaZeroNoop => {
1254 let state_max_error = max_abs_diff(tick.observed_state, tick.prior_state);
1255 if tick.structural_verification_enabled && state_max_error <= STRUCTURAL_TOLERANCE {
1256 (EndpointStructuralStatus::VerifiedNoop, state_max_error)
1257 } else {
1258 (EndpointStructuralStatus::Missing, state_max_error)
1259 }
1260 }
1261 EndpointKind::AlphaOneTarget => {
1262 let state_max_error = max_abs_diff(tick.observed_state, tick.reference_state);
1263 if tick.structural_verification_enabled && state_max_error <= STRUCTURAL_TOLERANCE {
1264 (EndpointStructuralStatus::VerifiedTarget, state_max_error)
1265 } else {
1266 (EndpointStructuralStatus::Missing, state_max_error)
1267 }
1268 }
1269 }
1270 }
1271
1272 fn endpoint_certificate_status(
1273 endpoint_kind: EndpointKind,
1274 structural_status: EndpointStructuralStatus,
1275 kappa_status: KappaCertificateStatus,
1276 ) -> EndpointCertificateStatus {
1277 match endpoint_kind {
1278 EndpointKind::Interior => EndpointCertificateStatus::Interior,
1279 EndpointKind::AlphaZeroNoop | EndpointKind::AlphaOneTarget => {
1280 if structural_status == EndpointStructuralStatus::Missing {
1281 EndpointCertificateStatus::RejectedMissingStructuralVerification
1282 } else if kappa_status == KappaCertificateStatus::FailClosedExcursion {
1283 EndpointCertificateStatus::RejectedKappaExcursion
1284 } else {
1285 EndpointCertificateStatus::VerifiedEndpoint
1286 }
1287 }
1288 }
1289 }
1290
1291 fn matrix_is_finite(matrix: Matrix3) -> bool {
1292 matrix
1293 .entries
1294 .iter()
1295 .all(|row| row.iter().all(|entry| entry.is_finite()))
1296 }
1297
1298 fn max_abs_diff(left: Matrix3, right: Matrix3) -> f64 {
1299 let mut max_error = 0.0_f64;
1300 for row in 0..3 {
1301 for col in 0..3 {
1302 max_error = max_error.max((left.entries[row][col] - right.entries[row][col]).abs());
1303 }
1304 }
1305 max_error
1306 }
1307}
1308
1309#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1311pub enum PrecheckReason {
1312 Passed,
1314 ScaffoldOnly,
1316 ContractsMissing,
1318 TicketJourneyUnmapped,
1320 OldWrongCore,
1322 NonPositiveEntry,
1324 SingularNormalizationTarget,
1326 NotFiniteInput,
1328 AlphaOutOfRange,
1330 UnknownContext,
1332 ForbiddenCategoryWrite,
1334}
1335
1336pub mod precheck {
1337 use super::qac::Matrix3;
1338 use super::PrecheckReason;
1339
1340 pub const PRECHECK_FORBIDDEN_JOURNEY_ID: &str = "J-PRECHECK-FORBIDDEN";
1342
1343 pub const PRECHECK_FORBIDDEN_STORY_ID: &str = "CCFV1-06";
1345
1346 pub const EPSILON_T_NAME_COLLISION_OLD_WRONG_PATH: &str = "epsilon_t name collision";
1348
1349 pub const POST_UPDATE_INVALIDITY_OLD_WRONG_PATH: &str = "Invalidity check after update";
1351
1352 pub const FORBIDDEN_WRITE_BYPASS_OLD_WRONG_PATH: &str = "Forbidden write bypassing guard";
1354
1355 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1357 pub struct AccumulatorSnapshot {
1358 pub update_count: u64,
1359 pub matrix_hash: u64,
1360 }
1361
1362 #[derive(Clone, Copy, Debug, PartialEq)]
1364 pub struct PrecheckTick {
1365 pub tick_id: u64,
1366 pub prior_state: Matrix3,
1367 pub reference_state: Matrix3,
1368 pub left_normalization: [f64; 3],
1369 pub right_normalization: [f64; 3],
1370 pub alpha_t: f64,
1371 pub context_known: bool,
1372 pub forbidden_category_write: bool,
1373 }
1374
1375 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1377 pub enum CertificateEventClass {
1378 None,
1379 PrecheckFailure,
1380 }
1381
1382 #[derive(Clone, Copy, Debug, PartialEq)]
1384 pub struct PrecheckReport {
1385 pub tick_id: u64,
1386 pub precheck_t: PrecheckReason,
1387 pub update_count_before: u64,
1388 pub update_count_after: u64,
1389 pub matrix_hash_before: u64,
1390 pub matrix_hash_after: u64,
1391 pub update_attempted: bool,
1392 pub fail_closed: bool,
1393 pub certificate_event_class: CertificateEventClass,
1394 }
1395
1396 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1398 pub struct PrecheckNegativeGuardReport {
1399 pub old_wrong_path: &'static str,
1400 pub rejected: bool,
1401 }
1402
1403 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1404 pub enum PrecheckError {
1405 InvalidInput,
1406 NotImplemented,
1407 }
1408
1409 pub fn run_precheck_before_update(
1411 tick: &PrecheckTick,
1412 before: AccumulatorSnapshot,
1413 ) -> Result<PrecheckReport, PrecheckError> {
1414 let precheck_t = precheck_reason(tick).unwrap_or(PrecheckReason::Passed);
1415 let fail_closed = precheck_t != PrecheckReason::Passed;
1416
1417 Ok(PrecheckReport {
1418 tick_id: tick.tick_id,
1419 precheck_t,
1420 update_count_before: before.update_count,
1421 update_count_after: before.update_count,
1422 matrix_hash_before: before.matrix_hash,
1423 matrix_hash_after: before.matrix_hash,
1424 update_attempted: false,
1425 fail_closed,
1426 certificate_event_class: if fail_closed {
1427 CertificateEventClass::PrecheckFailure
1428 } else {
1429 CertificateEventClass::None
1430 },
1431 })
1432 }
1433
1434 pub fn reject_epsilon_t_name_collision() -> Result<PrecheckNegativeGuardReport, PrecheckError> {
1436 Ok(negative_guard_report(
1437 EPSILON_T_NAME_COLLISION_OLD_WRONG_PATH,
1438 ))
1439 }
1440
1441 pub fn reject_post_update_invalidity_check(
1443 ) -> Result<PrecheckNegativeGuardReport, PrecheckError> {
1444 Ok(negative_guard_report(POST_UPDATE_INVALIDITY_OLD_WRONG_PATH))
1445 }
1446
1447 pub fn reject_forbidden_write_bypassing_guard(
1449 ) -> Result<PrecheckNegativeGuardReport, PrecheckError> {
1450 Ok(negative_guard_report(FORBIDDEN_WRITE_BYPASS_OLD_WRONG_PATH))
1451 }
1452
1453 fn precheck_reason(tick: &PrecheckTick) -> Option<PrecheckReason> {
1454 if !matrix_is_finite(tick.prior_state)
1455 || !matrix_is_finite(tick.reference_state)
1456 || !diagonal_is_finite(tick.left_normalization)
1457 || !diagonal_is_finite(tick.right_normalization)
1458 || !tick.alpha_t.is_finite()
1459 {
1460 return Some(PrecheckReason::NotFiniteInput);
1461 }
1462 if !matrix_is_strictly_positive(tick.prior_state)
1463 || !matrix_is_strictly_positive(tick.reference_state)
1464 {
1465 return Some(PrecheckReason::NonPositiveEntry);
1466 }
1467 if !diagonal_is_strictly_positive(tick.left_normalization)
1468 || !diagonal_is_strictly_positive(tick.right_normalization)
1469 {
1470 return Some(PrecheckReason::SingularNormalizationTarget);
1471 }
1472 if !(0.0..=1.0).contains(&tick.alpha_t) {
1473 return Some(PrecheckReason::AlphaOutOfRange);
1474 }
1475 if !tick.context_known {
1476 return Some(PrecheckReason::UnknownContext);
1477 }
1478 if tick.forbidden_category_write {
1479 return Some(PrecheckReason::ForbiddenCategoryWrite);
1480 }
1481 None
1482 }
1483
1484 fn matrix_is_finite(matrix: Matrix3) -> bool {
1485 matrix
1486 .entries
1487 .iter()
1488 .all(|row| row.iter().all(|entry| entry.is_finite()))
1489 }
1490
1491 fn matrix_is_strictly_positive(matrix: Matrix3) -> bool {
1492 matrix
1493 .entries
1494 .iter()
1495 .all(|row| row.iter().all(|entry| *entry > 0.0))
1496 }
1497
1498 fn diagonal_is_finite(diagonal: [f64; 3]) -> bool {
1499 diagonal.iter().all(|entry| entry.is_finite())
1500 }
1501
1502 fn diagonal_is_strictly_positive(diagonal: [f64; 3]) -> bool {
1503 diagonal.iter().all(|entry| *entry > 0.0)
1504 }
1505
1506 const fn negative_guard_report(old_wrong_path: &'static str) -> PrecheckNegativeGuardReport {
1507 PrecheckNegativeGuardReport {
1508 old_wrong_path,
1509 rejected: true,
1510 }
1511 }
1512}
1513
1514pub mod context {
1515 use super::qac::{qac_update_3x3, Matrix3, PositiveDiagonal3, QacError, QacInputs3};
1516
1517 const EPSILON_FLOOR: f64 = 1.0e-12;
1518 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
1519 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
1520
1521 pub const CONTEXT_ISOLATION_JOURNEY_ID: &str = "J-CONTEXT-ISOLATION";
1523
1524 pub const CONTEXT_ISOLATION_STORY_ID: &str = "CCFV1-07";
1526
1527 pub const GLOBAL_SHARED_TIMESTAMP_OLD_WRONG_PATH: &str = "Global tick/shared timestamp";
1529
1530 pub const CROSS_CONTEXT_NORMALIZATION_OLD_WRONG_PATH: &str =
1532 "Cross-context normalization leakage";
1533
1534 #[derive(Clone, Copy, Debug, PartialEq)]
1536 pub struct ContextAccumulator {
1537 pub context_id: &'static str,
1538 pub state: Matrix3,
1539 pub update_count: u64,
1540 pub last_update_unix_ms: u64,
1541 }
1542
1543 #[derive(Clone, Copy, Debug, PartialEq)]
1545 pub struct PerContextAccumulatorStore {
1546 pub first: ContextAccumulator,
1547 pub second: ContextAccumulator,
1548 }
1549
1550 #[derive(Clone, Copy, Debug, PartialEq)]
1552 pub struct ContextUpdateTick {
1553 pub context_id: &'static str,
1554 pub reference_state: Matrix3,
1555 pub left_normalization: [f64; 3],
1556 pub right_normalization: [f64; 3],
1557 pub alpha_t: f64,
1558 pub wall_clock_unix_ms: u64,
1559 }
1560
1561 #[derive(Clone, Copy, Debug, PartialEq)]
1563 pub struct ContextAccumulatorSnapshot {
1564 pub context_id: &'static str,
1565 pub state: Matrix3,
1566 pub state_hash: u64,
1567 pub update_count: u64,
1568 pub last_update_unix_ms: u64,
1569 }
1570
1571 #[derive(Clone, Copy, Debug, PartialEq)]
1573 pub struct ContextIsolationReport {
1574 pub active_context: &'static str,
1575 pub inactive_context: &'static str,
1576 pub active_before: ContextAccumulatorSnapshot,
1577 pub active_after: ContextAccumulatorSnapshot,
1578 pub inactive_before: ContextAccumulatorSnapshot,
1579 pub inactive_after: ContextAccumulatorSnapshot,
1580 }
1581
1582 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1584 pub struct ContextNegativeGuardReport {
1585 pub old_wrong_path: &'static str,
1586 pub rejected: bool,
1587 }
1588
1589 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1590 pub enum ContextIsolationError {
1591 InvalidInput,
1592 UnknownContext,
1593 NotImplemented,
1594 }
1595
1596 pub fn apply_active_context_update(
1598 store: &mut PerContextAccumulatorStore,
1599 tick: &ContextUpdateTick,
1600 ) -> Result<ContextIsolationReport, ContextIsolationError> {
1601 validate_store(store)?;
1602 validate_tick(tick)?;
1603
1604 let first_is_active = store.first.context_id == tick.context_id;
1605 let second_is_active = store.second.context_id == tick.context_id;
1606 if first_is_active == second_is_active {
1607 return Err(ContextIsolationError::UnknownContext);
1608 }
1609
1610 let active_before_accumulator = if first_is_active {
1611 store.first
1612 } else {
1613 store.second
1614 };
1615 let inactive_before_accumulator = if first_is_active {
1616 store.second
1617 } else {
1618 store.first
1619 };
1620
1621 if tick.wall_clock_unix_ms <= active_before_accumulator.last_update_unix_ms {
1622 return Err(ContextIsolationError::InvalidInput);
1623 }
1624
1625 let active_before = snapshot(active_before_accumulator);
1626 let inactive_before = snapshot(inactive_before_accumulator);
1627 let new_state = qac_update_3x3(&QacInputs3 {
1628 prior_a_t: active_before_accumulator.state,
1629 reference_r_t: tick.reference_state,
1630 left_l_t: PositiveDiagonal3::try_new(tick.left_normalization).map_err(map_qac_error)?,
1631 right_c_t: PositiveDiagonal3::try_new(tick.right_normalization)
1632 .map_err(map_qac_error)?,
1633 alpha_t: tick.alpha_t,
1634 epsilon_floor: EPSILON_FLOOR,
1635 })
1636 .map_err(map_qac_error)?;
1637
1638 let updated_active = ContextAccumulator {
1639 context_id: active_before_accumulator.context_id,
1640 state: new_state,
1641 update_count: active_before_accumulator
1642 .update_count
1643 .checked_add(1)
1644 .ok_or(ContextIsolationError::InvalidInput)?,
1645 last_update_unix_ms: tick.wall_clock_unix_ms,
1646 };
1647
1648 if first_is_active {
1649 store.first = updated_active;
1650 } else {
1651 store.second = updated_active;
1652 }
1653
1654 let active_after_accumulator = if first_is_active {
1655 store.first
1656 } else {
1657 store.second
1658 };
1659 let inactive_after_accumulator = if first_is_active {
1660 store.second
1661 } else {
1662 store.first
1663 };
1664
1665 Ok(ContextIsolationReport {
1666 active_context: active_before_accumulator.context_id,
1667 inactive_context: inactive_before_accumulator.context_id,
1668 active_before,
1669 active_after: snapshot(active_after_accumulator),
1670 inactive_before,
1671 inactive_after: snapshot(inactive_after_accumulator),
1672 })
1673 }
1674
1675 pub fn reject_global_shared_timestamp(
1677 ) -> Result<ContextNegativeGuardReport, ContextIsolationError> {
1678 Ok(negative_guard_report(
1679 GLOBAL_SHARED_TIMESTAMP_OLD_WRONG_PATH,
1680 ))
1681 }
1682
1683 pub fn reject_cross_context_normalization_leakage(
1685 ) -> Result<ContextNegativeGuardReport, ContextIsolationError> {
1686 Ok(negative_guard_report(
1687 CROSS_CONTEXT_NORMALIZATION_OLD_WRONG_PATH,
1688 ))
1689 }
1690
1691 fn validate_store(store: &PerContextAccumulatorStore) -> Result<(), ContextIsolationError> {
1692 if store.first.context_id.is_empty()
1693 || store.second.context_id.is_empty()
1694 || store.first.context_id == store.second.context_id
1695 {
1696 Err(ContextIsolationError::InvalidInput)
1697 } else {
1698 Ok(())
1699 }
1700 }
1701
1702 fn validate_tick(tick: &ContextUpdateTick) -> Result<(), ContextIsolationError> {
1703 if tick.context_id.is_empty() || tick.wall_clock_unix_ms == 0 {
1704 Err(ContextIsolationError::InvalidInput)
1705 } else {
1706 Ok(())
1707 }
1708 }
1709
1710 fn snapshot(accumulator: ContextAccumulator) -> ContextAccumulatorSnapshot {
1711 ContextAccumulatorSnapshot {
1712 context_id: accumulator.context_id,
1713 state: accumulator.state,
1714 state_hash: matrix_hash(accumulator.state),
1715 update_count: accumulator.update_count,
1716 last_update_unix_ms: accumulator.last_update_unix_ms,
1717 }
1718 }
1719
1720 fn matrix_hash(matrix: Matrix3) -> u64 {
1721 let mut hash = FNV_OFFSET;
1722 for row in matrix.entries {
1723 for value in row {
1724 for byte in value.to_bits().to_le_bytes() {
1725 hash ^= byte as u64;
1726 hash = hash.wrapping_mul(FNV_PRIME);
1727 }
1728 }
1729 }
1730 hash
1731 }
1732
1733 const fn negative_guard_report(old_wrong_path: &'static str) -> ContextNegativeGuardReport {
1734 ContextNegativeGuardReport {
1735 old_wrong_path,
1736 rejected: true,
1737 }
1738 }
1739
1740 const fn map_qac_error(_error: QacError) -> ContextIsolationError {
1741 ContextIsolationError::InvalidInput
1742 }
1743}
1744
1745pub mod forbidden_policy {
1746 use super::qac::{qac_update_3x3, Matrix3, PositiveDiagonal3, QacError, QacInputs3};
1747 use super::PrecheckReason;
1748
1749 pub const FORBIDDEN_POLICY_JOURNEY_ID: &str = "J-PRECHECK-FORBIDDEN";
1751
1752 pub const FORBIDDEN_POLICY_STORY_ID: &str = "CCFV1-08";
1754
1755 pub const PINNED_ZERO_QAC_OLD_WRONG_PATH: &str = "Pinned zeros inside canonical QAC math";
1757
1758 pub const SKIPPED_MATRIX_COLUMNS_OLD_WRONG_PATH: &str = "Skipped matrix columns";
1760
1761 pub const INVISIBLE_FACEWISE_WRITE_OLD_WRONG_PATH: &str = "Invisible facewise write";
1763
1764 const MATRIX_CELLS: usize = 9;
1765 const MASK_BITS: u16 = 0x01ff;
1766 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
1767 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
1768
1769 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1771 pub struct CategoryIndex {
1772 pub row: usize,
1773 pub col: usize,
1774 }
1775
1776 impl CategoryIndex {
1777 pub const fn new(row: usize, col: usize) -> Self {
1778 Self { row, col }
1779 }
1780
1781 pub fn validate(self) -> Result<Self, ForbiddenPolicyError> {
1782 if self.row < 3 && self.col < 3 {
1783 Ok(self)
1784 } else {
1785 Err(ForbiddenPolicyError::InvalidInput)
1786 }
1787 }
1788
1789 pub const fn bit_index(self) -> usize {
1790 self.row * 3 + self.col
1791 }
1792 }
1793
1794 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1796 pub struct ForbiddenCategoryMask3 {
1797 bits: u16,
1798 }
1799
1800 impl ForbiddenCategoryMask3 {
1801 pub const fn empty() -> Self {
1802 Self { bits: 0 }
1803 }
1804
1805 pub fn try_from_bits(bits: u16) -> Result<Self, ForbiddenPolicyError> {
1806 if bits & !MASK_BITS == 0 {
1807 Ok(Self { bits })
1808 } else {
1809 Err(ForbiddenPolicyError::InvalidInput)
1810 }
1811 }
1812
1813 pub fn try_from_categories(
1814 categories: &[CategoryIndex],
1815 ) -> Result<Self, ForbiddenPolicyError> {
1816 let mut bits = 0_u16;
1817 for category in categories {
1818 let category = category.validate()?;
1819 bits |= 1_u16 << category.bit_index();
1820 }
1821 Self::try_from_bits(bits)
1822 }
1823
1824 pub const fn bits(self) -> u16 {
1825 self.bits
1826 }
1827
1828 pub fn forbids(self, category: CategoryIndex) -> bool {
1829 if category.row >= 3 || category.col >= 3 {
1830 return false;
1831 }
1832 (self.bits & (1_u16 << category.bit_index())) != 0
1833 }
1834
1835 pub fn forbidden_count(self) -> usize {
1836 let mut count = 0_usize;
1837 let mut bit = 0_usize;
1838 while bit < MATRIX_CELLS {
1839 if (self.bits & (1_u16 << bit)) != 0 {
1840 count += 1;
1841 }
1842 bit += 1;
1843 }
1844 count
1845 }
1846 }
1847
1848 #[derive(Clone, Copy, Debug, PartialEq)]
1850 pub struct ForbiddenPolicyTick {
1851 pub tick_id: u64,
1852 pub prior_state: Matrix3,
1853 pub reference_state: Matrix3,
1854 pub left_normalization: [f64; 3],
1855 pub right_normalization: [f64; 3],
1856 pub alpha_t: f64,
1857 pub epsilon_floor: f64,
1858 pub forbidden_mask: ForbiddenCategoryMask3,
1859 pub target_category: CategoryIndex,
1860 pub update_count_before: u64,
1861 pub matrix_hash_before: u64,
1862 }
1863
1864 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1866 pub enum ForbiddenPolicyEventClass {
1867 None,
1868 ForbiddenCategoryWrite,
1869 }
1870
1871 #[derive(Clone, Copy, Debug, PartialEq)]
1873 pub struct ForbiddenPolicyReport {
1874 pub tick_id: u64,
1875 pub forbidden_policy_mask_bits: u16,
1876 pub precheck_reason: PrecheckReason,
1877 pub forbidden_category_write: bool,
1878 pub update_count_before: u64,
1879 pub update_count_after: u64,
1880 pub matrix_hash_before: u64,
1881 pub matrix_hash_after: u64,
1882 pub precheck_fail_closed: bool,
1883 pub certificate_event_class: ForbiddenPolicyEventClass,
1884 pub valid_update_min_entry: f64,
1885 pub epsilon_floor: f64,
1886 pub canonical_entries_epsilon_floored: bool,
1887 }
1888
1889 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1891 pub struct ForbiddenPolicyNegativeGuardReport {
1892 pub old_wrong_path: &'static str,
1893 pub rejected: bool,
1894 }
1895
1896 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1897 pub enum ForbiddenPolicyError {
1898 InvalidInput,
1899 NotImplemented,
1900 }
1901
1902 pub fn apply_forbidden_category_policy(
1904 tick: &ForbiddenPolicyTick,
1905 ) -> Result<ForbiddenPolicyReport, ForbiddenPolicyError> {
1906 validate_tick(tick)?;
1907 let target_category = tick.target_category.validate()?;
1908
1909 if tick.forbidden_mask.forbids(target_category) {
1910 return Ok(ForbiddenPolicyReport {
1911 tick_id: tick.tick_id,
1912 forbidden_policy_mask_bits: tick.forbidden_mask.bits(),
1913 precheck_reason: PrecheckReason::ForbiddenCategoryWrite,
1914 forbidden_category_write: true,
1915 update_count_before: tick.update_count_before,
1916 update_count_after: tick.update_count_before,
1917 matrix_hash_before: tick.matrix_hash_before,
1918 matrix_hash_after: tick.matrix_hash_before,
1919 precheck_fail_closed: true,
1920 certificate_event_class: ForbiddenPolicyEventClass::ForbiddenCategoryWrite,
1921 valid_update_min_entry: 0.0,
1924 epsilon_floor: tick.epsilon_floor,
1925 canonical_entries_epsilon_floored: false,
1926 });
1927 }
1928
1929 let updated_state = qac_update_3x3(&QacInputs3 {
1930 prior_a_t: tick.prior_state,
1931 reference_r_t: tick.reference_state,
1932 left_l_t: PositiveDiagonal3::try_new(tick.left_normalization).map_err(map_qac_error)?,
1933 right_c_t: PositiveDiagonal3::try_new(tick.right_normalization)
1934 .map_err(map_qac_error)?,
1935 alpha_t: tick.alpha_t,
1936 epsilon_floor: tick.epsilon_floor,
1937 })
1938 .map_err(map_qac_error)?;
1939
1940 let valid_update_min_entry = matrix_min(updated_state);
1941 let canonical_entries_epsilon_floored = valid_update_min_entry >= tick.epsilon_floor;
1942 if !canonical_entries_epsilon_floored {
1943 return Err(ForbiddenPolicyError::InvalidInput);
1944 }
1945
1946 Ok(ForbiddenPolicyReport {
1947 tick_id: tick.tick_id,
1948 forbidden_policy_mask_bits: tick.forbidden_mask.bits(),
1949 precheck_reason: PrecheckReason::Passed,
1950 forbidden_category_write: false,
1951 update_count_before: tick.update_count_before,
1952 update_count_after: tick
1953 .update_count_before
1954 .checked_add(1)
1955 .ok_or(ForbiddenPolicyError::InvalidInput)?,
1956 matrix_hash_before: tick.matrix_hash_before,
1957 matrix_hash_after: matrix_hash(updated_state),
1958 precheck_fail_closed: false,
1959 certificate_event_class: ForbiddenPolicyEventClass::None,
1960 valid_update_min_entry,
1961 epsilon_floor: tick.epsilon_floor,
1962 canonical_entries_epsilon_floored,
1963 })
1964 }
1965
1966 pub fn reject_pinned_zero_qac_math(
1968 ) -> Result<ForbiddenPolicyNegativeGuardReport, ForbiddenPolicyError> {
1969 Ok(negative_guard_report(PINNED_ZERO_QAC_OLD_WRONG_PATH))
1970 }
1971
1972 pub fn reject_skipped_matrix_columns(
1974 ) -> Result<ForbiddenPolicyNegativeGuardReport, ForbiddenPolicyError> {
1975 Ok(negative_guard_report(SKIPPED_MATRIX_COLUMNS_OLD_WRONG_PATH))
1976 }
1977
1978 pub fn reject_invisible_facewise_write(
1980 ) -> Result<ForbiddenPolicyNegativeGuardReport, ForbiddenPolicyError> {
1981 Ok(negative_guard_report(
1982 INVISIBLE_FACEWISE_WRITE_OLD_WRONG_PATH,
1983 ))
1984 }
1985
1986 fn validate_tick(tick: &ForbiddenPolicyTick) -> Result<(), ForbiddenPolicyError> {
1987 tick.target_category.validate()?;
1988 if tick.tick_id == 0
1989 || !tick.alpha_t.is_finite()
1990 || !tick.epsilon_floor.is_finite()
1991 || tick.epsilon_floor <= 0.0
1992 || !matrix_is_finite(tick.prior_state)
1993 || !matrix_is_finite(tick.reference_state)
1994 || !diagonal_is_finite(tick.left_normalization)
1995 || !diagonal_is_finite(tick.right_normalization)
1996 || tick.matrix_hash_before != matrix_hash(tick.prior_state)
1997 {
1998 return Err(ForbiddenPolicyError::InvalidInput);
1999 }
2000 Ok(())
2001 }
2002
2003 fn matrix_hash(matrix: Matrix3) -> u64 {
2004 let mut hash = FNV_OFFSET;
2005 for row in matrix.entries {
2006 for value in row {
2007 for byte in value.to_bits().to_le_bytes() {
2008 hash ^= byte as u64;
2009 hash = hash.wrapping_mul(FNV_PRIME);
2010 }
2011 }
2012 }
2013 hash
2014 }
2015
2016 fn matrix_min(matrix: Matrix3) -> f64 {
2017 let mut minimum = matrix.entries[0][0];
2018 for row in matrix.entries {
2019 for value in row {
2020 if value < minimum {
2021 minimum = value;
2022 }
2023 }
2024 }
2025 minimum
2026 }
2027
2028 fn matrix_is_finite(matrix: Matrix3) -> bool {
2029 matrix
2030 .entries
2031 .iter()
2032 .all(|row| row.iter().all(|entry| entry.is_finite()))
2033 }
2034
2035 fn diagonal_is_finite(diagonal: [f64; 3]) -> bool {
2036 diagonal.iter().all(|entry| entry.is_finite())
2037 }
2038
2039 const fn negative_guard_report(
2040 old_wrong_path: &'static str,
2041 ) -> ForbiddenPolicyNegativeGuardReport {
2042 ForbiddenPolicyNegativeGuardReport {
2043 old_wrong_path,
2044 rejected: true,
2045 }
2046 }
2047
2048 const fn map_qac_error(_error: QacError) -> ForbiddenPolicyError {
2049 ForbiddenPolicyError::InvalidInput
2050 }
2051}
2052
2053pub mod envelope {
2054 pub const ENVELOPE_MONITOR_JOURNEY_ID: &str = "J-ENVELOPE-MONITOR";
2056
2057 pub const ENVELOPE_MONITOR_STORY_ID: &str = "CCFV1-09";
2059
2060 pub const MIN_GATE_CONVERGENCE_OLD_WRONG_PATH: &str =
2062 "Min-gate implies unconditional convergence";
2063
2064 pub const NO_TARGET_MOTION_TELEMETRY_OLD_WRONG_PATH: &str = "No target-motion telemetry";
2066
2067 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2069 pub enum EnvelopeStatus {
2070 InsideEnvelope,
2071 NotCertified,
2072 }
2073
2074 #[derive(Clone, Copy, Debug, PartialEq)]
2076 pub struct EnvelopeMonitorTick {
2077 pub tick_id: u64,
2078 pub previous_b_t: f64,
2079 pub alpha_t: f64,
2080 pub e_t: f64,
2081 pub delta_t: f64,
2082 pub kappa_t: f64,
2083 pub monitored_assumptions_hold: bool,
2084 }
2085
2086 #[derive(Clone, Copy, Debug, PartialEq)]
2088 pub struct EnvelopeMonitorReport {
2089 pub tick_id: u64,
2090 pub previous_b_t: f64,
2091 pub alpha_t: f64,
2092 pub e_t: f64,
2093 pub delta_t: f64,
2094 pub kappa_t: f64,
2095 pub b_t: f64,
2096 pub status: EnvelopeStatus,
2097 pub b_t_bounds_e_t: bool,
2098 pub theorem_failure_claimed: bool,
2099 pub convergence_claimed: bool,
2100 }
2101
2102 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2104 pub struct EnvelopeNegativeGuardReport {
2105 pub old_wrong_path: &'static str,
2106 pub rejected: bool,
2107 }
2108
2109 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2110 pub enum EnvelopeMonitorError {
2111 InvalidInput,
2112 NotImplemented,
2113 }
2114
2115 pub fn compute_envelope_tick(
2117 tick: &EnvelopeMonitorTick,
2118 ) -> Result<EnvelopeMonitorReport, EnvelopeMonitorError> {
2119 validate_tick(tick)?;
2120 let b_t = (1.0 - tick.alpha_t) * tick.previous_b_t + (tick.delta_t + tick.kappa_t);
2121 if !b_t.is_finite() {
2122 return Err(EnvelopeMonitorError::InvalidInput);
2123 }
2124 let b_t_bounds_e_t = b_t >= tick.e_t;
2125 let status = if tick.monitored_assumptions_hold && b_t_bounds_e_t {
2126 EnvelopeStatus::InsideEnvelope
2127 } else {
2128 EnvelopeStatus::NotCertified
2129 };
2130
2131 Ok(EnvelopeMonitorReport {
2132 tick_id: tick.tick_id,
2133 previous_b_t: tick.previous_b_t,
2134 alpha_t: tick.alpha_t,
2135 e_t: tick.e_t,
2136 delta_t: tick.delta_t,
2137 kappa_t: tick.kappa_t,
2138 b_t,
2139 status,
2140 b_t_bounds_e_t,
2141 theorem_failure_claimed: false,
2142 convergence_claimed: false,
2143 })
2144 }
2145
2146 pub fn reject_min_gate_unconditional_convergence(
2148 ) -> Result<EnvelopeNegativeGuardReport, EnvelopeMonitorError> {
2149 Ok(negative_guard_report(MIN_GATE_CONVERGENCE_OLD_WRONG_PATH))
2150 }
2151
2152 pub fn reject_missing_target_motion_telemetry(
2154 ) -> Result<EnvelopeNegativeGuardReport, EnvelopeMonitorError> {
2155 Ok(negative_guard_report(
2156 NO_TARGET_MOTION_TELEMETRY_OLD_WRONG_PATH,
2157 ))
2158 }
2159
2160 fn validate_tick(tick: &EnvelopeMonitorTick) -> Result<(), EnvelopeMonitorError> {
2161 if tick.tick_id == 0
2162 || !tick.previous_b_t.is_finite()
2163 || tick.previous_b_t < 0.0
2164 || !tick.alpha_t.is_finite()
2165 || !(0.0..=1.0).contains(&tick.alpha_t)
2166 || !tick.e_t.is_finite()
2167 || tick.e_t < 0.0
2168 || !tick.delta_t.is_finite()
2169 || tick.delta_t < 0.0
2170 || !tick.kappa_t.is_finite()
2171 || tick.kappa_t < 0.0
2172 {
2173 return Err(EnvelopeMonitorError::InvalidInput);
2174 }
2175 Ok(())
2176 }
2177
2178 const fn negative_guard_report(old_wrong_path: &'static str) -> EnvelopeNegativeGuardReport {
2179 EnvelopeNegativeGuardReport {
2180 old_wrong_path,
2181 rejected: true,
2182 }
2183 }
2184}
2185
2186pub mod partition {
2187 pub const LIVE_STATE_ENDPOINT_JOURNEY_ID: &str = "J-LIVE-STATE-ENDPOINT";
2189
2190 pub const STOER_WAGNER_PARTITION_STORY_ID: &str = "CCFV1-10";
2192
2193 pub const STOER_WAGNER_CANONICAL_SOURCE: &str = "stoer_wagner";
2195
2196 pub const COGNITUM_PARTITION_HINT_POLICY: &str = "hint_only";
2198
2199 pub const EXTERNAL_PARTITION_TRUSTED_OLD_WRONG_PATH: &str =
2201 "External partition trusted without verification";
2202
2203 #[derive(Clone, Copy, Debug, PartialEq)]
2205 pub struct WeightedGraph4 {
2206 pub weights: [[f64; 4]; 4],
2207 }
2208
2209 impl WeightedGraph4 {
2210 pub const fn new(weights: [[f64; 4]; 4]) -> Self {
2211 Self { weights }
2212 }
2213 }
2214
2215 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2217 pub struct Partition4 {
2218 pub side: [bool; 4],
2219 }
2220
2221 impl Partition4 {
2222 pub const fn new(side: [bool; 4]) -> Self {
2223 Self { side }
2224 }
2225 }
2226
2227 #[derive(Clone, Copy, Debug, PartialEq)]
2229 pub struct PartitionVerificationInput4 {
2230 pub graph: WeightedGraph4,
2231 pub external_hint: Option<Partition4>,
2232 pub disagreement_tolerance: usize,
2233 }
2234
2235 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2237 pub enum PartitionTelemetryEvent {
2238 None,
2239 PartitionDisagreement,
2240 }
2241
2242 #[derive(Clone, Copy, Debug, PartialEq)]
2244 pub struct PartitionVerificationReport4 {
2245 pub min_cut_weight: f64,
2246 pub canonical_partition: Partition4,
2247 pub canonical_partition_source: &'static str,
2248 pub cognitum_partition_hint: &'static str,
2249 pub disagreement_distance: usize,
2250 pub policy_tolerance: usize,
2251 pub event: PartitionTelemetryEvent,
2252 }
2253
2254 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2256 pub struct PartitionNegativeGuardReport {
2257 pub old_wrong_path: &'static str,
2258 pub rejected: bool,
2259 pub event: PartitionTelemetryEvent,
2260 }
2261
2262 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2263 pub enum PartitionVerificationError {
2264 InvalidInput,
2265 NotImplemented,
2266 }
2267
2268 pub fn verify_partition_4(
2270 input: &PartitionVerificationInput4,
2271 ) -> Result<PartitionVerificationReport4, PartitionVerificationError> {
2272 validate_input(input)?;
2273 let min_cut = stoer_wagner_min_cut_4(input.graph)?;
2274 let disagreement_distance = input
2275 .external_hint
2276 .map(|hint| partition_distance(min_cut.partition, hint))
2277 .unwrap_or(0);
2278 let event = if input.external_hint.is_some()
2279 && disagreement_distance > input.disagreement_tolerance
2280 {
2281 PartitionTelemetryEvent::PartitionDisagreement
2282 } else {
2283 PartitionTelemetryEvent::None
2284 };
2285
2286 Ok(PartitionVerificationReport4 {
2287 min_cut_weight: min_cut.weight,
2288 canonical_partition: min_cut.partition,
2289 canonical_partition_source: STOER_WAGNER_CANONICAL_SOURCE,
2290 cognitum_partition_hint: COGNITUM_PARTITION_HINT_POLICY,
2291 disagreement_distance,
2292 policy_tolerance: input.disagreement_tolerance,
2293 event,
2294 })
2295 }
2296
2297 pub fn reject_external_partition_trusted_without_verification(
2299 input: &PartitionVerificationInput4,
2300 ) -> Result<PartitionNegativeGuardReport, PartitionVerificationError> {
2301 let report = verify_partition_4(input)?;
2302 Ok(PartitionNegativeGuardReport {
2303 old_wrong_path: EXTERNAL_PARTITION_TRUSTED_OLD_WRONG_PATH,
2304 rejected: input.external_hint.is_some()
2305 && report.event == PartitionTelemetryEvent::PartitionDisagreement,
2306 event: report.event,
2307 })
2308 }
2309
2310 fn validate_input(
2311 input: &PartitionVerificationInput4,
2312 ) -> Result<(), PartitionVerificationError> {
2313 if input.disagreement_tolerance > 4 {
2314 return Err(PartitionVerificationError::InvalidInput);
2315 }
2316 if input
2317 .external_hint
2318 .is_some_and(|partition| !partition_is_nontrivial(partition))
2319 {
2320 return Err(PartitionVerificationError::InvalidInput);
2321 }
2322
2323 for row in 0..4 {
2324 if input.graph.weights[row][row].abs() > 1.0e-12 {
2325 return Err(PartitionVerificationError::InvalidInput);
2326 }
2327 for col in 0..4 {
2328 let weight = input.graph.weights[row][col];
2329 if !weight.is_finite() || weight < 0.0 {
2330 return Err(PartitionVerificationError::InvalidInput);
2331 }
2332 if (input.graph.weights[row][col] - input.graph.weights[col][row]).abs() > 1.0e-12 {
2333 return Err(PartitionVerificationError::InvalidInput);
2334 }
2335 }
2336 }
2337
2338 Ok(())
2339 }
2340
2341 #[derive(Clone, Copy, Debug, PartialEq)]
2342 struct MinCut4 {
2343 weight: f64,
2344 partition: Partition4,
2345 }
2346
2347 fn stoer_wagner_min_cut_4(
2348 graph: WeightedGraph4,
2349 ) -> Result<MinCut4, PartitionVerificationError> {
2350 let mut weights = graph.weights;
2351 let mut groups = [
2352 Partition4::new([true, false, false, false]),
2353 Partition4::new([false, true, false, false]),
2354 Partition4::new([false, false, true, false]),
2355 Partition4::new([false, false, false, true]),
2356 ];
2357 let mut active = [true; 4];
2358 let mut best = MinCut4 {
2359 weight: f64::INFINITY,
2360 partition: Partition4::new([false, true, false, false]),
2361 };
2362
2363 while active_count(active) > 1 {
2364 let count = active_count(active);
2365 let mut added = [false; 4];
2366 let mut connection_weights = [0.0_f64; 4];
2367 let mut previous = None;
2368
2369 for step in 0..count {
2370 let selected = select_most_tightly_connected(active, added, connection_weights)
2371 .ok_or(PartitionVerificationError::InvalidInput)?;
2372
2373 if step == count - 1 {
2374 let source = previous.ok_or(PartitionVerificationError::InvalidInput)?;
2375 let candidate = MinCut4 {
2376 weight: connection_weights[selected],
2377 partition: normalize_partition(groups[selected]),
2378 };
2379 if candidate.weight < best.weight
2380 || ((candidate.weight - best.weight).abs() <= 1.0e-12
2381 && partition_lex_less(candidate.partition, best.partition))
2382 {
2383 best = candidate;
2384 }
2385 merge_vertices(source, selected, &mut weights, &mut groups, &mut active);
2386 } else {
2387 added[selected] = true;
2388 previous = Some(selected);
2389 for vertex in 0..4 {
2390 if active[vertex] && !added[vertex] {
2391 connection_weights[vertex] += weights[selected][vertex];
2392 }
2393 }
2394 }
2395 }
2396 }
2397
2398 if !best.weight.is_finite() {
2399 return Err(PartitionVerificationError::InvalidInput);
2400 }
2401
2402 Ok(best)
2403 }
2404
2405 fn active_count(active: [bool; 4]) -> usize {
2406 active.iter().filter(|is_active| **is_active).count()
2407 }
2408
2409 fn select_most_tightly_connected(
2410 active: [bool; 4],
2411 added: [bool; 4],
2412 connection_weights: [f64; 4],
2413 ) -> Option<usize> {
2414 let mut selected: Option<usize> = None;
2415 for vertex in 0..4 {
2416 if !active[vertex] || added[vertex] {
2417 continue;
2418 }
2419 if selected.is_none_or(|current| {
2420 connection_weights[vertex] > connection_weights[current]
2421 || ((connection_weights[vertex] - connection_weights[current]).abs() <= 1.0e-12
2422 && vertex < current)
2423 }) {
2424 selected = Some(vertex);
2425 }
2426 }
2427 selected
2428 }
2429
2430 fn merge_vertices(
2431 source: usize,
2432 target: usize,
2433 weights: &mut [[f64; 4]; 4],
2434 groups: &mut [Partition4; 4],
2435 active: &mut [bool; 4],
2436 ) {
2437 for vertex in 0..4 {
2438 groups[source].side[vertex] =
2439 groups[source].side[vertex] || groups[target].side[vertex];
2440 }
2441
2442 for vertex in 0..4 {
2443 if active[vertex] && vertex != source && vertex != target {
2444 weights[source][vertex] += weights[target][vertex];
2445 weights[vertex][source] = weights[source][vertex];
2446 }
2447 }
2448
2449 active[target] = false;
2450 }
2451
2452 fn normalize_partition(partition: Partition4) -> Partition4 {
2453 if partition.side[0] {
2454 let mut side = partition.side;
2455 for entry in &mut side {
2456 *entry = !*entry;
2457 }
2458 Partition4::new(side)
2459 } else {
2460 partition
2461 }
2462 }
2463
2464 fn partition_is_nontrivial(partition: Partition4) -> bool {
2465 let first = partition.side[0];
2466 partition.side.iter().any(|side| *side != first)
2467 }
2468
2469 fn partition_distance(canonical: Partition4, hint: Partition4) -> usize {
2470 let canonical = normalize_partition(canonical);
2471 let hint = normalize_partition(hint);
2472 let direct = hamming_distance(canonical, hint);
2473 let complement = hamming_distance(canonical, complement_partition(hint));
2474 direct.min(complement)
2475 }
2476
2477 fn hamming_distance(left: Partition4, right: Partition4) -> usize {
2478 let mut distance = 0;
2479 for index in 0..4 {
2480 if left.side[index] != right.side[index] {
2481 distance += 1;
2482 }
2483 }
2484 distance
2485 }
2486
2487 fn complement_partition(partition: Partition4) -> Partition4 {
2488 let mut side = partition.side;
2489 for entry in &mut side {
2490 *entry = !*entry;
2491 }
2492 Partition4::new(side)
2493 }
2494
2495 fn partition_lex_less(left: Partition4, right: Partition4) -> bool {
2496 for index in 0..4 {
2497 match (left.side[index], right.side[index]) {
2498 (false, true) => return true,
2499 (true, false) => return false,
2500 _ => {}
2501 }
2502 }
2503 false
2504 }
2505}
2506
2507#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2509pub struct ScaffoldReport {
2510 pub version: &'static str,
2511 pub journey_id: &'static str,
2512 pub precheck_t: PrecheckReason,
2513 pub kappa_hat_t: &'static str,
2514 pub kappa_floor_t: &'static str,
2515 pub alpha_t: &'static str,
2516 pub rho: &'static str,
2517 pub old_wrong_core_guard: &'static str,
2518}
2519
2520pub const fn scaffold_report() -> ScaffoldReport {
2522 ScaffoldReport {
2523 version: CCF_CORE_V1_VERSION,
2524 journey_id: CONTRACT_JOURNEY_ID,
2525 precheck_t: PrecheckReason::ScaffoldOnly,
2526 kappa_hat_t: "computed-from-update-matrices",
2527 kappa_floor_t: "dynamic-floor-computed",
2528 alpha_t: "rho(g_t)",
2529 rho: "gate-coupled-rho",
2530 old_wrong_core_guard: OLD_WRONG_CORE_GUARD,
2531 }
2532}
2533
2534#[cfg(test)]
2535mod tests {
2536 use super::*;
2537
2538 #[test]
2539 fn scaffold_report_pins_story_148_contract_values() {
2540 let report = scaffold_report();
2541
2542 assert_eq!(report.version, "1.0.1");
2543 assert_eq!(report.journey_id, "J-CORE-TEST-MATRIX");
2544 assert_eq!(report.precheck_t, PrecheckReason::ScaffoldOnly);
2545 assert_eq!(
2546 report.old_wrong_core_guard,
2547 "God-spec implementation with no enforceable contracts"
2548 );
2549 }
2550
2551 #[test]
2552 fn scaffold_report_exposes_filed_prov6_vocabulary() {
2553 let report = scaffold_report();
2554
2555 assert_eq!(report.kappa_hat_t, "computed-from-update-matrices");
2556 assert_eq!(report.kappa_floor_t, "dynamic-floor-computed");
2557 assert_eq!(report.alpha_t, "rho(g_t)");
2558 assert_eq!(report.rho, "gate-coupled-rho");
2559 }
2560}