pub mod known_types;
pub use known_types::{AgentType, HookEventType, KNOWN_AGENT_TYPES, KNOWN_HOOK_EVENT_TYPES};
pub use crate::generated::types::*;
mod extensions;
pub use extensions::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_id_has_evt_prefix_and_valid_uuid_v7() {
let id = new_event_id();
assert!(
id.starts_with("evt_"),
"ID must start with evt_ prefix: {id}"
);
let uuid_part = id.strip_prefix("evt_").unwrap();
let parsed = uuid::Uuid::parse_str(uuid_part).expect("ID must contain a valid UUID");
assert_eq!(parsed.get_version_num(), 7, "UUID version must be 7");
}
#[test]
fn test_consecutive_event_ids_are_monotonically_ordered() {
let id1 = new_event_id();
let id2 = new_event_id();
assert!(id1 <= id2, "UUIDv7 IDs must be monotonically ordered");
}
#[test]
fn test_timestamp_ends_with_z_suffix() {
let ts = current_timestamp();
assert!(
ts.ends_with('Z'),
"Timestamp must end with 'Z' for UTC: {ts}"
);
}
#[test]
fn test_verdict_response_serializes_optional_fields_omitted_when_none() {
let resp = VerdictResponse::allow("evt-001".to_string(), 5.0);
let json = serde_json::to_value(&resp).expect("serialization must succeed");
assert!(json.get("reason").is_none());
assert!(json.get("severity").is_none());
assert!(json.get("threat_category").is_none());
assert!(json.get("rule_id").is_none());
assert!(json.get("details_url").is_none());
assert_eq!(json["schema_version"], "1.0");
assert_eq!(json["event_id"], "evt-001");
assert_eq!(json["latency_ms"], 5.0);
}
#[test]
fn test_verdict_allow_serializes_to_allow() {
let json = serde_json::to_string(&Verdict::Allow).unwrap();
assert_eq!(json, "\"allow\"");
}
#[test]
fn test_verdict_approve_serializes_to_approve() {
let json = serde_json::to_string(&Verdict::Approve).unwrap();
assert_eq!(json, "\"approve\"");
}
#[test]
fn test_verdict_deny_serializes_to_deny() {
let json = serde_json::to_string(&Verdict::Deny).unwrap();
assert_eq!(json, "\"deny\"");
}
fn canonical_cloudevent() -> serde_json::Value {
serde_json::json!({
"specversion": "1.0",
"id": "evt_019d8af1-f8da-73b3-92eb-79a99e59b10b",
"source": "claude-code",
"type": "pre_tool_use",
"time": "2026-04-16T12:00:00Z",
"datacontenttype": "application/json",
"subject": "sess_abc123",
"data": {"tool_name": "Bash", "tool_input": {"command": "ls -la"}},
"os": "linux",
"arch": "x86_64",
"localipv4": "192.168.1.42",
"publicipv4": "203.0.113.7",
"clientversion": "0.2.0",
"agentversion": "1.2.0"
})
}
#[test]
fn cloudevent_envelope_round_trip() {
let wire = canonical_cloudevent();
let envelope: EventEnvelope =
serde_json::from_value(wire.clone()).expect("canonical CloudEvent must deserialise");
let back = serde_json::to_value(&envelope).expect("must serialise back");
assert_eq!(back["specversion"], "1.0");
assert_eq!(back["id"], wire["id"]);
assert_eq!(back["source"], "claude-code");
assert_eq!(back["type"], "pre_tool_use");
assert_eq!(back["time"], wire["time"]);
}
#[test]
fn cloudevent_unknown_type_preserved_verbatim() {
let mut wire = canonical_cloudevent();
wire["type"] = serde_json::Value::String("session_replay".to_string());
let envelope: EventEnvelope = serde_json::from_value(wire.clone())
.expect("unknown type must deserialise (open string)");
let back = serde_json::to_value(&envelope).expect("must serialise back");
assert_eq!(back["type"], "session_replay");
}
#[test]
fn cloudevent_unknown_source_preserved_verbatim() {
let mut wire = canonical_cloudevent();
wire["source"] = serde_json::Value::String("meta-llama".to_string());
let envelope: EventEnvelope = serde_json::from_value(wire.clone())
.expect("unknown source must deserialise (open string)");
let back = serde_json::to_value(&envelope).expect("must serialise back");
assert_eq!(back["source"], "meta-llama");
}
#[test]
fn cloudevent_data_payload_opaque() {
let mut wire = canonical_cloudevent();
wire["data"] = serde_json::json!({"arbitrary": ["shape", 42, true, null]});
let envelope: EventEnvelope = serde_json::from_value(wire.clone()).unwrap();
let back = serde_json::to_value(&envelope).unwrap();
assert_eq!(back["data"], wire["data"]);
}
}