1use serde::{Deserialize, Serialize};
9use std::collections::BTreeSet;
10
11pub const ATP_RUNTIME_EVIDENCE_DIAGNOSTIC_SCHEMA: &str =
13 "asupersync.atp.diagnostics.runtime_evidence.v1";
14
15pub const ATP_RUNTIME_EVIDENCE_EXPLANATION_SCHEMA: &str =
17 "asupersync.atp.diagnostics.runtime_explanation.v1";
18
19pub const ATP_NETWORK_TRUTH_PRESSURE_SCHEMA: &str =
21 "asupersync.atp.diagnostics.network_truth_pressure.v1";
22
23pub const ATP_NETWORK_TRUTH_MAX_SIGNALS: usize = 16;
25
26const REDACTED: &str = "<redacted>";
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum AtpRuntimeSignalClass {
32 ProtocolProof,
34 RuntimeProof,
36 AdvisoryRisk,
38 Unavailable,
40}
41
42impl AtpRuntimeSignalClass {
43 #[must_use]
45 pub const fn is_proof(self) -> bool {
46 matches!(self, Self::ProtocolProof | Self::RuntimeProof)
47 }
48
49 #[must_use]
51 pub const fn as_str(self) -> &'static str {
52 match self {
53 Self::ProtocolProof => "protocol_proof",
54 Self::RuntimeProof => "runtime_proof",
55 Self::AdvisoryRisk => "advisory_risk",
56 Self::Unavailable => "unavailable",
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum AtpRuntimeSignalSource {
65 CxRegion,
67 TransferActor,
69 ObligationTracker,
71 CancellationDrain,
73 ReplayCrashpack,
75 SpectralWaitGraph,
77 ConformalAlert,
79 EProcessAlert,
81 EvidenceLedger,
83}
84
85impl AtpRuntimeSignalSource {
86 #[must_use]
88 pub const fn as_str(self) -> &'static str {
89 match self {
90 Self::CxRegion => "cx_region",
91 Self::TransferActor => "transfer_actor",
92 Self::ObligationTracker => "obligation_tracker",
93 Self::CancellationDrain => "cancellation_drain",
94 Self::ReplayCrashpack => "replay_crashpack",
95 Self::SpectralWaitGraph => "spectral_wait_graph",
96 Self::ConformalAlert => "conformal_alert",
97 Self::EProcessAlert => "eprocess_alert",
98 Self::EvidenceLedger => "evidence_ledger",
99 }
100 }
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum AtpNetworkTruthSignalKind {
107 MeasuredFact,
109 InferredPressure,
111 AdvisoryEstimate,
113 Unsupported,
115}
116
117impl AtpNetworkTruthSignalKind {
118 #[must_use]
120 pub const fn as_str(self) -> &'static str {
121 match self {
122 Self::MeasuredFact => "measured_fact",
123 Self::InferredPressure => "inferred_pressure",
124 Self::AdvisoryEstimate => "advisory_estimate",
125 Self::Unsupported => "unsupported",
126 }
127 }
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
132#[serde(rename_all = "snake_case")]
133pub enum AtpNetworkTruthMetric {
134 Rtt,
136 AckDelay,
138 Loss,
140 Pto,
142 CongestionWindow,
144 BytesInFlight,
146 SocketPressure,
148 DiskLag,
150 CpuEncodeDecodePressure,
152 RepairRoi,
154 RelayDirectDelta,
156 PathMigration,
158 CancellationPressure,
160 ObligationDrainLatency,
162}
163
164impl AtpNetworkTruthMetric {
165 pub const ALL: [Self; 14] = [
167 Self::Rtt,
168 Self::AckDelay,
169 Self::Loss,
170 Self::Pto,
171 Self::CongestionWindow,
172 Self::BytesInFlight,
173 Self::SocketPressure,
174 Self::DiskLag,
175 Self::CpuEncodeDecodePressure,
176 Self::RepairRoi,
177 Self::RelayDirectDelta,
178 Self::PathMigration,
179 Self::CancellationPressure,
180 Self::ObligationDrainLatency,
181 ];
182
183 #[must_use]
185 pub const fn as_str(self) -> &'static str {
186 match self {
187 Self::Rtt => "rtt",
188 Self::AckDelay => "ack_delay",
189 Self::Loss => "loss",
190 Self::Pto => "pto",
191 Self::CongestionWindow => "congestion_window",
192 Self::BytesInFlight => "bytes_in_flight",
193 Self::SocketPressure => "socket_pressure",
194 Self::DiskLag => "disk_lag",
195 Self::CpuEncodeDecodePressure => "cpu_encode_decode_pressure",
196 Self::RepairRoi => "repair_roi",
197 Self::RelayDirectDelta => "relay_direct_delta",
198 Self::PathMigration => "path_migration",
199 Self::CancellationPressure => "cancellation_pressure",
200 Self::ObligationDrainLatency => "obligation_drain_latency",
201 }
202 }
203}
204
205#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
207#[serde(rename_all = "snake_case")]
208pub enum AtpNetworkPressureLevel {
209 Nominal,
211 Watch,
213 Degraded,
215 Critical,
217}
218
219impl AtpNetworkPressureLevel {
220 #[must_use]
222 pub const fn as_str(self) -> &'static str {
223 match self {
224 Self::Nominal => "nominal",
225 Self::Watch => "watch",
226 Self::Degraded => "degraded",
227 Self::Critical => "critical",
228 }
229 }
230}
231
232#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
234pub struct AtpNetworkTruthSignal {
235 pub metric: AtpNetworkTruthMetric,
237 pub kind: AtpNetworkTruthSignalKind,
239 pub value: Option<i64>,
241 pub unit: String,
243 pub pressure_score_ppm: u32,
245 pub source_ref: Option<String>,
247 pub detail: Option<String>,
249}
250
251impl AtpNetworkTruthSignal {
252 #[must_use]
254 pub fn new(
255 metric: AtpNetworkTruthMetric,
256 kind: AtpNetworkTruthSignalKind,
257 value: Option<i64>,
258 unit: impl Into<String>,
259 pressure_score_ppm: u32,
260 source_ref: Option<String>,
261 detail: Option<String>,
262 ) -> Self {
263 Self {
264 metric,
265 kind,
266 value,
267 unit: unit.into(),
268 pressure_score_ppm: pressure_score_ppm.min(1_000_000),
269 source_ref,
270 detail,
271 }
272 }
273
274 #[must_use]
276 pub fn measured(
277 metric: AtpNetworkTruthMetric,
278 value: i64,
279 unit: impl Into<String>,
280 pressure_score_ppm: u32,
281 source_ref: impl Into<String>,
282 ) -> Self {
283 Self::new(
284 metric,
285 AtpNetworkTruthSignalKind::MeasuredFact,
286 Some(value),
287 unit,
288 pressure_score_ppm,
289 Some(source_ref.into()),
290 None,
291 )
292 }
293
294 #[must_use]
296 pub fn inferred(
297 metric: AtpNetworkTruthMetric,
298 value: i64,
299 unit: impl Into<String>,
300 pressure_score_ppm: u32,
301 source_ref: impl Into<String>,
302 detail: impl Into<String>,
303 ) -> Self {
304 Self::new(
305 metric,
306 AtpNetworkTruthSignalKind::InferredPressure,
307 Some(value),
308 unit,
309 pressure_score_ppm,
310 Some(source_ref.into()),
311 Some(detail.into()),
312 )
313 }
314
315 #[must_use]
317 pub fn advisory(
318 metric: AtpNetworkTruthMetric,
319 value: i64,
320 unit: impl Into<String>,
321 pressure_score_ppm: u32,
322 source_ref: impl Into<String>,
323 detail: impl Into<String>,
324 ) -> Self {
325 Self::new(
326 metric,
327 AtpNetworkTruthSignalKind::AdvisoryEstimate,
328 Some(value),
329 unit,
330 pressure_score_ppm,
331 Some(source_ref.into()),
332 Some(detail.into()),
333 )
334 }
335
336 #[must_use]
338 pub fn unsupported(metric: AtpNetworkTruthMetric, reason: impl Into<String>) -> Self {
339 Self::new(
340 metric,
341 AtpNetworkTruthSignalKind::Unsupported,
342 None,
343 "unsupported",
344 0,
345 None,
346 Some(reason.into()),
347 )
348 }
349}
350
351#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
353pub struct AtpNetworkTruthPressureModel {
354 pub schema_version: String,
356 pub path_id: Option<String>,
358 pub deterministic_timestamp_micros: u64,
360 pub signals: Vec<AtpNetworkTruthSignal>,
362 pub redaction_policy: String,
364}
365
366impl AtpNetworkTruthPressureModel {
367 #[must_use]
369 pub fn new(deterministic_timestamp_micros: u64) -> Self {
370 Self {
371 schema_version: ATP_NETWORK_TRUTH_PRESSURE_SCHEMA.to_string(),
372 path_id: None,
373 deterministic_timestamp_micros,
374 signals: Vec::new(),
375 redaction_policy: "atp-network-truth-default".to_string(),
376 }
377 }
378
379 pub fn add_signal(&mut self, signal: AtpNetworkTruthSignal) -> bool {
381 if self.signals.len() >= ATP_NETWORK_TRUTH_MAX_SIGNALS {
382 return false;
383 }
384 self.signals.push(signal);
385 true
386 }
387
388 #[must_use]
390 pub fn overall_pressure_score_ppm(&self) -> u32 {
391 self.signals
392 .iter()
393 .map(|signal| signal.pressure_score_ppm)
394 .max()
395 .unwrap_or(0)
396 }
397
398 #[must_use]
400 pub fn pressure_level(&self) -> AtpNetworkPressureLevel {
401 pressure_level_for_score(self.overall_pressure_score_ppm())
402 }
403
404 #[must_use]
406 pub fn pressure_level_with_hysteresis(
407 &self,
408 previous: Option<AtpNetworkPressureLevel>,
409 ) -> AtpNetworkPressureLevel {
410 let candidate = self.pressure_level();
411 let Some(previous) = previous else {
412 return candidate;
413 };
414 if previous > candidate && self.overall_pressure_score_ppm() >= retention_floor(previous) {
415 previous
416 } else {
417 candidate
418 }
419 }
420
421 #[must_use]
423 pub fn unsupported_metrics(&self) -> Vec<AtpNetworkTruthMetric> {
424 let mut unsupported = self
425 .signals
426 .iter()
427 .filter(|signal| signal.kind == AtpNetworkTruthSignalKind::Unsupported)
428 .map(|signal| signal.metric)
429 .collect::<Vec<_>>();
430 unsupported.sort();
431 unsupported.dedup();
432 unsupported
433 }
434
435 #[must_use]
437 pub fn missing_required_metrics(&self) -> Vec<AtpNetworkTruthMetric> {
438 let present = self
439 .signals
440 .iter()
441 .map(|signal| signal.metric)
442 .collect::<BTreeSet<_>>();
443 AtpNetworkTruthMetric::ALL
444 .into_iter()
445 .filter(|metric| !present.contains(metric))
446 .collect()
447 }
448
449 #[must_use]
451 pub fn summary_line(&self) -> String {
452 format!(
453 "network truth pressure {} score_ppm={} signals={} unsupported={} missing={}",
454 self.pressure_level().as_str(),
455 self.overall_pressure_score_ppm(),
456 self.signals.len(),
457 self.unsupported_metrics().len(),
458 self.missing_required_metrics().len()
459 )
460 }
461
462 #[must_use]
464 pub fn redacted_for_user(&self) -> Self {
465 let mut redacted = self.clone();
466 redacted.path_id = redacted.path_id.as_deref().map(redact_token);
467 for signal in &mut redacted.signals {
468 signal.source_ref = signal.source_ref.as_deref().map(redact_token);
469 signal.detail = signal.detail.as_deref().map(|_| REDACTED.to_string());
470 }
471 redacted.redaction_policy = format!("{}+user_safe", self.redaction_policy);
472 redacted
473 }
474}
475
476fn pressure_level_for_score(score_ppm: u32) -> AtpNetworkPressureLevel {
477 match score_ppm {
478 0..=200_000 => AtpNetworkPressureLevel::Nominal,
479 200_001..=600_000 => AtpNetworkPressureLevel::Watch,
480 600_001..=850_000 => AtpNetworkPressureLevel::Degraded,
481 _ => AtpNetworkPressureLevel::Critical,
482 }
483}
484
485fn retention_floor(level: AtpNetworkPressureLevel) -> u32 {
486 match level {
487 AtpNetworkPressureLevel::Nominal => 0,
488 AtpNetworkPressureLevel::Watch => 150_000,
489 AtpNetworkPressureLevel::Degraded => 500_000,
490 AtpNetworkPressureLevel::Critical => 750_000,
491 }
492}
493
494#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
496pub struct AtpObligationEvidenceCounts {
497 pub created: u64,
499 pub committed: u64,
501 pub aborted: u64,
503 pub outstanding: u64,
505 pub futurelock_waiters: u64,
507}
508
509impl AtpObligationEvidenceCounts {
510 #[must_use]
512 pub const fn proves_no_obligation_leak(&self) -> bool {
513 self.outstanding == 0 && self.created == self.committed.saturating_add(self.aborted)
514 }
515}
516
517#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
519pub struct AtpCancellationDrainEvidence {
520 pub requested: bool,
522 pub drained: bool,
524 pub losers_drained: u64,
526 pub drain_certificate_id: Option<String>,
528 pub reason: String,
530}
531
532impl AtpCancellationDrainEvidence {
533 #[must_use]
535 pub const fn proves_drain(&self) -> bool {
536 !self.requested || self.drained
537 }
538}
539
540#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
542pub struct AtpFinalizerEvidence {
543 pub ran: bool,
545 pub completed: bool,
547 pub outcome: String,
549}
550
551#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
553pub struct AtpReplayEvidencePointer {
554 pub trace_id: Option<String>,
556 pub crashpack_id: Option<String>,
558 pub replay_command: String,
560 pub redaction_policy: String,
562}
563
564#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
566pub struct AtpRuntimeEvidenceSignal {
567 pub signal_id: String,
569 pub source: AtpRuntimeSignalSource,
571 pub class: AtpRuntimeSignalClass,
573 pub summary: String,
575 pub evidence_ref: Option<String>,
577 pub unavailable_reason: Option<String>,
579}
580
581impl AtpRuntimeEvidenceSignal {
582 #[must_use]
584 pub fn proof(
585 signal_id: impl Into<String>,
586 source: AtpRuntimeSignalSource,
587 summary: impl Into<String>,
588 evidence_ref: impl Into<String>,
589 ) -> Self {
590 Self {
591 signal_id: signal_id.into(),
592 source,
593 class: AtpRuntimeSignalClass::RuntimeProof,
594 summary: summary.into(),
595 evidence_ref: Some(evidence_ref.into()),
596 unavailable_reason: None,
597 }
598 }
599
600 #[must_use]
602 pub fn advisory(
603 signal_id: impl Into<String>,
604 source: AtpRuntimeSignalSource,
605 summary: impl Into<String>,
606 evidence_ref: impl Into<String>,
607 ) -> Self {
608 Self {
609 signal_id: signal_id.into(),
610 source,
611 class: AtpRuntimeSignalClass::AdvisoryRisk,
612 summary: summary.into(),
613 evidence_ref: Some(evidence_ref.into()),
614 unavailable_reason: None,
615 }
616 }
617
618 #[must_use]
620 pub fn unavailable(
621 signal_id: impl Into<String>,
622 source: AtpRuntimeSignalSource,
623 reason: impl Into<String>,
624 ) -> Self {
625 let reason = reason.into();
626 Self {
627 signal_id: signal_id.into(),
628 source,
629 class: AtpRuntimeSignalClass::Unavailable,
630 summary: format!("{} unavailable: {reason}", source.as_str()),
631 evidence_ref: None,
632 unavailable_reason: Some(reason),
633 }
634 }
635}
636
637#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
639pub struct AtpRuntimeEvidenceEnvelope {
640 pub schema_version: String,
642 pub transfer_id: String,
644 pub cx_region_id: Option<String>,
646 pub transfer_actor_id: Option<String>,
648 pub obligation_counts: AtpObligationEvidenceCounts,
650 pub cancellation: Option<AtpCancellationDrainEvidence>,
652 pub finalizer: Option<AtpFinalizerEvidence>,
654 pub replay: Option<AtpReplayEvidencePointer>,
656 pub signals: Vec<AtpRuntimeEvidenceSignal>,
658 pub network_truth: Option<AtpNetworkTruthPressureModel>,
660 pub redaction_policy: String,
662}
663
664impl AtpRuntimeEvidenceEnvelope {
665 #[must_use]
667 pub fn new(transfer_id: impl Into<String>) -> Self {
668 Self {
669 schema_version: ATP_RUNTIME_EVIDENCE_DIAGNOSTIC_SCHEMA.to_string(),
670 transfer_id: transfer_id.into(),
671 cx_region_id: None,
672 transfer_actor_id: None,
673 obligation_counts: AtpObligationEvidenceCounts::default(),
674 cancellation: None,
675 finalizer: None,
676 replay: None,
677 signals: Vec::new(),
678 network_truth: None,
679 redaction_policy: "atp-runtime-evidence-default".to_string(),
680 }
681 }
682
683 #[must_use]
685 pub fn redacted_for_user(&self) -> Self {
686 let mut redacted = self.clone();
687 redacted.transfer_id = redact_token(&redacted.transfer_id);
688 redacted.cx_region_id = redacted.cx_region_id.as_deref().map(redact_token);
689 redacted.transfer_actor_id = redacted.transfer_actor_id.as_deref().map(redact_token);
690 if let Some(replay) = &mut redacted.replay {
691 replay.trace_id = replay.trace_id.as_deref().map(redact_token);
692 replay.crashpack_id = replay.crashpack_id.as_deref().map(redact_token);
693 replay.replay_command = REDACTED.to_string();
694 }
695 for signal in &mut redacted.signals {
696 signal.evidence_ref = signal.evidence_ref.as_deref().map(redact_token);
697 }
698 redacted.network_truth = redacted
699 .network_truth
700 .as_ref()
701 .map(AtpNetworkTruthPressureModel::redacted_for_user);
702 redacted.redaction_policy = format!("{}+user_safe", self.redaction_policy);
703 redacted
704 }
705}
706
707#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
709pub struct AtpRuntimeDiagnosticDocument {
710 pub schema_version: String,
712 pub transfer_id: String,
714 pub headline: String,
716 pub human_summary: String,
718 pub proof_claims: Vec<String>,
720 pub advisory_risks: Vec<String>,
722 pub network_truth_explanations: Vec<String>,
724 pub unavailable_signals: Vec<String>,
726 pub evidence: AtpRuntimeEvidenceEnvelope,
728}
729
730#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
732pub struct AtpRuntimeEvidenceBridge;
733
734impl AtpRuntimeEvidenceBridge {
735 #[must_use]
737 pub fn explain(envelope: AtpRuntimeEvidenceEnvelope) -> AtpRuntimeDiagnosticDocument {
738 let mut proof_claims = Vec::new();
739 let mut advisory_risks = Vec::new();
740 let mut network_truth_explanations = Vec::new();
741 let mut unavailable_signals = Vec::new();
742
743 if let Some(region_id) = &envelope.cx_region_id {
744 proof_claims.push(format!("transfer is owned by Cx region {region_id}"));
745 }
746 if let Some(actor_id) = &envelope.transfer_actor_id {
747 proof_claims.push(format!("transfer actor {actor_id} is recorded"));
748 }
749 if envelope.obligation_counts.proves_no_obligation_leak() {
750 proof_claims.push(format!(
751 "obligation accounting closed cleanly: created={} committed={} aborted={} outstanding=0",
752 envelope.obligation_counts.created,
753 envelope.obligation_counts.committed,
754 envelope.obligation_counts.aborted
755 ));
756 } else {
757 advisory_risks.push(format!(
758 "obligation accounting is incomplete: outstanding={} futurelock_waiters={}",
759 envelope.obligation_counts.outstanding,
760 envelope.obligation_counts.futurelock_waiters
761 ));
762 }
763 if let Some(cancellation) = &envelope.cancellation {
764 if cancellation.proves_drain() {
765 proof_claims.push(format!(
766 "cancellation drain completed: requested={} losers_drained={}",
767 cancellation.requested, cancellation.losers_drained
768 ));
769 } else {
770 advisory_risks.push(format!(
771 "cancellation requested but drain is not proven: {}",
772 cancellation.reason
773 ));
774 }
775 }
776 if let Some(finalizer) = &envelope.finalizer {
777 if finalizer.ran && finalizer.completed {
778 proof_claims.push(format!("finalizers completed: {}", finalizer.outcome));
779 } else {
780 advisory_risks.push(format!("finalizers incomplete: {}", finalizer.outcome));
781 }
782 }
783 if let Some(replay) = &envelope.replay {
784 proof_claims.push(format!(
785 "deterministic replay pointer is present: command={}",
786 replay.replay_command
787 ));
788 }
789
790 for signal in &envelope.signals {
791 let line = format!(
792 "{} [{}]: {}",
793 signal.source.as_str(),
794 signal.class.as_str(),
795 signal.summary
796 );
797 match signal.class {
798 class if class.is_proof() => proof_claims.push(line),
799 AtpRuntimeSignalClass::AdvisoryRisk => advisory_risks.push(line),
800 AtpRuntimeSignalClass::Unavailable => {
801 unavailable_signals.push(signal.unavailable_reason.clone().unwrap_or(line));
802 }
803 AtpRuntimeSignalClass::ProtocolProof | AtpRuntimeSignalClass::RuntimeProof => {
804 unreachable!("proof classes handled by is_proof")
805 }
806 }
807 }
808 if let Some(network_truth) = &envelope.network_truth {
809 network_truth_explanations.push(network_truth.summary_line());
810 for metric in network_truth.unsupported_metrics() {
811 unavailable_signals.push(format!("network truth {} unsupported", metric.as_str()));
812 }
813 for metric in network_truth.missing_required_metrics() {
814 unavailable_signals.push(format!("network truth {} missing", metric.as_str()));
815 }
816 if network_truth.pressure_level() >= AtpNetworkPressureLevel::Watch {
817 advisory_risks.push(format!(
818 "network truth pressure is {} (score_ppm={})",
819 network_truth.pressure_level().as_str(),
820 network_truth.overall_pressure_score_ppm()
821 ));
822 }
823 }
824
825 proof_claims.sort();
826 proof_claims.dedup();
827 advisory_risks.sort();
828 advisory_risks.dedup();
829 network_truth_explanations.sort();
830 network_truth_explanations.dedup();
831 unavailable_signals.sort();
832 unavailable_signals.dedup();
833
834 let headline = if advisory_risks.is_empty() && unavailable_signals.is_empty() {
835 "ATP runtime evidence supports the transfer explanation".to_string()
836 } else {
837 "ATP runtime evidence includes advisory or unavailable signals".to_string()
838 };
839 let human_summary = render_summary(
840 &headline,
841 &proof_claims,
842 &advisory_risks,
843 &network_truth_explanations,
844 );
845
846 AtpRuntimeDiagnosticDocument {
847 schema_version: ATP_RUNTIME_EVIDENCE_EXPLANATION_SCHEMA.to_string(),
848 transfer_id: envelope.transfer_id.clone(),
849 headline,
850 human_summary,
851 proof_claims,
852 advisory_risks,
853 network_truth_explanations,
854 unavailable_signals,
855 evidence: envelope,
856 }
857 }
858
859 #[must_use]
861 pub fn explain_for_user(envelope: &AtpRuntimeEvidenceEnvelope) -> AtpRuntimeDiagnosticDocument {
862 Self::explain(envelope.redacted_for_user())
863 }
864}
865
866fn render_summary(
867 headline: &str,
868 proof_claims: &[String],
869 advisory_risks: &[String],
870 network_truth_explanations: &[String],
871) -> String {
872 let mut parts = vec![headline.to_string()];
873 if let Some(first_proof) = proof_claims.first() {
874 parts.push(format!("Proof: {first_proof}."));
875 }
876 if let Some(first_risk) = advisory_risks.first() {
877 parts.push(format!("Advisory: {first_risk}."));
878 }
879 if let Some(first_network_truth) = network_truth_explanations.first() {
880 parts.push(format!("Network truth: {first_network_truth}."));
881 }
882 parts.join(" ")
883}
884
885fn redact_token(value: &str) -> String {
886 if value.is_empty() {
887 return REDACTED.to_string();
888 }
889 let suffix = value
890 .chars()
891 .rev()
892 .take(6)
893 .collect::<Vec<_>>()
894 .into_iter()
895 .rev()
896 .collect::<String>();
897 format!("{REDACTED}:{suffix}")
898}
899
900#[cfg(test)]
901mod tests {
902 use super::*;
903
904 fn sample_envelope() -> AtpRuntimeEvidenceEnvelope {
905 let mut envelope = AtpRuntimeEvidenceEnvelope::new("transfer-abcdef123456");
906 envelope.cx_region_id = Some("region-root-42".to_string());
907 envelope.transfer_actor_id = Some("actor-send-7".to_string());
908 envelope.obligation_counts = AtpObligationEvidenceCounts {
909 created: 3,
910 committed: 2,
911 aborted: 1,
912 outstanding: 0,
913 futurelock_waiters: 0,
914 };
915 envelope.cancellation = Some(AtpCancellationDrainEvidence {
916 requested: true,
917 drained: true,
918 losers_drained: 2,
919 drain_certificate_id: Some("drain-cert-1".to_string()),
920 reason: "operator_cancel".to_string(),
921 });
922 envelope.finalizer = Some(AtpFinalizerEvidence {
923 ran: true,
924 completed: true,
925 outcome: "all_finalizers_joined".to_string(),
926 });
927 envelope.replay = Some(AtpReplayEvidencePointer {
928 trace_id: Some("trace-123456789".to_string()),
929 crashpack_id: Some("crashpack-abcdef".to_string()),
930 replay_command: "asupersync lab replay trace-123456789 --redacted".to_string(),
931 redaction_policy: "atp-runtime-evidence-default".to_string(),
932 });
933 envelope.signals.push(AtpRuntimeEvidenceSignal::proof(
934 "decision-ledger",
935 AtpRuntimeSignalSource::EvidenceLedger,
936 "decision ledger row binds path choice to transfer evidence",
937 "evidence-ledger-row-1",
938 ));
939 envelope.signals.push(AtpRuntimeEvidenceSignal::advisory(
940 "spectral-risk",
941 AtpRuntimeSignalSource::SpectralWaitGraph,
942 "spectral wait graph is degraded but not a correctness proof",
943 "spectral-report-1",
944 ));
945 envelope.signals.push(AtpRuntimeEvidenceSignal::unavailable(
946 "conformal-risk",
947 AtpRuntimeSignalSource::ConformalAlert,
948 "insufficient calibration window",
949 ));
950 envelope
951 }
952
953 #[test]
954 fn bridge_keeps_proof_claims_separate_from_advisory_risk() {
955 let doc = AtpRuntimeEvidenceBridge::explain(sample_envelope());
956 assert_eq!(doc.schema_version, ATP_RUNTIME_EVIDENCE_EXPLANATION_SCHEMA);
957 assert!(
958 doc.proof_claims
959 .iter()
960 .any(|claim| claim.contains("obligation accounting closed cleanly"))
961 );
962 assert!(
963 doc.proof_claims
964 .iter()
965 .any(|claim| claim.contains("cancellation drain completed"))
966 );
967 assert!(
968 doc.advisory_risks
969 .iter()
970 .any(|risk| risk.contains("spectral_wait_graph [advisory_risk]"))
971 );
972 assert!(
973 doc.proof_claims
974 .iter()
975 .all(|claim| !claim.contains("advisory_risk")),
976 "advisory signals must not be upgraded to proof claims"
977 );
978 assert_eq!(
979 doc.unavailable_signals,
980 vec!["insufficient calibration window".to_string()]
981 );
982 }
983
984 #[test]
985 fn envelope_round_trips_through_json() {
986 let envelope = sample_envelope();
987 let encoded = serde_json::to_string(&envelope).expect("serialize envelope");
988 let decoded: AtpRuntimeEvidenceEnvelope =
989 serde_json::from_str(&encoded).expect("deserialize envelope");
990 assert_eq!(decoded, envelope);
991 assert_eq!(
992 decoded.schema_version,
993 ATP_RUNTIME_EVIDENCE_DIAGNOSTIC_SCHEMA
994 );
995 }
996
997 #[test]
998 fn user_explanation_redacts_correlation_ids_and_replay_command() {
999 let envelope = sample_envelope();
1000 let doc = AtpRuntimeEvidenceBridge::explain_for_user(&envelope);
1001 assert!(doc.transfer_id.starts_with(REDACTED));
1002 assert_eq!(
1003 doc.evidence
1004 .replay
1005 .as_ref()
1006 .expect("replay pointer")
1007 .replay_command,
1008 REDACTED
1009 );
1010 assert!(
1011 doc.human_summary.len() < 400,
1012 "human explanation must stay concise"
1013 );
1014 }
1015
1016 #[test]
1017 fn incomplete_obligations_downgrade_to_advisory_risk() {
1018 let mut envelope = sample_envelope();
1019 envelope.obligation_counts.outstanding = 1;
1020 let doc = AtpRuntimeEvidenceBridge::explain(envelope);
1021 assert!(
1022 doc.advisory_risks
1023 .iter()
1024 .any(|risk| risk.contains("obligation accounting is incomplete"))
1025 );
1026 assert!(
1027 doc.proof_claims
1028 .iter()
1029 .all(|claim| !claim.contains("obligation accounting closed cleanly"))
1030 );
1031 }
1032
1033 #[test]
1034 fn network_truth_pressure_keeps_measurements_out_of_proof_claims() {
1035 let mut envelope = sample_envelope();
1036 let mut network_truth = AtpNetworkTruthPressureModel::new(42_000);
1037 network_truth.path_id = Some("path-local-correlation-123456".to_string());
1038 assert!(network_truth.add_signal(AtpNetworkTruthSignal::measured(
1039 AtpNetworkTruthMetric::Rtt,
1040 25_000,
1041 "micros",
1042 120_000,
1043 "pathlog-rtt-123456",
1044 )));
1045 assert!(network_truth.add_signal(AtpNetworkTruthSignal::inferred(
1046 AtpNetworkTruthMetric::Loss,
1047 22,
1048 "permille",
1049 720_000,
1050 "pathlog-loss-123456",
1051 "loss inferred from ACK gap and retransmit evidence",
1052 )));
1053 assert!(network_truth.add_signal(AtpNetworkTruthSignal::unsupported(
1054 AtpNetworkTruthMetric::CongestionWindow,
1055 "platform did not expose cwnd",
1056 )));
1057 envelope.network_truth = Some(network_truth);
1058
1059 let doc = AtpRuntimeEvidenceBridge::explain(envelope);
1060 assert!(
1061 doc.network_truth_explanations
1062 .iter()
1063 .any(|line| line.contains("network truth pressure degraded"))
1064 );
1065 assert!(
1066 doc.advisory_risks
1067 .iter()
1068 .any(|risk| risk.contains("network truth pressure is degraded"))
1069 );
1070 assert!(
1071 doc.proof_claims
1072 .iter()
1073 .all(|claim| !claim.contains("network truth")),
1074 "measured network facts must not become proof claims"
1075 );
1076 assert!(
1077 doc.unavailable_signals
1078 .iter()
1079 .any(|signal| signal == "network truth congestion_window unsupported")
1080 );
1081 }
1082
1083 #[test]
1084 fn network_truth_model_bounds_cardinality_and_redacts_user_view() {
1085 let mut model = AtpNetworkTruthPressureModel::new(7);
1086 model.path_id = Some("path-sensitive-abcdef".to_string());
1087 for idx in 0..ATP_NETWORK_TRUTH_MAX_SIGNALS {
1088 assert!(model.add_signal(AtpNetworkTruthSignal::advisory(
1089 AtpNetworkTruthMetric::RelayDirectDelta,
1090 i64::try_from(idx).expect("idx fits"),
1091 "ppm",
1092 250_000,
1093 format!("relay-delta-sensitive-{idx}"),
1094 "relay path id contained a local endpoint",
1095 )));
1096 }
1097 assert!(!model.add_signal(AtpNetworkTruthSignal::measured(
1098 AtpNetworkTruthMetric::Rtt,
1099 1,
1100 "micros",
1101 1,
1102 "overflow",
1103 )));
1104
1105 let redacted = model.redacted_for_user();
1106 assert_eq!(redacted.signals.len(), ATP_NETWORK_TRUTH_MAX_SIGNALS);
1107 assert!(
1108 redacted
1109 .path_id
1110 .as_deref()
1111 .unwrap_or_default()
1112 .starts_with(REDACTED)
1113 );
1114 assert!(
1115 redacted
1116 .signals
1117 .iter()
1118 .all(|signal| signal.detail.as_deref() == Some(REDACTED))
1119 );
1120 }
1121
1122 #[test]
1123 fn network_truth_pressure_hysteresis_prevents_noisy_downshift() {
1124 let mut model = AtpNetworkTruthPressureModel::new(99);
1125 assert!(model.add_signal(AtpNetworkTruthSignal::inferred(
1126 AtpNetworkTruthMetric::DiskLag,
1127 510_000,
1128 "ppm",
1129 510_000,
1130 "disk-lag-row",
1131 "disk write lag remained near degraded threshold",
1132 )));
1133 assert_eq!(model.pressure_level(), AtpNetworkPressureLevel::Watch);
1134 assert_eq!(
1135 model.pressure_level_with_hysteresis(Some(AtpNetworkPressureLevel::Degraded)),
1136 AtpNetworkPressureLevel::Degraded
1137 );
1138 }
1139}