Skip to content
Arxo Arxo

Policy Evaluation

The engine evaluates policy invariants against metric results after analysis. You define rules (metric key, operator, threshold); the engine reports policy violations when a metric value does not satisfy the rule. This is ideal for CI/CD gates and quality gates.

Policy is configured via PolicyConfig (from arxo-types::config::schema):

policy:
invariants:
- metric: "scc.cycle_count"
op: "<="
value: 0
- metric: "propagation_cost.system.ratio"
op: "<="
value: 0.15
- metric: "layer_violations.violations_count"
op: "<="
value: 5

Each invariant has:

  • metric (string): Metric value key from the computed metrics (e.g. scc.cycle_count, layer_violations.violations_count, propagation_cost.system.ratio). Must match a key produced by an enabled metric.
  • op (operator): Comparison operator.
  • value (number): Threshold to compare against.

Supported operators (from Operator in arxo-types::config::schema):

YAML/JSONMeaning
<=Actual value must be less than or equal to threshold
>=Actual value must be greater than or equal to threshold
==Actual value must equal threshold
<Actual value must be less than threshold
>Actual value must be greater than threshold

When you run analysis (e.g. via Orchestrator::run), the result is OrchestrationResult. Policy violations are in violations:

use arxo_engine::core::OrchestrationResult;
fn check_policy(result: &OrchestrationResult) {
if result.violations.is_empty() {
println!("All policy invariants passed");
return;
}
for v in &result.violations {
println!(
"Policy violation: metric={} actual={:.2} op={:?} expected={:.2}",
v.metric, v.actual, v.op, v.expected
);
}
}

Each PolicyViolation (from the engine’s policy module) has:

  • metric: The invariant’s metric key.
  • expected: The threshold from the invariant (value).
  • actual: The value from the metric results (or NaN if the metric was not computed).
  • op: The operator from the invariant.

If a metric key is not present in the results (e.g. metric disabled or not run), the engine treats it as a violation with actual: NaN.

The engine can evaluate invariants in fail-fast mode: as soon as one invariant fails, evaluation stops and only that violation (and any already collected) is returned. This is useful in CI to fail quickly. Whether fail-fast is used is an internal option when the engine calls the policy evaluator; when integrating, you can implement fail-fast yourself by stopping on the first violation:

fn ci_gate(result: &OrchestrationResult) -> Result<(), String> {
if let Some(v) = result.violations.first() {
return Err(format!(
"Policy failed: {} = {} (expected {:?} {})",
v.metric, v.actual, v.op, v.expected
));
}
Ok(())
}

Use the same keys that appear in MetricResult::values from each metric. Examples:

  • SCC: scc.cycle_count, scc.max_cycle_size
  • Layer/effect violations: layer_violations.violations_count, layer_violations.effect_violations, effect_violations.violation_count, effect_violations.capability_leaks, effect_violations.health
  • Propagation cost: propagation_cost.system.ratio, propagation_cost.structural.module.ratio
  • Centrality: keys from the centrality metric’s values map
  • Other metrics: Inspect the metric’s UI schema or result payload for key names

For complete effect-boundary key docs and formulas, see Effect Violations.

Presets (e.g. ci, quick) determine which metrics run; only keys from enabled metrics are available for policy.

When using the FFI or JSON output, the report payload includes policy violations (e.g. in the JSON report’s violations array and in SARIF as result objects). You can parse the output and enforce gates from any language.