Skip to main content

Module diff

Module diff 

Source
Expand description

Pre-merge behavior-diff explainer for shieldset changes.

This module implements aperion-shield --diff, the native Rust port of scripts/shield-diff.py (the Python prototype shipped alongside docs/shieldset-as-code.md). Both produce a source-compatible JSON output schema, so CI wired up against the Python prototype keeps working unchanged when the flag flips to aperion-shield --diff.

§Why this exists

Shieldset changes are policy changes – they need PR review like code, but diff shieldset.before.yaml shieldset.after.yaml only tells you the YAML changed. It does not tell you which calls in the real corpus will now flip from allow to block, or worse, from block to allow. That is what this mode is for.

§Pipeline

  shieldset.before.yaml ─┐
  shieldset.after.yaml  ─┤── load Engine x2  ── evaluate corpus x2
  corpus.jsonl          ─┘                       │
                                                 v
                                         DecisionLine sets x2
                                                 │
                                                 v
                      diff rulesets (added/removed/modified/unchanged)
                                                 │
                                                 v
                      pair decisions by index, attribute every flip to
                      the changed rule(s) that fired under the after-state
                                                 │
                                                 v
                      render text / markdown / json

§In-process, not subprocess

The Python prototype shells out to aperion-shield --check twice. This native port skips the subprocess: both runs use the same Engine::evaluate path the proxy uses. It is materially faster on big corpora (no JSON re-encode + re-decode trip per line) and removes the runtime PATH dependency the Python prototype carried.

Re-exports§

pub use evaluate::evaluate_corpus;
pub use evaluate::DecisionLine;
pub use evaluate::EvalOptions;

Modules§

evaluate
In-process corpus evaluation.
render
Output formatters for aperion-shield --diff.

Structs§

DiffOptions
CLI-level options for aperion-shield --diff. Mirrors the Python prototype 1:1 so the --format json output schema stays source-compatible.
RuleDelta
Per-rule change: YAML-level (textual) + behavioral (corpus-level). Mirrors shield-diff.py::RuleDelta. Serialised in --format json output – keep the field names stable.

Enums§

OutputFormat
Output format for the diff report.

Constants§

DECISIONS

Functions§

diff_rulesets
Classify every rule that appears in either ruleset. The YAML diff is rendered eagerly so we don’t pay the cost twice if the renderer is asked to embed it.
flips_to_allow
How many flipped lines ended at allow. Used by --fail-if-allows-loosened.
load_ruleset_yaml
Parse a shieldset YAML file into a BTreeMap<rule_id, rule_body> where rule_body is the rule’s YAML node MINUS its id field. Used for diffing rules textually. Tolerates both the wrapped (shieldset:\n rules:) and bare (rules:) forms, matching the Python prototype.
loosening_count
How many flipped lines moved toward a more permissive decision. identity_verification counts at the same severity as approval because both gate the call before it runs upstream.
populate_behavior
Walk paired before/after decision lists, fill in fires_before / fires_after, build the global flip counter, attribute each flip to the rule(s) that materially changed under the after-state, and return the global flip counter.
run_diff_mode
Top-level entry point for aperion-shield --diff. Returns the shell exit code: 0 for success, 1 for a policy-gate trip (e.g. --fail-if-flipped), 2 for an internal / I/O error.
yaml_dump_rule
Dump one rule (id + body) back to YAML for textual diffing. Always emits id first to keep the diff stable across runs.

Type Aliases§

FlipCounter
Aggregate counter: (decision_before, decision_after) -> count. Lexically ordered so render order is deterministic.