use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::exit;
use behavior_contracts::behavior::{run_behavior, ComponentExec};
use behavior_contracts::canonical::{
canonical_json, canonical_value, py_float_repr, CanonicalFailure, CanonicalFailureCode,
};
use behavior_contracts::codec::{decode_value, encode_value};
use behavior_contracts::expr::{evaluate as evaluate_expression, ExprFailure};
use behavior_contracts::guard::assert_portable_component_graph;
use behavior_contracts::plan::{
run_plan, run_plan_parallel, ExecOutcome, ExecutionPlanSpec, OpSpec, PlanFailure, RelationKind,
};
use behavior_contracts::template::{render_template, TemplateFailure};
use behavior_contracts::value::{deep_equals, Value};
use behavior_contracts::SpecVersions;
use serde_json::Value as J;
fn vectors_dir() -> PathBuf {
if let Ok(env) = std::env::var("DSL_CONTRACTS_VECTORS") {
return PathBuf::from(env);
}
let manifest = env!("CARGO_MANIFEST_DIR");
Path::new(manifest)
.parent()
.unwrap()
.join("conformance")
.join("vectors")
}
fn load_json(dir: &Path, file: &str) -> J {
let raw = std::fs::read_to_string(dir.join(file))
.unwrap_or_else(|e| panic!("cannot read {file}: {e}"));
serde_json::from_str(&raw).unwrap_or_else(|e| panic!("cannot parse {file}: {e}"))
}
struct Tally {
passed: u32,
failed: u32,
}
fn line(ok: bool, name: &str, detail: &str) {
if ok {
println!(" \u{2713} {name}");
} else {
println!(" \u{2717} {name}");
if !detail.is_empty() {
println!(" {detail}");
}
}
}
fn bump(t: &mut Tally, ok: bool) {
if ok {
t.passed += 1;
} else {
t.failed += 1;
}
}
fn preflight(dir: &Path) -> std::collections::HashMap<&'static str, J> {
let specs: [(&str, &str, &str, i64); 7] = [
(
"expression.json",
"expression",
"exprVersion",
SpecVersions::EXPRESSION,
),
(
"template.json",
"template",
"templateVersion",
SpecVersions::TEMPLATE,
),
("plan.json", "plan", "planVersion", SpecVersions::PLAN),
(
"canonical.json",
"canonical",
"canonicalVersion",
SpecVersions::CANONICAL,
),
(
"behavior.json",
"behavior",
"behaviorVersion",
SpecVersions::BEHAVIOR,
),
("guard.json", "guard", "guardVersion", SpecVersions::GUARD),
("c2-catalog-swap.json", "c2", "c2Version", SpecVersions::C2),
];
let loaded: Vec<(&str, &str, i64, J)> = specs
.iter()
.map(|(f, suite, vk, want)| (*suite, *vk, *want, load_json(dir, f)))
.collect();
let mismatches: Vec<(&str, i64, i64)> = loaded
.iter()
.filter_map(|(suite, vk, want, doc)| {
let got = doc.get(*vk).and_then(|v| v.as_i64()).unwrap_or(-1);
if got != *want {
Some((*suite, got, *want))
} else {
None
}
})
.collect();
if !mismatches.is_empty() {
for (suite, got, want) in &mismatches {
eprintln!("FAIL-CLOSED: {suite} suite version {got} != supported {want}.");
}
eprintln!(
"Refusing to run: {} suite version mismatch(es). No vectors executed.",
mismatches.len()
);
exit(2);
}
loaded
.into_iter()
.map(|(suite, _, _, doc)| (suite, doc))
.collect()
}
fn as_scope(v: Option<&J>) -> Vec<(String, Value)> {
match v {
Some(J::Object(o)) => o
.iter()
.map(|(k, val)| (k.clone(), decode_value(val).expect("decode scope")))
.collect(),
_ => Vec::new(),
}
}
fn run_expression(t: &mut Tally, doc: &J) {
let vectors = doc["vectors"].as_array().unwrap();
println!(
"\nexpression.json (v{}) — {} vectors",
doc["exprVersion"],
vectors.len()
);
for v in vectors {
let name = v["name"].as_str().unwrap_or("?");
let scope = as_scope(v.get("scope"));
let expect = &v["expect"];
let (mut ok, mut detail) = (false, String::new());
match evaluate_expression(&v["expr"], &scope) {
Ok(result) => {
if let Some(exp) = expect.get("value") {
let want = decode_value(exp).expect("decode expect.value");
ok = deep_equals(&result, &want);
if !ok {
detail = format!("expected value {}, got {}", exp, encode_value(&result));
}
} else {
detail = format!("expected Failure({}), got a value", expect["failure"]);
}
}
Err(e) => {
let e: ExprFailure = e;
if let Some(f) = expect.get("failure").and_then(|f| f.as_str()) {
ok = e.code.as_str() == f;
if !ok {
detail = format!("expected Failure({f}), got Failure({})", e.code.as_str());
}
} else {
detail = format!("expected a value, got Failure({})", e.code.as_str());
}
}
}
line(ok, name, &detail);
bump(t, ok);
}
}
fn run_template(t: &mut Tally, doc: &J) {
let vectors = doc["vectors"].as_array().unwrap();
println!(
"\ntemplate.json (v{}) — {} vectors",
doc["templateVersion"],
vectors.len()
);
for v in vectors {
let name = v["name"].as_str().unwrap_or("?");
let tmpl = v["template"].as_str().unwrap();
let params: Vec<(String, Value)> = v["params"]
.as_object()
.unwrap()
.iter()
.map(|(k, val)| (k.clone(), decode_value(val).expect("decode param")))
.collect();
let expect = &v["expect"];
let (mut ok, mut detail) = (false, String::new());
match render_template(tmpl, ¶ms) {
Ok(out) => {
if let Some(exp) = expect.get("ok").and_then(|x| x.as_str()) {
ok = out == exp;
if !ok {
detail = format!("expected {exp:?}, got {out:?}");
}
} else {
detail = format!("expected Failure({}), got {out:?}", expect["failure"]);
}
}
Err(e) => {
let e: TemplateFailure = e;
if let Some(f) = expect.get("failure").and_then(|f| f.as_str()) {
ok = e.code.as_str() == f;
if !ok {
detail = format!("expected Failure({f}), got Failure({})", e.code.as_str());
}
} else {
detail = format!("expected value, got Failure({})", e.code.as_str());
}
}
}
line(ok, name, &detail);
bump(t, ok);
}
}
fn run_canonical(t: &mut Tally, doc: &J) {
let vectors = doc["vectors"].as_array().unwrap();
println!(
"\ncanonical.json (v{}) — {} vectors",
doc["canonicalVersion"],
vectors.len()
);
for v in vectors {
let name = v["name"].as_str().unwrap_or("?");
let kind = v["kind"].as_str().unwrap();
let expect = &v["expect"];
let (mut ok, mut detail) = (false, String::new());
let result: Result<String, CanonicalFailure> = match decode_value(&v["value"]) {
Ok(val) => match kind {
"canonicalValue" => canonical_value(&val),
"canonicalJson" => canonical_json(&val),
"floatRepr" => match &val {
Value::Float(f) => py_float_repr(*f),
other => panic!("floatRepr expects a float, got {}", other.type_name()),
},
other => panic!("unknown kind: {other}"),
},
Err(e) => match e.code {
Some(code) => Err(CanonicalFailure {
code: match code {
"FORBIDDEN_KEY" => CanonicalFailureCode::ForbiddenKey,
_ => CanonicalFailureCode::InvalidValue,
},
message: e.msg,
}),
None => panic!("decode value: {}", e.msg),
},
};
match result {
Ok(out) => {
if let Some(exp) = expect.get("ok").and_then(|x| x.as_str()) {
ok = out == exp;
if !ok {
detail = format!("expected {exp:?}, got {out:?}");
}
} else {
detail = format!("expected Failure({}), got {out:?}", expect["failure"]);
}
}
Err(e) => {
if let Some(f) = expect.get("failure").and_then(|f| f.as_str()) {
ok = e.code.as_str() == f;
if !ok {
detail = format!("expected Failure({f}), got Failure({})", e.code.as_str());
}
} else {
detail = format!("expected value, got Failure({})", e.code.as_str());
}
}
}
line(ok, name, &detail);
bump(t, ok);
}
}
fn parse_op(o: &J) -> OpSpec {
let relation_kind = o
.get("relationKind")
.and_then(|r| r.as_str())
.map(|s| match s {
"connection" => RelationKind::Connection,
_ => RelationKind::Single,
});
OpSpec {
id: o["id"].as_str().unwrap().to_string(),
parent: o.get("parent").and_then(|p| p.as_u64()).map(|p| p as usize),
bind_field: o
.get("bindField")
.and_then(|b| b.as_str())
.map(|s| s.to_string()),
relation_kind,
policy: o
.get("policy")
.and_then(|p| p.as_str())
.map(|s| s.to_string()),
}
}
fn parse_plan(p: &J) -> Option<ExecutionPlanSpec> {
if p.is_null() {
return None;
}
let groups = p["groups"]
.as_array()
.unwrap()
.iter()
.map(|g| {
g.as_array()
.unwrap()
.iter()
.map(|i| i.as_u64().unwrap() as usize)
.collect()
})
.collect();
let concurrency = p.get("concurrency").and_then(|c| c.as_i64()).unwrap_or(1);
Some(ExecutionPlanSpec {
groups,
concurrency,
})
}
fn same_set(a: &[String], b: &[J]) -> bool {
let mut sa: Vec<&str> = a.iter().map(|s| s.as_str()).collect();
let mut sb: Vec<&str> = b.iter().map(|s| s.as_str().unwrap()).collect();
sa.sort_unstable();
sb.sort_unstable();
sa == sb
}
fn run_plan_suite(t: &mut Tally, doc: &J) {
let vectors = doc["vectors"].as_array().unwrap();
println!(
"\nplan.json (v{}) — {} vectors",
doc["planVersion"],
vectors.len()
);
for v in vectors {
let name = v["name"].as_str().unwrap_or("?");
let plan = parse_plan(v.get("plan").unwrap_or(&J::Null));
let ops: Vec<OpSpec> = v["ops"].as_array().unwrap().iter().map(parse_op).collect();
let exec_map = v["exec"].as_object().unwrap();
let expect = &v["expect"];
let (mut ok, mut detail) = (false, String::new());
let exec = |op: &OpSpec, _bound: Option<&Value>| -> ExecOutcome {
let o = exec_map
.get(&op.id)
.unwrap_or_else(|| panic!("no mock outcome for op '{}'", op.id));
if let Some(okv) = o.get("ok") {
ExecOutcome::Ok(decode_value(okv).expect("decode exec.ok"))
} else {
ExecOutcome::Error(o["error"].as_str().unwrap_or("").to_string())
}
};
let par = run_plan_parallel(plan.as_ref(), &ops, exec);
let seq = run_plan(plan.as_ref(), &ops, |op: &OpSpec, b: Option<&Value>| {
exec(op, b)
});
let paths_agree = match (&seq, &par) {
(Ok(a), Ok(b)) => {
let ta = canonical_json(&Value::Obj(a.final_tree())).expect("canonical seq tree");
let tb = canonical_json(&Value::Obj(b.final_tree())).expect("canonical par tree");
ta == tb && a.executed == b.executed && a.skipped == b.skipped
}
(Err(a), Err(b)) => a.code == b.code && a.message == b.message,
_ => false,
};
if !paths_agree {
line(false, name, "run_plan_parallel diverged from run_plan");
bump(t, false);
continue;
}
match seq {
Ok(res) => {
if let Some(f) = expect.get("failure") {
detail = format!("expected Failure({f}), got a full run");
} else {
let tree = Value::Obj(res.final_tree());
let exp_tree_pairs: Vec<(String, Value)> = expect["tree"]
.as_object()
.unwrap()
.iter()
.map(|(k, val)| (k.clone(), decode_value(val).expect("decode expect.tree")))
.collect();
let exp_tree = Value::Obj(exp_tree_pairs);
let got_c = canonical_json(&tree).expect("canonical tree");
let want_c = canonical_json(&exp_tree).expect("canonical exp tree");
let tree_ok = got_c == want_c;
let exec_ok = same_set(&res.executed, expect["executed"].as_array().unwrap());
let skip_ok = same_set(&res.skipped, expect["skipped"].as_array().unwrap());
ok = tree_ok && exec_ok && skip_ok;
if !ok {
let mut parts = Vec::new();
if !tree_ok {
parts.push(format!("tree {got_c} != {want_c}"));
}
if !exec_ok {
parts.push(format!(
"executed {:?} != {}",
res.executed, expect["executed"]
));
}
if !skip_ok {
parts.push(format!(
"skipped {:?} != {}",
res.skipped, expect["skipped"]
));
}
detail = parts.join("; ");
}
}
}
Err(e) => {
let e: PlanFailure = e;
if let Some(f) = expect.get("failure").and_then(|f| f.as_str()) {
ok = e.code.as_str() == f;
if !ok {
detail = format!("expected Failure({f}), got Failure({})", e.code.as_str());
}
} else {
detail = format!("expected a full run, got Failure({})", e.code.as_str());
}
}
}
line(ok, name, &detail);
bump(t, ok);
}
}
struct ScriptedHandlers {
queues: HashMap<String, (Vec<J>, bool)>,
}
impl ScriptedHandlers {
fn from_spec(spec: &serde_json::Map<String, J>) -> Self {
let mut queues = HashMap::new();
for (name, raw) in spec {
let (items, single) = match raw {
J::Array(a) => (a.clone(), false),
other => (vec![other.clone()], true),
};
queues.insert(name.clone(), (items, single));
}
ScriptedHandlers { queues }
}
}
impl ComponentExec for ScriptedHandlers {
fn exec(
&mut self,
component: &str,
ports: &[(String, Value)],
bound: Option<&Value>,
) -> Option<ExecOutcome> {
self.exec_ctx("", component, ports, bound)
}
fn exec_ctx(
&mut self,
node_id: &str,
component: &str,
ports: &[(String, Value)],
_bound: Option<&Value>,
) -> Option<ExecOutcome> {
let (queue, single) = self.queues.get_mut(component)?;
let raw = if *single || queue.len() <= 1 {
queue[0].clone()
} else {
queue.remove(0)
};
match raw.get("echo").and_then(|e| e.as_str()) {
Some("ctx") => {
return Some(ExecOutcome::Ok(Value::Obj(vec![
("nodeId".to_string(), Value::Str(node_id.to_string())),
("component".to_string(), Value::Str(component.to_string())),
])));
}
Some("items") => {
let items = ports
.iter()
.find(|(k, _)| k == "items")
.map(|(_, v)| v.clone())
.unwrap_or(Value::Null);
return Some(ExecOutcome::Ok(items));
}
_ => {}
}
if let Some(okv) = raw.get("ok") {
Some(ExecOutcome::Ok(
decode_value(okv).expect("decode handler ok"),
))
} else {
Some(ExecOutcome::Error(
raw.get("error")
.and_then(|e| e.as_str())
.unwrap_or("")
.to_string(),
))
}
}
}
fn run_behavior_suite(t: &mut Tally, doc: &J) {
let vectors = doc["vectors"].as_array().unwrap();
println!(
"\nbehavior.json (v{}) — {} vectors",
doc["behaviorVersion"],
vectors.len()
);
for v in vectors {
let name = v["name"].as_str().unwrap_or("?");
let ir = &v["ir"];
let input: Vec<(String, Value)> = match v.get("input") {
Some(J::Object(o)) => o
.iter()
.map(|(k, val)| (k.clone(), decode_value(val).expect("decode input")))
.collect(),
_ => Vec::new(),
};
let empty = serde_json::Map::new();
let handlers_spec = v
.get("handlers")
.and_then(|h| h.as_object())
.unwrap_or(&empty);
let mut handlers = ScriptedHandlers::from_spec(handlers_spec);
let entry = v.get("entry").and_then(|e| e.as_str());
let expect = &v["expect"];
let (mut ok, mut detail) = (false, String::new());
match run_behavior(ir, &mut handlers, &input, entry) {
Ok(result) => {
if let Some(exp) = expect.get("value") {
let want = decode_value(exp).expect("decode expect.value");
ok = deep_equals(&result, &want);
if !ok {
detail = format!("expected value {}, got {}", exp, encode_value(&result));
}
} else {
detail = format!("expected Failure({}), got a value", expect["failure"]);
}
}
Err(e) => {
if let Some(f) = expect.get("failure").and_then(|f| f.as_str()) {
ok = e.code() == f;
if !ok {
detail = format!("expected Failure({f}), got Failure({})", e.code());
}
} else {
detail = format!("expected a value, got Failure({})", e.code());
}
}
}
line(ok, name, &detail);
bump(t, ok);
}
}
fn run_guard_suite(t: &mut Tally, doc: &J) {
let vectors = doc["vectors"].as_array().unwrap();
println!(
"\nguard.json (v{}) — {} vectors",
doc["guardVersion"],
vectors.len()
);
for v in vectors {
let name = v["name"].as_str().unwrap_or("?");
let expect = &v["expect"];
let (mut ok, mut detail) = (false, String::new());
match assert_portable_component_graph(&v["ir"]) {
Ok(()) => {
if expect.get("ok").is_some() {
ok = true;
} else {
detail = format!(
"expected Failure({} at {}), guard passed",
expect["failure"], expect["path"]
);
}
}
Err(e) => {
if let Some(f) = expect.get("failure").and_then(|f| f.as_str()) {
let want_path = expect.get("path").and_then(|p| p.as_str()).unwrap_or("");
ok = f == "PORTABILITY" && e.path == want_path;
if !ok {
detail = format!("expected reject path {want_path}, got {}", e.path);
}
} else {
detail = format!("expected guard to pass, got PORTABILITY at {}", e.path);
}
}
}
line(ok, name, &detail);
bump(t, ok);
}
}
fn c2_normalize(ir: &mut J, role_by_name: &HashMap<String, String>) -> Result<(), String> {
let comps = ir
.get_mut("components")
.and_then(|c| c.as_array_mut())
.ok_or("c2 ir: components must be an array")?;
for c in comps {
let body = c
.get_mut("body")
.and_then(|b| b.as_array_mut())
.ok_or("c2 ir: body must be an array")?;
for n in body {
let target = if n.get("map").is_some() {
n.get_mut("map").unwrap()
} else {
n
};
if let Some(comp) = target.get("component") {
let comp_name = comp
.as_str()
.ok_or("c2 ir: component must be a string")?
.to_string();
let role = role_by_name
.get(&comp_name)
.ok_or_else(|| format!("unmapped component '{comp_name}'"))?;
*target.get_mut("component").unwrap() = J::String(role.clone());
}
}
}
Ok(())
}
fn run_c2_suite(t: &mut Tally, doc: &J) {
let consumers = doc["consumers"].as_object().unwrap();
let mut consumer_names: Vec<&String> = consumers.keys().collect();
consumer_names.sort();
let n_vec: usize = consumer_names
.iter()
.map(|c| consumers[*c]["vectors"].as_array().unwrap().len())
.sum();
println!(
"\nc2-catalog-swap.json (v{}) — {n_vec} vectors + structural",
doc["c2Version"]
);
for cname in &consumer_names {
for v in consumers[*cname]["vectors"].as_array().unwrap() {
let name = format!("{cname}: {}", v["name"].as_str().unwrap_or("?"));
let (mut ok, mut detail) = (false, String::new());
if let Err(e) = assert_portable_component_graph(&v["ir"]) {
line(
false,
&name,
&format!("shared guard rejected consumer IR: {e}"),
);
bump(t, false);
continue;
}
let input: Vec<(String, Value)> = match v.get("input") {
Some(J::Object(o)) => o
.iter()
.map(|(k, val)| (k.clone(), decode_value(val).expect("decode input")))
.collect(),
_ => Vec::new(),
};
let empty = serde_json::Map::new();
let handlers_spec = v
.get("handlers")
.and_then(|h| h.as_object())
.unwrap_or(&empty);
let mut handlers = ScriptedHandlers::from_spec(handlers_spec);
let entry = v.get("entry").and_then(|e| e.as_str());
match run_behavior(&v["ir"], &mut handlers, &input, entry) {
Ok(result) => {
let want = decode_value(&v["expect"]["value"]).expect("decode expect.value");
ok = deep_equals(&result, &want);
if !ok {
detail = format!(
"expected value {}, got {}",
v["expect"]["value"],
encode_value(&result)
);
}
}
Err(e) => {
detail = format!("expected a value, got Failure({})", e.code());
}
}
line(ok, &name, &detail);
bump(t, ok);
}
}
let (a, b) = (consumer_names[0], consumer_names[1]);
let role_of = |cname: &str| -> HashMap<String, String> {
consumers[cname]["catalog"]
.as_object()
.unwrap()
.iter()
.map(|(role, name)| (name.as_str().unwrap().to_string(), role.clone()))
.collect()
};
let va = consumers[a.as_str()]["vectors"].as_array().unwrap();
let vb = consumers[b.as_str()]["vectors"].as_array().unwrap();
for i in 0..va.len().min(vb.len()) {
let (mut ok, mut detail) = (false, String::new());
let mut ir_a = va[i]["ir"].clone();
let mut ir_b = vb[i]["ir"].clone();
if ir_a == ir_b {
detail = "raw IRs must differ before normalization".to_string();
} else {
match c2_normalize(&mut ir_a, &role_of(a))
.and_then(|()| c2_normalize(&mut ir_b, &role_of(b)))
{
Ok(()) => {
ok = ir_a == ir_b;
if !ok {
detail = "IRs must be equal modulo catalog names".to_string();
}
}
Err(e) => detail = e,
}
}
line(
ok,
&format!(
"structural[{i}]: IR equal modulo catalog ({})",
va[i]["name"].as_str().unwrap_or("?")
),
&detail,
);
bump(t, ok);
}
}
fn main() {
println!("dsl-contracts conformance kit — Rust runner");
let dir = vectors_dir();
let docs = preflight(&dir);
let mut t = Tally {
passed: 0,
failed: 0,
};
run_expression(&mut t, &docs["expression"]);
run_template(&mut t, &docs["template"]);
run_plan_suite(&mut t, &docs["plan"]);
run_canonical(&mut t, &docs["canonical"]);
run_behavior_suite(&mut t, &docs["behavior"]);
run_guard_suite(&mut t, &docs["guard"]);
run_c2_suite(&mut t, &docs["c2"]);
let total = t.passed + t.failed;
println!(
"\n{} passed, {} failed / {total} vectors across 7 suites",
t.passed, t.failed
);
exit(if t.failed > 0 { 1 } else { 0 });
}