Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m45s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m32s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / joulecontract-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / map-rs (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (pull_request) Has been cancelled
§5.3–5.6 stated rules and nothing emitted them. The types did not even derive `Serialize`, so the profile could not cross a wire, let alone be checked by anyone else — a shape nothing emits is a proposal, which is the same gap the C2PA emitter closed for `wai` and the negative-vector replay closed for `joulecontract`. So: serde on the trace and dynamics types, a `gen_trace_vectors` binary writing `jcp/conformance/v1/energy_trace.json`, and a test that reads those bytes back from disk — the same bytes a third party would — and checks the reference implementation reproduces every recorded verdict. The pack carries the refusals, not just the acceptances, because the refusals are the load-bearing half. An implementation that admits an unbound outcome, a double-counted activity, an over-accounted total or a frozen counter has failed in the direction that flatters, which is the direction nobody notices: - conservation: the named residual, the provenance/coverage floor, and all three refusals (`Unbound`, `DuplicateActivity`, `OverAccounted`) - budget: 0.5 % over a one-joule grant is 5000 ppm against a 1 ppm floor; the same floor over `Estimator` windows is exact and physically meaningless; a ratio against a zero grant is not a figure - physical: refuted above the burst envelope, refuted **below** the idle floor (the frozen-counter shape), `NotRefuted` which is not verification, and a zero-duration window as unboundable rather than clean - shape: a total balanced by padding does not read as sustained; character is scale-invariant at 37× the magnitude; under three windows it is unresolvable - dynamism: an undeclared baseline makes no comparison, a coarser baseline does not apply to finer windows, and a cause explains only with evidence attached Falsified rather than assumed: flipping one recorded residual by a single microjoule — 250000 to 250001 — fails the replay naming the case and the field. The pack was then regenerated and the value confirmed back at 250000. Two anti-vacuity guards, because this file has been written three times this session and twice the first version could not fail: the pack path is asserted rather than skipped, and a test requires every declared section plus at least 20 replayed cases, so a wrong path or a renamed key fails instead of passing having checked nothing. Verified: 49 tests in `jcp-efficiency` — 43 unit, 6 pack. Co-Authored-By: Claude Opus 5 <[email protected]>
184 lines
8 KiB
Rust
184 lines
8 KiB
Rust
//! Replay the published `energy_trace/1` pack from disk.
|
||
//!
|
||
//! SPEC §5.3–5.6 states rules, and `gen_trace_vectors` emits the vectors. This
|
||
//! reads those bytes back — the same bytes a third party would — and checks the
|
||
//! reference implementation reproduces every recorded verdict. A pack only its
|
||
//! own generator can read is not a conformance pack.
|
||
//!
|
||
//! The refusals matter more than the acceptances. An implementation that admits
|
||
//! an unbound outcome, a double-counted activity or a frozen counter has failed
|
||
//! in the direction that flatters, which is the direction nobody notices.
|
||
|
||
use jcp_efficiency::dynamics::{
|
||
DrawShape, EnergyWindow, PhysicalVerdict, PowerEnvelope,
|
||
};
|
||
use jcp_efficiency::trace::{OutcomeTrace, TraceError};
|
||
use serde_json::Value;
|
||
|
||
fn pack() -> Value {
|
||
let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||
.join("../../../conformance/v1/energy_trace.json");
|
||
// Assert, not skip. A `return` here would make this test pass green in the
|
||
// one case it exists to catch — the pack going missing or moving.
|
||
assert!(
|
||
p.exists(),
|
||
"conformance pack missing at {} — it is what this test replays",
|
||
p.display()
|
||
);
|
||
let bytes = std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
|
||
serde_json::from_slice(&bytes).expect("pack parses")
|
||
}
|
||
|
||
#[test]
|
||
fn the_pack_declares_its_profile_and_spec() {
|
||
let v = pack();
|
||
assert_eq!(v["profile"], "energy_trace/1");
|
||
assert!(v["spec"].as_str().unwrap().contains("5.3-5.6"));
|
||
}
|
||
|
||
/// Every positive conservation vector reproduces its recorded reconciliation.
|
||
#[test]
|
||
fn conservation_positives_reproduce_their_recorded_verdicts() {
|
||
let v = pack();
|
||
let cases = v["conservation"]["positive"].as_array().expect("positives");
|
||
assert!(cases.len() >= 2, "expected the published positive cases");
|
||
for c in cases {
|
||
let note = c["note"].as_str().unwrap_or("?");
|
||
let t: OutcomeTrace =
|
||
serde_json::from_value(c["input"].clone()).unwrap_or_else(|e| panic!("{note}: {e}"));
|
||
let r = t.reconcile().unwrap_or_else(|e| panic!("{note}: must reconcile: {e}"));
|
||
let exp = &c["expected"];
|
||
assert_eq!(
|
||
serde_json::to_value(r.accounted_uj).unwrap(), exp["accounted_uj"],
|
||
"{note}: accounted"
|
||
);
|
||
assert_eq!(
|
||
serde_json::to_value(r.residual_uj).unwrap(), exp["residual_uj"],
|
||
"{note}: the residual is the whole point"
|
||
);
|
||
assert_eq!(
|
||
serde_json::to_value(r.provenance_floor).unwrap(), exp["provenance_floor"],
|
||
"{note}: provenance takes the floor"
|
||
);
|
||
assert_eq!(
|
||
serde_json::to_value(r.coverage_floor).unwrap(), exp["coverage_floor"],
|
||
"{note}: coverage takes the floor"
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Every refusal is actually refused, and for the recorded reason. An
|
||
/// implementation that accepted these would be wrong in the flattering
|
||
/// direction.
|
||
#[test]
|
||
fn conservation_negatives_are_refused_for_the_recorded_reason() {
|
||
let v = pack();
|
||
let cases = v["conservation"]["negative"].as_array().expect("negatives");
|
||
assert!(cases.len() >= 3, "expected the three published refusals");
|
||
for c in cases {
|
||
let note = c["note"].as_str().unwrap_or("?");
|
||
let t: OutcomeTrace =
|
||
serde_json::from_value(c["input"].clone()).unwrap_or_else(|e| panic!("{note}: {e}"));
|
||
let err = t.reconcile().expect_err(&format!("{note}: must be refused"));
|
||
let want = c["expected"]["error"].as_str().unwrap();
|
||
let got = match err {
|
||
TraceError::Unbound => "Unbound",
|
||
TraceError::DuplicateActivity { .. } => "DuplicateActivity",
|
||
TraceError::OverAccounted { .. } => "OverAccounted",
|
||
};
|
||
assert_eq!(got, want, "{note}: refused, but for the wrong reason");
|
||
}
|
||
}
|
||
|
||
/// The physical bound refutes above the envelope and below the floor — the
|
||
/// second being the shape a frozen counter takes.
|
||
#[test]
|
||
fn physical_bounds_reproduce_their_recorded_verdicts() {
|
||
let v = pack();
|
||
let env: PowerEnvelope =
|
||
serde_json::from_value(v["physical"]["envelope"].clone()).expect("envelope");
|
||
let cases = v["physical"]["cases"].as_array().expect("cases");
|
||
assert!(cases.len() >= 4);
|
||
let mut saw_below = false;
|
||
for c in cases {
|
||
let note = c["note"].as_str().unwrap_or("?");
|
||
let w: EnergyWindow =
|
||
serde_json::from_value(c["input"].clone()).unwrap_or_else(|e| panic!("{note}: {e}"));
|
||
let got = match env.check(&w) {
|
||
PhysicalVerdict::NotRefuted { .. } => "NotRefuted",
|
||
PhysicalVerdict::RefutedAbove { .. } => "RefutedAbove",
|
||
PhysicalVerdict::RefutedBelow { claimed_uj, floor_uj } => {
|
||
saw_below = true;
|
||
if let Some(f) = c["expected"]["floor_uj"].as_u64() {
|
||
assert_eq!(floor_uj, f, "{note}: floor");
|
||
assert_eq!(claimed_uj, 0, "{note}: a frozen counter claims nothing");
|
||
}
|
||
"RefutedBelow"
|
||
}
|
||
PhysicalVerdict::Unboundable => "Unboundable",
|
||
};
|
||
assert_eq!(got, c["expected"]["verdict"].as_str().unwrap(), "{note}");
|
||
}
|
||
assert!(saw_below, "the frozen-counter refutation must be in the pack");
|
||
}
|
||
|
||
/// Character survives a change of scale, and a padded total does not read as
|
||
/// sustained. These two together are what the shape check is for.
|
||
#[test]
|
||
fn draw_character_reproduces_its_recorded_verdicts() {
|
||
let v = pack();
|
||
let floor = v["shape"]["idle_floor_uj"].as_u64().expect("idle floor");
|
||
let cases = v["shape"]["cases"].as_array().expect("cases");
|
||
assert!(cases.len() >= 6);
|
||
let mut saw_inconsistent = false;
|
||
for c in cases {
|
||
let note = c["note"].as_str().unwrap_or("?");
|
||
let declared: DrawShape =
|
||
serde_json::from_value(c["input"]["declared"].clone()).expect("declared");
|
||
let windows: Vec<EnergyWindow> =
|
||
serde_json::from_value(c["input"]["windows"].clone()).expect("windows");
|
||
let got = jcp_efficiency::dynamics::check_shape(declared, &windows, floor);
|
||
let want = c["expected"]["verdict"].as_str().unwrap();
|
||
let name = match &got {
|
||
jcp_efficiency::dynamics::ShapeVerdict::Consistent { shape } => {
|
||
if let Some(s) = c["expected"]["shape"].as_str() {
|
||
assert_eq!(
|
||
serde_json::to_value(shape).unwrap().as_str().unwrap(), s,
|
||
"{note}: shape"
|
||
);
|
||
}
|
||
"Consistent"
|
||
}
|
||
jcp_efficiency::dynamics::ShapeVerdict::Inconsistent { .. } => {
|
||
saw_inconsistent = true;
|
||
"Inconsistent"
|
||
}
|
||
jcp_efficiency::dynamics::ShapeVerdict::Unresolvable { windows } => {
|
||
if let Some(n) = c["expected"]["windows"].as_u64() {
|
||
assert_eq!(*windows as u64, n, "{note}: window count");
|
||
}
|
||
"Unresolvable"
|
||
}
|
||
};
|
||
assert_eq!(name, want, "{note}");
|
||
}
|
||
assert!(saw_inconsistent, "the padding refutation must be in the pack");
|
||
}
|
||
|
||
/// Anti-vacuity across the whole pack: a wrong path, a renamed key or an empty
|
||
/// array would otherwise let every test above pass having checked nothing.
|
||
#[test]
|
||
fn the_pack_carries_every_section_it_claims() {
|
||
let v = pack();
|
||
for section in ["conservation", "budget", "physical", "shape", "dynamism"] {
|
||
assert!(!v[section].is_null(), "section {section} missing from the pack");
|
||
}
|
||
let total: usize = v["conservation"]["positive"].as_array().map_or(0, Vec::len)
|
||
+ v["conservation"]["negative"].as_array().map_or(0, Vec::len)
|
||
+ v["physical"]["cases"].as_array().map_or(0, Vec::len)
|
||
+ v["shape"]["cases"].as_array().map_or(0, Vec::len)
|
||
+ v["dynamism"]["cases"].as_array().map_or(0, Vec::len)
|
||
+ v["budget"]["positive"].as_array().map_or(0, Vec::len)
|
||
+ v["budget"]["negative"].as_array().map_or(0, Vec::len);
|
||
assert!(total >= 20, "expected the full published pack, replayed {total} cases");
|
||
}
|