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
646#[derive(Clone, Copy, Debug, PartialEq, Eq)]
652pub struct ReceiptIndexRecord {
653 pub index_key: [u8; 16],
655 pub layout_id: [u8; 8],
657 pub phase: u8,
659 pub compat_impact: u8,
661 pub migration_flags: u8,
663 pub changed_fields: u64,
665 pub changed_field_count: u16,
667 pub segment_changed_mask: u16,
669 pub changed_segment_count: u8,
671 pub policy_flags: u32,
673 pub validation_bundle_id: u16,
675 pub old_size: u32,
677 pub new_size: u32,
679 pub changed_bytes: u32,
681 pub had_failure: bool,
683 pub failed_error_code: u32,
685 pub failed_invariant_idx: u8,
687 pub failure_stage: u8,
689}
690
691impl DecodedReceipt {
692 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
701 if bytes.len() < RECEIPT_SIZE_LEGACY {
702 return None;
703 }
704 let mut layout_id = [0u8; 8];
705 layout_id.copy_from_slice(&bytes[0..8]);
706 let changed_fields = u64::from_le_bytes([
707 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
708 ]);
709 let changed_bytes = u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
710 let changed_regions = u16::from_le_bytes([bytes[20], bytes[21]]);
711 let old_size = u32::from_le_bytes([bytes[22], bytes[23], bytes[24], bytes[25]]);
712 let new_size = u32::from_le_bytes([bytes[26], bytes[27], bytes[28], bytes[29]]);
713 let invariants_checked = u16::from_le_bytes([bytes[30], bytes[31]]);
714 let flags = bytes[32];
715 let was_resized = flags & (1 << 0) != 0;
716 let invariants_passed = flags & (1 << 1) != 0;
717 let cpi_invoked = flags & (1 << 2) != 0;
718 let committed = flags & (1 << 3) != 0;
719 let had_failure = flags & (1 << 4) != 0;
720
721 let mut before_fingerprint = [0u8; 8];
722 before_fingerprint.copy_from_slice(&bytes[33..41]);
723 let mut after_fingerprint = [0u8; 8];
724 after_fingerprint.copy_from_slice(&bytes[41..49]);
725 let segment_changed_mask = u16::from_le_bytes([bytes[49], bytes[50]]);
726 let policy_flags = u32::from_le_bytes([bytes[51], bytes[52], bytes[53], bytes[54]]);
727 let journal_appends = u16::from_le_bytes([bytes[55], bytes[56]]);
728 let cpi_count = bytes[57];
729 let phase = bytes[58];
730 let validation_bundle_id = u16::from_le_bytes([bytes[59], bytes[60]]);
731 let compat_impact = bytes[61];
732 let migration_flags = bytes[62];
733
734 let (failed_invariant_idx, failed_error_code, failure_stage) =
739 if bytes.len() >= RECEIPT_SIZE {
740 let idx = bytes[63];
741 let code = u32::from_le_bytes([bytes[64], bytes[65], bytes[66], bytes[67]]);
742 let stage = bytes[68];
743 (idx, code, stage)
744 } else {
745 (FAILED_INVARIANT_NONE, 0u32, FailureStage::None as u8)
746 };
747
748 Some(Self {
749 layout_id,
750 changed_fields,
751 changed_bytes,
752 changed_regions,
753 old_size,
754 new_size,
755 invariants_checked,
756 was_resized,
757 invariants_passed,
758 cpi_invoked,
759 committed,
760 before_fingerprint,
761 after_fingerprint,
762 segment_changed_mask,
763 policy_flags,
764 journal_appends,
765 cpi_count,
766 phase,
767 validation_bundle_id,
768 compat_impact,
769 migration_flags,
770 had_failure,
771 failed_error_code,
772 failed_invariant_idx,
773 failure_stage,
774 })
775 }
776
777 #[inline]
782 pub fn index_key(&self) -> [u8; 16] {
783 let mut key = [0u8; 16];
784 key[..8].copy_from_slice(&self.layout_id);
785 key[8..].copy_from_slice(&self.after_fingerprint);
786 key
787 }
788
789 #[inline(always)]
791 pub fn changed_field_count(&self) -> u16 {
792 self.changed_fields.count_ones() as u16
793 }
794
795 #[inline(always)]
797 pub fn changed_segment_count(&self) -> u8 {
798 self.segment_changed_mask.count_ones() as u8
799 }
800
801 #[inline]
803 pub fn index_record(&self) -> ReceiptIndexRecord {
804 ReceiptIndexRecord {
805 index_key: self.index_key(),
806 layout_id: self.layout_id,
807 phase: self.phase,
808 compat_impact: self.compat_impact,
809 migration_flags: self.migration_flags,
810 changed_fields: self.changed_fields,
811 changed_field_count: self.changed_field_count(),
812 segment_changed_mask: self.segment_changed_mask,
813 changed_segment_count: self.changed_segment_count(),
814 policy_flags: self.policy_flags,
815 validation_bundle_id: self.validation_bundle_id,
816 old_size: self.old_size,
817 new_size: self.new_size,
818 changed_bytes: self.changed_bytes,
819 had_failure: self.had_failure,
820 failed_error_code: self.failed_error_code,
821 failed_invariant_idx: self.failed_invariant_idx,
822 failure_stage: self.failure_stage,
823 }
824 }
825
826 #[inline(always)]
828 pub fn failure_stage_enum(&self) -> FailureStage {
829 FailureStage::from_tag(self.failure_stage)
830 }
831
832 #[inline(always)]
834 pub fn has_changes(&self) -> bool {
835 self.changed_bytes > 0 || self.was_resized
836 }
837
838 #[inline(always)]
840 pub fn fingerprint_changed(&self) -> bool {
841 self.before_fingerprint != self.after_fingerprint
842 }
843
844 #[inline(always)]
846 pub fn phase_enum(&self) -> Phase {
847 Phase::from_tag(self.phase)
848 }
849
850 #[inline(always)]
852 pub fn compat_impact_enum(&self) -> CompatImpact {
853 CompatImpact::from_tag(self.compat_impact)
854 }
855
856 pub fn explain(&self) -> ReceiptExplain {
862 let phase = self.phase_enum();
863 let compat = self.compat_impact_enum();
864
865 let mutation_desc = if !self.has_changes() {
866 "No mutations detected"
867 } else if self.was_resized {
868 "Account was resized"
869 } else {
870 "Account data modified in-place"
871 };
872
873 let integrity_desc = if self.had_failure {
874 match self.failure_stage_enum() {
878 FailureStage::Invariant => "INVARIANT FAILED. execution aborted",
879 FailureStage::Validation => "Account validation failed. execution aborted",
880 FailureStage::Handler => "Handler aborted before invariant evaluation",
881 FailureStage::Post => "Failure during receipt commit path",
882 FailureStage::Teardown => "Failure during close/teardown",
883 FailureStage::None => "FAILURE flagged without stage (malformed receipt)",
884 }
885 } else if !self.committed {
886 "Receipt was NOT committed (incomplete)"
887 } else if self.invariants_passed && self.invariants_checked > 0 {
888 "All invariants passed"
889 } else if self.invariants_checked > 0 {
890 "INVARIANT VIOLATION detected"
891 } else {
892 "No invariants checked"
893 };
894
895 let cpi_desc = if self.cpi_invoked {
896 "CPI was invoked during execution"
897 } else {
898 "No CPI calls"
899 };
900
901 ReceiptExplain {
902 phase_name: phase.name(),
903 compat_label: compat.name(),
904 policy_name: "unknown",
905 mutation_summary: mutation_desc,
906 integrity_summary: integrity_desc,
907 cpi_summary: cpi_desc,
908 changed_field_count: self.changed_fields.count_ones() as u16,
909 segment_count: self.segment_changed_mask.count_ones() as u8,
910 fingerprint_changed: self.fingerprint_changed(),
911 segment_role_names: [""; 8],
912 segment_role_count: 0,
913 }
914 }
915}
916
917pub struct ReceiptExplain {
923 pub phase_name: &'static str,
925 pub compat_label: &'static str,
927 pub policy_name: &'static str,
929 pub mutation_summary: &'static str,
931 pub integrity_summary: &'static str,
933 pub cpi_summary: &'static str,
935 pub changed_field_count: u16,
937 pub segment_count: u8,
939 pub fingerprint_changed: bool,
941 pub segment_role_names: [&'static str; 8],
944 pub segment_role_count: u8,
946}
947
948impl ReceiptExplain {
949 #[inline]
956 pub const fn with_policy_name(mut self, name: &'static str) -> Self {
957 self.policy_name = name;
958 self
959 }
960
961 #[inline]
967 pub const fn with_segment_role(mut self, idx: u8, name: &'static str) -> Self {
968 if (idx as usize) < 8 {
969 self.segment_role_names[idx as usize] = name;
970 if idx >= self.segment_role_count {
971 self.segment_role_count = idx + 1;
972 }
973 }
974 self
975 }
976
977 #[inline]
980 pub const fn summary(&self) -> &'static str {
981 if !crate::const_str_eq(self.phase_name, "Update")
985 && !crate::const_str_eq(self.phase_name, "Init")
986 && !crate::const_str_eq(self.phase_name, "Close")
987 && !crate::const_str_eq(self.phase_name, "Migrate")
988 {
989 return "Read-only operation, no state changes";
990 }
991 if crate::const_str_eq(self.phase_name, "Init") {
992 return "Account initialized";
993 }
994 if crate::const_str_eq(self.phase_name, "Close") {
995 return "Account closed";
996 }
997 if crate::const_str_eq(self.phase_name, "Migrate") {
998 if self.fingerprint_changed {
999 return "Migration applied, layout fingerprint updated";
1000 }
1001 return "Migration applied";
1002 }
1003 if !self.fingerprint_changed && self.changed_field_count == 0 {
1005 return "Update executed with no observable state changes";
1006 }
1007 if self.fingerprint_changed && self.changed_field_count > 0 && self.segment_count > 1 {
1008 return "State mutated across multiple segments with fingerprint change";
1009 }
1010 if self.fingerprint_changed && self.changed_field_count > 0 {
1011 return "State mutated with fingerprint change";
1012 }
1013 if self.fingerprint_changed {
1014 return "Fingerprint changed without field-level mutations";
1015 }
1016 if self.changed_field_count > 0 && self.segment_count > 1 {
1017 return "State mutated across multiple segments";
1018 }
1019 if self.changed_field_count > 0 {
1020 return "State mutated";
1021 }
1022 "Update completed"
1023 }
1024}
1025
1026pub struct ReceiptNarrative {
1036 pub fragments: [&'static str; 8],
1038 pub count: u8,
1040 pub risk_level: NarrativeRisk,
1042}
1043
1044#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1046#[repr(u8)]
1047pub enum NarrativeRisk {
1048 None = 0,
1050 Low = 1,
1052 Medium = 2,
1054 High = 3,
1056 Critical = 4,
1058}
1059
1060impl NarrativeRisk {
1061 pub const fn name(self) -> &'static str {
1063 match self {
1064 Self::None => "none",
1065 Self::Low => "low",
1066 Self::Medium => "medium",
1067 Self::High => "high",
1068 Self::Critical => "critical",
1069 }
1070 }
1071}
1072
1073impl core::fmt::Display for NarrativeRisk {
1074 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1075 f.write_str(self.name())
1076 }
1077}
1078
1079impl ReceiptNarrative {
1080 pub fn from_explain(explain: &ReceiptExplain) -> Self {
1085 let mut frags: [&'static str; 8] = [""; 8];
1086 let mut n = 0u8;
1087 let mut risk = NarrativeRisk::None;
1088
1089 let phase_frag = match explain.phase_name {
1091 "Init" => "Account was initialized.",
1092 "Close" => "Account was closed.",
1093 "Migrate" => "Migration was applied to the account.",
1094 "ReadOnly" => "Read-only operation executed.",
1095 _ => "State mutation executed.",
1096 };
1097 if n < 8 {
1098 frags[n as usize] = phase_frag;
1099 n += 1;
1100 }
1101
1102 if explain.changed_field_count > 0 {
1104 risk = NarrativeRisk::Low;
1105 if explain.segment_count > 1 {
1106 if n < 8 {
1107 frags[n as usize] = "Changes span multiple segments.";
1108 n += 1;
1109 }
1110 }
1111 }
1112
1113 if explain.fingerprint_changed {
1115 if n < 8 {
1116 frags[n as usize] = "Layout fingerprint changed.";
1117 n += 1;
1118 }
1119 if risk as u8 == NarrativeRisk::Low as u8 {
1120 risk = NarrativeRisk::Medium;
1121 }
1122 }
1123
1124 match explain.compat_label {
1126 "Append" => {
1127 if n < 8 {
1128 frags[n as usize] = "Append-safe extension applied.";
1129 n += 1;
1130 }
1131 }
1132 "Migration" => {
1133 if n < 8 {
1134 frags[n as usize] = "Migration-level change detected.";
1135 n += 1;
1136 }
1137 risk = NarrativeRisk::High;
1138 }
1139 "Breaking" => {
1140 if n < 8 {
1141 frags[n as usize] = "Breaking compatibility change.";
1142 n += 1;
1143 }
1144 risk = NarrativeRisk::High;
1145 }
1146 _ => {}
1147 }
1148
1149 if !crate::const_str_eq(explain.cpi_summary, "No CPI calls") {
1151 if n < 8 {
1152 frags[n as usize] = "Cross-program invocation occurred.";
1153 n += 1;
1154 }
1155 }
1156
1157 if crate::const_str_eq(explain.integrity_summary, "INVARIANT VIOLATION detected") {
1159 if n < 8 {
1160 frags[n as usize] = "INVARIANT VIOLATION: post-mutation checks failed.";
1161 n += 1;
1162 }
1163 risk = NarrativeRisk::Critical;
1164 }
1165 if crate::const_str_eq(
1166 explain.integrity_summary,
1167 "Receipt was NOT committed (incomplete)",
1168 ) {
1169 if n < 8 {
1170 frags[n as usize] = "Receipt was not committed. Mutation may be incomplete.";
1171 n += 1;
1172 }
1173 risk = NarrativeRisk::Critical;
1174 }
1175
1176 if explain.segment_role_count > 0 {
1178 let mut i = 0u8;
1179 while i < explain.segment_role_count && i < 8 {
1180 let role = explain.segment_role_names[i as usize];
1181 if crate::const_str_eq(role, "audit") || crate::const_str_eq(role, "Audit") {
1182 if n < 8 {
1183 frags[n as usize] = "Audit segment was touched.";
1184 n += 1;
1185 }
1186 }
1187 i += 1;
1188 }
1189 }
1190
1191 if crate::const_str_eq(explain.phase_name, "Migrate") {
1193 if (risk as u8) < NarrativeRisk::High as u8 {
1194 risk = NarrativeRisk::High;
1195 }
1196 }
1197
1198 Self {
1199 fragments: frags,
1200 count: n,
1201 risk_level: risk,
1202 }
1203 }
1204}
1205
1206impl core::fmt::Display for ReceiptNarrative {
1207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1208 let mut i = 0u8;
1209 while i < self.count {
1210 if i > 0 {
1211 write!(f, " ")?;
1212 }
1213 write!(f, "{}", self.fragments[i as usize])?;
1214 i += 1;
1215 }
1216 write!(f, " [risk: {}]", self.risk_level.name())
1217 }
1218}