clawft_kernel/node_facts/
measured.rs1use clawft_types::placement::{Capability, Provenance};
12use serde_json::Value;
13
14pub const MAX_MEASURED_BYTES: usize = 4 << 20;
16
17#[derive(Debug, Clone, Default, PartialEq)]
19pub struct Measured {
20 pub caps: Vec<Capability>,
22 pub skipped: usize,
24}
25
26pub fn parse_measured(text: &str) -> Result<Measured, String> {
28 if text.len() > MAX_MEASURED_BYTES {
29 return Err("measured results file too large".into());
30 }
31 let doc: Value = serde_json::from_str(text).map_err(|e| format!("not JSON: {e}"))?;
32 let list = match &doc {
33 Value::Array(v) => v.clone(),
34 Value::Object(o) => match o.get("capabilities") {
35 Some(Value::Array(v)) => v.clone(),
36 _ => return Err("expected a list or {\"capabilities\": [...]}".into()),
37 },
38 _ => return Err("expected a list or {\"capabilities\": [...]}".into()),
39 };
40 let mut out = Measured::default();
41 for mut entry in list {
42 if let Some(Value::Object(attrs)) = entry.get_mut("attrs") {
43 attrs.retain(|_, v| !v.is_null());
44 }
45 match serde_json::from_value::<Capability>(entry) {
46 Ok(c) if c.id.family() == "perf" && c.provenance == Provenance::Measured => {
47 out.caps.push(c);
48 }
49 _ => out.skipped += 1,
50 }
51 }
52 Ok(out)
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn keeps_measured_perf_and_drops_nulls() {
61 let text = r#"[
62 {"id":"perf.cog.cycle_ms","attrs":{"cog_id":"fall-detect","value":790.5,"interval_s":null},
63 "provenance":"measured","state":"available","exclusive":false},
64 {"id":"perf.cog.cycle_ms","attrs":{"cog_id":"x","value":1.0},"provenance":"claimed"},
65 {"id":"cpu.arch.aarch64","provenance":"measured"},
66 {"id":"BAD ID","provenance":"measured"}
67 ]"#;
68 let m = parse_measured(text).unwrap();
69 assert_eq!(m.caps.len(), 1);
70 assert_eq!(m.skipped, 3);
71 assert!(!m.caps[0].attrs.contains_key("interval_s"));
72 }
73
74 #[test]
75 fn accepts_probe_document_and_rejects_other_shapes() {
76 let doc = r#"{"cog":"a","capabilities":[{"id":"perf.infer.tok_s","attrs":{"model":"m","value":42.0},"provenance":"measured"}]}"#;
77 assert_eq!(parse_measured(doc).unwrap().caps.len(), 1);
78 assert!(parse_measured(r#"{"x":1}"#).is_err());
79 assert!(parse_measured("nope").is_err());
80 }
81}