jcp: dual ceilings — a money budget beside the joule budget (SPEC §4.8 + C22) #19
Loading…
Reference in a new issue
No description provided.
Delete branch "jcp/dual-ceilings"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 }) besidebudget_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_ifmeans a joules-only grant encodes byte-identically to one minted before this field existed, so its content id and signature are unchanged.gen-vectorswas run into a temp dir andvectors.json/standing.json/meta.jsoneach diffed byte-identical against the committed copies. Only the newdual_ceilings.jsonis added.Notable
attenuateinherits 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.currency-mismatch), never converted.40 suites green, clippy clean, rebased on current main.
Judgment call worth a second look:
decide_pricedchecks 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]>dcharlot referenced this pull request2026-11-06 13:54:17 -05:00