1use std::borrow::Borrow;
26use std::fmt;
27use std::num::NonZeroU64;
28
29use serde::{Deserialize, Deserializer, Serialize};
30
31use crate::wire_schema::{DescribeWire, WireSchema};
32
33#[must_use]
45pub fn is_topology_token(value: &str) -> bool {
46 !value.is_empty()
47 && value.chars().all(|character| {
48 character.is_ascii_lowercase()
49 || character.is_ascii_digit()
50 || matches!(character, '_' | '-')
51 })
52}
53
54macro_rules! topology_identifier {
55 ($(#[$doc:meta])* $name:ident, $error:ident, $kind:literal) => {
56 $(#[$doc])*
57 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58 pub struct $name(String);
59
60 impl $name {
61 pub fn new(value: impl Into<String>) -> Result<Self, TopologyIdError> {
63 let value = value.into();
64 if is_topology_token(&value) {
65 Ok(Self(value))
66 } else {
67 Err(TopologyIdError::$error(value))
68 }
69 }
70
71 pub const KIND: &'static str = $kind;
73
74 #[must_use]
76 pub fn as_str(&self) -> &str {
77 &self.0
78 }
79 }
80
81 impl fmt::Display for $name {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 formatter.write_str(self.as_str())
84 }
85 }
86
87 impl AsRef<str> for $name {
88 fn as_ref(&self) -> &str {
89 self.as_str()
90 }
91 }
92
93 impl Borrow<str> for $name {
94 fn borrow(&self) -> &str {
95 self.as_str()
96 }
97 }
98
99 impl PartialEq<str> for $name {
100 fn eq(&self, other: &str) -> bool {
101 self.as_str() == other
102 }
103 }
104
105 impl PartialEq<&str> for $name {
106 fn eq(&self, other: &&str) -> bool {
107 self.as_str() == *other
108 }
109 }
110
111 impl std::str::FromStr for $name {
112 type Err = TopologyIdError;
113
114 fn from_str(value: &str) -> Result<Self, Self::Err> {
115 Self::new(value)
116 }
117 }
118
119 impl TryFrom<String> for $name {
120 type Error = TopologyIdError;
121
122 fn try_from(value: String) -> Result<Self, Self::Error> {
123 Self::new(value)
124 }
125 }
126
127 impl From<$name> for String {
128 fn from(value: $name) -> Self {
129 value.0
130 }
131 }
132
133 impl Serialize for $name {
134 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
135 serializer.serialize_str(self.as_str())
136 }
137 }
138
139 impl<'de> Deserialize<'de> for $name {
140 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
141 Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
142 }
143 }
144
145 impl DescribeWire for $name {
146 fn wire_schema() -> WireSchema {
149 WireSchema::opaque(stringify!($name), WireSchema::String)
150 }
151 }
152 };
153}
154
155topology_identifier!(
156 RobotId,
158 Robot,
159 "robot id"
160);
161
162topology_identifier!(
163 ComponentInstanceId,
165 ComponentInstance,
166 "component instance id"
167);
168
169topology_identifier!(
170 ServiceId,
176 Service,
177 "service id"
178);
179
180#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
182pub enum TopologyIdError {
183 #[error("robot id must be a non-empty normalized token, got {0:?}")]
184 Robot(String),
185 #[error("component instance id must be a non-empty normalized token, got {0:?}")]
186 ComponentInstance(String),
187 #[error("service id must be a non-empty normalized token, got {0:?}")]
188 Service(String),
189}
190
191#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
198pub struct ParticipantId(String);
199
200impl ParticipantId {
201 pub fn new(value: impl Into<String>) -> Result<Self, ParticipantIdError> {
203 let value = value.into();
204 if is_topology_token(&value) {
205 Ok(Self(value))
206 } else {
207 Err(ParticipantIdError(value))
208 }
209 }
210
211 #[must_use]
213 pub fn as_str(&self) -> &str {
214 &self.0
215 }
216}
217
218impl std::fmt::Display for ParticipantId {
219 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 formatter.write_str(&self.0)
221 }
222}
223
224impl AsRef<str> for ParticipantId {
225 fn as_ref(&self) -> &str {
226 self.as_str()
227 }
228}
229
230impl std::str::FromStr for ParticipantId {
231 type Err = ParticipantIdError;
232
233 fn from_str(value: &str) -> Result<Self, Self::Err> {
234 Self::new(value)
235 }
236}
237
238impl TryFrom<String> for ParticipantId {
239 type Error = ParticipantIdError;
240
241 fn try_from(value: String) -> Result<Self, Self::Error> {
242 Self::new(value)
243 }
244}
245
246impl From<ParticipantId> for String {
247 fn from(value: ParticipantId) -> Self {
248 value.0
249 }
250}
251
252impl Serialize for ParticipantId {
253 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
254 serializer.serialize_str(self.as_str())
255 }
256}
257
258impl<'de> Deserialize<'de> for ParticipantId {
259 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
260 Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
261 }
262}
263
264impl DescribeWire for ParticipantId {
265 fn wire_schema() -> WireSchema {
268 WireSchema::opaque("ParticipantId", WireSchema::String)
269 }
270}
271
272#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
274#[error("participant id must be a non-empty lowercase token, got '{0}'")]
275pub struct ParticipantIdError(String);
276
277#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
283pub struct ParticipantArtifactId(String);
284
285impl ParticipantArtifactId {
286 pub fn new(value: impl Into<String>) -> Result<Self, ParticipantArtifactIdError> {
288 let value = value.into();
289 if is_topology_token(&value) {
290 Ok(Self(value))
291 } else {
292 Err(ParticipantArtifactIdError(value))
293 }
294 }
295
296 #[must_use]
298 pub fn as_str(&self) -> &str {
299 &self.0
300 }
301}
302
303impl fmt::Display for ParticipantArtifactId {
304 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
305 formatter.write_str(self.as_str())
306 }
307}
308
309impl AsRef<str> for ParticipantArtifactId {
310 fn as_ref(&self) -> &str {
311 self.as_str()
312 }
313}
314
315impl std::str::FromStr for ParticipantArtifactId {
316 type Err = ParticipantArtifactIdError;
317
318 fn from_str(value: &str) -> Result<Self, Self::Err> {
319 Self::new(value)
320 }
321}
322
323impl TryFrom<String> for ParticipantArtifactId {
324 type Error = ParticipantArtifactIdError;
325
326 fn try_from(value: String) -> Result<Self, Self::Error> {
327 Self::new(value)
328 }
329}
330
331impl From<ParticipantArtifactId> for String {
332 fn from(value: ParticipantArtifactId) -> Self {
333 value.0
334 }
335}
336
337impl Serialize for ParticipantArtifactId {
338 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
339 serializer.serialize_str(self.as_str())
340 }
341}
342
343impl<'de> Deserialize<'de> for ParticipantArtifactId {
344 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
345 Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
346 }
347}
348
349impl DescribeWire for ParticipantArtifactId {
350 fn wire_schema() -> WireSchema {
353 WireSchema::opaque("ParticipantArtifactId", WireSchema::String)
354 }
355}
356
357#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
359#[error("participant artifact id must be a non-empty lowercase token, got '{0}'")]
360pub struct ParticipantArtifactIdError(String);
361
362const ZID_BYTES: usize = 16;
364
365const ZID_HEX_LEN: usize = ZID_BYTES * 2;
367
368const CANONICAL_TOP_NIBBLE: u128 = 1 << 124;
374
375fn mint_canonical_value() -> u128 {
381 let mut bytes = [0_u8; ZID_BYTES];
382 #[expect(
383 clippy::expect_used,
384 reason = "a session identity is the root of bus provenance; a host without randomness cannot safely start one"
385 )]
386 getrandom::fill(&mut bytes).expect("the host must provide randomness");
387 let mut value = u128::from_be_bytes(bytes);
388 if value >> 124 == 0 {
389 value |= CANONICAL_TOP_NIBBLE;
390 }
391 value
392}
393
394fn canonical_hex(value: u128) -> String {
395 format!("{value:032x}")
396}
397
398#[derive(Clone, Copy, PartialEq, Eq, Hash)]
411pub struct ExecutionId(u128);
412
413impl ExecutionId {
414 pub const LEN: usize = ZID_HEX_LEN;
416
417 pub fn mint() -> Self {
426 ExecutionId(mint_canonical_value())
427 }
428
429 pub fn parse(value: &str) -> Result<Self, IdentityError> {
438 if value.len() != ZID_HEX_LEN || !value.bytes().all(is_lowercase_hex) {
439 return Err(IdentityError(format!(
440 "an execution id is exactly {ZID_HEX_LEN} lowercase hexadecimal \
441 characters, got '{value}'"
442 )));
443 }
444 let value = u128::from_str_radix(value, 16)
445 .map_err(|error| IdentityError(format!("'{value}' is not hexadecimal: {error}")))?;
446 ExecutionId::try_from(value)
447 }
448}
449
450impl TryFrom<u128> for ExecutionId {
451 type Error = IdentityError;
452
453 fn try_from(value: u128) -> Result<Self, IdentityError> {
454 if value >> 124 == 0 {
455 return Err(IdentityError(format!(
456 "an execution id renders as {ZID_HEX_LEN} characters, so its most \
457 significant nibble is never zero"
458 )));
459 }
460 Ok(ExecutionId(value))
461 }
462}
463
464impl From<ExecutionId> for u128 {
465 fn from(execution: ExecutionId) -> Self {
466 execution.0
467 }
468}
469
470impl fmt::Display for ExecutionId {
471 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
472 formatter.write_str(&canonical_hex(self.0))
473 }
474}
475
476impl fmt::Debug for ExecutionId {
477 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
478 write!(formatter, "ExecutionId({self})")
479 }
480}
481
482impl Serialize for ExecutionId {
483 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
484 serializer.serialize_str(&self.to_string())
485 }
486}
487
488impl<'de> Deserialize<'de> for ExecutionId {
489 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
490 let value = String::deserialize(deserializer)?;
491 ExecutionId::parse(&value).map_err(serde::de::Error::custom)
492 }
493}
494
495impl DescribeWire for ExecutionId {
496 fn wire_schema() -> WireSchema {
499 WireSchema::opaque("ExecutionId", WireSchema::String)
500 }
501}
502
503#[derive(Clone, Copy, PartialEq, Eq, Hash)]
512pub struct ProducerId(u128);
513
514impl ProducerId {
515 pub const LEN: usize = ZID_HEX_LEN;
517
518 pub fn parse(value: &str) -> Result<Self, IdentityError> {
523 if value.len() != ZID_HEX_LEN || !value.bytes().all(is_lowercase_hex) {
524 return Err(IdentityError(format!(
525 "a producer id is exactly {ZID_HEX_LEN} lowercase hexadecimal characters \
526 and is not zero, got '{value}'"
527 )));
528 }
529 let value = u128::from_str_radix(value, 16)
530 .map_err(|error| IdentityError(format!("'{value}' is not hexadecimal: {error}")))?;
531 ProducerId::try_from(value)
532 }
533}
534
535impl TryFrom<u128> for ProducerId {
536 type Error = IdentityError;
537
538 fn try_from(value: u128) -> Result<Self, IdentityError> {
539 if value >> 124 == 0 {
540 return Err(IdentityError(
541 "a producer id must have a non-zero leading nibble".to_string(),
542 ));
543 }
544 Ok(ProducerId(value))
545 }
546}
547
548impl From<ProducerId> for u128 {
549 fn from(producer: ProducerId) -> Self {
550 producer.0
551 }
552}
553
554impl fmt::Display for ProducerId {
555 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
556 formatter.write_str(&canonical_hex(self.0))
557 }
558}
559
560impl fmt::Debug for ProducerId {
561 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
562 write!(formatter, "ProducerId({self})")
563 }
564}
565
566impl Serialize for ProducerId {
567 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
568 serializer.serialize_bytes(&self.0.to_le_bytes())
572 }
573}
574
575impl<'de> Deserialize<'de> for ProducerId {
576 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
577 let bytes = serde_bytes::ByteBuf::deserialize(deserializer)?;
578 let bytes = <[u8; ZID_BYTES]>::try_from(bytes.as_ref()).map_err(|_| {
579 serde::de::Error::custom(format!(
580 "producer id must be {ZID_BYTES} bytes, got {}",
581 bytes.len()
582 ))
583 })?;
584 ProducerId::try_from(u128::from_le_bytes(bytes)).map_err(serde::de::Error::custom)
585 }
586}
587
588impl DescribeWire for ProducerId {
589 fn wire_schema() -> WireSchema {
593 WireSchema::opaque("ProducerId", WireSchema::Bytes)
594 }
595}
596
597#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
604#[serde(transparent)]
605pub struct TimelineId(NonZeroU64);
606
607impl TimelineId {
608 pub fn mint() -> Self {
610 let mut bytes = [0_u8; 8];
611 #[expect(
612 clippy::expect_used,
613 reason = "a timeline names one world history, so two histories separated by a \
614 predictable identity would be indistinguishable to every reader; a host \
615 whose randomness source is unavailable has no correct value to return"
616 )]
617 getrandom::fill(&mut bytes).expect("the host must provide randomness");
618 TimelineId(NonZeroU64::new(u64::from_le_bytes(bytes)).unwrap_or(NonZeroU64::MIN))
621 }
622
623 pub const fn from_raw(value: u64) -> Option<Self> {
625 match NonZeroU64::new(value) {
626 Some(value) => Some(TimelineId(value)),
627 None => None,
628 }
629 }
630
631 pub const fn get(self) -> u64 {
633 self.0.get()
634 }
635}
636
637impl fmt::Display for TimelineId {
638 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
639 write!(formatter, "t{:016x}", self.0.get())
640 }
641}
642
643impl fmt::Debug for TimelineId {
644 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
645 write!(formatter, "TimelineId({self})")
646 }
647}
648
649impl DescribeWire for TimelineId {
650 fn wire_schema() -> WireSchema {
653 WireSchema::opaque("TimelineId", WireSchema::U64)
654 }
655}
656
657#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
664#[error("{0}")]
665pub struct IdentityError(String);
666
667const fn is_lowercase_hex(byte: u8) -> bool {
668 byte.is_ascii_digit() || byte.is_ascii_lowercase() && byte <= b'f'
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674
675 #[test]
676 fn topology_ids_share_one_grammar_and_bare_string_wire_form() {
677 let robot = RobotId::new("warehouse_rover").expect("canonical robot id");
678 let component =
679 ComponentInstanceId::new("front-lidar").expect("canonical component instance");
680 assert_eq!(RobotId::KIND, "robot id");
681 assert_eq!(ComponentInstanceId::KIND, "component instance id");
682 assert_eq!(
683 serde_json::to_string(&robot).unwrap(),
684 "\"warehouse_rover\""
685 );
686 assert_eq!(
687 serde_json::from_str::<ComponentInstanceId>("\"front-lidar\"").unwrap(),
688 component
689 );
690
691 assert_eq!(
692 RobotId::new("Warehouse Rover"),
693 Err(TopologyIdError::Robot("Warehouse Rover".to_string()))
694 );
695 assert_eq!(
696 ComponentInstanceId::new("front/lidar"),
697 Err(TopologyIdError::ComponentInstance(
698 "front/lidar".to_string()
699 ))
700 );
701 }
702
703 #[test]
704 fn participant_ids_are_typed_canonical_tokens() {
705 let id = ParticipantId::new("front_camera").expect("a canonical participant id");
706 assert_eq!(id.as_str(), "front_camera");
707 assert_eq!(id.to_string(), "front_camera");
708 assert_eq!(
709 serde_json::to_string(&id).expect("id serializes"),
710 "\"front_camera\""
711 );
712 assert_eq!(
713 serde_json::from_str::<ParticipantId>("\"front_camera\"").expect("id deserializes"),
714 id
715 );
716 }
717
718 #[test]
719 fn participant_ids_reject_noncanonical_and_path_tokens() {
720 for value in ["", "FrontCamera", "front camera", "../brain", "brain/extra"] {
721 assert!(ParticipantId::new(value).is_err(), "{value:?}");
722 assert!(
723 serde_json::from_str::<ParticipantId>(&format!("\"{value}\"")).is_err(),
724 "{value:?}"
725 );
726 }
727 }
728
729 #[test]
730 fn a_minted_execution_always_renders_at_the_canonical_width() {
731 let first = ExecutionId::mint();
732 let second = ExecutionId::mint();
733 assert_ne!(first, second);
734
735 let rendered = first.to_string();
736 assert_eq!(rendered.len(), ExecutionId::LEN);
737 assert!(!rendered.starts_with('0'));
738 assert!(rendered.bytes().all(is_lowercase_hex));
739 assert!(!rendered.contains('/') && !rendered.contains('*'));
740 assert_eq!(ExecutionId::parse(&rendered), Ok(first));
741 }
742
743 #[test]
744 fn minting_does_not_pin_the_leading_digit_to_half_the_alphabet() {
745 let saw_even_leading_digit = (0..64).any(|_| {
749 let leading = ExecutionId::mint().to_string().as_bytes()[0];
750 let digit = if leading.is_ascii_digit() {
751 leading - b'0'
752 } else {
753 leading - b'a' + 10
754 };
755 digit % 2 == 0
756 });
757 assert!(
758 saw_even_leading_digit,
759 "a minted execution covers the whole nonzero leading-digit range"
760 );
761 }
762
763 #[test]
764 fn only_the_canonical_execution_form_parses() {
765 let canonical = ExecutionId::mint().to_string();
766
767 assert!(ExecutionId::parse("").is_err());
768 assert!(ExecutionId::parse("deadbeef").is_err());
769 assert!(
770 ExecutionId::parse(&canonical.to_uppercase()).is_err(),
771 "uppercase renders back differently, so it is not the same identity"
772 );
773 assert!(
774 ExecutionId::parse(&format!("0{}", &canonical[1..])).is_err(),
775 "a leading zero would render back one character shorter"
776 );
777 assert!(
778 ExecutionId::parse(&format!("{canonical}0")).is_err(),
779 "an over-long run of digits is not a session identity"
780 );
781 assert!(ExecutionId::parse(&"z".repeat(ExecutionId::LEN)).is_err());
782 assert!(
783 ExecutionId::parse(&format!("x{canonical}")).is_err(),
784 "the key root is bare, so there is no prefix to strip"
785 );
786 }
787
788 #[test]
789 fn an_execution_round_trips_through_its_session_identity_value() {
790 let execution = ExecutionId::mint();
791 let value = u128::from(execution);
792 assert_eq!(ExecutionId::try_from(value), Ok(execution));
793 assert_eq!(format!("{value:x}"), execution.to_string());
794 assert!(
795 ExecutionId::try_from(u128::from(execution) >> 4).is_err(),
796 "a value that renders narrower than the canonical width is not an execution"
797 );
798 assert!(ExecutionId::try_from(0).is_err());
799 }
800
801 #[test]
802 fn a_producer_round_trips_in_the_canonical_transport_form() {
803 let minted = ProducerId::try_from((1_u128 << 124) | 0x0123_4567_89ab_cdef).unwrap();
804 assert_eq!(minted.to_string().len(), ProducerId::LEN);
805 assert_eq!(ProducerId::parse(&minted.to_string()), Ok(minted));
806
807 let wide = ProducerId::try_from(u128::MAX).unwrap();
808 assert_eq!(wide.to_string(), "f".repeat(ZID_HEX_LEN));
809 assert_eq!(ProducerId::parse(&wide.to_string()), Ok(wide));
810
811 assert!(ProducerId::try_from(0).is_err());
812 assert!(ProducerId::parse("").is_err());
813 assert!(ProducerId::parse("01").is_err());
814 assert!(ProducerId::parse("AB").is_err());
815 assert!(ProducerId::parse(&"f".repeat(ZID_HEX_LEN + 1)).is_err());
816 assert!(ProducerId::parse(&format!("0{}", "f".repeat(ZID_HEX_LEN - 1))).is_err());
817 }
818
819 #[test]
820 fn producer_ids_round_trip_through_the_wire_encoding() {
821 let producer = ProducerId::try_from((1_u128 << 124) | 0x0123_4567_89ab_cdef).unwrap();
822 let encoded = rmp_serde::to_vec_named(&producer).unwrap();
823 let decoded: ProducerId = rmp_serde::from_slice(&encoded).unwrap();
824 assert_eq!(decoded, producer);
825 assert_ne!(producer, ProducerId::try_from((1_u128 << 124) | 1).unwrap());
826 }
827
828 #[test]
833 fn each_declared_identity_shape_is_the_shape_its_serializer_writes() {
834 fn declared<T: Serialize + DescribeWire>(value: &T) -> WireSchema {
835 let json = serde_json::to_value(value).expect("the identity serializes");
836 let schema = T::wire_schema();
837 assert_eq!(schema.conforms(&json), Ok(()), "{json}");
838 schema
839 }
840
841 assert_eq!(
842 declared(&RobotId::new("rover").expect("canonical robot id")),
843 WireSchema::opaque("RobotId", WireSchema::String)
844 );
845 assert_eq!(
846 declared(&ComponentInstanceId::new("base").expect("canonical component")),
847 WireSchema::opaque("ComponentInstanceId", WireSchema::String)
848 );
849 assert_eq!(
850 declared(&ParticipantId::new("drive").expect("canonical participant")),
851 WireSchema::opaque("ParticipantId", WireSchema::String)
852 );
853 assert_eq!(
854 declared(&ParticipantArtifactId::new("drive").expect("canonical artifact")),
855 WireSchema::opaque("ParticipantArtifactId", WireSchema::String)
856 );
857 assert_eq!(
858 declared(&ExecutionId::mint()),
859 WireSchema::opaque("ExecutionId", WireSchema::String)
860 );
861 assert_eq!(
862 declared(&ProducerId::try_from(1_u128 << 124).expect("canonical producer")),
863 WireSchema::opaque("ProducerId", WireSchema::Bytes)
864 );
865 assert_eq!(
866 declared(&TimelineId::mint()),
867 WireSchema::opaque("TimelineId", WireSchema::U64)
868 );
869 }
870
871 #[test]
872 fn timelines_have_no_zero_value_and_no_generation_order() {
873 assert_eq!(TimelineId::from_raw(0), None);
874 let timeline = TimelineId::mint();
875 assert_eq!(TimelineId::from_raw(timeline.get()), Some(timeline));
876 assert_ne!(timeline, TimelineId::mint());
880 }
881}