use serde::Deserialize as _;
use crate::generated::types::{
BundleFact, BundleFactRef, DirectiveTemplate, Exception, PolicyArtifact, PolicyBundle,
PolicyBundleEffectClasses, PolicyBundleMeta, PolicyMode, T1PredicateTree, T2RegisterProgram,
T3Hold,
};
use super::tier1::ScanTable;
use super::types::{
SkippedItem, StateLayout, MODE_ENFORCE, MODE_MONITOR, SKIP_BAD_PATTERN, SKIP_BELOW_FLOOR,
SKIP_BODY_PARSE_ERROR, SKIP_FLOAT_PRESENT, SKIP_UNKNOWN_KIND,
};
pub const SUPPORTED_BUNDLE_CLIENT_VERSION: &str = "2.0.0";
pub const KNOWN_KINDS: &[&str] = &[
KIND_T1_PREDICATE_TREE,
KIND_T2_REGISTER_PROGRAM,
KIND_T3_HOLD,
KIND_EXCEPTION,
];
pub const KIND_T1_PREDICATE_TREE: &str = "t1_predicate_tree";
pub const KIND_T2_REGISTER_PROGRAM: &str = "t2_register_program";
pub const KIND_T3_HOLD: &str = "t3_hold";
pub const KIND_EXCEPTION: &str = "exception";
pub const VERDICT_READ_ALIASES: &[(&str, &str)] = &[("approve", "ask"), ("deny", "block")];
const VERDICT_BODY_KEYS: &[&str] = &[
"verdict",
"on_timeout",
"verdict_on_approve",
"verdict_on_reject",
];
fn normalise_verdict_aliases(body: &mut serde_json::Map<String, serde_json::Value>) {
for key in VERDICT_BODY_KEYS {
let Some(slot) = body.get_mut(*key) else {
continue;
};
let Some(raw) = slot.as_str() else {
continue;
};
if let Some((_, canonical)) = VERDICT_READ_ALIASES.iter().find(|(alias, _)| *alias == raw) {
*slot = serde_json::Value::String((*canonical).to_string());
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BundleError {
Envelope(String),
BelowFloor {
required: String,
supported: &'static str,
},
}
impl std::fmt::Display for BundleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BundleError::Envelope(detail) => {
write!(f, "bundle envelope did not parse: {detail}")
}
BundleError::BelowFloor {
required,
supported,
} => write!(
f,
"bundle requires client {required}; this engine implements {supported}"
),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LoadedArtifact {
pub envelope: PolicyArtifact,
pub body: ArtifactBody,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArtifactBody {
T1(Box<T1PredicateTree>),
T2(Box<T2RegisterProgram>),
T3(Box<T3Hold>),
Exception(Box<Exception>),
}
impl LoadedArtifact {
pub fn artifact_id(&self) -> Option<&str> {
self.envelope.artifact_id.as_deref()
}
pub fn atom_id(&self) -> Option<&str> {
self.envelope.atom_id.as_deref()
}
pub fn composed_mode(&self, enforcement_enabled: bool) -> PolicyMode {
if !enforcement_enabled {
return PolicyMode(MODE_MONITOR.to_string());
}
match self.envelope.mode.as_ref() {
Some(mode) => mode.clone(),
None => PolicyMode(MODE_ENFORCE.to_string()),
}
}
pub fn as_t2(&self) -> Option<&T2RegisterProgram> {
match &self.body {
ArtifactBody::T2(body) => Some(body),
_ => None,
}
}
pub fn as_exception(&self) -> Option<&Exception> {
match &self.body {
ArtifactBody::Exception(body) => Some(body),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Bundle {
pub artifacts: Vec<LoadedArtifact>,
pub exceptions: Vec<LoadedArtifact>,
pub facts: Vec<BundleFact>,
pub fact_refs: Vec<BundleFactRef>,
pub effect_classes: Option<PolicyBundleEffectClasses>,
pub directive_templates: Vec<DirectiveTemplate>,
pub meta: Option<PolicyBundleMeta>,
pub enforcement_enabled: bool,
pub state_layout: StateLayout,
pub scan: ScanTable,
pub skipped: Vec<SkippedItem>,
}
pub fn load(doc: serde_json::Value) -> Result<Bundle, BundleError> {
load_with_client_version(doc, SUPPORTED_BUNDLE_CLIENT_VERSION)
}
pub fn load_with_client_version(
mut doc: serde_json::Value,
client_version: &str,
) -> Result<Bundle, BundleError> {
if let Some(root) = doc.as_object_mut() {
root.entry("rules")
.or_insert_with(|| serde_json::Value::Array(Vec::new()));
}
let mut skipped: Vec<SkippedItem> = Vec::new();
if let Some(artifacts) = doc
.get_mut("artifacts")
.and_then(serde_json::Value::as_array_mut)
{
artifacts.retain(|raw| match PolicyArtifact::deserialize(raw) {
Ok(_) => true,
Err(_) => {
skipped.push(SkippedItem {
kind: raw
.get("kind")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_string(),
id: raw
.get("artifact_id")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_string(),
reason: SKIP_BODY_PARSE_ERROR.to_string(),
});
false
}
});
}
let envelope =
PolicyBundle::deserialize(&doc).map_err(|e| BundleError::Envelope(e.to_string()))?;
if let Some(floor) = envelope
.client_floor
.as_ref()
.and_then(|f| f.min_client_version.as_ref())
{
if !version_ge(client_version, floor) {
return Err(BundleError::BelowFloor {
required: floor.clone(),
supported: SUPPORTED_BUNDLE_CLIENT_VERSION,
});
}
}
let mut envelopes = envelope.artifacts;
envelopes.sort_by(|a, b| {
a.artifact_id
.as_deref()
.unwrap_or("")
.cmp(b.artifact_id.as_deref().unwrap_or(""))
});
let mut artifacts = Vec::new();
let mut exceptions = Vec::new();
for raw in envelopes {
match parse_artifact(&raw, client_version) {
Ok(body) => {
let loaded = LoadedArtifact {
envelope: raw,
body,
};
if matches!(loaded.body, ArtifactBody::Exception(_)) {
exceptions.push(loaded);
} else {
artifacts.push(loaded);
}
}
Err(reason) => skipped.push(SkippedItem {
kind: raw.kind.as_ref().map(|k| k.0.clone()).unwrap_or_default(),
id: raw.artifact_id.clone().unwrap_or_default(),
reason,
}),
}
}
skipped.sort_by(|a, b| (&a.id, &a.kind).cmp(&(&b.id, &b.kind)));
let state_layout = super::tier2::bundle_state_layout(&artifacts);
let scan = ScanTable::compile(&artifacts);
Ok(Bundle {
artifacts,
exceptions,
facts: envelope.facts,
fact_refs: envelope.fact_refs,
effect_classes: envelope.effect_classes,
directive_templates: envelope.directive_templates,
meta: envelope.meta,
enforcement_enabled: envelope.enforcement_enabled,
state_layout,
scan,
skipped,
})
}
fn parse_artifact(envelope: &PolicyArtifact, client_version: &str) -> Result<ArtifactBody, String> {
if let Some(floor) = envelope.min_client_version.as_ref() {
if !version_ge(client_version, floor) {
return Err(SKIP_BELOW_FLOOR.to_string());
}
}
let kind = envelope.kind.as_ref().map(|k| k.0.as_str()).unwrap_or("");
if !KNOWN_KINDS.contains(&kind) {
return Err(SKIP_UNKNOWN_KIND.to_string());
}
let mut raw_body = envelope.body.clone();
let body = serde_json::Value::Object(raw_body.clone());
if contains_float(&body) {
return Err(SKIP_FLOAT_PRESENT.to_string());
}
normalise_verdict_aliases(&mut raw_body);
let body = serde_json::Value::Object(raw_body);
match kind {
KIND_T1_PREDICATE_TREE => {
let parsed: T1PredicateTree =
serde_json::from_value(body).map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
super::tier1::validate_node(parsed.node.as_ref())
.map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
super::tier1::validate_patterns(parsed.node.as_ref())
.map_err(|_| SKIP_BAD_PATTERN.to_string())?;
Ok(ArtifactBody::T1(Box::new(parsed)))
}
KIND_T2_REGISTER_PROGRAM => {
let parsed: T2RegisterProgram =
serde_json::from_value(body).map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
super::tier2::validate_program(&parsed)
.map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
Ok(ArtifactBody::T2(Box::new(parsed)))
}
KIND_T3_HOLD => {
let parsed: T3Hold =
serde_json::from_value(body).map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
super::tier3::validate_hold(&parsed).map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
super::tier1::validate_patterns(parsed.trigger.as_ref())
.map_err(|_| SKIP_BAD_PATTERN.to_string())?;
Ok(ArtifactBody::T3(Box::new(parsed)))
}
KIND_EXCEPTION => {
let parsed: Exception =
serde_json::from_value(body).map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
super::exception::validate_exception(&parsed)
.map_err(|_| SKIP_BODY_PARSE_ERROR.to_string())?;
Ok(ArtifactBody::Exception(Box::new(parsed)))
}
_ => Err(SKIP_UNKNOWN_KIND.to_string()),
}
}
fn contains_float(value: &serde_json::Value) -> bool {
match value {
serde_json::Value::Number(n) => n.as_i64().is_none() && n.as_u64().is_none(),
serde_json::Value::Array(items) => items.iter().any(contains_float),
serde_json::Value::Object(map) => map.values().any(contains_float),
_ => false,
}
}
fn version_ge(left: &str, right: &str) -> bool {
fn parts(text: &str) -> Vec<u64> {
text.split('.')
.map(|chunk| {
let digits: String = chunk.chars().filter(char::is_ascii_digit).collect();
digits.parse().unwrap_or(0)
})
.collect()
}
let (a, b) = (parts(left), parts(right));
let width = a.len().max(b.len());
for i in 0..width {
let (x, y) = (
a.get(i).copied().unwrap_or(0),
b.get(i).copied().unwrap_or(0),
);
if x != y {
return x > y;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generated::types::Verdict;
fn doc(artifacts: serde_json::Value) -> serde_json::Value {
serde_json::json!({
"schema_version": 2,
"organization_id": "org",
"revision": 1,
"built_at": "2026-09-01T00:00:00Z",
"enforcement_enabled": true,
"signature": null,
"artifacts": artifacts,
})
}
fn t1(id: &str) -> serde_json::Value {
serde_json::json!({
"artifact_id": id,
"kind": "t1_predicate_tree",
"body": {"node": {"op": "leaf", "leaf": {"pred": "equals", "field": "tool.name", "value": "Bash"}}, "verdict": "block", "reason": "r"},
})
}
#[test]
fn a_missing_rules_array_is_not_a_rejection() {
let bundle = load(doc(serde_json::json!([]))).expect("the envelope parses");
assert!(bundle.artifacts.is_empty());
assert!(bundle.skipped.is_empty());
}
#[test]
fn a_malformed_envelope_rejects_the_whole_bundle() {
let err = load(serde_json::json!({"schema_version": 2})).unwrap_err();
assert!(matches!(err, BundleError::Envelope(_)));
}
#[test]
fn an_unknown_kind_skips_one_item_and_activates_the_rest() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "a", "kind": "from_the_future", "body": {}},
t1("b"),
])))
.expect("the bundle loads");
assert_eq!(bundle.artifacts.len(), 1, "the readable artifact activated");
assert_eq!(
bundle.skipped,
vec![SkippedItem {
kind: "from_the_future".to_string(),
id: "a".to_string(),
reason: SKIP_UNKNOWN_KIND.to_string(),
}]
);
}
#[test]
fn a_body_that_is_not_an_object_skips_one_item_and_keeps_the_rest() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "bad", "kind": "t1_predicate_tree", "body": "not-an-object"},
t1("good"),
])))
.expect("one malformed artifact does not cost the fleet its denies");
assert_eq!(bundle.artifacts.len(), 1);
assert_eq!(bundle.artifacts[0].artifact_id(), Some("good"));
assert_eq!(
bundle.skipped,
vec![SkippedItem {
kind: "t1_predicate_tree".to_string(),
id: "bad".to_string(),
reason: SKIP_BODY_PARSE_ERROR.to_string(),
}]
);
}
#[test]
fn a_schema_one_rule_kind_on_the_artifact_plane_is_unknown_not_fatal() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "a", "kind": "command", "body": {}},
])))
.expect("the bundle loads");
assert_eq!(bundle.skipped[0].reason, SKIP_UNKNOWN_KIND);
}
#[test]
fn a_float_anywhere_in_a_body_skips_that_item_only() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "a", "kind": "t2_register_program", "body": {"pre": [["CMP_GE", 0, 1.5]]}},
t1("b"),
])))
.expect("the bundle loads");
assert_eq!(bundle.artifacts.len(), 1);
assert_eq!(bundle.skipped[0].reason, SKIP_FLOAT_PRESENT);
}
#[test]
fn an_unreadable_body_skips_that_item_only() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "a", "kind": "t1_predicate_tree", "body": {"node": "not an object"}},
t1("b"),
])))
.expect("the bundle loads");
assert_eq!(bundle.artifacts.len(), 1);
assert_eq!(bundle.skipped[0].reason, SKIP_BODY_PARSE_ERROR);
}
#[test]
fn an_artifact_above_our_floor_is_skipped_and_the_rest_activates() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "a", "kind": "t1_predicate_tree", "min_client_version": "99.0.0", "body": {"node": {"op": "leaf"}}},
t1("b"),
])))
.expect("the bundle loads");
assert_eq!(bundle.artifacts.len(), 1);
assert_eq!(bundle.skipped[0].reason, SKIP_BELOW_FLOOR);
}
#[test]
fn a_root_floor_above_ours_withholds_the_whole_document() {
let mut document = doc(serde_json::json!([t1("a")]));
document["client_floor"] = serde_json::json!({"min_client_version": "99.0.0"});
assert!(matches!(
load(document).unwrap_err(),
BundleError::BelowFloor { .. }
));
}
#[test]
fn the_corpus_floor_of_two_zero_zero_loads() {
let mut document = doc(serde_json::json!([t1("a")]));
document["client_floor"] = serde_json::json!({"min_client_version": "2.0.0"});
assert!(load(document).is_ok());
}
#[test]
fn exceptions_are_held_apart_from_the_evaluating_artifacts() {
let bundle = load(doc(serde_json::json!([
{"artifact_id": "x", "kind": "exception", "body": {"scope": "exact_action", "selector": {"action_hash": "abc"}, "verdict": "allow"}},
t1("a"),
])))
.expect("the bundle loads");
assert_eq!(bundle.artifacts.len(), 1);
assert_eq!(bundle.exceptions.len(), 1);
}
#[test]
fn enforcement_disabled_composes_every_artifact_as_monitor() {
let mut document = doc(serde_json::json!([t1("a")]));
document["enforcement_enabled"] = serde_json::json!(false);
let bundle = load(document).expect("the bundle loads");
assert_eq!(
bundle.artifacts[0]
.composed_mode(bundle.enforcement_enabled)
.0,
MODE_MONITOR
);
}
#[test]
fn a_deny_alias_loads_instead_of_skipping_the_artifact() {
let bundle = load(doc(serde_json::json!([{
"artifact_id": "sa2",
"kind": "t3_hold",
"body": {
"trigger": {"op": "leaf", "leaf": {"pred": "exists", "field": "tool.name"}},
"on_timeout": "deny",
"verdict_on_approve": "approve",
"verdict_on_reject": "deny",
},
}])))
.expect("the bundle loads");
assert!(bundle.skipped.is_empty(), "{:?}", bundle.skipped);
let ArtifactBody::T3(body) = &bundle.artifacts[0].body else {
panic!("expected a t3_hold");
};
assert_eq!(body.on_timeout, Some(Verdict::Block));
assert_eq!(body.verdict_on_approve, Some(Verdict::Ask));
assert_eq!(body.verdict_on_reject, Some(Verdict::Block));
}
#[test]
fn aliases_are_read_on_every_body_kind_not_only_the_hold() {
for (kind, body) in [
(
"t1_predicate_tree",
serde_json::json!({"node": {"op": "leaf", "leaf": {"pred": "exists", "field": "tool.name"}}, "verdict": "deny"}),
),
(
"t2_register_program",
serde_json::json!({"pre": [], "verdict": "deny"}),
),
(
"exception",
serde_json::json!({"scope": "exact_action", "selector": {"action_hash": "a"}, "verdict": "deny"}),
),
] {
let bundle = load(doc(
serde_json::json!([{ "artifact_id": "a", "kind": kind, "body": body }]),
))
.unwrap_or_else(|e| panic!("{kind} did not load: {e}"));
assert!(
bundle.skipped.is_empty(),
"{kind} was skipped: {:?}",
bundle.skipped
);
}
}
#[test]
fn an_alias_is_read_but_never_written() {
for (_, canonical) in VERDICT_READ_ALIASES {
let round_trip = serde_json::to_string(
&serde_json::from_str::<Verdict>(&format!("\"{canonical}\"")).expect("canonical"),
)
.expect("serialises");
assert_eq!(round_trip, format!("\"{canonical}\""));
}
for (alias, _) in VERDICT_READ_ALIASES {
assert!(
serde_json::from_str::<Verdict>(&format!("\"{alias}\"")).is_err(),
"the enum itself must NOT accept {alias} — the alias is a read pass, not a fifth value"
);
}
}
#[test]
fn every_verdict_typed_body_field_is_covered_by_the_alias_pass() {
let schema: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/schemas/policy-bundle.schema.json"
))
.expect("the schema is readable"),
)
.expect("the schema parses");
let defs = &schema["$defs"];
for kind in KNOWN_KINDS {
let properties = defs[*kind]["properties"]
.as_object()
.unwrap_or_else(|| panic!("$defs.{kind}.properties"));
for (name, property) in properties {
let is_verdict = property["$ref"]
.as_str()
.is_some_and(|r| r.ends_with("#/$defs/Verdict"));
if is_verdict {
assert!(
VERDICT_BODY_KEYS.contains(&name.as_str()),
"$defs.{kind}.{name} is Verdict-typed but VERDICT_BODY_KEYS does not list it"
);
}
let nested = serde_json::to_string(property).unwrap_or_default();
if !is_verdict {
assert!(
!nested.contains("#/$defs/Verdict"),
"$defs.{kind}.{name} nests a Verdict; normalise_verdict_aliases does not recurse"
);
}
}
}
}
#[test]
fn version_compare_is_dotted_integer() {
assert!(version_ge("2.0.0", "2.0.0"));
assert!(version_ge("2.1.0", "2.0.9"));
assert!(!version_ge("2.0.0", "2.0.1"));
assert!(version_ge("2.0.0", "2.0"));
assert!(!version_ge("2.0.0-rc.1", "2.0.1"));
}
}