open-standards/jcp/jcp-rs
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 6ffa2cf962 jcp: close two §4.9 spec bugs found in review — metered path + caveat comparison
Both were cases where the NORMATIVE TEXT was wrong or under-specified, which is
worse than a code bug: an implementor reading §4.9 would have diverged from the
reference and believed they were conforming.

1. The spec said caveats "MUST all hold for a decision to be allowed", full stop,
   while the implementation deliberately skipped MaxCostUj in the two-phase
   `authorize` path (the cost is genuinely unknowable there). So the spec promised
   something the reference did not do, and a `MaxCostUj` caveat was in practice
   unenforced for any host that authorized, ran, then charged — `charge()` never
   re-checked.

   §4.9 now states the rule explicitly: a cost-dependent caveat MUST be enforced
   at the DEBIT POINT, an implementation MUST NOT report a grant as authorized in
   a way implying it passed, and every cost-independent caveat MUST bind in both
   phases. `Runtime::charge` enforces it.

   The doctrine question that mattered: on a breach the work has ALREADY run, so
   the joules are real. `charge` still debits them — energy that was spent is
   always accounted — and records the breach in the verdict, so the sealed receipt
   is evidence. Suppressing the debit to make the record look clean would be the
   dishonest move; the spec now names both failure modes as non-conforming. This
   follows the existing budget-overrun precedent ("records the overshoot rather
   than hiding it") rather than inventing a new one.

2. The spec asserted that "weakening" an inherited caveat is an amplification, but
   never defined weakening — while the code compares by exact structural equality.
   So `MaxCostUj(400)` under a parent's `MaxCostUj(500)` was refused as *dropped*
   despite being strictly tighter, with nothing saying so.

   §4.9 now defines comparison as EXACT IDENTITY and says why: a per-variant
   ordering would have to be defined for each predicate, and a verifier that got
   it wrong would silently widen authority. A delegator wanting a tighter bound
   adds it alongside the inherited one — since all caveats must hold, the tighter
   governs. Conservative on purpose: it can never widen, and it can be relaxed
   later without breaking existing grants, whereas the reverse is not true.

Also adds `Caveat::is_cost_dependent()` / `cost_breached()` to the schema so the
authorize-skip and the charge-enforce share one definition instead of two
`matches!` arms that could drift.

Tests pin both phases: authorize allows what it cannot evaluate and never debits;
charge denies the breach AND still accounts the 900 µJ. C24 extended to cover the
debit-point rule.

42 suites green, clippy clean, all conformance checks pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-19 12:18:38 -04:00
..
conformance/v1 jcp: JCR-1 receipts bind their payload — jcp/2 + version dispatch (SPEC §8, C23) 2026-07-18 03:04:19 -04:00
crates jcp: close two §4.9 spec bugs found in review — metered path + caveat comparison 2026-07-19 12:18:38 -04:00
Cargo.lock jcp-x402: dual-ceiling gate for the x402 agentic-payment wire (WIP: crate + bridge frames) 2026-07-18 03:17:21 -04:00
Cargo.toml jcp-x402: dual-ceiling gate for the x402 agentic-payment wire (WIP: crate + bridge frames) 2026-07-18 03:17:21 -04:00