Three places where joule-ui let a figure be read as more than it was. The `Meter` trait defaulted `provenance()` to `HwShunt`. Any meter that did not think to override it, a deterministic stub included, had its figures sealed as hardware readings with `measured_uj` set. The default is now `Estimator`: a meter opts in to `ModelBased` for a model and to `HwShunt` only for readings of a hardware counter it has seen to be live. In this workspace the only meters that are hardware readings are the two `SpanMeter` adapters (the examples' and joule-ui-serve's), and both already return their span's own grade, so nothing that reads hardware changes. The stubs in the joule-ui-interaction, joule-ui-serve and joule-ui-mcp tests and the `metered_surface` example now come out `Estimator`, as they should. The conformance pack's `StepMeter` now declares `HwShunt` explicitly. C2 and C4 were sealed with it standing in for a hardware counter, so they carry `measured_uj` and exercise that path of the signed core; the explicit override, documented as a fixture grade that attests nothing (like the pack's steward key), is what keeps them byte-identical. SPEC §5 and §8 and the conformance README say so. `Authorizer::charge(action, measured_uj: u64)` handed the authorizer a bare number, so an authorizer that keeps its own record of the debit (a JCP grant) could not tell an estimate from a reading; the `jcp_metered` example worked around it by reading the provenance back off the meter. `charge` now takes `&Energy`, the exact figure the interaction is sealed with, provenance included. Every implementor and caller in the workspace is updated, and `jcp_metered`'s authorizer maps the charged figure's own provenance onto `jcp_schema::Energy` instead of holding an `Rc` to the meter. `run_interaction` turned a missing sample into `Energy::estimator(0)`. Work that ran was sealed at 0 µJ and charged nothing, so on a host with no reading a budget never bound, which SPEC §6 says is exactly what the budget is for. Now, when either sample is `None`, the handler's own wall-clock time is charged at the meter's `estimator_milliwatts()` (default `DEFAULT_ESTIMATOR_MILLIWATTS` = 20 W, the Sandbox estimator's default), rounded up and never 0 (`estimate_uj`), graded `Estimator`. A denial, whose handler never ran, still records 0 µJ. The signed core and its profile tag are unchanged. SPEC: §5 states the default and carries the shared vendor-counter liveness MUST verbatim (HwShunt only while the counter is demonstrably live for the domain it claims; for IOReport the CPU rail advancing in >= 5 intervals, in >= 4 of every 5, over >= 300 ms while the probe burns CPU; a frozen or lumped counter never yields a hardware-graded figure; no claim of a domain the counter does not read). The "real RAPL/IOReport" wording at §5 and "the hardware Meter" at §9 now describe the reference meter as a hardware reading while a vendor counter is live and its labelled estimate otherwise. §6.2 specifies the no-reading estimate and forbids recording work that ran at 0 µJ; §6.3 becomes "charge the cost, with its provenance". Tests (joule-ui-interaction, new): `a_meter_is_an_estimator_unless_it_opts_in`, `work_that_ran_without_a_reading_is_estimated_never_zero`, `the_estimate_is_wall_time_at_the_declared_power_rounded_up`, `a_budget_binds_on_a_host_with_no_reading` (a 1 J budget stops ~20 ms interactions at 20 W within three, every debit `Estimator`), and `charge_is_handed_the_sealed_figure_and_its_provenance`. All 31 tests across joule-ui-interaction, -conformance (incl. the published-pack tests), -serve and -mcp pass; the workspace builds with all targets; the three examples run (`jcp_metered` here: five ~3 J `Estimator` interactions spend the 15 J grant and the real JCP runtime denies the sixth). `gen_vectors` regenerated into a scratch directory is byte-identical to `conformance/v1/` (all five files, MANIFEST included). Falsified, each restored and re-run green: - default back to `HwShunt` with the fixture override removed: the vectors regenerate identical (under the old default they rested on the default), and `a_meter_is_an_estimator_unless_it_opts_in`, `charge_is_handed_the_sealed_figure_and_its_provenance`, joule-ui-serve's `firing_an_action_meters_seals_and_returns_the_next_surface` and joule-ui-mcp's `an_mcp_app_action_is_metered_sealed_and_attributed` fail; - new default, override removed: `c2_interaction_is_reproduced_from_the_pack` and `c2_interaction_is_metered_and_allowed` fail, and the regenerated c2, c4 and MANIFEST differ (`"measured_uj": null, "provenance": "Estimator"`), so the override is what keeps the pack; - `charge` handed `Energy::measured(uj)` regardless of grade: `charge_is_handed_the_sealed_figure_and_its_provenance` and `a_budget_binds_on_a_host_with_no_reading` fail; - the no-reading arm back to `Energy::estimator(0)`: `work_that_ran_without_a_reading_is_estimated_never_zero` and `a_budget_binds_on_a_host_with_no_reading` fail. Clippy --no-deps on the changed crates adds nothing; the remaining warnings are in untouched code (joule-ui-core's tests, the conformance pack test). Not done here: joule-ui's energy figure has no coverage field, and both `SpanMeter` adapters grade a span whose only records are hardware GPU records (NVML without a CPU probe) `HwShunt`, which claims the whole interaction. §5's coverage clause forbids that; fixing it needs either a coverage field in the signed core (a new profile tag) or a downgrade rule, which is a decision, not a cleanup. Co-Authored-By: Claude Opus 5.5 <[email protected]>
3 KiB
joule-ui conformance vectors — v1
Deterministic vectors for independent joule-ui implementations (SPEC §8). Regenerate with:
cargo run -p joule-ui-conformance --bin gen_vectors
A conforming implementation reproduces these from the published JSON alone —
that is what makes joule-ui a vendor-neutral standard rather than one library's
behaviour. Fixed Ed25519 steward key (0x17×32, published, attests nothing) and
fixed meter steps make every vector reproducible.
| Vector | Class | What a conformant implementation must do |
|---|---|---|
c1-validation.json |
C1 — surface validation | Run validate(tree, registry) over both surfaces. The valid one yields no errors; the invalid one yields the exhaustive list (every planted fault — unknown widget, missing required prop, mistyped prop, forbidden child — not just the first). |
c2-interaction.json |
C2 — interaction metering | run_interaction over a deterministic meter (step 4200 µJ) reproduces the Interaction: allow, energy 4200 µJ [HwShunt], and the content addresses of surface, inputs, and outputs. The surface id is the BLAKE3 of the canonical valid_surface JSON; inputs/outputs are content-addressed, never embedded. |
c3-authorization.json |
C3 — authorization | A zero-budget Authorizer denies the action before the handler runs: expected_handler_ran is false, the verdict is deny("budget-exceeded"), and the outputs are the genesis sentinel. A conformant runtime MUST NOT execute a denied action's handler. |
c4-chain.json |
C4 — chain integrity | verify_chain accepts the three-link chain (allow, allow, deny). Removing index 1 MUST break it at index 1: every signature verifies, seq increments from zero, and each link's prev is the prior link's content id, so no interaction can be excised, reordered, or mutated without detection. |
MANIFEST.json |
— | Verify each vector's BLAKE3 content address before relying on it. |
What conformance pins, and what it does not
joule-ui pins the protocol and the receipt, not the product surfaces around
them. There is no renderer vector (the renderer is the host's), no transport
vector (the consumer's — MCP/SSE/Web MoQ), and no widget-library vector (product
surface). The metering and capability mechanisms are recognized edges (SPEC
§9): C2/C3/C4 use a deterministic stub meter and a stub budget so the vectors are
reproducible, while a deployment wires the Sandbox PowerMeter (a hardware
reading while a vendor counter is live, its labelled estimate otherwise) and a
JCP grant. The stub meter declares HwShunt explicitly, so C2 and C4 exercise
the reading path of the signed core (measured_uj present); like the steward
key, that grade is a fixture and attests nothing. Outside the pack, a meter that
declares no grade is Estimator (SPEC §5). What conformance guarantees is the load-bearing claim — that an
interaction is validated, authorized-before-running, metered, and sealed into a
tamper-evident chain, identically across implementations.