open-standards/joule-ui/conformance/README.md
Transaction Science 7609bfeaef joule-ui: a meter is an estimator until it opts in, and a charge carries its grade
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]>
2026-09-30 20:10:14 -04:00

37 lines
3 KiB
Markdown
Raw Permalink 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.

# 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.