use std::sync::LazyLock;
use serde_json::Value;
const BUNDLE_SCHEMA: &str = include_str!("../../../schemas/policy-bundle.schema.json");
const ENUMS_SCHEMA: &str = include_str!("../../../schemas/enums.schema.json");
#[cfg(test)]
pub(super) fn agent_function_vocabulary() -> Vec<String> {
let enums: Value = serde_json::from_str(ENUMS_SCHEMA).expect("enums schema is JSON");
enums
.pointer("/$defs/AgentFunction/enum")
.and_then(Value::as_array)
.expect("AgentFunction is a closed enum")
.iter()
.map(|v| v.as_str().expect("string value").to_string())
.collect()
}
pub const ENUMS_URI: &str = "https://schemas.openlatch.ai/client/v1/enums.schema.json";
const BUNDLE_URI: &str = "https://schemas.openlatch.ai/client/v1/policy-bundle.schema.json";
const RULE_POINTER: &str = "/properties/rules/items";
static REGISTRY: LazyLock<jsonschema::Registry> = LazyLock::new(|| {
let enums: Value = serde_json::from_str(ENUMS_SCHEMA).expect("embedded enums schema is JSON");
jsonschema::Registry::new()
.add(ENUMS_URI, jsonschema::Resource::from_contents(enums))
.expect("enums URI is valid")
.prepare()
.expect("enums schema is a usable resource")
});
static RULE_VALIDATOR: LazyLock<Result<jsonschema::Validator, String>> = LazyLock::new(|| {
let bundle: Value = serde_json::from_str(BUNDLE_SCHEMA)
.map_err(|e| format!("bundle schema is not JSON: {e}"))?;
let subschema = bundle
.pointer(RULE_POINTER)
.cloned()
.ok_or_else(|| format!("bundle schema has no subschema at {RULE_POINTER}"))?;
jsonschema::options()
.with_registry(®ISTRY)
.with_base_uri(BUNDLE_URI)
.build(&subschema)
.map_err(|e| format!("rule subschema does not compile: {e}"))
});
pub fn validate_rule(rule: &crate::generated::types::PolicyRule) -> Result<(), String> {
let value = serde_json::to_value(rule).map_err(|e| e.to_string())?;
validate_value(&value)
}
pub(crate) fn validate_value(rule: &Value) -> Result<(), String> {
let validator = match RULE_VALIDATOR.as_ref() {
Ok(v) => v,
Err(e) => {
tracing::warn!(
target: "policy",
error = %e,
"the embedded rule subschema did not compile; accepting every rule unvalidated"
);
return Ok(());
}
};
match validator.validate(rule) {
Ok(()) => Ok(()),
Err(e) => Err(format!("{} at {}", e, e.instance_path())),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn command_rule() -> Value {
json!({
"rule_id": "OL-CMD-001",
"kind": "command",
"match_pattern": "*rm -rf*",
"action": "deny",
"mode": "enforce",
"severity": "critical",
"reason": "Recursive delete"
})
}
fn request_rule(action: &str) -> Value {
json!({
"rule_id": "OL-REQ-001",
"kind": "request",
"rule_version": 1,
"action": action,
"mode": "observe",
"severity": "low",
"reason": "Cache prefix churn"
})
}
#[test]
fn the_embedded_subschema_compiles() {
assert!(
RULE_VALIDATOR.as_ref().is_ok(),
"{:?}",
RULE_VALIDATOR.as_ref().err()
);
}
#[test]
fn canonical_command_rule_passes() {
assert_eq!(validate_value(&command_rule()), Ok(()));
}
#[test]
fn command_rule_without_match_pattern_is_rejected() {
let mut rule = command_rule();
rule.as_object_mut().unwrap().remove("match_pattern");
assert!(validate_value(&rule).is_err());
}
#[test]
fn command_rule_carrying_rule_version_is_rejected() {
let mut rule = command_rule();
rule.as_object_mut()
.unwrap()
.insert("rule_version".into(), json!(1));
assert!(validate_value(&rule).is_err());
}
#[test]
fn command_rule_carrying_request_fields_is_rejected() {
let mut rule = command_rule();
rule.as_object_mut()
.unwrap()
.insert("select".into(), json!({ "min_messages": 4 }));
assert!(validate_value(&rule).is_err());
}
#[test]
fn a_null_valued_request_key_still_violates_the_command_branch() {
let mut rule = command_rule();
rule.as_object_mut()
.unwrap()
.insert("select".into(), Value::Null);
assert!(validate_value(&rule).is_err());
}
#[test]
fn rule_missing_kind_is_rejected() {
let mut rule = command_rule();
rule.as_object_mut().unwrap().remove("kind");
assert!(validate_value(&rule).is_err());
}
#[test]
fn command_kind_with_a_request_action_is_rejected() {
let mut rule = command_rule();
rule.as_object_mut()
.unwrap()
.insert("action".into(), json!("prefix_reorder"));
assert!(validate_value(&rule).is_err());
}
#[test]
fn prefix_reorder_with_exclude_layers_validates() {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut().unwrap().insert(
"select".into(),
json!({ "model_in": ["claude-opus-5"], "exclude_layers": ["tools"] }),
);
assert_eq!(validate_value(&rule), Ok(()));
}
#[test]
fn an_unknown_churn_layer_is_rejected() {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut()
.unwrap()
.insert("select".into(), json!({ "exclude_layers": ["nope"] }));
assert!(validate_value(&rule).is_err());
}
#[test]
fn a_nonsense_function_in_conditions_is_gate_rejected() {
let scoped = |value: Value| {
let mut rule = command_rule();
rule.as_object_mut().unwrap().insert(
"conditions".into(),
json!([{ "field": "agent.function", "op": "in", "value": value }]),
);
rule
};
assert_eq!(
validate_value(&scoped(json!(["marketing", "unknown"]))),
Ok(())
);
assert!(validate_value(&scoped(json!(["astrology"]))).is_err());
assert!(validate_value(&scoped(json!(["marketing", "astrology"]))).is_err());
assert!(validate_value(&scoped(json!([]))).is_err());
}
#[test]
fn conditions_on_a_request_rule_are_gate_rejected() {
let conditions = json!([{ "field": "agent.function", "op": "in", "value": ["sales"] }]);
for action in ["prefix_reorder", "history_trim", "prompt_edit"] {
let mut rule = request_rule(action);
let obj = rule.as_object_mut().unwrap();
obj.insert("conditions".into(), conditions.clone());
if action == "history_trim" {
obj.insert("params".into(), json!({ "keep_messages": 20 }));
}
if action == "prompt_edit" {
obj.insert("params".into(), json!({ "marker": "<!--ol-->" }));
}
assert!(validate_value(&rule).is_err(), "action {action}");
}
let mut command = command_rule();
command
.as_object_mut()
.unwrap()
.insert("conditions".into(), conditions);
assert_eq!(validate_value(&command), Ok(()));
}
#[test]
fn prefix_reorder_accepts_a_mechanism() {
for mechanism in ["insert_breakpoints", "reorder_blocks"] {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "mechanism": mechanism }));
assert_eq!(validate_value(&rule), Ok(()), "mechanism {mechanism}");
}
}
#[test]
fn prefix_reorder_still_validates_without_params() {
assert_eq!(validate_value(&request_rule("prefix_reorder")), Ok(()));
let mut empty = request_rule("prefix_reorder");
empty
.as_object_mut()
.unwrap()
.insert("params".into(), json!({}));
assert_eq!(validate_value(&empty), Ok(()));
}
#[test]
fn prefix_reorder_rejects_the_other_actions_params() {
for params in [
json!({ "keep_messages": 20 }),
json!({ "marker": "<!--ol-->" }),
json!({ "max_system_tokens": 2000 }),
json!({ "mechanism": "reorder_blocks", "keep_messages": 20 }),
] {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut()
.unwrap()
.insert("params".into(), params.clone());
assert!(validate_value(&rule).is_err(), "params {params}");
}
}
#[test]
fn an_out_of_vocabulary_mechanism_is_accepted() {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "mechanism": "some_future_lever" }));
assert_eq!(validate_value(&rule), Ok(()));
}
#[test]
fn mechanism_is_rejected_on_the_other_request_actions() {
let mut trim = request_rule("history_trim");
trim.as_object_mut().unwrap().insert(
"params".into(),
json!({ "keep_messages": 20, "mechanism": "reorder_blocks" }),
);
assert!(validate_value(&trim).is_err());
let mut edit = request_rule("prompt_edit");
edit.as_object_mut().unwrap().insert(
"params".into(),
json!({ "marker": "<!--ol-->", "mechanism": "reorder_blocks" }),
);
assert!(validate_value(&edit).is_err());
}
#[test]
fn request_rule_without_rule_version_is_rejected() {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut().unwrap().remove("rule_version");
assert!(validate_value(&rule).is_err());
}
#[test]
fn request_rule_with_a_match_pattern_is_rejected() {
let mut rule = request_rule("prefix_reorder");
rule.as_object_mut()
.unwrap()
.insert("match_pattern".into(), json!("*rm*"));
assert!(validate_value(&rule).is_err());
}
#[test]
fn history_trim_requires_keep_messages_and_rejects_the_others() {
let mut ok = request_rule("history_trim");
ok.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "keep_messages": 20 }));
assert_eq!(validate_value(&ok), Ok(()));
let mut no_params = request_rule("history_trim");
no_params
.as_object_mut()
.unwrap()
.insert("select".into(), json!({ "min_messages": 40 }));
assert!(validate_value(&no_params).is_err());
let mut with_marker = request_rule("history_trim");
with_marker.as_object_mut().unwrap().insert(
"params".into(),
json!({ "keep_messages": 20, "marker": "x" }),
);
assert!(validate_value(&with_marker).is_err());
let mut with_layers = request_rule("history_trim");
let obj = with_layers.as_object_mut().unwrap();
obj.insert("params".into(), json!({ "keep_messages": 20 }));
obj.insert("select".into(), json!({ "exclude_layers": ["tools"] }));
assert!(validate_value(&with_layers).is_err());
}
#[test]
fn prompt_edit_takes_exactly_one_of_marker_or_max_system_tokens() {
let mut marker = request_rule("prompt_edit");
marker
.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "marker": "<!--ol-->" }));
assert_eq!(validate_value(&marker), Ok(()));
let mut tokens = request_rule("prompt_edit");
tokens
.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "max_system_tokens": 2000 }));
assert_eq!(validate_value(&tokens), Ok(()));
let mut both = request_rule("prompt_edit");
both.as_object_mut().unwrap().insert(
"params".into(),
json!({ "marker": "<!--ol-->", "max_system_tokens": 2000 }),
);
assert!(validate_value(&both).is_err());
let mut neither = request_rule("prompt_edit");
neither
.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "keep_messages": 5 }));
assert!(validate_value(&neither).is_err());
}
#[test]
fn a_long_marker_is_not_constrained_by_the_schema() {
let mut rule = request_rule("prompt_edit");
rule.as_object_mut()
.unwrap()
.insert("params".into(), json!({ "marker": "x".repeat(300) }));
assert_eq!(validate_value(&rule), Ok(()));
}
#[test]
fn every_schema_example_passes_the_gate() {
let bundle: Value = serde_json::from_str(BUNDLE_SCHEMA).expect("bundle schema is JSON");
let examples = bundle["examples"].as_array().expect("examples block");
let mut checked = 0;
for (i, example) in examples.iter().enumerate() {
for rule in example["rules"].as_array().expect("example rules") {
assert_eq!(
validate_value(rule),
Ok(()),
"example {i}, rule {}",
rule["rule_id"]
);
checked += 1;
}
}
assert!(checked >= 4, "the examples must exercise both planes");
}
#[test]
fn the_agent_function_vocabulary_is_written_identically_in_both_schemas() {
let bundle: Value = serde_json::from_str(BUNDLE_SCHEMA).expect("bundle schema is JSON");
let open = bundle
.pointer("/properties/client_config/properties/agent_context/properties/function/x-known-values")
.expect("agent_context.function declares x-known-values");
assert_eq!(open, &json!(agent_function_vocabulary()));
}
#[test]
fn an_unknown_kind_passes_the_gate_untouched() {
let mut rule = command_rule();
rule.as_object_mut()
.unwrap()
.insert("kind".into(), json!("network"));
assert_eq!(validate_value(&rule), Ok(()));
}
}