SDK constraints: budget limit truncates floats (0.29 -> 28 cents), execution_date compared as text, negative amounts pass
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
Research direction
Locate the constraint logic in the ap2.sdk.constraints module (likely ap2/sdk/constraints.py) containing BudgetEvaluator, ExecutionDateEvaluator and Amount. Fix float truncation with Decimal, parse dates to timezone-aware datetimes, and add ge=0 to Amount.amount. Run constraints_tests.py and constraints_line_items_tests.py; done when all tests pass and the three reported cases match the spec.
Written by the indexing model from the issue text.
Description
Hi, I am building an independent implementation of the payment-mandate constraint check (Rust and TypeScript: https://github.com/kennzyoguh-lang/kaldrix-x402-guard) and compared it with ap2.sdk.constraints.check_payment_constraints on 327 generated cases (checked at e1ea56d, 29 April 2026). The SDK's own constraints_tests.py and constraints_line_items_tests.py pass here (62 tests). Our verdicts agree with the SDK on about 310 of the cases; the comparison also found one place where my own code was laxer than the SDK (a pre-set payment_instrument compared by id alone), which is fixed on our side. The remaining differences come down to the three behaviours below, where the SDK looks different from the specification text.
Cases, generator and a reproduction script: https://github.com/kennzyoguh-lang/kaldrix-x402-guard/tree/main/tests/vectors (repro_reference_findings.py runs against a checkout of this repo).
1. BudgetEvaluator: int(self.constraint.max * 100) loses a cent
The spec (Budget, Evaluation) says the requested amount plus the total of previous amounts MUST be less than or equal to max. The budget is in whole currency units and amounts are in minor units, and the conversion truncates a float product: int(0.29 * 100) is 28 and int(1.15 * 100) is 114. A payment equal to the budget is refused:
run([{'type': 'payment.budget', 'max': 0.29, 'currency': 'USD'}], closed(amount=29), MandateContext(total_amount=0, total_uses=0))
# ['Cumulative spend 29 exceeds budget limit 28 (past spend: 0)']
Suggestion: convert with Decimal(str(max)) * 100, or round before truncating.
2. ExecutionDateEvaluator compares the dates as strings
exec_date < self.constraint.not_before and exec_date > self.constraint.not_after compare str values. The spec says the execution date MUST be later than or equal to not_before and earlier than or equal to not_after, which is about instants. With not_after = 2026-11-30T23:59:59Z:
2026-11-30T23:30:00-05:00(that is 04:30Z on 1 December, after the window) gives no violation.2026-12-01T00:30:00+01:00(that is 23:30Z on 30 November, inside the window) gives 'is after allowed window'.- With
not_before = 2026-11-30T00:00:00Z,2026-11-30 12:00:00(space instead of T) gives 'is before allowed window'.
Suggestion: parse both sides to timezone-aware datetimes before comparing, and say what a date-only value means (the spec examples use both forms).
3. Negative amounts are accepted
Amount.amount is an int with no lower bound. An amount_range with no min accepts a payment of -5, and BudgetEvaluator adds the amount to the running total, so a negative amount also passes a budget that is already used up. Suggestion: ge=0 on Amount.amount, or an explicit check in check_payment_constraints.
A question rather than a bug: agent_recurrence.frequency
AgentRecurrenceEvaluator only checks max_occurrences. frequency is never evaluated and calculate_period_start is not called anywhere (it also has no case for BIWEEKLY or QUARTERLY), while the spec says the constraint holds only if the payment is 'sufficiently separated in time from the previous presentation to meet the frequency definition'. Is that intentional for 0.2, or planned? We copied the SDK's behaviour and say so, rather than guess.
I am not affiliated with Google or the FIDO Alliance, and this is not an endorsement claim in either direction. Happy to send a pull request for any of these if that would help.
- Dominant language
- Python
- Stars
- 3.2k
- Forks
- 514
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from google-agentic-commerce/AP2
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
google-agentic-commerce/AP2#374 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
google-agentic-commerce/AP2#320 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
google-agentic-commerce/AP2#294 · 1 comment ·
-
[Feat]: Revocation path for open Mandates — rescinding standing agent authority bound to a cnf keyOpen
Difficulty 5/5 Over a week Newbie friendliness 35/100
google-agentic-commerce/AP2#386 ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
google-agentic-commerce/AP2#381 · 1 comment ·
All issues in google-agentic-commerce/AP2
Similar issues
-
bug P2 rust
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
jscott3201/rusty-bacnet#2085 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
evarness-ai/iris-harness#401 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
sistemicorp/scripts#62 ·
-
area/mac help wanted needs-qa
Difficulty 1/5 Under an hour Newbie friendliness 76/100