use std::collections::HashMap;
use areev_cal::render as shared;
use areev_context::policy::{FormatPolicy, MetadataLevel, OutputFormat};
use areev_context::render::RendererRegistry;
use areev_core::format::deserialize::DeserializedGrain;
use areev_core::format::header::MgHeader;
use areev_core::types::GrainType;
const CREATED_AT_SEC: u32 = 1_740_000_000;
fn deserialized(gt: GrainType, fields: &[(&str, serde_json::Value)]) -> DeserializedGrain {
let mut map = HashMap::new();
for (k, v) in fields {
map.insert(k.to_string(), v.clone());
}
DeserializedGrain {
header: MgHeader {
version: 1,
flags: 0,
grain_type: gt.type_byte(),
ns_hash: 0,
created_at_sec: CREATED_AT_SEC,
},
grain_type: gt,
fields: map,
hash: areev_core::error::Hash::from_bytes(&[0xAB; 32]),
}
}
fn cal_render(
gt: GrainType,
fields: &[(&str, serde_json::Value)],
hash_hex: &str,
format: &OutputFormat,
level: MetadataLevel,
) -> String {
let mut map = serde_json::Map::new();
for (k, v) in fields {
map.insert(k.to_string(), v.clone());
}
map.insert(
"created_at".into(),
serde_json::json!((CREATED_AT_SEC as i64) * 1000),
);
let fields_value = serde_json::Value::Object(map);
let view = shared::GrainView {
grain_type: gt.as_str(),
hash: hash_hex,
fields: &fields_value,
created_at_sec: shared::created_at_sec_from_fields(&fields_value),
};
let detail = match level {
MetadataLevel::None => shared::MetadataDetail::None,
MetadataLevel::Minimal => shared::MetadataDetail::Minimal,
MetadataLevel::Full => shared::MetadataDetail::Full,
};
match format {
OutputFormat::Sml => shared::render_grain_sml(&view, detail),
OutputFormat::Markdown => shared::render_grain_markdown(&view),
OutputFormat::PlainText => shared::render_grain_text_line(&view, None),
OutputFormat::Json => shared::grain_json(&view).to_string(),
OutputFormat::Toon => shared::toon_row(&view),
}
}
fn representative_fields(gt: GrainType) -> Vec<(&'static str, serde_json::Value)> {
match gt {
GrainType::Fact => vec![
("subject", serde_json::json!("john")),
("relation", serde_json::json!("likes")),
("object", serde_json::json!("coffee")),
("confidence", serde_json::json!(0.95)),
],
GrainType::Event => vec![
("content", serde_json::json!("what's the refund window?")),
("role", serde_json::json!("user")),
],
GrainType::State => vec![(
"context",
serde_json::json!({"label": "checkout step 2", "cart_items": 3}),
)],
GrainType::Workflow => vec![
("name", serde_json::json!("deploy")),
("trigger", serde_json::json!("push")),
("nodes", serde_json::json!(["build", "test"])),
(
"edges",
serde_json::json!([{"src": "build", "dst": "test", "cond": "green"}]),
),
],
GrainType::Tool => vec![
("tool_name", serde_json::json!("search_api")),
("tool_content", serde_json::json!("12 results")),
("is_error", serde_json::json!(false)),
("duration_ms", serde_json::json!(340)),
],
GrainType::Observation => vec![
("observer_id", serde_json::json!("agent-1")),
("subject", serde_json::json!("latency")),
("object", serde_json::json!("p95 rising")),
],
GrainType::Goal => vec![
("description", serde_json::json!("Ship the launch")),
("goal_state", serde_json::json!("active")),
("priority", serde_json::json!("high")),
("progress", serde_json::json!(0.4)),
],
GrainType::Reasoning => vec![
("conclusion", serde_json::json!("prefer smaller batches")),
("inference_method", serde_json::json!("induction")),
("premises", serde_json::json!(["a", "b"])),
],
GrainType::Consensus => vec![
("agreed_content", serde_json::json!("release on Friday")),
("agreement_count", serde_json::json!(3)),
("dissent_count", serde_json::json!(1)),
],
GrainType::Consent => vec![
("subject_did", serde_json::json!("did:ex:john")),
("scope", serde_json::json!("marketing")),
("is_withdrawal", serde_json::json!(false)),
],
GrainType::Skill => vec![
("name", serde_json::json!("negotiation")),
("description", serde_json::json!("closing enterprise deals")),
("domain", serde_json::json!("sales")),
],
GrainType::Recommendation => vec![
("target_ref", serde_json::json!("qry:agent_context")),
("severity", serde_json::json!("warn")),
(
"summary",
serde_json::json!({"template": "t1", "args": {"metric": "hit_rate"}}),
),
],
GrainType::Trigger => vec![
("kind", serde_json::json!("polling")),
(
"workflow",
serde_json::json!(
"a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90"
),
),
("connector", serde_json::json!("gmail")),
("scope", serde_json::json!("mailbox:accounts@example.com")),
],
}
}
#[test]
fn both_surfaces_render_every_type_identically() {
let registry = RendererRegistry::new();
let all = [
GrainType::Fact,
GrainType::Event,
GrainType::State,
GrainType::Workflow,
GrainType::Tool,
GrainType::Observation,
GrainType::Goal,
GrainType::Reasoning,
GrainType::Consensus,
GrainType::Consent,
GrainType::Skill,
GrainType::Recommendation,
];
let formats = [
OutputFormat::Sml,
OutputFormat::Markdown,
OutputFormat::PlainText,
OutputFormat::Toon,
];
let levels = [
MetadataLevel::None,
MetadataLevel::Minimal,
MetadataLevel::Full,
];
for gt in all {
let fields = representative_fields(gt);
let grain = deserialized(gt, &fields);
let hash_hex = grain.hash.to_hex();
for format in &formats {
for level in levels {
let via_context = registry
.get(gt)
.unwrap()
.render(&grain, &FormatPolicy::new(format.clone()).metadata(level));
let via_cal = cal_render(gt, &fields, &hash_hex, format, level);
assert_eq!(
via_context, via_cal,
"render diverged for {gt:?} in {format:?} at {level:?}"
);
}
}
}
}
#[test]
fn json_envelope_matches_modulo_surface_supplied_created_at() {
let registry = RendererRegistry::new();
let fields = representative_fields(GrainType::Fact);
let grain = deserialized(GrainType::Fact, &fields);
let hash_hex = grain.hash.to_hex();
let via_context: serde_json::Value = serde_json::from_str(
®istry.get(GrainType::Fact).unwrap().render(
&grain,
&FormatPolicy::new(OutputFormat::Json).metadata(MetadataLevel::None),
),
)
.unwrap();
let mut via_cal: serde_json::Value = serde_json::from_str(&cal_render(
GrainType::Fact,
&fields,
&hash_hex,
&OutputFormat::Json,
MetadataLevel::None,
))
.unwrap();
via_cal["fields"].as_object_mut().unwrap().remove("created_at");
assert_eq!(via_context, via_cal);
}
#[test]
fn summaries_agree_across_surfaces() {
let registry = RendererRegistry::new();
for gt in [GrainType::Fact, GrainType::Goal, GrainType::Tool] {
let fields = representative_fields(gt);
let grain = deserialized(gt, &fields);
let hash_hex = grain.hash.to_hex();
let mut map = serde_json::Map::new();
for (k, v) in &fields {
map.insert(k.to_string(), v.clone());
}
let fields_value = serde_json::Value::Object(map);
let view = shared::GrainView {
grain_type: gt.as_str(),
hash: &hash_hex,
fields: &fields_value,
created_at_sec: CREATED_AT_SEC,
};
assert_eq!(
registry
.get(gt)
.unwrap()
.render_summary(&grain, &FormatPolicy::default()),
shared::render_grain_summary(&view),
"summary diverged for {gt:?}"
);
}
}