Timeline & TimelineStatus Lifecycle
tnp beginner 5 min read
ELI5
A timeline is like a numbered ticket roll at a deli counter — every new operation tears off the next ticket. The status label on the roll says whether the counter is open for business (Official/Draft), running an experiment (Experimental), or closed (Archived).
Technical Deep Dive
Class Diagram
classDiagram class TimelineID { +bytes: Vec~u8~ +new(bytes: &[u8]) TimelineID } class TimelineStatus { <<enumeration>> Official Draft Experimental Archived } class Timeline { +id: TimelineID +status: TimelineStatus +nodes: Vec~NodeID~ +created_timestamp_ms: u64 +last_modified_ms: u64 +node_timestamps: HashMap~NodeID, u64~ +add_node(NodeID) +nodes_before_timestamp(u64) Result~Vec~NodeID~~ +state_hash() Vec~u8~ +is_active() bool +age_seconds() u64 +node_count() usize } Timeline "1" --> "1" TimelineID Timeline "1" --> "1" TimelineStatusStatus State Machine
stateDiagram-v2 [*] --> Official : new canonical timeline [*] --> Draft : fork_timeline(..., Draft) [*] --> Experimental : fork_timeline(..., Experimental) Official --> Archived : manual archive Draft --> Archived : archive_threshold_days elapsed (inferred) Experimental --> Archived : archive_threshold_days elapsed (inferred) Archived --> [*]
note right of Official : is_active() = true note right of Draft : is_active() = true note right of Experimental : is_active() = false note right of Archived : is_active() = falseNote: status transitions other than the initial construction are not shown in the current source; the state machine above reflects the intended model described in lib.rs comments and TNPConfig.archive_threshold_days.
add_node Behaviour
pub fn add_node(&mut self, node_id: NodeID) { let now = /* SystemTime::now() as ms */; if !self.nodes.contains(node_id) { self.nodes.push(node_id.clone()); } self.node_timestamps.insert(node_id, now); self.last_modified_ms = now;}Dedup is positional (nodes.contains), so inserting the same NodeID twice only adds one entry to nodes but updates the timestamp map each time.
state_hash
XOR-folds each byte of each NodeID into a 32-byte buffer:
hash[i % 32] ^= byteDeterministic given identical nodes in identical order. Order-dependent: two timelines with the same set of nodes in different insertion order will produce different hashes.
nodes_before_timestamp
Filters nodes by looking up each NodeID in node_timestamps and keeping only entries where ts <= timestamp_ms. Returns Ok([]) for an empty timeline (not an error).
Key Terms
- TimelineStatus → enum with four variants; governs
is_active()output and drives archiving logic - is_active → returns
trueonly forOfficialandDraftstatuses - node_timestamps → secondary map recording insertion wall-clock time per node; used by
nodes_before_timestamp - state_hash → 32-byte XOR fingerprint of the timeline’s ordered node list
Q&A
Q: If a node is inserted twice, how many entries appear in node_timestamps vs nodes?
A: node_timestamps gets a fresh timestamp (overwritten), while nodes still has exactly one entry (the duplicate is rejected by contains).
Q: Does age_seconds use created_timestamp_ms or last_modified_ms?
A: created_timestamp_ms — it measures the timeline’s age since creation, not last activity.
Q: Does nodes_before_timestamp preserve insertion order?
A: Yes — it iterates self.nodes in push order and filters; insertion order is preserved in the output.
Examples
let mut tl = Timeline::new(TimelineID::new(b"exp-branch"), TimelineStatus::Experimental);assert!(!tl.is_active()); // Experimental is NOT active
tl.add_node(NodeID::new(b"op1".to_vec()));let before = tl.nodes_before_timestamp(u64::MAX).unwrap();assert_eq!(before.len(), 1);
let hash = tl.state_hash(); // 32-byte XOR fingerprintassert_eq!(hash.len(), 32);neighbors on the map
- Temporal DAG Data Model tracing why topological sort returns a cycle error
- Timeline Reconstruction implementing a new timeline UI control
- FNP Lamport Clocks & Causal Ordering understanding causal ordering in distributed systems