1use crate::diff::StateSnapshot;
39
40pub const MAX_RECEIPT_FIELDS: usize = 64;
42
43#[inline]
49fn fast_fingerprint(data: &[u8]) -> [u8; 8] {
50 let len = data.len() as u32;
51 let mut lo = 0x243f_6a88_u32 ^ len;
52 let mut hi = 0x85a3_08d3_u32 ^ len.rotate_left(16);
53
54 let mut i = 0usize;
55 while i + 8 <= data.len() {
56 let a = u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
57 let b = u32::from_le_bytes([data[i + 4], data[i + 5], data[i + 6], data[i + 7]]);
58 lo = (lo.rotate_left(5) ^ a).wrapping_add(0x9e37_79b9);
59 hi = (hi.rotate_left(7) ^ b).wrapping_add(0x7f4a_7c15);
60 i += 8;
61 }
62
63 if i < data.len() {
64 let mut tail = [0u8; 8];
65 let mut tail_i = 0usize;
66 while i < data.len() {
67 tail[tail_i] = data[i];
68 tail_i += 1;
69 i += 1;
70 }
71 let a = u32::from_le_bytes([tail[0], tail[1], tail[2], tail[3]]);
72 let b = u32::from_le_bytes([tail[4], tail[5], tail[6], tail[7]]);
73 lo = lo.rotate_left(5) ^ a;
74 hi = hi.rotate_left(7) ^ b;
75 }
76
77 let mixed_lo = lo ^ hi.rotate_left(13);
78 let mixed_hi = hi ^ lo.rotate_left(11) ^ len;
79 let mut out = [0u8; 8];
80 out[0..4].copy_from_slice(&mixed_lo.to_le_bytes());
81 out[4..8].copy_from_slice(&mixed_hi.to_le_bytes());
82 if out == [0u8; 8] {
83 out[0] = 1;
84 }
85 out
86}
87
88pub struct StateReceipt<const SNAP_SIZE: usize> {
92 pub layout_id: [u8; 8],
94 snapshot: StateSnapshot<SNAP_SIZE>,
96 pub changed_fields: u64,
98 pub changed_bytes: usize,
100 pub changed_regions: usize,
102 pub was_resized: bool,
104 pub old_size: usize,
106 pub new_size: usize,
108 pub invariants_passed: bool,
110 pub invariants_checked: u16,
112 pub cpi_invoked: bool,
114 committed: bool,
116 pub had_failure: bool,
124 pub failed_error_code: u32,
128 pub failed_invariant_idx: u8,
133 pub failure_stage: u8,
135 pub before_fingerprint: [u8; 8],
137 pub after_fingerprint: [u8; 8],
139 pub segment_changed_mask: u16,
141 pub policy_flags: u32,
143 pub journal_appends: u16,
145 pub cpi_count: u8,
147 pub phase: u8,
149 pub validation_bundle_id: u16,
151 pub compat_impact: u8,
153 pub migration_flags: u8,
155}
156
157#[repr(u8)]
165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166pub enum FailureStage {
167 None = 0,
169 Validation = 1,
171 Handler = 2,
173 Invariant = 3,
175 Post = 4,
177 Teardown = 5,
179}
180
181impl FailureStage {
182 #[inline(always)]
183 pub fn from_tag(tag: u8) -> Self {
184 match tag {
185 1 => Self::Validation,
186 2 => Self::Handler,
187 3 => Self::Invariant,
188 4 => Self::Post,
189 5 => Self::Teardown,
190 _ => Self::None,
191 }
192 }
193
194 #[inline(always)]
195 pub fn name(self) -> &'static str {
196 match self {
197 Self::None => "none",
198 Self::Validation => "validation",
199 Self::Handler => "handler",
200 Self::Invariant => "invariant",
201 Self::Post => "post",
202 Self::Teardown => "teardown",
203 }
204 }
205}
206
207pub const FAILED_INVARIANT_NONE: u8 = 0xFF;
210
211#[repr(u8)]
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum Phase {
215 Update = 0,
217 Init = 1,
219 Close = 2,
221 Migrate = 3,
223 ReadOnly = 4,
225}
226
227impl Phase {
228 #[inline(always)]
230 pub fn from_tag(tag: u8) -> Self {
231 match tag {
232 1 => Self::Init,
233 2 => Self::Close,
234 3 => Self::Migrate,
235 4 => Self::ReadOnly,
236 _ => Self::Update,
237 }
238 }
239
240 #[inline(always)]
242 pub fn name(self) -> &'static str {
243 match self {
244 Self::Update => "Update",
245 Self::Init => "Init",
246 Self::Close => "Close",
247 Self::Migrate => "Migrate",
248 Self::ReadOnly => "ReadOnly",
249 }
250 }
251}
252
253#[repr(u8)]
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256pub enum CompatImpact {
257 None = 0,
259 Append = 1,
261 Migration = 2,
263 Breaking = 3,
265}
266
267impl CompatImpact {
268 #[inline(always)]
270 pub fn from_tag(tag: u8) -> Self {
271 match tag {
272 1 => Self::Append,
273 2 => Self::Migration,
274 3 => Self::Breaking,
275 _ => Self::None,
276 }
277 }
278
279 #[inline(always)]
281 pub fn name(self) -> &'static str {
282 match self {
283 Self::None => "none",
284 Self::Append => "append",
285 Self::Migration => "migration",
286 Self::Breaking => "breaking",
287 }
288 }
289}
290
291impl<const SNAP_SIZE: usize> StateReceipt<SNAP_SIZE> {
292 #[inline]
296 pub fn begin(layout_id: &[u8; 8], data: &[u8]) -> Self {
297 Self {
298 layout_id: *layout_id,
299 snapshot: StateSnapshot::capture(data),
300 changed_fields: 0,
301 changed_bytes: 0,
302 changed_regions: 0,
303 was_resized: false,
304 old_size: data.len(),
305 new_size: data.len(),
306 invariants_passed: false,
307 invariants_checked: 0,
308 cpi_invoked: false,
309 committed: false,
310 had_failure: false,
311 failed_error_code: 0,
312 failed_invariant_idx: FAILED_INVARIANT_NONE,
313 failure_stage: FailureStage::None as u8,
314 before_fingerprint: fast_fingerprint(data),
315 after_fingerprint: [0; 8],
316 segment_changed_mask: 0,
317 policy_flags: 0,
318 journal_appends: 0,
319 cpi_count: 0,
320 phase: Phase::Update as u8,
321 validation_bundle_id: 0,
322 compat_impact: CompatImpact::None as u8,
323 migration_flags: 0,
324 }
325 }
326
327 #[inline]
331 pub fn commit(&mut self, current_data: &[u8]) {
332 let diff = self.snapshot.diff(current_data);
333 self.changed_bytes = diff.changed_byte_count();
334 self.was_resized = diff.was_resized();
335 self.new_size = current_data.len();
336
337 let regions = diff.changed_regions::<16>();
338 self.changed_regions = regions.len();
339
340 self.after_fingerprint = fast_fingerprint(current_data);
341 self.committed = true;
342 }
343
344 #[inline]
349 pub fn commit_with_fields(&mut self, current_data: &[u8], fields: &[(&str, usize, usize)]) {
350 self.commit(current_data);
351 self.changed_fields =
352 crate::diff::field_diff_mask(self.snapshot.data(), current_data, fields);
353 }
354
355 #[inline]
360 pub fn commit_with_segments(&mut self, current_data: &[u8], segments: &[(usize, usize)]) {
361 self.commit(current_data);
362 let snap_data = self.snapshot.data();
363 let mut mask: u16 = 0;
364 let compare_len = if snap_data.len() < current_data.len() {
365 snap_data.len()
366 } else {
367 current_data.len()
368 };
369 for (i, &(offset, size)) in segments.iter().enumerate() {
370 if i >= 16 {
371 break;
372 }
373 let end = offset + size;
374 if end <= compare_len {
375 if snap_data[offset..end] != current_data[offset..end] {
376 mask |= 1 << i;
377 }
378 } else if offset < compare_len {
379 mask |= 1 << i;
381 } else if self.was_resized {
382 mask |= 1 << i;
384 }
385 }
386 self.segment_changed_mask = mask;
387 }
388
389 #[inline(always)]
391 pub fn set_invariants(&mut self, passed: bool, checked: u16) {
392 self.invariants_passed = passed;
393 self.invariants_checked = checked;
394 }
395
396 #[inline(always)]
398 pub fn set_invariants_passed(&mut self, passed: bool) {
399 self.invariants_passed = passed;
400 }
401
402 #[inline(always)]
404 pub fn set_cpi_invoked(&mut self, invoked: bool) {
405 self.cpi_invoked = invoked;
406 }
407
408 #[inline(always)]
410 pub fn set_cpi_count(&mut self, count: u8) {
411 self.cpi_count = count;
412 self.cpi_invoked = count > 0;
413 }
414
415 #[inline(always)]
419 pub fn set_policy_flags(&mut self, flags: u32) {
420 self.policy_flags = flags;
421 }
422
423 #[inline(always)]
425 pub fn set_journal_appends(&mut self, count: u16) {
426 self.journal_appends = count;
427 }
428
429 #[inline(always)]
431 pub fn set_phase(&mut self, phase: Phase) {
432 self.phase = phase as u8;
433 }
434
435 #[inline(always)]
437 pub fn set_validation_bundle_id(&mut self, id: u16) {
438 self.validation_bundle_id = id;
439 }
440
441 #[inline(always)]
443 pub fn set_compat_impact(&mut self, impact: CompatImpact) {
444 self.compat_impact = impact as u8;
445 }
446
447 #[inline(always)]
449 pub fn set_migration_flags(&mut self, flags: u8) {
450 self.migration_flags = flags;
451 }
452
453 #[inline]
467 pub fn set_failure(&mut self, code: u32, invariant_idx: u8, stage: FailureStage) {
468 self.had_failure = true;
469 self.failed_error_code = code;
470 self.failed_invariant_idx = invariant_idx;
471 self.failure_stage = stage as u8;
472 self.invariants_passed = false;
476 }
477
478 #[inline]
480 pub fn set_invariant_failure(&mut self, code: u32, invariant_idx: u8) {
481 self.set_failure(code, invariant_idx, FailureStage::Invariant);
482 }
483
484 #[inline(always)]
486 pub fn is_committed(&self) -> bool {
487 self.committed
488 }
489
490 #[inline(always)]
492 pub fn has_changes(&self) -> bool {
493 self.changed_bytes > 0 || self.was_resized
494 }
495
496 #[inline(always)]
498 pub fn fingerprint_changed(&self) -> bool {
499 self.before_fingerprint != self.after_fingerprint
500 }
501
502 #[inline]
540 pub fn to_bytes(&self) -> [u8; RECEIPT_SIZE] {
541 let mut out = [0u8; RECEIPT_SIZE];
542 out[0..8].copy_from_slice(&self.layout_id);
544 out[8..16].copy_from_slice(&self.changed_fields.to_le_bytes());
546 out[16..20].copy_from_slice(&(self.changed_bytes as u32).to_le_bytes());
548 out[20..22].copy_from_slice(&(self.changed_regions as u16).to_le_bytes());
550 out[22..26].copy_from_slice(&(self.old_size as u32).to_le_bytes());
552 out[26..30].copy_from_slice(&(self.new_size as u32).to_le_bytes());
554 out[30..32].copy_from_slice(&self.invariants_checked.to_le_bytes());
556 let mut flags: u8 = 0;
558 if self.was_resized {
559 flags |= 1 << 0;
560 }
561 if self.invariants_passed {
562 flags |= 1 << 1;
563 }
564 if self.cpi_invoked {
565 flags |= 1 << 2;
566 }
567 if self.committed {
568 flags |= 1 << 3;
569 }
570 if self.had_failure {
571 flags |= 1 << 4;
572 }
573 out[32] = flags;
574 out[33..41].copy_from_slice(&self.before_fingerprint);
576 out[41..49].copy_from_slice(&self.after_fingerprint);
578 out[49..51].copy_from_slice(&self.segment_changed_mask.to_le_bytes());
580 out[51..55].copy_from_slice(&self.policy_flags.to_le_bytes());
582 out[55..57].copy_from_slice(&self.journal_appends.to_le_bytes());
584 out[57] = self.cpi_count;
586 out[58] = self.phase;
588 out[59..61].copy_from_slice(&self.validation_bundle_id.to_le_bytes());
590 out[61] = self.compat_impact;
592 out[62] = self.migration_flags;
594 out[63] = self.failed_invariant_idx;
596 out[64..68].copy_from_slice(&self.failed_error_code.to_le_bytes());
598 out[68] = self.failure_stage;
600 out
602 }
603}
604
605pub const RECEIPT_SIZE: usize = 72;
607
608pub const RECEIPT_SIZE_LEGACY: usize = 64;
612
613pub struct DecodedReceipt {
615 pub layout_id: [u8; 8],
616 pub changed_fields: u64,
617 pub changed_bytes: u32,
618 pub changed_regions: u16,
619 pub old_size: u32,
620 pub new_size: u32,
621 pub invariants_checked: u16,
622 pub was_resized: bool,
623 pub invariants_passed: bool,
624 pub cpi_invoked: bool,
625 pub committed: bool,
626 pub before_fingerprint: [u8; 8],
627 pub after_fingerprint: [u8; 8],
628 pub segment_changed_mask: u16,
629 pub policy_flags: u32,
630 pub journal_appends: u16,
631 pub cpi_count: u8,
632 pub phase: u8,
633 pub validation_bundle_id: u16,
634 pub compat_impact: u8,
635 pub migration_flags: u8,
636 pub had_failure: bool,
638 pub failed_error_code: u32,
640 pub failed_invariant_idx: u8,
642 pub failure_stage: u8,
644}
645
646impl DecodedReceipt {
647 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
656 if bytes.len() < RECEIPT_SIZE_LEGACY {
657 return None;
658 }
659 let mut layout_id = [0u8; 8];
660 layout_id.copy_from_slice(&bytes[0..8]);
661 let changed_fields = u64::from_le_bytes([
662 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
663 ]);
664 let changed_bytes = u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
665 let changed_regions = u16::from_le_bytes([bytes[20], bytes[21]]);
666 let old_size = u32::from_le_bytes([bytes[22], bytes[23], bytes[24], bytes[25]]);
667 let new_size = u32::from_le_bytes([bytes[26], bytes[27], bytes[28], bytes[29]]);
668 let invariants_checked = u16::from_le_bytes([bytes[30], bytes[31]]);
669 let flags = bytes[32];
670 let was_resized = flags & (1 << 0) != 0;
671 let invariants_passed = flags & (1 << 1) != 0;
672 let cpi_invoked = flags & (1 << 2) != 0;
673 let committed = flags & (1 << 3) != 0;
674 let had_failure = flags & (1 << 4) != 0;
675
676 let mut before_fingerprint = [0u8; 8];
677 before_fingerprint.copy_from_slice(&bytes[33..41]);
678 let mut after_fingerprint = [0u8; 8];
679 after_fingerprint.copy_from_slice(&bytes[41..49]);
680 let segment_changed_mask = u16::from_le_bytes([bytes[49], bytes[50]]);
681 let policy_flags = u32::from_le_bytes([bytes[51], bytes[52], bytes[53], bytes[54]]);
682 let journal_appends = u16::from_le_bytes([bytes[55], bytes[56]]);
683 let cpi_count = bytes[57];
684 let phase = bytes[58];
685 let validation_bundle_id = u16::from_le_bytes([bytes[59], bytes[60]]);
686 let compat_impact = bytes[61];
687 let migration_flags = bytes[62];
688
689 let (failed_invariant_idx, failed_error_code, failure_stage) =
694 if bytes.len() >= RECEIPT_SIZE {
695 let idx = bytes[63];
696 let code = u32::from_le_bytes([bytes[64], bytes[65], bytes[66], bytes[67]]);
697 let stage = bytes[68];
698 (idx, code, stage)
699 } else {
700 (FAILED_INVARIANT_NONE, 0u32, FailureStage::None as u8)
701 };
702
703 Some(Self {
704 layout_id,
705 changed_fields,
706 changed_bytes,
707 changed_regions,
708 old_size,
709 new_size,
710 invariants_checked,
711 was_resized,
712 invariants_passed,
713 cpi_invoked,
714 committed,
715 before_fingerprint,
716 after_fingerprint,
717 segment_changed_mask,
718 policy_flags,
719 journal_appends,
720 cpi_count,
721 phase,
722 validation_bundle_id,
723 compat_impact,
724 migration_flags,
725 had_failure,
726 failed_error_code,
727 failed_invariant_idx,
728 failure_stage,
729 })
730 }
731
732 #[inline(always)]
734 pub fn failure_stage_enum(&self) -> FailureStage {
735 FailureStage::from_tag(self.failure_stage)
736 }
737
738 #[inline(always)]
740 pub fn has_changes(&self) -> bool {
741 self.changed_bytes > 0 || self.was_resized
742 }
743
744 #[inline(always)]
746 pub fn fingerprint_changed(&self) -> bool {
747 self.before_fingerprint != self.after_fingerprint
748 }
749
750 #[inline(always)]
752 pub fn phase_enum(&self) -> Phase {
753 Phase::from_tag(self.phase)
754 }
755
756 #[inline(always)]
758 pub fn compat_impact_enum(&self) -> CompatImpact {
759 CompatImpact::from_tag(self.compat_impact)
760 }
761
762 pub fn explain(&self) -> ReceiptExplain {
768 let phase = self.phase_enum();
769 let compat = self.compat_impact_enum();
770
771 let mutation_desc = if !self.has_changes() {
772 "No mutations detected"
773 } else if self.was_resized {
774 "Account was resized"
775 } else {
776 "Account data modified in-place"
777 };
778
779 let integrity_desc = if self.had_failure {
780 match self.failure_stage_enum() {
784 FailureStage::Invariant => "INVARIANT FAILED. execution aborted",
785 FailureStage::Validation => "Account validation failed. execution aborted",
786 FailureStage::Handler => "Handler aborted before invariant evaluation",
787 FailureStage::Post => "Failure during receipt commit path",
788 FailureStage::Teardown => "Failure during close/teardown",
789 FailureStage::None => "FAILURE flagged without stage (malformed receipt)",
790 }
791 } else if !self.committed {
792 "Receipt was NOT committed (incomplete)"
793 } else if self.invariants_passed && self.invariants_checked > 0 {
794 "All invariants passed"
795 } else if self.invariants_checked > 0 {
796 "INVARIANT VIOLATION detected"
797 } else {
798 "No invariants checked"
799 };
800
801 let cpi_desc = if self.cpi_invoked {
802 "CPI was invoked during execution"
803 } else {
804 "No CPI calls"
805 };
806
807 ReceiptExplain {
808 phase_name: phase.name(),
809 compat_label: compat.name(),
810 policy_name: "unknown",
811 mutation_summary: mutation_desc,
812 integrity_summary: integrity_desc,
813 cpi_summary: cpi_desc,
814 changed_field_count: self.changed_fields.count_ones() as u16,
815 segment_count: self.segment_changed_mask.count_ones() as u8,
816 fingerprint_changed: self.fingerprint_changed(),
817 segment_role_names: [""; 8],
818 segment_role_count: 0,
819 }
820 }
821}
822
823pub struct ReceiptExplain {
829 pub phase_name: &'static str,
831 pub compat_label: &'static str,
833 pub policy_name: &'static str,
835 pub mutation_summary: &'static str,
837 pub integrity_summary: &'static str,
839 pub cpi_summary: &'static str,
841 pub changed_field_count: u16,
843 pub segment_count: u8,
845 pub fingerprint_changed: bool,
847 pub segment_role_names: [&'static str; 8],
850 pub segment_role_count: u8,
852}
853
854impl ReceiptExplain {
855 #[inline]
862 pub const fn with_policy_name(mut self, name: &'static str) -> Self {
863 self.policy_name = name;
864 self
865 }
866
867 #[inline]
873 pub const fn with_segment_role(mut self, idx: u8, name: &'static str) -> Self {
874 if (idx as usize) < 8 {
875 self.segment_role_names[idx as usize] = name;
876 if idx >= self.segment_role_count {
877 self.segment_role_count = idx + 1;
878 }
879 }
880 self
881 }
882
883 #[inline]
886 pub const fn summary(&self) -> &'static str {
887 if !crate::const_str_eq(self.phase_name, "Update")
891 && !crate::const_str_eq(self.phase_name, "Init")
892 && !crate::const_str_eq(self.phase_name, "Close")
893 && !crate::const_str_eq(self.phase_name, "Migrate")
894 {
895 return "Read-only operation, no state changes";
896 }
897 if crate::const_str_eq(self.phase_name, "Init") {
898 return "Account initialized";
899 }
900 if crate::const_str_eq(self.phase_name, "Close") {
901 return "Account closed";
902 }
903 if crate::const_str_eq(self.phase_name, "Migrate") {
904 if self.fingerprint_changed {
905 return "Migration applied, layout fingerprint updated";
906 }
907 return "Migration applied";
908 }
909 if !self.fingerprint_changed && self.changed_field_count == 0 {
911 return "Update executed with no observable state changes";
912 }
913 if self.fingerprint_changed && self.changed_field_count > 0 && self.segment_count > 1 {
914 return "State mutated across multiple segments with fingerprint change";
915 }
916 if self.fingerprint_changed && self.changed_field_count > 0 {
917 return "State mutated with fingerprint change";
918 }
919 if self.fingerprint_changed {
920 return "Fingerprint changed without field-level mutations";
921 }
922 if self.changed_field_count > 0 && self.segment_count > 1 {
923 return "State mutated across multiple segments";
924 }
925 if self.changed_field_count > 0 {
926 return "State mutated";
927 }
928 "Update completed"
929 }
930}
931
932pub struct ReceiptNarrative {
942 pub fragments: [&'static str; 8],
944 pub count: u8,
946 pub risk_level: NarrativeRisk,
948}
949
950#[derive(Clone, Copy, Debug, PartialEq, Eq)]
952#[repr(u8)]
953pub enum NarrativeRisk {
954 None = 0,
956 Low = 1,
958 Medium = 2,
960 High = 3,
962 Critical = 4,
964}
965
966impl NarrativeRisk {
967 pub const fn name(self) -> &'static str {
969 match self {
970 Self::None => "none",
971 Self::Low => "low",
972 Self::Medium => "medium",
973 Self::High => "high",
974 Self::Critical => "critical",
975 }
976 }
977}
978
979impl core::fmt::Display for NarrativeRisk {
980 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
981 f.write_str(self.name())
982 }
983}
984
985impl ReceiptNarrative {
986 pub fn from_explain(explain: &ReceiptExplain) -> Self {
991 let mut frags: [&'static str; 8] = [""; 8];
992 let mut n = 0u8;
993 let mut risk = NarrativeRisk::None;
994
995 let phase_frag = match explain.phase_name {
997 "Init" => "Account was initialized.",
998 "Close" => "Account was closed.",
999 "Migrate" => "Migration was applied to the account.",
1000 "ReadOnly" => "Read-only operation executed.",
1001 _ => "State mutation executed.",
1002 };
1003 if n < 8 {
1004 frags[n as usize] = phase_frag;
1005 n += 1;
1006 }
1007
1008 if explain.changed_field_count > 0 {
1010 risk = NarrativeRisk::Low;
1011 if explain.segment_count > 1 {
1012 if n < 8 {
1013 frags[n as usize] = "Changes span multiple segments.";
1014 n += 1;
1015 }
1016 }
1017 }
1018
1019 if explain.fingerprint_changed {
1021 if n < 8 {
1022 frags[n as usize] = "Layout fingerprint changed.";
1023 n += 1;
1024 }
1025 if risk as u8 == NarrativeRisk::Low as u8 {
1026 risk = NarrativeRisk::Medium;
1027 }
1028 }
1029
1030 match explain.compat_label {
1032 "Append" => {
1033 if n < 8 {
1034 frags[n as usize] = "Append-safe extension applied.";
1035 n += 1;
1036 }
1037 }
1038 "Migration" => {
1039 if n < 8 {
1040 frags[n as usize] = "Migration-level change detected.";
1041 n += 1;
1042 }
1043 risk = NarrativeRisk::High;
1044 }
1045 "Breaking" => {
1046 if n < 8 {
1047 frags[n as usize] = "Breaking compatibility change.";
1048 n += 1;
1049 }
1050 risk = NarrativeRisk::High;
1051 }
1052 _ => {}
1053 }
1054
1055 if !crate::const_str_eq(explain.cpi_summary, "No CPI calls") {
1057 if n < 8 {
1058 frags[n as usize] = "Cross-program invocation occurred.";
1059 n += 1;
1060 }
1061 }
1062
1063 if crate::const_str_eq(explain.integrity_summary, "INVARIANT VIOLATION detected") {
1065 if n < 8 {
1066 frags[n as usize] = "INVARIANT VIOLATION: post-mutation checks failed.";
1067 n += 1;
1068 }
1069 risk = NarrativeRisk::Critical;
1070 }
1071 if crate::const_str_eq(
1072 explain.integrity_summary,
1073 "Receipt was NOT committed (incomplete)",
1074 ) {
1075 if n < 8 {
1076 frags[n as usize] = "Receipt was not committed. Mutation may be incomplete.";
1077 n += 1;
1078 }
1079 risk = NarrativeRisk::Critical;
1080 }
1081
1082 if explain.segment_role_count > 0 {
1084 let mut i = 0u8;
1085 while i < explain.segment_role_count && i < 8 {
1086 let role = explain.segment_role_names[i as usize];
1087 if crate::const_str_eq(role, "audit") || crate::const_str_eq(role, "Audit") {
1088 if n < 8 {
1089 frags[n as usize] = "Audit segment was touched.";
1090 n += 1;
1091 }
1092 }
1093 i += 1;
1094 }
1095 }
1096
1097 if crate::const_str_eq(explain.phase_name, "Migrate") {
1099 if (risk as u8) < NarrativeRisk::High as u8 {
1100 risk = NarrativeRisk::High;
1101 }
1102 }
1103
1104 Self {
1105 fragments: frags,
1106 count: n,
1107 risk_level: risk,
1108 }
1109 }
1110}
1111
1112impl core::fmt::Display for ReceiptNarrative {
1113 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1114 let mut i = 0u8;
1115 while i < self.count {
1116 if i > 0 {
1117 write!(f, " ")?;
1118 }
1119 write!(f, "{}", self.fragments[i as usize])?;
1120 i += 1;
1121 }
1122 write!(f, " [risk: {}]", self.risk_level.name())
1123 }
1124}