Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions greptile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
{
"autoReview": [],
"strictness": 1,
"customContext": {
"rules": [
{
"rule": "pay-kit#317 `solana-foundation/pay-kit@a58c78e:go/protocols/mpp/wire/challenge_test.go:56`: `now` was fixed at 1900, so the leap-second cases proved only that parsing succeeded, not the mapping to the last nanosecond of `:59` (https://github.com/solana-foundation/pay-kit/pull/317#discussion_r3982265139).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@efff41c:harness/protocol-runners/kotlin.json:3`: the manifest execs an installed Gradle distribution that no CI job builds, so on a clean checkout the runner cannot start (https://github.com/caliperforge/pay-kit/pull/13#discussion_r4166253848).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@8c59ab9:harness/test/protocol-conformance.test.ts:195`: a spawn failure, a non-zero exit or empty stdout counted as the expected divergence, so a runner that never ran looked green.",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@2291822:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:77`: decoding with unknown keys ignored silently dropped the `hash` payload of `credential_with_source` (https://github.com/caliperforge/pay-kit/pull/13#discussion_r4166253870).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@c4341ee:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:57-66`: the same lenient decoding dropped the challenge `description` (https://github.com/caliperforge/pay-kit/pull/13#discussion_r4166253852).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#320 `solana-foundation/pay-kit@a55ee48:php/tests/PayCore/Rfc3339Test.php:48`: long-fraction vectors were checked with `format('c')`, which drops microseconds, so a parser that discards the fraction still passed (https://github.com/solana-foundation/pay-kit/pull/320#discussion_r3992887690).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#320 `solana-foundation/pay-kit@a55ee48:php/tests/PayCore/Rfc3339Test.php:88`: no case exercised the last-day-of-month rule for `23:59:60Z`, so deleting the check kept the suite green (https://github.com/solana-foundation/pay-kit/pull/320#discussion_r3992887696).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#340 `solana-foundation/pay-kit@b2638e1:ruby/lib/pay_kit/config.rb:271`: an empty `PAY_KIT_RPC_URL` overrode the network default with `\"\"`, unlike the Python SDK it mirrors (https://github.com/solana-foundation/pay-kit/pull/340#discussion_r4095737040).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#340 `solana-foundation/pay-kit@b2638e1:ruby/lib/pay_kit/config.rb:277`: a zero or negative `PAY_KIT_MPP_EXPIRES_IN` was accepted, issuing already-expired challenges, unlike the Python SDK it mirrors (https://github.com/solana-foundation/pay-kit/pull/340#discussion_r4095737053).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#340 `solana-foundation/pay-kit@b2638e1:ruby/lib/pay_kit/config.rb:308`: `yes`/`no`/`on`/`off` for `PAY_KIT_PREFLIGHT` raised at boot, unlike the Python SDK it mirrors (https://github.com/solana-foundation/pay-kit/pull/340#discussion_r4095737067).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#340 `solana-foundation/pay-kit@b2638e1:ruby/lib/pay_kit/config.rb:268`: lowercase `pay_kit_*` variables were silently ignored, unlike the Python SDK it mirrors (https://github.com/solana-foundation/pay-kit/pull/340#discussion_r4095737071).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#15 `caliperforge/pay-kit@1f02e55:swift/Sources/mpp-protocol-runner/main.swift:74`: `challenge.parse` dropped `description` (https://github.com/caliperforge/pay-kit/pull/15#discussion_r4173474978).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#15 `caliperforge/pay-kit@1f02e55:swift/Sources/mpp-protocol-runner/main.swift:97`: `credential.format` required `transaction` for `type: \"transaction\"`, so the canonical `basic_credential` vector could not be formatted (https://github.com/caliperforge/pay-kit/pull/15#discussion_r4173474983).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#322 `solana-foundation/pay-kit@c35533e:swift/Sources/SolanaPayKit/Protocols/Mpp/Core/Models.swift:125`: a review asked to allow leap seconds only in June and December; RFC 3339 §5.7 and Appendix D do not restrict them, the UTC month-end check matches the Rust reference, and the finding was withdrawn (https://github.com/solana-foundation/pay-kit/pull/322#discussion_r4035505691).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#282 `solana-foundation/pay-kit@e2f2b1d:harness/src/protocol/vectors.ts:436`: `collectExpiresCases()` had no callers, though comments said the per-language tests used it (https://github.com/solana-foundation/pay-kit/pull/282#discussion_r3751682861).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#282 `solana-foundation/pay-kit@faf3250:typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts:449`: the test imported `../shared/rfc3339.js`, which did not exist at that SHA, so the whole file failed to load (https://github.com/solana-foundation/pay-kit/pull/282#discussion_r3960191945).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#282 `solana-foundation/pay-kit@faf3250:typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts:478`: the loop tested only `parseRfc3339` while the production guards still used `new Date(...)` and `Date.parse`, so the tests could pass without exercising the shipped code (https://github.com/solana-foundation/pay-kit/pull/282#discussion_r3960191963).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#313 `solana-foundation/pay-kit@a752d51:typescript/packages/mpp/src/shared/rfc3339.ts:38`: rejected `second = 60`, which RFC 3339 allows for a positive leap second (https://github.com/solana-foundation/pay-kit/pull/313#discussion_r3962113765).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#14 `caliperforge/pay-kit@208fbd7:harness/test/protocol-conformance.test.ts:179`: an expectation checked only that `description` was absent, so `result: {}` would also pass (https://github.com/caliperforge/pay-kit/pull/14#discussion_r4173234527).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@66449a5:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:66`: <a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Challenge description is dropped** When a valid header contains a top-level `description`, such as the canonical `full_challenge`, this response omits it. The parsed challenge no longer matches the expected object, and the basic-only runner test does not catch the difference. (https://github.com/caliperforge/pay-kit/pull/13#discussion_r4166253852).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@66449a5:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:65`: <a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Plain opaque values fail parsing** The SDK treats `opaque` as a pass-through string. When a valid challenge contains a plain value such as `trace-123`, decoding it as base64url JSON throws, so the runner returns `parse_error` instead of the parsed challenge. (https://github.com/caliperforge/pay-kit/pull/13#discussion_r4166253859).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#13 `caliperforge/pay-kit@8c59ab9:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:77`: <a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Hash payload is lost** The canonical `credential_with_source` case contains a valid `hash` payload. This code deserializes it while ignoring unknown fields, but the Kotlin payload type has no `hash` property. Formatting then emits an Authorization header without the hash value. (https://github.com/caliperforge/pay-kit/pull/13#discussion_r4166253870).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#16 `caliperforge/pay-kit@00614e3:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:78`: <a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Opaque values are misparsed** When a valid Payment challenge includes `opaque`, the runner tries to decode it as base64url JSON even though the SDK and protocol adapter treat it as an opaque string. A value that is not JSON becomes a parse error; a JSON-bearing value is returned with the wrong type. (https://github.com/caliperforge/pay-kit/pull/16#discussion_r4186902439).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#16 `caliperforge/pay-kit@00614e3:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:85`: <a href=\"#\"><img alt=\"P2\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9\" align=\"top\"></a> **Missing request becomes valid** If a `credential.format` input omits the required `challenge.request`, the runner inserts an empty request and can return a successful Authorization header. This hides an invalid input and produces a header that does not represent the supplied challenge. (https://github.com/caliperforge/pay-kit/pull/16#discussion_r4186902463).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#16 `caliperforge/pay-kit@00614e3:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:77`: <a href=\"#\"><img alt=\"P2\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9\" align=\"top\"></a> **Empty optional values disappear** When a parsed challenge contains an explicitly empty `expires` or `digest`, these checks omit the field from the result. The adapter distinguishes a present empty value from an absent field, so the runner returns the wrong object for those headers. (https://github.com/caliperforge/pay-kit/pull/16#discussion_r4186902477).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#16 `caliperforge/pay-kit@00614e3:harness/kotlin-protocol-runner/src/main/kotlin/com/solana/paykit/protocolrunner/Main.kt:33`: <a href=\"#\"><img alt=\"P2\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9\" align=\"top\"></a> **Conformance skips recognized operations** Seven recognized operations always return fixed errors, and the Kotlin divergence tests assert those responses instead of exercising protocol behavior. A green Kotlin protocol job therefore does not verify encoding, challenge generation, or those header operations. Implement them or separate these gap checks from conformance coverage. (https://github.com/caliperforge/pay-kit/pull/16#discussion_r4186902484).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#20 `caliperforge/pay-kit@473293a:php/src/Gate.php:136`: <a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Inherited protocol limits are ignored** When a gate has no explicit `accept` list, this method returns true for any fee-free protocol without checking `Config`. With a configuration that accepts only MPP, `gate->accepts(Protocol::X402)` therefore returns true even though the middleware excludes X402. Callers of this new public method get an admission decision that contradicts the configured protocol limit. (https://github.com/caliperforge/pay-kit/pull/20#discussion_r4188609106).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#14 `caliperforge/pay-kit@17dd9a2:harness/test/protocol-conformance.test.ts:212`: <a href=\"#\"><img alt=\"P2\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9\" align=\"top\"></a> **Formatter cases are skipped** This filter selects no successful `credential.format` vector and no `receipt.format` vector: neither operation is in `smokeOps`, and the Kotlin map has only a failing credential-format case. The credential formatter could fail every valid request without failing this suite, while the declared receipt-format gap gets no conformance check. Add a successful formatter case and track the receipt-format gap. (https://github.com/caliperforge/pay-kit/pull/14#discussion_r4173234531).",
"scope": [
"**"
]
},
{
"rule": "pay-kit#14 `caliperforge/pay-kit@b976cea:harness/test/protocol-conformance.test.ts:179`: <a href=\"#\"><img alt=\"P2\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9\" align=\"top\"></a> **Challenge fields go unchecked** For challenges with a description, this expectation checks only that `description` is absent. A response with `result: {}` would also pass, so the test would miss regressions in required challenge fields or the decoded request. Check those fields against the vector while allowing the known omission. (https://github.com/caliperforge/pay-kit/pull/14#discussion_r4173234527).",
"scope": [
"**"
]
}
],
"files": [
{
"path": "docs/paykit-interface.md",
"scope": [
"**"
]
}
]
}
}
10 changes: 8 additions & 2 deletions ruby/lib/pay_kit/gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,18 @@ def fees?
!fees.empty?
end

def accepts?(protocol)
accept.include?(protocol.to_sym)
end

def x402_accepted?
accept.include?(:x402)
Kernel.warn("PayKit::Gate#x402_accepted? is deprecated; use accepts?(:x402)", uplevel: 1, category: :deprecated)
accepts?(:x402)
end

def mpp_accepted?
accept.include?(:mpp)
Kernel.warn("PayKit::Gate#mpp_accepted? is deprecated; use accepts?(:mpp)", uplevel: 1, category: :deprecated)
accepts?(:mpp)
end

# Format a BigDecimal as a fixed-point decimal string, trimming
Expand Down
8 changes: 4 additions & 4 deletions ruby/lib/pay_kit/rack/payment_required.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ def challenge_for(gate, request)
accepts = []
headers = {}

if gate.x402_accepted?
if gate.accepts?(:x402)
accepts << x402_adapter.accepts_entry(gate, request)
headers.merge!(x402_adapter.challenge_headers(gate, request))
end

if gate.mpp_accepted?
if gate.accepts?(:mpp)
accepts << mpp_adapter.accepts_entry(gate, request)
headers.merge!(mpp_adapter.challenge_headers(gate, request))
end
Expand All @@ -172,11 +172,11 @@ def challenge_for(gate, request)
# Returns nil when the request has no payment header at all
# (caller should respond with a challenge).
def verify(gate, request)
if gate.x402_accepted? && x402_adapter.detect?(request)
if gate.accepts?(:x402) && x402_adapter.detect?(request)
return x402_adapter.verify_and_settle(gate, request)
end

if gate.mpp_accepted? && mpp_adapter.detect?(request)
if gate.accepts?(:mpp) && mpp_adapter.detect?(request)
return mpp_adapter.verify_and_settle(gate, request)
end

Expand Down
28 changes: 26 additions & 2 deletions ruby/test/pay_kit/gate_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,35 @@ def test_x402_auto_disabled_when_fees_present
pay_to: SELLER,
fee_within: {PLATFORM => usd("1.00")})

refute gate.x402_accepted?
assert gate.mpp_accepted?
refute gate.accepts?(:x402)
assert gate.accepts?(:mpp)
end
end

def test_accepts_answers_from_accept
PayKitTestHelpers.with_config(accept: %i[mpp]) do
gate = build(:report, amount: usd("0.10"))

assert gate.accepts?(:mpp)
assert gate.accepts?("mpp")
refute gate.accepts?(:x402)
refute gate.accepts?(:stripe)
end
end

def test_deprecated_predicates_delegate_and_warn
deprecated = Warning[:deprecated]
Warning[:deprecated] = true
PayKitTestHelpers.with_config(accept: %i[x402 mpp]) do
gate = build(:report, amount: usd("0.10"))

assert_output(nil, /accepts\?/) { assert_equal gate.accepts?(:x402), gate.x402_accepted? }
assert_output(nil, /accepts\?/) { assert_equal gate.accepts?(:mpp), gate.mpp_accepted? }
end
ensure
Warning[:deprecated] = deprecated
end

def test_explicit_x402_with_fees_raises
PayKitTestHelpers.with_config(accept: %i[x402 mpp]) do
assert_raises(PayKit::ConfigurationError) do
Expand Down
Loading