use serde_json::json;
use std::sync::Arc;
use zen_engine::policy::{EvaluateRequest, PolicyWorkspace};
use zen_expression::variable::Variable;
fn workspace_with(doc: serde_json::Value) -> PolicyWorkspace {
let mut ws = PolicyWorkspace::new();
ws.set_policy("p", serde_json::from_value(doc).unwrap());
ws
}
fn request(input: serde_json::Value, goals: Vec<&str>, trace: bool) -> EvaluateRequest {
EvaluateRequest {
policy_path: Arc::from("p"),
input: Variable::from(input),
goals: goals.into_iter().map(Arc::from).collect(),
trace,
}
}
fn executed_block_ids(ws: &PolicyWorkspace, req: &EvaluateRequest) -> Vec<String> {
let result = ws.evaluate(req).expect("evaluate succeeded");
result
.trace
.expect("trace populated")
.executions
.iter()
.map(|e| e.block_id.to_string())
.collect()
}
fn match_laziness_doc() -> serde_json::Value {
json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "score", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-a", "type": "expression", "props": { "data": {
"key": "customer.aVal",
"value": "customer.score * 2"
} } },
{ "id": "e-b", "type": "expression", "props": { "data": {
"key": "customer.bVal",
"value": "customer.score * 3"
} } },
{ "id": "m", "type": "match", "props": { "data": {
"key": "customer.out",
"arms": [
{ "id": "a1", "condition": "customer.score >= 10", "value": "customer.aVal" },
{ "id": "a2", "condition": "", "value": "customer.bVal" }
]
} } }
]
})
}
#[test]
fn unmatched_match_arm_dependencies_do_not_execute() {
let ws = workspace_with(match_laziness_doc());
let req = request(json!({ "customer": { "score": 20 } }), vec![], true);
let executed = executed_block_ids(&ws, &req);
assert!(
executed.contains(&"e-a".to_string()),
"matched arm dependency must run: {executed:?}"
);
assert!(executed.contains(&"m".to_string()));
assert!(
!executed.contains(&"e-b".to_string()),
"unmatched arm dependency must stay lazy: {executed:?}",
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/customer/out"), Some(&json!(40)));
assert_eq!(output.pointer("/customer/bVal"), None);
}
#[test]
fn default_match_arm_dependencies_execute_when_selected() {
let ws = workspace_with(match_laziness_doc());
let req = request(json!({ "customer": { "score": 5 } }), vec![], true);
let executed = executed_block_ids(&ws, &req);
assert!(
executed.contains(&"e-b".to_string()),
"default arm dependency must run: {executed:?}"
);
assert!(
!executed.contains(&"e-a".to_string()),
"non-selected arm dependency must stay lazy: {executed:?}",
);
}
#[test]
fn dependencies_execute_before_dependents() {
let ws = workspace_with(match_laziness_doc());
let req = request(json!({ "customer": { "score": 20 } }), vec![], true);
let executed = executed_block_ids(&ws, &req);
let pos = |id: &str| {
executed
.iter()
.position(|e| e == id)
.unwrap_or_else(|| panic!("{id} missing from {executed:?}"))
};
assert!(
pos("e-a") < pos("m"),
"dependency must commit before dependent: {executed:?}"
);
}
#[test]
fn goals_prune_unrelated_blocks() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "score", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-x", "type": "expression", "props": { "data": {
"key": "customer.x",
"value": "customer.score * 2"
} } },
{ "id": "e-y", "type": "expression", "props": { "data": {
"key": "customer.y",
"value": "customer.score * 3"
} } }
]
});
let ws = workspace_with(doc);
let req = request(
json!({ "customer": { "score": 10 } }),
vec!["customer.x"],
true,
);
let executed = executed_block_ids(&ws, &req);
assert!(executed.contains(&"e-x".to_string()));
assert!(
!executed.contains(&"e-y".to_string()),
"goal pruning must skip e-y: {executed:?}"
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/customer/x"), Some(&json!(20)));
assert_eq!(output.pointer("/customer/y"), None);
}
#[test]
fn decision_table_unmatched_row_dependencies_do_not_execute() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "age", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-adult", "type": "expression", "props": { "data": {
"key": "customer.adultMsg",
"value": "\"adult\""
} } },
{ "id": "e-child", "type": "expression", "props": { "data": {
"key": "customer.childMsg",
"value": "\"child\""
} } },
{ "id": "dt", "type": "decisionTable", "props": { "data": {
"hitPolicy": "first",
"inputs": [ { "id": "i1", "name": "", "field": "customer.age" } ],
"outputs": [ { "id": "o1", "name": "", "field": "customer.msg" } ],
"rules": [
{ "i1": ">= 18", "o1": "customer.adultMsg" },
{ "i1": "", "o1": "customer.childMsg" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "age": 30 } }), vec![], true);
let executed = executed_block_ids(&ws, &req);
assert!(
executed.contains(&"e-adult".to_string()),
"matched row dependency must run: {executed:?}"
);
assert!(
!executed.contains(&"e-child".to_string()),
"unmatched row dependency must stay lazy: {executed:?}",
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/customer/msg"), Some(&json!("adult")));
}
fn iterated_doc() -> serde_json::Value {
json!({
"blocks": [
{ "id": "dm-customer", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "name", "type": "string", "array": false, "optional": false },
{ "id": "p2", "name": "companies", "type": "relationship", "target": "company", "array": true, "optional": false }
]
} } },
{ "id": "dm-company", "type": "dataModel", "props": { "data": {
"name": "company",
"properties": [
{ "id": "p3", "name": "name", "type": "string", "array": false, "optional": false },
{ "id": "p4", "name": "revenue", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-top", "type": "expression", "props": { "data": {
"key": "company.isTop",
"value": "max(map(company.customer.companies as c, c.revenue)) == company.revenue"
} } }
]
})
}
#[test]
fn iterated_owner_binding_does_not_leak_into_output() {
let ws = workspace_with(iterated_doc());
let input = json!({ "customer": { "name": "Alice", "companies": [
{ "name": "Acme", "revenue": 500 },
{ "name": "Mega", "revenue": 1000 }
] } });
for trace in [false, true] {
let req = request(input.clone(), vec![], trace);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
let companies = output
.pointer("/customer/companies")
.and_then(|v| v.as_array())
.expect("companies present");
assert_eq!(companies.len(), 2);
assert_eq!(companies[0].get("isTop"), Some(&json!(false)));
assert_eq!(companies[1].get("isTop"), Some(&json!(true)));
for company in companies {
assert!(
company.get("customer").is_none(),
"synthetic owner binding leaked into output (trace={trace}): {company:#?}",
);
}
}
}
#[test]
fn iterated_match_unselected_arm_dependencies_do_not_execute() {
let doc = json!({
"blocks": [
{ "id": "dm-customer", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "companies", "type": "relationship", "target": "company", "array": true, "optional": false }
]
} } },
{ "id": "dm-company", "type": "dataModel", "props": { "data": {
"name": "company",
"properties": [
{ "id": "p2", "name": "revenue", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-big", "type": "expression", "props": { "data": {
"key": "customer.bigRate",
"value": "2"
} } },
{ "id": "e-small", "type": "expression", "props": { "data": {
"key": "customer.smallRate",
"value": "3"
} } },
{ "id": "m", "type": "match", "props": { "data": {
"key": "company.bonus",
"arms": [
{ "id": "hi", "condition": "company.revenue >= 100", "value": "company.revenue * customer.bigRate" },
{ "id": "lo", "condition": "", "value": "company.revenue * customer.smallRate" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(
json!({ "customer": { "companies": [ { "revenue": 200 }, { "revenue": 300 } ] } }),
vec![],
true,
);
let executed = executed_block_ids(&ws, &req);
assert!(
executed.contains(&"e-big".to_string()),
"selected arm dependency must run: {executed:?}"
);
assert!(
!executed.contains(&"e-small".to_string()),
"arm unselected by every instance must stay lazy: {executed:?}",
);
assert_eq!(
executed.iter().filter(|id| id.as_str() == "m").count(),
2,
"one execution per instance: {executed:?}",
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(
output.pointer("/customer/companies/0/bonus"),
Some(&json!(400))
);
assert_eq!(
output.pointer("/customer/companies/1/bonus"),
Some(&json!(600))
);
}
#[test]
fn iterated_failure_does_not_leak_owner_binding_into_partial_trace() {
let doc = json!({
"blocks": [
{ "id": "dm-customer", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "companies", "type": "relationship", "target": "company", "array": true, "optional": false }
]
} } },
{ "id": "dm-company", "type": "dataModel", "props": { "data": {
"name": "company",
"properties": [
{ "id": "p2", "name": "revenue", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-fail", "type": "expression", "props": { "data": {
"key": "company.score",
"value": "company.customer.missing.deep + company.revenue"
} } }
]
});
let ws = workspace_with(doc);
let input = json!({ "customer": { "companies": [ { "revenue": 200 } ] } });
let req = request(input.clone(), vec![], true);
let _ = ws.evaluate(&req);
let probe = workspace_with(iterated_doc());
let _ = probe;
let input_after: serde_json::Value = req.input.into();
let company = input_after
.pointer("/customer/companies/0")
.expect("instance present");
assert!(
company.get("customer").is_none(),
"synthetic owner binding leaked into caller input after failure: {company:#?}",
);
}
#[test]
fn enhance_trace_tolerates_runtime_error_in_post_selection_match_arm() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "flag", "type": "string", "array": false, "optional": false }
]
} } },
{ "id": "m", "type": "match", "props": { "data": {
"key": "customer.out",
"arms": [
{ "id": "a1", "condition": "customer.flag == ''", "value": "\"empty\"" },
{ "id": "a2", "condition": "number(customer.flag) > 2", "value": "\"big\"" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "flag": "" } }), vec![], true);
let plain = ws.evaluate(&req).expect("evaluate must succeed");
let output: serde_json::Value = plain.output.into();
assert_eq!(output.pointer("/customer/out"), Some(&json!("empty")));
let enhanced = ws
.enhance_trace(&req)
.expect("enhance_trace must tolerate a post-selection arm error");
let trace = enhanced.trace.expect("trace populated");
let m = trace
.executions
.iter()
.find(|e| e.block_id.as_ref() == "m")
.expect("m missing from trace");
let trace_json = serde_json::to_value(&m.trace).unwrap();
assert_eq!(trace_json.pointer("/matchedArm"), Some(&json!("a1")));
assert_eq!(
trace_json.pointer("/arms"),
Some(&json!([
{ "id": "a1", "result": true },
{ "id": "a2", "result": false }
]))
);
}
#[test]
fn enhance_trace_propagates_runtime_error_before_match_selection() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "flag", "type": "string", "array": false, "optional": false }
]
} } },
{ "id": "m", "type": "match", "props": { "data": {
"key": "customer.out",
"arms": [
{ "id": "a1", "condition": "number(customer.flag) > 2", "value": "\"big\"" },
{ "id": "a2", "condition": "", "value": "\"fallback\"" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "flag": "" } }), vec![], true);
assert!(ws.evaluate(&req).is_err());
assert!(
ws.enhance_trace(&req).is_err(),
"pre-selection arm error must still propagate in extras mode"
);
}
#[test]
fn enhance_trace_tolerates_runtime_error_in_post_selection_table_row() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "flag", "type": "string", "array": false, "optional": false }
]
} } },
{ "id": "dt", "type": "decisionTable", "props": { "data": {
"hitPolicy": "first",
"inputs": [ { "id": "i1", "name": "", "field": "customer.flag" } ],
"outputs": [ { "id": "o1", "name": "", "field": "customer.out" } ],
"rules": [
{ "i1": "== ''", "o1": "\"empty\"" },
{ "i1": "number($) > 2", "o1": "\"big\"" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "flag": "" } }), vec![], true);
let plain = ws.evaluate(&req).expect("evaluate must succeed");
let output: serde_json::Value = plain.output.into();
assert_eq!(output.pointer("/customer/out"), Some(&json!("empty")));
let enhanced = ws
.enhance_trace(&req)
.expect("enhance_trace must tolerate a post-selection row error");
let trace = enhanced.trace.expect("trace populated");
let dt = trace
.executions
.iter()
.find(|e| e.block_id.as_ref() == "dt")
.expect("dt missing from trace");
let trace_json = serde_json::to_value(&dt.trace).unwrap();
assert_eq!(trace_json.pointer("/matchedRows"), Some(&json!([0])));
}
#[test]
fn enhance_trace_tolerates_error_after_failed_column_in_unmatched_row() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "flag", "type": "string", "array": false, "optional": false }
]
} } },
{ "id": "dt", "type": "decisionTable", "props": { "data": {
"hitPolicy": "first",
"inputs": [
{ "id": "i1", "name": "", "field": "customer.flag" },
{ "id": "i2", "name": "", "field": "customer.flag" }
],
"outputs": [ { "id": "o1", "name": "", "field": "customer.out" } ],
"rules": [
{ "i1": "== 'x'", "i2": "number($) > 2", "o1": "\"first\"" },
{ "i1": "== ''", "o1": "\"second\"" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "flag": "" } }), vec![], true);
let plain = ws.evaluate(&req).expect("evaluate must succeed");
let output: serde_json::Value = plain.output.into();
assert_eq!(output.pointer("/customer/out"), Some(&json!("second")));
let enhanced = ws.enhance_trace(&req).expect(
"enhance_trace must tolerate an error in a column evaluate would short-circuit past",
);
let trace = enhanced.trace.expect("trace populated");
let dt = trace
.executions
.iter()
.find(|e| e.block_id.as_ref() == "dt")
.expect("dt missing from trace");
let trace_json = serde_json::to_value(&dt.trace).unwrap();
assert_eq!(trace_json.pointer("/matchedRows"), Some(&json!([1])));
}
#[test]
fn enhance_trace_tolerates_error_after_failed_column_in_collect_table() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "flag", "type": "string", "array": false, "optional": false }
]
} } },
{ "id": "dt", "type": "decisionTable", "props": { "data": {
"hitPolicy": "collect",
"inputs": [
{ "id": "i1", "name": "", "field": "customer.flag" },
{ "id": "i2", "name": "", "field": "customer.flag" }
],
"outputs": [ { "id": "o1", "name": "", "field": "customer.out" } ],
"rules": [
{ "i1": "== 'x'", "i2": "number($) > 2", "o1": "\"first\"" },
{ "i1": "== ''", "o1": "\"second\"" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "flag": "" } }), vec![], true);
let plain = ws.evaluate(&req).expect("evaluate must succeed");
let output: serde_json::Value = plain.output.into();
assert_eq!(output.pointer("/customer/out"), Some(&json!(["second"])));
let enhanced = ws
.enhance_trace(&req)
.expect("enhance_trace must tolerate post-short-circuit errors in collect tables");
let trace = enhanced.trace.expect("trace populated");
let dt = trace
.executions
.iter()
.find(|e| e.block_id.as_ref() == "dt")
.expect("dt missing from trace");
let trace_json = serde_json::to_value(&dt.trace).unwrap();
assert_eq!(trace_json.pointer("/matchedRows"), Some(&json!([1])));
}
#[test]
fn iterated_write_named_like_owner_entity_survives_write_back() {
let doc = json!({
"blocks": [
{ "id": "dm-customer", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "companies", "type": "relationship", "target": "company", "array": true, "optional": false }
]
} } },
{ "id": "dm-company", "type": "dataModel", "props": { "data": {
"name": "company",
"properties": [
{ "id": "p2", "name": "name", "type": "string", "array": false, "optional": false }
]
} } },
{ "id": "e-owner-name", "type": "expression", "props": { "data": {
"key": "company.customer",
"value": "company.name + '!'"
} } }
]
});
let ws = workspace_with(doc);
let req = request(
json!({ "customer": { "companies": [ { "name": "Acme" }, { "name": "Mega" } ] } }),
vec![],
false,
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(
output.pointer("/customer/companies/0/customer"),
Some(&json!("Acme!")),
"write named like the owner entity must survive write_back: {output:#?}",
);
assert_eq!(
output.pointer("/customer/companies/1/customer"),
Some(&json!("Mega!"))
);
}
#[test]
fn cyclic_property_demand_terminates() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "seed", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-a", "type": "expression", "props": { "data": {
"key": "customer.a",
"value": "customer.b + 1"
} } },
{ "id": "e-b", "type": "expression", "props": { "data": {
"key": "customer.b",
"value": "customer.a + 1"
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "seed": 1 } }), vec![], false);
let _ = ws.evaluate(&req);
}
fn writes_reads_doc() -> serde_json::Value {
json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "score", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-base", "type": "expression", "props": { "data": {
"key": "customer.base",
"value": "customer.score * 2"
} } },
{ "id": "a-rich", "type": "assertion", "props": { "data": {
"output": "customer.rich",
"conditions": [
{ "id": "c1", "expression": "customer.base >= 30", "operator": "and", "depth": 0 }
]
} } },
{ "id": "m-tier", "type": "match", "props": { "data": {
"key": "customer.tier",
"arms": [
{ "id": "hi", "condition": "customer.rich", "value": "\"gold\"" },
{ "id": "lo", "condition": "", "value": "\"basic\"" }
]
} } },
{ "id": "dt", "type": "decisionTable", "props": { "data": {
"hitPolicy": "first",
"inputs": [
{ "id": "i1", "name": "", "field": "customer.tier" }
],
"outputs": [
{ "id": "o1", "name": "", "field": "customer.discount" }
],
"rules": [
{ "i1": "\"gold\"", "o1": "customer.base * 0.01" },
{ "i1": "", "o1": "0" }
]
} } }
]
})
}
fn writes_of(ex: &zen_engine::policy::BlockExecution) -> Vec<(String, serde_json::Value)> {
ex.writes
.iter()
.map(|w| (w.path.to_string(), w.value.clone().into()))
.collect()
}
fn reads_of(ex: &zen_engine::policy::BlockExecution) -> Vec<String> {
ex.reads.iter().map(|r| r.to_string()).collect()
}
#[test]
fn trace_records_writes_and_reads_per_execution() {
let ws = workspace_with(writes_reads_doc());
let req = request(json!({ "customer": { "score": 20 } }), vec![], true);
let result = ws.enhance_trace(&req).expect("evaluate succeeded");
let trace = result.trace.expect("trace populated");
let by_id = |id: &str| {
trace
.executions
.iter()
.find(|e| e.block_id.as_ref() == id)
.unwrap_or_else(|| panic!("{id} missing from trace"))
};
let expression = by_id("e-base");
assert_eq!(
writes_of(expression),
vec![("customer.base".to_string(), json!(40))]
);
assert_eq!(reads_of(expression), vec!["customer.score"]);
let assertion = by_id("a-rich");
assert_eq!(
writes_of(assertion),
vec![("customer.rich".to_string(), json!(true))]
);
assert_eq!(reads_of(assertion), vec!["customer.base"]);
let match_block = by_id("m-tier");
assert_eq!(
writes_of(match_block),
vec![("customer.tier".to_string(), json!("gold"))]
);
assert_eq!(reads_of(match_block), vec!["customer.rich"]);
let table = by_id("dt");
assert_eq!(
writes_of(table),
vec![("customer.discount".to_string(), json!(0.4))]
);
assert_eq!(reads_of(table), vec!["customer.base", "customer.tier"]);
}
#[test]
fn collect_table_trace_records_final_array_write() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "score", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "dt-collect", "type": "decisionTable", "props": { "data": {
"hitPolicy": "collect",
"inputs": [
{ "id": "i1", "name": "", "field": "customer.score" }
],
"outputs": [
{ "id": "o1", "name": "", "field": "customer.flags" }
],
"rules": [
{ "i1": ">= 10", "o1": "\"big\"" },
{ "i1": ">= 0", "o1": "\"pos\"" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "score": 20 } }), vec![], true);
let result = ws.enhance_trace(&req).expect("evaluate succeeded");
let trace = result.trace.expect("trace populated");
let table = trace
.executions
.iter()
.find(|e| e.block_id.as_ref() == "dt-collect")
.expect("dt-collect missing from trace");
assert_eq!(
writes_of(table),
vec![("customer.flags".to_string(), json!(["big", "pos"]))]
);
}
#[test]
fn iterated_trace_records_writes_and_reads_per_instance() {
let doc = json!({
"blocks": [
{ "id": "dm-customer", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "companies", "type": "relationship", "target": "company", "array": true, "optional": false }
]
} } },
{ "id": "dm-company", "type": "dataModel", "props": { "data": {
"name": "company",
"properties": [
{ "id": "p2", "name": "revenue", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-rate", "type": "expression", "props": { "data": {
"key": "customer.rate",
"value": "2"
} } },
{ "id": "m-bonus", "type": "match", "props": { "data": {
"key": "company.bonus",
"arms": [
{ "id": "hi", "condition": "company.revenue >= 100", "value": "company.revenue * customer.rate" },
{ "id": "lo", "condition": "", "value": "0" }
]
} } }
]
});
let ws = workspace_with(doc);
let req = request(
json!({ "customer": { "companies": [ { "revenue": 200 }, { "revenue": 50 } ] } }),
vec![],
true,
);
let result = ws.enhance_trace(&req).expect("evaluate succeeded");
let trace = result.trace.expect("trace populated");
let instances: Vec<_> = trace
.executions
.iter()
.filter(|e| e.block_id.as_ref() == "m-bonus")
.collect();
assert_eq!(instances.len(), 2);
assert_eq!(
instances[0].instance_path.as_deref(),
Some("customer.companies.0")
);
assert_eq!(
writes_of(instances[0]),
vec![("company.bonus".to_string(), json!(400))]
);
assert_eq!(
reads_of(instances[0]),
vec!["company.revenue", "customer.rate"]
);
assert_eq!(
instances[1].instance_path.as_deref(),
Some("customer.companies.1")
);
assert_eq!(
writes_of(instances[1]),
vec![("company.bonus".to_string(), json!(0))]
);
assert_eq!(reads_of(instances[1]), vec!["company.revenue"]);
}
fn nested_totals_doc() -> serde_json::Value {
json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "base", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-ta", "type": "expression", "props": { "data": {
"key": "totals.a",
"value": "customer.base * 2"
} } },
{ "id": "e-tb", "type": "expression", "props": { "data": {
"key": "totals.b",
"value": "customer.base * 3"
} } }
]
})
}
#[test]
fn sibling_writers_under_shared_nested_root_all_execute() {
let ws = workspace_with(nested_totals_doc());
let req = request(json!({ "customer": { "base": 10 } }), vec![], true);
let executed = executed_block_ids(&ws, &req);
assert!(
executed.contains(&"e-ta".to_string()),
"first sibling writer must run: {executed:?}"
);
assert!(
executed.contains(&"e-tb".to_string()),
"second sibling writer must run: {executed:?}"
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/totals/a"), Some(&json!(20)));
assert_eq!(output.pointer("/totals/b"), Some(&json!(30)));
}
#[test]
fn goal_on_shared_nested_root_runs_all_sibling_writers() {
let ws = workspace_with(nested_totals_doc());
let req = request(json!({ "customer": { "base": 10 } }), vec!["totals"], true);
let executed = executed_block_ids(&ws, &req);
assert!(executed.contains(&"e-ta".to_string()));
assert!(
executed.contains(&"e-tb".to_string()),
"ancestor goal must fan out to every descendant writer: {executed:?}"
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/totals/a"), Some(&json!(20)));
assert_eq!(output.pointer("/totals/b"), Some(&json!(30)));
}
#[test]
fn entity_rooted_sibling_writers_under_shared_nested_root_all_execute() {
let doc = json!({
"blocks": [
{ "id": "dm", "type": "dataModel", "props": { "data": {
"name": "customer",
"properties": [
{ "id": "p1", "name": "base", "type": "number", "array": false, "optional": false }
]
} } },
{ "id": "e-ta", "type": "expression", "props": { "data": {
"key": "customer.totals.a",
"value": "customer.base * 2"
} } },
{ "id": "e-tb", "type": "expression", "props": { "data": {
"key": "customer.totals.b",
"value": "customer.base * 3"
} } }
]
});
let ws = workspace_with(doc);
let req = request(json!({ "customer": { "base": 10 } }), vec![], true);
let executed = executed_block_ids(&ws, &req);
assert!(executed.contains(&"e-ta".to_string()));
assert!(
executed.contains(&"e-tb".to_string()),
"entity-rooted sibling writer must run: {executed:?}"
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/customer/totals/a"), Some(&json!(20)));
assert_eq!(output.pointer("/customer/totals/b"), Some(&json!(30)));
}
#[test]
fn goal_on_nested_leaf_prunes_sibling_writer() {
let ws = workspace_with(nested_totals_doc());
let req = request(
json!({ "customer": { "base": 10 } }),
vec!["totals.a"],
true,
);
let executed = executed_block_ids(&ws, &req);
assert!(executed.contains(&"e-ta".to_string()));
assert!(
!executed.contains(&"e-tb".to_string()),
"leaf goal must stay lazy and skip the sibling writer: {executed:?}"
);
let result = ws.evaluate(&req).expect("evaluate succeeded");
let output: serde_json::Value = result.output.into();
assert_eq!(output.pointer("/totals/a"), Some(&json!(20)));
assert_eq!(output.pointer("/totals/b"), None);
}