use crate::model::facts::{KeyDescription, KeyShape, Registration};
use crate::model::timeline::{HlcStamp, TimelineRow};
use crate::report::{Provenance, TraceRelation, TraceRow};
use zenkey::slice::ProcedureDecl;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceTarget {
pub origin: String,
pub producer: Option<String>,
pub chain_chunk: String,
pub registry_loaded: bool,
}
impl TraceTarget {
pub fn relation_of(&self, desc: &KeyDescription) -> Option<TraceRelation> {
let KeyShape::V1(facts) = &desc.facts.shape else {
return None;
};
if facts.origin != self.origin {
return None;
}
if !self.registry_loaded {
return Some(TraceRelation::SameOriginRegistryNotLoaded);
}
let in_chain = matches!(desc.facts.registration, Registration::Registered(_))
&& facts.producer == self.producer
&& facts.subject.first().map(String::as_str) == Some(self.chain_chunk.as_str());
Some(if in_chain {
TraceRelation::DeclaredChain
} else {
TraceRelation::SameOriginUndeclared
})
}
}
pub fn idiom_of(decl: Option<&ProcedureDecl>) -> String {
decl.and_then(|d| d.kind.as_ref())
.map(|k| k.token().to_string())
.unwrap_or_else(|| "undeclared".to_string())
}
pub fn hlc_delta_ms(sample_ntp64: u64, reference_ntp64: u64) -> i64 {
let diff = i128::from(sample_ntp64) - i128::from(reference_ntp64);
i64::try_from(diff * 1000 / (1i128 << 32)).unwrap_or(if diff < 0 { i64::MIN } else { i64::MAX })
}
pub fn stamped_by(stamp: &HlcStamp) -> String {
match stamp.provenance {
Provenance::SelfStamped => "self".to_string(),
Provenance::Foreign => format!("foreign:{}", stamp.stamper),
Provenance::Unattributable => format!("unattributable:{}", stamp.stamper),
}
}
pub fn trace_row(
row: &TimelineRow,
relation: TraceRelation,
reply_ntp64: Option<u64>,
break_before: Option<u64>,
) -> TraceRow {
TraceRow {
key: row.key.clone(),
relation,
arrival_delta_ms: row.t_us as f64 / 1_000.0,
hlc: row.hlc.as_ref().map(HlcStamp::to_wire),
hlc_delta_ms: match (&row.hlc, reply_ntp64) {
(Some(h), Some(r)) => Some(hlc_delta_ms(h.ntp64, r)),
_ => None,
},
stamped_by: row.hlc.as_ref().map(stamped_by),
kind: row.kind,
payload_bytes: row.payload_bytes,
break_before,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::facts::describe_key;
use crate::model::registry::SliceSet;
use crate::report::{LaneId, RowKind};
use zenkey::slice::{RegistrySlice, SubjectDecl};
const ORIGIN: &str = "h-3fa9c2d41b7e";
fn slices() -> SliceSet {
let mut slice = RegistrySlice::new("1.0", "t", "demo");
slice.subjects = vec![
SubjectDecl::new("artifact/{kind}", zenkey::Class::State),
SubjectDecl::new("artifact/{ulid}", zenkey::Class::Events),
];
let mut proc_decl = ProcedureDecl::new("artifact/request");
proc_decl.kind = Some(zenkey::Declared::Other("long-running".into()));
slice.procedures = vec![proc_decl];
let mut other = RegistrySlice::new("1.0", "t", "other");
other.subjects = vec![SubjectDecl::new("artifact/copy", zenkey::Class::State)];
SliceSet::from_slices(vec![slice, other])
}
fn target(registry_loaded: bool) -> TraceTarget {
TraceTarget {
origin: ORIGIN.into(),
producer: Some("demo".into()),
chain_chunk: "artifact".into(),
registry_loaded,
}
}
#[test]
fn the_chain_is_the_registered_subject_sharing_the_first_chunk() {
let s = slices();
let rel = |key: &str| target(true).relation_of(&describe_key("", key, Some(&s)));
assert_eq!(
rel(&format!("v1/{ORIGIN}/state/demo/artifact/pcap")),
Some(TraceRelation::DeclaredChain)
);
assert_eq!(
rel(&format!("v1/{ORIGIN}/events/demo/artifact/01H")),
Some(TraceRelation::DeclaredChain)
);
assert_eq!(
rel(&format!("v1/{ORIGIN}/state/other/artifact/copy")),
Some(TraceRelation::SameOriginUndeclared)
);
assert_eq!(
rel(&format!("v1/{ORIGIN}/telemetry/demo/noise")),
Some(TraceRelation::SameOriginUndeclared)
);
assert_eq!(rel("v1/h-bbbbbbbbbbbb/state/demo/artifact/pcap"), None);
assert_eq!(rel("plain/key"), None);
}
#[test]
fn without_a_registry_the_chain_is_unjudgeable_not_undeclared() {
let rel = |key: &str| target(false).relation_of(&describe_key("", key, None));
assert_eq!(
rel(&format!("v1/{ORIGIN}/state/demo/artifact/pcap")),
Some(TraceRelation::SameOriginRegistryNotLoaded)
);
assert_eq!(rel("v1/h-bbbbbbbbbbbb/state/demo/artifact/pcap"), None);
}
#[test]
fn a_service_target_matches_keys_with_no_producer_chunk() {
let mut slice = RegistrySlice::new("1.0", "t", "catalog");
slice.service_origin = Some(zenkey::Declared::Known(
zenkey::origin::ServiceOrigin::new("@catalog").unwrap(),
));
slice.subjects = vec![SubjectDecl::new("entity/{id}", zenkey::Class::State)];
let s = SliceSet::from_slices(vec![slice]);
let t = TraceTarget {
origin: "@catalog".into(),
producer: None,
chain_chunk: "entity".into(),
registry_loaded: true,
};
assert_eq!(
t.relation_of(&describe_key("", "v1/@catalog/state/entity/x", Some(&s))),
Some(TraceRelation::DeclaredChain)
);
}
#[test]
fn the_idiom_is_the_declared_token_or_undeclared() {
let s = slices();
let decl = s.get("demo").unwrap().procedures.first();
assert_eq!(idiom_of(decl), "long-running");
assert_eq!(idiom_of(None), "undeclared");
let mut write = ProcedureDecl::new("set");
write.kind = Some(zenkey::Declared::Known(zenkey::ProcedureKind::Write));
assert_eq!(idiom_of(Some(&write)), "write");
}
#[test]
fn hlc_delta_is_signed_milliseconds() {
let one_s = 1u64 << 32;
assert_eq!(hlc_delta_ms(one_s, 0), 1000);
assert_eq!(hlc_delta_ms(0, one_s), -1000);
let point_four_ms = one_s * 4 / 10_000;
assert_eq!(hlc_delta_ms(point_four_ms, 0), 0);
assert_eq!(hlc_delta_ms(0, point_four_ms), 0);
assert_eq!(hlc_delta_ms(one_s + one_s / 2, one_s), 500);
}
#[test]
fn a_trace_row_takes_its_clocks_from_the_timeline_row() {
let stamped = TimelineRow {
key: format!("v1/{ORIGIN}/state/demo/artifact/pcap"),
lane: LaneId::Origin {
origin: ORIGIN.into(),
producer: Some("demo".into()),
},
t_us: 12_345,
hlc: Some(HlcStamp {
ntp64: (1u64 << 32) * 2,
stamper: "33".into(),
provenance: Provenance::Foreign,
}),
source: None,
sn: None,
kind: RowKind::Put,
payload_bytes: 7,
};
let row = trace_row(
&stamped,
TraceRelation::DeclaredChain,
Some(1u64 << 32),
Some(3),
);
assert_eq!(row.arrival_delta_ms, 12.345);
assert_eq!(row.hlc.as_deref(), Some("8589934592/33"));
assert_eq!(row.hlc_delta_ms, Some(1000));
assert_eq!(row.stamped_by.as_deref(), Some("foreign:33"));
assert_eq!(row.break_before, Some(3));
let row = trace_row(&stamped, TraceRelation::DeclaredChain, None, None);
assert!(row.hlc.is_some());
assert_eq!(row.hlc_delta_ms, None);
let unstamped = TimelineRow {
hlc: None,
lane: LaneId::Unstamped,
..stamped
};
let row = trace_row(
&unstamped,
TraceRelation::SameOriginUndeclared,
Some(1u64 << 32),
None,
);
assert_eq!(
(row.hlc, row.hlc_delta_ms, row.stamped_by),
(None, None, None)
);
}
}