Skip to main content

cu29_runtime/
continuity.rs

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