open-standards/jcp/jcp-rs/crates/jcp-openpay/Cargo.toml
Transaction Science bc564085f4
All checks were successful
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 3m27s
jcp-openpay: the gate for the card and instant rails, and a mandate becomes its ceilings
OpenPay now runs a metered charge on card and instant rails and asks a gate to
bound it and record the measurement with it. Its reference gate does the first
and keeps a book. `JcpGate` is the gate that does both, and adds what a payment
rail has no field for.

**A second ceiling.** Each reservation is a hold on a grant carrying a money
ceiling and a joule ceiling, enforced independently with no rate between them.
A charge the money allows is refused when the energy the operator allowed does
not, and the receipt names which ceiling bound — a statement a card network has
no way to make.

**A signed measurement.** Every capture and every resolution seals a receipt
committing to the amount that moved, the rail's own reference for it — the
acquirer's payment id, the instant rail's transaction id — and the joules
measured, at the grade of the meter that produced them. A different amount,
reference, figure or grade is a different receipt. A grade is carried as stated
and never upgraded: an unrecognised claim about how a figure was obtained is
taken as the weakest grade, not the strongest, and a unit this profile does not
meter in is refused rather than read as joules.

**And a mandate becomes the ceilings.** AP2 payment mandates were evaluated in
OpenPay and went nowhere; the mandate-to-limit path was two halves.
`mint_from_mandate` joins them. The mandate is evaluated in full first — payee,
instrument, window, recurrence, reference, and any constraint not understood
refuses — with the session's maximum read as the amount, since a metered charge
is admitted on its upper bound. Then `payment.budget` less what history shows
spent is the session ceiling; `payment.amount_range.max` bounds each single
reservation, and stands as the session ceiling too where there is no budget,
because a mandate with no budget authorizes one payment and not a series; and a
mandate that bounds no money mints nothing. The joule ceilings come from the
operator alone: the mandate has authority over the money and none over the
energy. Nothing that verified the mandate, no gate — the amounts read are about
to be enforced as if the user had signed them.

The gate is tested through the `MeteredGate` trait, in the order the rail
drivers call it, and the drivers are tested where they live. An earlier draft
hosted fake acquirers here, which pulled the rails' whole HTTP stack into this
workspace's lockfile — eleven hundred lines to hold two test doubles. The trait
is the contract between the two, so that is what each side tests against.

The capability document names the code behind each rail projection and states
the ordering rule both follow when a gate decision and a rail operation
disagree.

Co-Authored-By: Claude <[email protected]>
2026-11-10 16:21:18 -05:00

23 lines
1.2 KiB
TOML

[package]
name = "jcp-openpay"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "OpenPay profile of JCP — a metered-settlement gate for the card and instant rails. Each reservation is a dual-ceiling hold (money from the payment, joules from the operator, no rate between them), each capture and each resolution is decided outside the model, and every one seals a receipt binding the amount that moved to the energy measured for it. The rails keep their transport; JCP adds the second ceiling and the signed measurement."
[dependencies]
jcp-schema = { workspace = true }
jcp-grant = { workspace = true }
jcp-runtime = { workspace = true }
jcp-receipt = { workspace = true }
ed25519-dalek = { workspace = true }
serde_json = { workspace = true }
# The gate contract only — not the rail drivers or their HTTP stack.
op-metered = { path = "../../../../openpay/crates/op-metered", default-features = false }
op-core = { path = "../../../../openpay/crates/op-core" }
# The agent-mandate reader: a verified payment mandate becomes this gate's money ceiling.
op-agentic = { path = "../../../../openpay/crates/op-agentic" }
[lints]
workspace = true