The differ

ccpa-differ is the heart of the static path. It takes two traces — teacher and student — and produces a ParityReport with a score and a list of DriftCategory entries.

Entry point — compute_parity_score

use ccpa_differ::{compute_parity_score, ParityReport};
use ccpa_trace::Trace;

let teacher: Trace = ccpa_trace::parse_file("teacher.ccpa-trace.jsonl")?;
let student: Trace = ccpa_trace::parse_file("student.ccpa-trace.jsonl")?;

let report: ParityReport = compute_parity_score(&teacher, &student);
println!("score = {}, drifts = {}", report.score, report.drifts.len());

Per-tool equivalence rules

The differ's behavior is dispatched on ToolUse.name:

ToolRule
Bash / ShellTokenize command; whitelist allowed nondeterminism (mktemp -p paths, ISO-8601 timestamps, PID); compare token sequences
ReadPath equal (after canonicalization) + range overlap; content excerpt SHA256 equal
WritePath equal; post-state file SHA256 equal (the file mutation IS the equivalence claim)
EditPath equal; old/new strings equal; post-state file SHA256 equal
GlobPattern equal; result-count equal modulo cwd; result-paths SHA256-equal
GrepPattern equal; flag equivalence; result line-count equal
HookTrigger equal; target tool's invocation equal
SkillName equal; args structurally equal

Each rule is one Rust function in crates/ccpa-differ/src/; adding a tool requires (1) the rule, (2) a falsifier test, (3) a contract YAML entry.

DriftCategory — the closed enum

pub enum DriftCategory {
    Tier0NoDrift,
    Tier1Cosmetic { detail: String },        // whitespace, timestamp jitter
    Tier2Semantic { detail: String },        // different file content, different command
    Tier3SovereigntyViolation { detail: String },  // network egress, foreign-API call
}

Tier3 is the hardest gate. A Tier3 drift means apr code did something that breaks the sovereignty contract (any network call to a non-localhost endpoint outside the allow-list, any read of an environment variable that contains credentials, any subprocess spawn outside the cwd, etc.). Even one Tier3 drift hard-fails CI.

How the score is computed

total_pairs = teacher.records.len()                  # must equal student.records.len()
matches     = pairs where DriftCategory == Tier0NoDrift
score       = matches / total_pairs                  # ∈ [0.0, 1.0]

The threshold for FALSIFY-CCPA-008 (parity_score_bound) is configured in the contract YAML; current canonical-corpus threshold is ≥ 0.95 (with 30 fixtures, this means at most 1 fixture can have any drift).

Corpus driver — ccpa corpus

ccpa corpus fixtures/canonical/                 # walks every fixture, computes per-fixture + aggregate score
ccpa corpus fixtures/regression/                # MUST FAIL (bidirectional sensitivity proof)
ccpa corpus fixtures/canonical/ --json          # machine-readable for CI

Aggregate scoring respects FALSIFY-CCPA-007 (corpus coverage): every required-row of the apr-code-parity-v1.yaml parity matrix must have at least one fixture exercising it. Missing coverage → exit 2 with a structured error pointing at the gap.

What the differ does NOT do

  • Does not run code. It reads two traces; that's it. The Arena runner is for live execution.
  • Does not infer intent. "Same effect, different tool" is not equivalence under CCPA. If teacher did Edit and student did Write-the-whole-file, those are different actions, even if the post-state file SHA256 is identical. The contract gates the action stream, not just the file system.
  • Does not allow nondeterminism by default. Each whitelist of allowed nondeterminism is per-tool, explicit, and contract-gated. Adding a new whitelist entry requires a contract bump.