jcp-x402: refuse a payment scheme this crate does not implement #442
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/x402-refuse-unimplemented-scheme"
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?
ingest_requirementsparsedscheme, defaulted an empty value to"exact", and then nothing ever read the field —git grep '\.scheme\b' -- jcp/returns zero hits. So a requirement naming any other scheme was gated exactly as if it wereexact.Why that is a live money bug, not a tidy-up
x402's
uptomeans "charge up tomaxAmountRequired, settle for the actual amount." Accepted asexact, its per-request maximum is debited as the price:spent_minoraccumulatesprice.minorat authorization (jcp-runtimelib.rs ~315 and ~931);seal_settlementpassesPrice::new(&req.asset, 0)— its own comment reads "records the coupling; debits nothing, reads nothing."Nothing reconciles authorized down to settled. A session settling at a tenth of its cap exhausts a money ceiling ten times too fast.
The fix is to refuse, not to reconcile
This crate implements
exact, where the authorized price is the settled price — sospent_minoraccumulatingprice.minorat authorization is exact by construction and there is nothing to reconcile.Honouring
uptowould need a settle-down debit that does not exist, and inventing one means makingspent_minornon-monotonic — a change to the ceiling's soundness model, not a bug fix. Supportinguptois a feature; proceeding under semantics the code does not implement is the defect. Refusing closes the defect completely and leaves the feature properly scoped.X402Error::UnsupportedSchemeis a new variant rather thanMalformed, because the document is well formed — this crate simply does not honour what it asks for.An absent scheme stays
exact: that is the v1 default and the semantics the money path implements. A mutation that refused absent schemes too broke an existing test which omits the field, so that default is load-bearing rather than decorative.The check is an allowlist, not a denylist of
upto, and is ordered before any figure is parsed — a caller who fixed a broken amount would otherwise be led to retry into semantics this code does not implement.A test caught my own false claim
My first version wrote "settled before any figure below is read" in the comment while placing the check after
maxAmountRequiredwas parsed. The document then reported a malformed amount instead of the unsupported scheme, andthe_scheme_is_refused_before_the_amount_is_parsedfailed. The comment was false until the check moved above every figure — the same defect class this PR fixes, in my own prose.Falsification
an_unimplemented_scheme_is_refused_rather_than_treated_as_exact+ 1 FAILEDan_absent_scheme_is_the_v1_exact_default+ 1 FAILED (an existing test too)an_unimplemented_scheme_is_refused_rather_than_treated_as_exactFAILEDProvenance of the finding
Surfaced by the 2026-10-22 landscape sweep, ranked against EMVCo's Agentic Payments — Framework for Specifications v1.0 DRAFT. The EMVCo draft was not retrieved or used: it is log-in gated and its reproduction licence excludes derivatives. What this reads is the shipped counterpart, x402 (
x402-foundation/x402, Apache-2.0, spec v2).Verification
cargo check --workspace --all-targets --locked(jcp-rs) — 0 errorscargo test --workspace --locked— 66 test binaries green, 0 failurescargo clippy -p jcp-x402 --all-targets --no-deps --locked -- -D warnings— 0 findingscheck-spec-refs1773 citations / 16 standards ·check-lockfiles47 current🤖 Generated with Claude Code