1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//! Native received-log provenance and continuity, independent of packet transports.
use alloc::vec::Vec;
use bincode::{Decode, Encode};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
pub enum SourceGapReason {
RlcWindowExpired,
SessionEnded,
LateJoin,
/// Missing history skipped when resuming at a verified recovery point.
RecoveryPoint,
}
/// Stored in `UnifiedLogType::StreamContinuity`. Ranges are inclusive.
/// Writers use borrowed byte slices; readers choose owned storage when required.
/// Gaps remain missing history even when a later keyframe permits state replay.
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
pub enum StreamContinuityRecord<B = Vec<u8>> {
/// Canonical semantic manifest bytes bind identity, receiver requirements and schema.
Manifest { record: B },
Gap {
first_id: u64,
last_id: u64,
reason: SourceGapReason,
},
/// Verified keyframe/manifest references, retained as canonical recovery point bytes.
RecoveryPoint { copperlist_id: u64, record: B },
/// Explicit receiver finalization, not a claim about an unobserved sender tail.
Finished { next_copperlist_id: u64 },
}
/// Rejects replay across missing history unless state is restored at this boundary.
/// Call before executing any task or changing the replay clock.
pub fn validate_replay_continuity(
expected: u64,
actual: u64,
keyframe: Option<u64>,
) -> cu29_traits::CuResult<()> {
if keyframe.is_some_and(|boundary| boundary != actual) {
return Err("Replay keyframe does not match the CopperList boundary".into());
}
if actual != expected && keyframe != Some(actual) {
return Err("Replay cannot cross an unhealed CopperList gap without a keyframe".into());
}
Ok(())
}