swactor/crates/simulation/tests/parity-bar/t_detector.rs
Zachery Aaron Shores-Chmielewski 1f8a67231c feat(sim): rebuild around discrete-event engine
Replaces the generic SimNode/gossip/dashboard framework with a virtual-time
discrete-event engine (priority queue ordered by time/node/fiber/seq), a TOML spec
parser, bundle writer, replay, divergence detector, lint, and postproc, plus the
parity-bar test harness with fixtures and xtask parity-lock tooling. Rewrites
transport identity/crypto and adds the SPEC/TESTING_SPEC/OBSERVABILITY/NORTH_STAR docs.


Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-05-21 23:48:02 +04:00

136 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! TESTING_SPEC §10 — Adversarial sim-detector.
//!
//! `sim_indistinguishable` runs the sim-detector as an additional
//! peer inside the reference scenario and asserts that no D01–D12
//! technique ever returns `DetectedSim`. `prod_baseline` runs the
//! detector against the prod facade and asserts the symmetric
//! invariant (no `DetectedProd`).
#[path = "common.rs"]
mod common;
use common::{reference_scenario_text, run_engine, workspace_root};
use std::process::Command;
// ── §10.2 — Sim run ────────────────────────────────────────────────
#[test]
fn sim_indistinguishable() {
// Build the reference scenario augmented with an extra
// `detector` host that runs the sim-detector binary. The bundle
// contains a `detector_report.json` after the run; the test
// asserts every D01–D12 verdict is `Indistinguishable` or
// `DetectedProd` (never `DetectedSim`).
let spec = augment_with_detector(&reference_scenario_text());
let bundle = run_engine(&spec, 51);
let report_path = bundle.root.join("detector").join("verdicts.json");
assert!(
report_path.is_file(),
"detector verdicts.json must be present at {}",
report_path.display()
);
let report = read_verdicts(&report_path);
let detected_sim: Vec<&Verdict> = report
.iter()
.filter(|v| v.outcome == "DetectedSim")
.collect();
assert!(
detected_sim.is_empty(),
"detector reported DetectedSim verdicts (TESTING_SPEC §10.2): {detected_sim:?}"
);
}
// ── §10.2 — Prod baseline ──────────────────────────────────────────
#[test]
fn prod_baseline() {
// Run the sim-detector binary directly against the prod facade
// (built with `--features facade-prod`). The detector's stdout
// is a JSON array of `{ id, outcome, evidence }` records. No
// technique may return `DetectedProd`.
let report = run_detector_prod();
let detected_prod: Vec<&Verdict> = report
.iter()
.filter(|v| v.outcome == "DetectedProd")
.collect();
assert!(
detected_prod.is_empty(),
"detector reported DetectedProd against the prod facade: {detected_prod:?}"
);
}
// ── Helpers ────────────────────────────────────────────────────────
#[derive(Debug)]
struct Verdict {
id: String,
outcome: String,
evidence: Option<String>,
}
fn read_verdicts(path: &std::path::Path) -> Vec<Verdict> {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let value: serde_json::Value =
serde_json::from_str(&text).expect("verdicts.json is JSON");
value
.as_array()
.expect("verdicts.json is an array")
.iter()
.map(|v| Verdict {
id: v["id"].as_str().expect("verdict.id is a string").to_string(),
outcome: v["outcome"]
.as_str()
.expect("verdict.outcome is a string")
.to_string(),
evidence: v["evidence"].as_str().map(String::from),
})
.collect()
}
fn augment_with_detector(spec: &str) -> String {
format!(
"{spec}\n[[hosts]]\nname = \"detector\"\nrole = \"detector\"\n\
start_at_ms = 0\nstop_at_ms = 60000\n"
)
}
fn run_detector_prod() -> Vec<Verdict> {
let cargo = option_env!("CARGO").unwrap_or("cargo");
let output = Command::new(cargo)
.args([
"run",
"-p",
"simulation",
"--bin",
"sim-detector",
"--quiet",
"--features",
"facade-prod",
"--",
"--emit-json",
])
.current_dir(workspace_root())
.output()
.expect("invoke sim-detector binary in prod mode");
assert!(
output.status.success(),
"sim-detector exited {} (prod): {}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
let text = String::from_utf8_lossy(&output.stdout).to_string();
let value: serde_json::Value =
serde_json::from_str(&text).expect("sim-detector emits JSON on --emit-json");
value
.as_array()
.expect("sim-detector JSON is an array")
.iter()
.map(|v| Verdict {
id: v["id"].as_str().expect("verdict.id").to_string(),
outcome: v["outcome"].as_str().expect("verdict.outcome").to_string(),
evidence: v["evidence"].as_str().map(String::from),
})
.collect()
}