jcp-trace: an undefined wedge metric is not zero #292

Merged
dcharlot merged 1 commit from fix/jcp-trace-undefined-ratio-is-not-zero into main 2026-10-17 09:23:57 -04:00
Owner

The sibling instance I flagged in #291, now fixed. Same defect, different sealed receipt (eval/1).

The defect

// jcp/jcp-rs/crates/jcp-trace/src/lib.rs:281
t.uj_per_accepted_token = t.uj_total.checked_div(t.output_tokens).unwrap_or(0);

uj_per_accepted_token is a cost-per-result figure, so 0 is its optimum. A run that burned prompt tokens and accepted no output has no such figure — the division is undefined — and reporting 0 ranked it as the cheapest entry in the ledger.

As in jcp-efficiency, this is not a convenience accessor: eval_wire seals the figure into the signed core and verify() reads it straight back out rather than recomputing it, so the conflation was sealed and consumed as the headline.

The fix

mesh/SPEC.md §6 states the family convention — the figure is "omitted when no tokens were accepted" — and MeshReceipt already implements it. This brings eval/1 onto the same shape:

  • Totals::uj_per_accepted_token and EvalReceipt::uj_per_accepted_token → Option<u64>; Default is None.
  • eval_wire omits the key when the figure is undefined instead of sealing 0. Canonical CBOR sorts keys, so the conditional insert does not change the wire of a receipt that has one.
  • verify() reads it as optional, so an older receipt carrying the figure still round-trips.
  • examples/ingest.rs prints — rather than a number.

Tests

a_run_that_accepted_nothing_seals_no_wedge_metric — a trace with 800 prompt tokens and no accepted output: 8 000 µJ of real spend, no figure, the key absent from the signed core, and the envelope still verifying with the spend on it.

Falsification

mutation result
T1 restore the 0 fallback on the computation only the new test FAILED
T2 keep the Option but seal .unwrap_or(0) only the new test FAILED

T2 matters: it shows the wire half is load-bearing independently of the type change — an Option that gets flattened at seal time fixes nothing. In both runs the gated server::tests::http_roundtrip_returns_sealed_eval_receipt kept passing, so the normal path is untouched.

A test CI does not compile

server.rs:292 asserts on this exact wire field (v["uj_per_accepted_token"] == 76), but pub mod server is behind a non-default feature, so cargo test --workspace never compiles it — and the jcp row's feature_tests in standards-build.yml names only -p jcp-energy --features nvml.

I therefore ran it explicitly rather than assuming: it passes (the field is present there, since output_tokens = 120). Flagging it because CI would not have told us either way. Widening that matrix entry is a separate change and I have not made it here.

Verification

On the build host (aarch64):

  • cargo check --workspace --all-targets — exit 0
  • cargo test --workspace — exit 0, 0 failed suites, 64 passing suites
  • cargo test -p jcp-trace --features server --all-targets — exit 0, 15 passing (the gated path above)

Scope

No eval/1 conformance vector exists, so no sealed vector pins the field.

🤖 Generated with Claude Code

The sibling instance I flagged in #291, now fixed. Same defect, different sealed receipt (`eval/1`). ## The defect ```rust // jcp/jcp-rs/crates/jcp-trace/src/lib.rs:281 t.uj_per_accepted_token = t.uj_total.checked_div(t.output_tokens).unwrap_or(0); ``` `uj_per_accepted_token` is a **cost-per-result** figure, so `0` is its *optimum*. A run that burned prompt tokens and accepted no output has no such figure — the division is undefined — and reporting `0` ranked it as the **cheapest entry in the ledger**. As in `jcp-efficiency`, this is not a convenience accessor: `eval_wire` seals the figure into the signed core and `verify()` reads it straight back out rather than recomputing it, so the conflation was sealed and consumed as the headline. ## The fix `mesh/SPEC.md` §6 states the family convention — the figure is *"omitted when no tokens were accepted"* — and `MeshReceipt` already implements it. This brings `eval/1` onto the same shape: - `Totals::uj_per_accepted_token` and `EvalReceipt::uj_per_accepted_token` → `Option<u64>`; `Default` is `None`. - `eval_wire` **omits the key** when the figure is undefined instead of sealing `0`. Canonical CBOR sorts keys, so the conditional insert does not change the wire of a receipt that has one. - `verify()` reads it as optional, so an older receipt carrying the figure still round-trips. - `examples/ingest.rs` prints `—` rather than a number. ## Tests `a_run_that_accepted_nothing_seals_no_wedge_metric` — a trace with 800 prompt tokens and no accepted output: **8 000 µJ of real spend**, no figure, the key **absent from the signed core**, and the envelope still verifying with the spend on it. ### Falsification | mutation | result | |---|---| | **T1** restore the `0` fallback on the computation | **only** the new test FAILED | | **T2** keep the `Option` but seal `.unwrap_or(0)` | **only** the new test FAILED | T2 matters: it shows the *wire* half is load-bearing independently of the type change — an `Option` that gets flattened at seal time fixes nothing. In both runs the gated `server::tests::http_roundtrip_returns_sealed_eval_receipt` kept passing, so the normal path is untouched. ## A test CI does not compile `server.rs:292` asserts on this exact wire field (`v["uj_per_accepted_token"] == 76`), but `pub mod server` is behind a **non-default feature**, so `cargo test --workspace` never compiles it — and the jcp row's `feature_tests` in `standards-build.yml` names only `-p jcp-energy --features nvml`. I therefore ran it explicitly rather than assuming: it passes (the field is present there, since `output_tokens = 120`). Flagging it because CI would not have told us either way. Widening that matrix entry is a separate change and I have not made it here. ## Verification On the build host (aarch64): - `cargo check --workspace --all-targets` — exit **0** - `cargo test --workspace` — exit **0**, **0** failed suites, **64** passing suites - `cargo test -p jcp-trace --features server --all-targets` — exit **0**, 15 passing (the gated path above) ## Scope No `eval/1` conformance vector exists, so no sealed vector pins the field. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcp-trace: an undefined wedge metric is not zero
All checks were successful
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m0s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 11s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m35s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m13s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m11s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m15s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m36s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 57s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m8s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m46s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 2m53s
456b247b47
The sibling of the jcp-efficiency fix, in the `eval/1` receipt:

    t.uj_per_accepted_token = t.uj_total.checked_div(t.output_tokens).unwrap_or(0);

`uj_per_accepted_token` is a cost-per-result figure, so 0 is its
optimum. A run that burned prompt tokens and accepted no output has no
such figure — the division is undefined — and reporting 0 ranked it as
the cheapest entry in the ledger. Like the efficiency receipt, this one
seals the figure into the signed core (`eval_wire`) and reads it
straight back out in `verify()` rather than recomputing it, so the
conflation was sealed and consumed as the headline.

`mesh/SPEC.md` §6 states the family convention — the figure is
"omitted when no tokens were accepted" — and `MeshReceipt` already
implements it. This brings `eval/1` onto the same shape:

- `Totals::uj_per_accepted_token` and `EvalReceipt::uj_per_accepted_token`
  become `Option<u64>`; `Default` is `None`.
- `eval_wire` omits the key when the figure is undefined instead of
  sealing 0. Canonical CBOR sorts keys, so the conditional insert does
  not change the wire of a receipt that has one.
- `verify()` reads it as optional, so an older receipt that carries the
  figure still round-trips.
- `examples/ingest.rs` prints "—" rather than a number.

`a_run_that_accepted_nothing_seals_no_wedge_metric` builds a trace with
800 prompt tokens and no accepted output: 8 000 µJ of real spend, no
figure, the key absent from the signed core, and the envelope still
verifying with the spend on it.

Verified with `--features server` as well as the default build. The
`server` module is behind a non-default feature, so
`cargo test --workspace` never compiles it, and
`server::tests::http_roundtrip_returns_sealed_eval_receipt` asserts on
this exact wire field — it passes, but CI would not have told us: the
jcp row's `feature_tests` in standards-build.yml names only
`-p jcp-energy --features nvml`.

Co-Authored-By: Claude Opus 5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!292
No description provided.