energy: a broken draw reading condemned the counter it was meant to judge, in three more crates #196
2026-10-09
jouleclaw: the new plausibility types are crate-internal, not public surface
Some checks failed
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 32s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 57s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m8s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m55s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m27s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m9s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m9s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m14s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m13s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m6s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m42s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 45m15s
standards workspaces build + test / aac-rs (pull_request) Successful in 7m24s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 7m15s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 3m49s
standards workspaces build + test / map-rs (pull_request) Successful in 4m21s
standards workspaces build + test / mesh-rs (pull_request) Successful in 5m32s
standards workspaces build + test / openpay (pull_request) Successful in 34m58s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 5m23s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 1h20m15s
`jouleclaw-public-surface-documented.yml` failed on this branch with exactly one Lost reason, `jouleclaw-energy::src::liveness::plausible`. Two causes, and the second is the real one: - The `///` doc was there, but separated from the item by a `//` note, so the scanner walking back from the signature never reached it. - More to the point, `liveness` is a private module, so a `pub` item in it was never consumer-visible. The gate's own advice is to document it or downgrade it if it is not part of the consumer-visible surface, and the honest answer here is the latter: `CounterLiveness` is this crate's public vocabulary, `GpuPlausibility` is how the module reaches its verdict. So `GpuBound`, `GpuPlausibility` and `plausible()` are `pub(crate)`, which is what they always were in effect, and the note moved into the doc comment. This differs from jcp-energy deliberately: there the types sit in the crate root and are genuinely public. eoc-meter's copy was already `pub(crate)`. Co-Authored-By: Claude Opus 5 <[email protected]>
jouleclaw: cover the new liveness where the declined ones are enumerated
Some checks failed
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 33s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Failing after 38s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m8s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m56s
standards workspaces build + test / aac-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-code-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 backends (pull_request) Has been cancelled
standards workspaces build + test / jouleclaw-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
Two tests are titled "A counter given any verdict but `Live` meters nothing" and then enumerate three of what are now four non-live verdicts, so adding `Unjudgeable` left it the one whose declining nothing checked. The behaviour was already right — both `admits` functions compare against `Live` — so this is the tests catching up with their own claim. Co-Authored-By: Claude Opus 5 <[email protected]>
energy: the hardware oracle still encoded the rule the crates no longer use
Each of the four crates' `nvml_hardware` tests carries its own oracle: the
plausibility rule rewritten from the rule itself, in f64, from `nvidia-smi`
rather than the crate's own sampling, so that `energy-hardware.yml` on
jetson-hub checks the crate against an independent implementation and not
against itself. That only holds while the oracle encodes the rule the crate
implements, and all four still had the old conflation:
if rate_w > 1.5 * c || draws_w.iter().any(|d| *d > 1.5 * c) { return false }
jcp-energy's included — #185 fixed that crate's implementation and left its
oracle behind, so since then the two have disagreed about what a draw reading
past the board's own limit means. On jetson-hub's RTX 4050 the disagreement
is masked: that counter runs persistently at 4-30x the board's draw, so the
implementation reaches `Implausible` by the rate bound and the oracle reaches
it by the broken reading, and they agree on the verdict for different
reasons. It would surface on a board whose counter is sane and whose
telemetry spikes — the implementation would judge on the surviving reading
and say `Live`, the oracle would say `Implausible`, and the test asserts they
agree.
So the oracles now drop a draw reading past the limit, and only that reading,
and answer `Unjudgeable` when nothing usable is left — with a `Verdict`
variant for it and an arm in the mapping each test makes against the crate's
own status. The run's verdict is the most severe of its GPUs', as the crates'
now is, so a definite overrun outranks an indeterminate.
These tests are all `#![cfg(target_os = "linux")]`, two of them also behind
`--features nvml` and one behind `--features cuda`, so none of them compiles
in a default build on this machine; checked for aarch64-unknown-linux-gnu
with each crate's own feature.
Co-Authored-By: Claude Opus 5 <[email protected]>
eoc: a broken draw reading condemned the counter it was meant to judge
The third and last crate carrying the defect #185 fixed in jcp-energy. `gpu_plausibility::plausible` returned a bool and folded two unlike things into the one `false`: if rate_mw > cap || draws_mw.iter().any(|&d| u128::from(d) > cap) The left is the counter running faster than the board can draw — a finding about the counter. The right is the *board's own telemetry* reading past the board's own limit, which says the yardstick is broken, not the counter. Its test named the case too: `"a 590 W draw reading"` asserted `false`, and 590 W is what an RTX 4050 Laptop GPU reports for a second after it wakes. So the reading is dropped, and only that reading. Only when nothing usable is left is there no yardstick, and then the verdict is `BoardTelemetryUnusable` mapping to a new `CounterStatus::Unjudgeable`, kept apart from `Implausible` whose doc asserts "It is not counting the board's energy" — a conclusion this case does not support. `admits_vendor_counter` compares against `Live`, so the new variant is inadmissible without a change there. `GpuPlausibility` carries the rate, the bound and which bound it was, where the bool discarded the magnitude. A test pins the mean-draw bound at twice the usable 10 W reading plus 5 W, which it could not be if the 590 W spike were still in the mean — the old mean summed every reading, so a bad reading made that bound *more* permissive, and only the short-circuit hid it. Five tests enumerate the declined statuses by hand rather than matching exhaustively, so each now lists `Unjudgeable`; without that it would be the one status whose declining nothing checked. Two of them only compile under `--features cuda`. Checked for aarch64-unknown-linux-gnu with and without `--features cuda`; 75 tests pass in the crate and the workspace is clean. Co-Authored-By: Claude Opus 5 <[email protected]>
sandbox: cover the new status where the declined ones are enumerated
Two tests assert that a counter which is not live is never opened, and both listed the declined statuses by hand rather than matching exhaustively, so adding `Unjudgeable` left it the one status whose gating nothing checked. `nvml.rs`'s doc even claims the gate "declines every status but `Live`", which its own test did not cover. `counter_admits` compares against `Live`, so the behaviour was already right; this is the test catching up with it. The second site is Linux-gated, so it needs the aarch64-unknown-linux-gnu cross-check to compile at all. Co-Authored-By: Claude Opus 5 <[email protected]>
jouleclaw: a broken draw reading condemned the counter it was meant to judge
The same defect as the previous commit, in the second of the three crates that still carried it. `plausible_gpu_counter` returned a bool and folded two unlike things into the one `false`: if rate_mw > cap || draws_mw.iter().any(|&d| u128::from(d) > cap) The left is the counter running faster than the board can draw — a finding about the counter. The right is the *board's own telemetry* reading past the board's own limit, which says the yardstick is broken, not the counter. The test even named the case: `"a 590 W draw reading"` asserted `false`, which is the one reading an RTX 4050 Laptop GPU emits for a second after it wakes. So the reading is dropped, and only that reading. Only when nothing usable is left is there no yardstick, and then the verdict is `BoardTelemetryUnusable` mapping to a new `CounterLiveness::Unjudgeable` — kept apart from `Implausible`, whose doc asserts "it is not counting the board's energy", a conclusion this case does not support. In `nvml::mode_for` an unjudgeable counter falls back to the vendor power integral exactly as an implausible one does: neither can be graded `HwShunt`. `GpuPlausibility` also carries the rate, the bound and which bound it was, where the bool discarded the magnitude: a counter 1.1x over and one 20x over were the same answer, and no past finding could be re-graded against a changed margin. A test pins the mean-draw bound at twice the usable 10 W reading plus 5 W, which it could not be if the 590 W spike were still in the mean — the old code summed every reading, so a bad reading made that bound *more* permissive, and only the short-circuit hid it. Three `admits` functions list the variants exhaustively rather than using a wildcard, which is why adding one forced a decision at each: unjudgeable is inadmissible everywhere. The rapl.rs copy only surfaced under a whole- workspace build, and nvml.rs's only under `--features nvml`, neither of which a single-crate default build compiles. Checked for aarch64-unknown-linux-gnu with and without `--features nvml`, warning-free on both; 57 tests pass in the crate and the workspace is clean. Co-Authored-By: Claude Opus 5 <[email protected]>
sandbox: a broken draw reading condemned the counter it was meant to judge
`plausible_gpu_counter` returned a bool, and it folded two unlike things into the one `false`: if rate_mw > cap || draws_mw.iter().any(|&d| u128::from(d) > cap) The left is the counter running faster than the board can draw — a finding about the counter. The right is the *board's own telemetry* reading past the board's own limit, which says the yardstick is broken, not the counter. An RTX 4050 Laptop GPU reports about 590 W for a second after it wakes, so one such sample among sane ones declared the counter implausible. jcp-energy had the same function and the same bug; #185 fixed it there after the hardware test on jetson-hub refused the first shape of the fix. This is that fix ported, and three crates still carry the original: this one, jouleclaw-energy and eoc-meter. So the reading is dropped, and only that reading — one broken sample is not a broken bracket. Only when nothing usable is left is there no yardstick, and then the verdict is `BoardTelemetryUnusable`, an indeterminate, mapping to a new `CounterStatus::Unjudgeable` rather than to `Implausible` whose doc asserts "is not counting the board's energy" — a conclusion this case does not support. `counter_admits` compares against `Live`, so the new variant is inadmissible without a change there; the distinction is the diagnosis an operator gets, which is the difference between replacing a counter and waiting for the board to settle. Dropping the bad reading is also what the old short-circuit was really for, and it does the job properly. The mean summed every reading, so a 590 W spike inflated it and made the mean-draw bound *more* permissive — a bad reading could have excused an over-running counter. That only went unnoticed because any such reading failed the check first. A test now pins the bound at twice the usable 10 W reading plus 5 W, which it could not be if the spike were still in the mean. The bool also discarded the magnitude: a counter 1.1x over a bound and one 20x over were the same answer, and a past finding could not be re-graded against a different margin because the rate did not survive the check. `GpuPlausibility` carries the rate, the bound, and which bound it was. The run-level verdict is now the most severe of its GPUs' rather than an `all()` over bools, so one board's broken telemetry no longer condemns the run and one healthy GPU cannot excuse it. Checked for aarch64-unknown-linux-gnu as well, since the NVML consumer is Linux-gated. 272 tests pass in the workspace. Co-Authored-By: Claude Opus 5 <[email protected]>