jcp: dual ceilings — a money budget beside the joule budget (SPEC §4.8 + C22) #19

Merged
dcharlot merged 2 commits from jcp/dual-ceilings into main 2026-07-17 02:56:32 -04:00
Owner

A real-world AP2 mandate authorizes agent spend in a currency; JCP meters work in joules. There is no canonical joules-per-dollar, so inventing a rate would fabricate authority. Until now a currency-denominated mandate yielded no joule ceiling at all and simply could not be gated.

This adds an OPTIONAL second ceiling (budget_funds { currency, minor }) beside budget_uj. Both are enforced independently, the first to bind denies, and the sealed receipt records which one — a statement no single-dimension authorization can make. Two ceilings, two sources of authority: the mandate bounds the money, the operator bounds the energy. That is precisely why they are never converted.

Backward compatibility (verified, not assumed)

skip_serializing_if means a joules-only grant encodes byte-identically to one minted before this field existed, so its content id and signature are unchanged. gen-vectors was run into a temp dir and vectors.json / standing.json / meta.json each diffed byte-identical against the committed copies. Only the new dual_ceilings.json is added.

Notable

  • attenuate inherits the parent's money ceiling — dropping it would hand a child unbounded spend where the parent was bounded (an amplification). Adding the field with no default forced every Grant construction site to decide, turning that bug into a build error.
  • A foreign currency is refused (currency-mismatch), never converted.
  • An absent ceiling means "joules alone bound", not a zero ceiling.
  • SPEC §4.8 normative + conformance class C22 — executable in the harness and published as replayable vectors.

40 suites green, clippy clean, rebased on current main.

Judgment call worth a second look: decide_priced checks joules before money, so when both would bind the receipt names energy (JCP's primary dimension).

A real-world AP2 mandate authorizes agent spend in a **currency**; JCP meters work in **joules**. There is no canonical joules-per-dollar, so inventing a rate would fabricate authority. Until now a currency-denominated mandate yielded no joule ceiling at all and simply could not be gated. This adds an OPTIONAL second ceiling (`budget_funds { currency, minor }`) beside `budget_uj`. Both are enforced independently, the first to bind denies, and the sealed receipt records **which** one — a statement no single-dimension authorization can make. Two ceilings, two sources of authority: the mandate bounds the money, the operator bounds the energy. That is precisely why they are never converted. ### Backward compatibility (verified, not assumed) `skip_serializing_if` means a joules-only grant encodes **byte-identically** to one minted before this field existed, so its content id and signature are unchanged. `gen-vectors` was run into a temp dir and `vectors.json` / `standing.json` / `meta.json` each diffed **byte-identical** against the committed copies. Only the new `dual_ceilings.json` is added. ### Notable - `attenuate` **inherits** the parent's money ceiling — dropping it would hand a child unbounded spend where the parent was bounded (an amplification). Adding the field with no default forced every Grant construction site to decide, turning that bug into a build error. - A foreign currency is **refused** (`currency-mismatch`), never converted. - An absent ceiling means "joules alone bound", not a zero ceiling. - SPEC §4.8 normative + conformance class **C22** — executable in the harness and published as replayable vectors. 40 suites green, clippy clean, rebased on current main. Judgment call worth a second look: `decide_priced` checks joules **before** money, so when both would bind the receipt names energy (JCP's primary dimension).
A real-world AP2 mandate authorizes agent spend in a currency (dollars), while
JCP meters work in joules. The two cannot be reconciled by an exchange rate —
there is no canonical joules-per-dollar, and inventing one fabricates authority.
Until now a currency-denominated mandate yielded no joule ceiling at all
(Ap2Mandate::joule_budget -> None) and simply could not be gated. This adds the
second dimension so the mandate is enforceable without any conversion.

Design: a Grant may carry an OPTIONAL budget_funds { currency, minor } beside
budget_uj. Both ceilings are enforced independently; the first to bind denies,
and the sealed receipt records which one. A receipt that reads "denied: joule
ceiling, while the money ceiling had room" is a statement no single-dimension
authorization can make -- that is the point.

- jcp-schema: Funds (integer minor units -- money is typed, and the canonical
  encoding forbids floats); Grant.budget_funds: Option<Funds> with
  skip_serializing_if, so a joules-only grant encodes BYTE-IDENTICALLY to one
  minted before this field existed -- content id, signature, and every published
  conformance vector unchanged (proven by test, and the vectors still verify).
  New deny reasons FundsExceeded / CurrencyMismatch.
- jcp-runtime: Price, decide_priced -- deny on whichever ceiling binds first,
  refuse a foreign currency (never convert), debit both on allow / neither on
  deny, and treat an absent money ceiling as "joules alone bound" (not zero).
- jcp-grant: attenuate INHERITS the parent's money ceiling (dropping it would
  amplify); attenuate_priced + funds_narrows narrow it independently and refuse
  a raise, a drop, or a currency change.
- jcp-bridge / jcp-a2a: the priced path end to end -- Ap2Mandate::funds_ceiling,
  A2aProfile::from_mandate_priced (money ceiling from the mandate, joule ceiling
  from the operator), serve_task_priced. Tested: a USD mandate now gates and
  denies on funds-exceeded, sealed in a verifiable receipt.
- SPEC.md: normative §4.8 + conformance class C22.

The other ~11 files are the compiler-forced budget_funds: None at each Grant
literal -- deliberately NOT defaulted, so a new construction site must decide
rather than silently inherit unbounded money authority.

Full workspace green (40 suites), clippy clean, existing a2a example unchanged.
Follow-up (not done): a published C22 conformance vector in conformance/v1/.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Completes SPEC §4.8. The prior commit left this as a noted follow-up; a
conformance class without an executable check and a published vector is not a
standard, just an assertion.

- jcp-conformance: `c22_dual_ceilings` registered in checks(), so it runs in the
  harness alongside C1-C21 (verified: "✓ C22 dual ceilings: money beside joules,
  no rate invented"). It asserts every property §4.8 requires — a joules-only
  grant's encoding omits the field entirely; adding a money ceiling changes the
  grant's identity; an allow debits both dimensions; money binding while joules
  have room denies funds-exceeded and debits neither; the mirror (joules bind,
  money has room) denies budget-exceeded; a foreign currency is refused
  currency-mismatch rather than converted; an absent ceiling means "joules alone
  bound", not zero; and attenuation refuses a raise, a drop, or a
  re-denomination while a plain attenuate inherits.

- conformance/v1/dual_ceilings.json: a NEW file (following standing.json's
  precedent of one file per feature) carrying the fixed-key dual-ceiling grant
  and the three receipts that define C22 — allow / funds-exceeded /
  currency-mismatch — plus spent_after {uj:10, minor:300} proving the denials
  debited nothing. A cross-language verifier can replay all three.

Verified the backward-compatibility guarantee rather than trusting it:
gen-vectors was run into a temp dir and vectors.json, standing.json, and
meta.json all diffed BYTE-IDENTICAL against the committed copies. Only the new
file is added.

Workspace green (40 suites), clippy clean.

Co-Authored-By: Claude Opus 4.8 <[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!19
No description provided.