Title
[Bug]: An OpenPaymentMandate carrying an AgentRecurrence constraint cannot be signed — TypeError: Object of type Frequency is not JSON serializable
Body
Where
common.delegate_claims_from_model in code/sdk/python/ap2/sdk/sdjwt/common.py, commit e1ea56d, interacting with the third-party sd_jwt library's disclosure hashing.
What's wrong
delegate_claims_from_model serializes a Pydantic payload with:
return payload.model_dump(by_alias=True, exclude_none=True)
This is Pydantic's Python-mode dump (the default), which leaves enum-typed fields as the enum member itself rather than its underlying value — that conversion is what model_dump(mode="json") does instead, and mode="json" is not used here. The resulting dict — still containing a raw Frequency enum instance wherever an AgentRecurrence.frequency field is set — is later handed to the third-party sd_jwt library's disclosure-hashing code, which calls plain json.dumps() on it. Frequency (code/sdk/python/ap2/sdk/generated/...) is a plain Enum, not a str-mixed enum, so json.dumps has no way to serialize it and raises.
Reproduction
from ap2.sdk.mandate import MandateClient
from ap2.sdk.generated.open_payment_mandate import OpenPaymentMandate, AgentRecurrence
from ap2.sdk.generated.types.recurrence import Frequency
MandateClient().create(
payloads=[
OpenPaymentMandate(
constraints=[
AgentRecurrence(frequency=Frequency.ON_DEMAND, max_occurrences=1)
],
cnf=...,
iat=...,
exp=...,
)
],
issuer_key=issuer_jwk,
)
# TypeError: Object of type Frequency is not JSON serializable
Impact
Unlike a verification-time bug, this fails closed — the mandate simply cannot be signed, so no incorrectly-accepted mandate results from it. But it means AgentRecurrence, one of the documented constraint types, cannot currently be exercised end-to-end through the SDK at all: no caller can construct a valid signed OpenPaymentMandate carrying this constraint using the public create() API. This also means the constraint's own evaluator (AgentRecurrenceEvaluator) cannot be tested against a real signed chain by downstream integrators — only against synthetic/hand-built payloads.
Suggested fix
Use payload.model_dump(by_alias=True, exclude_none=True, mode="json") in delegate_claims_from_model so enum fields (and any other JSON-incompatible Python-mode types) are serialized to their JSON-safe representation before being handed to the disclosure-hashing code.
Note
This is unrelated to #297, which concerns AgentRecurrenceEvaluator's frequency/period semantics being underspecified (a design/spec gap in what the constraint enforces), not the constraint failing to serialize at signing time at all.
Title
[Bug]: An
OpenPaymentMandatecarrying anAgentRecurrenceconstraint cannot be signed —TypeError: Object of type Frequency is not JSON serializableBody
Where
common.delegate_claims_from_modelincode/sdk/python/ap2/sdk/sdjwt/common.py, commite1ea56d, interacting with the third-partysd_jwtlibrary's disclosure hashing.What's wrong
delegate_claims_from_modelserializes a Pydantic payload with:This is Pydantic's Python-mode dump (the default), which leaves enum-typed fields as the enum member itself rather than its underlying value — that conversion is what
model_dump(mode="json")does instead, andmode="json"is not used here. The resulting dict — still containing a rawFrequencyenum instance wherever anAgentRecurrence.frequencyfield is set — is later handed to the third-partysd_jwtlibrary's disclosure-hashing code, which calls plainjson.dumps()on it.Frequency(code/sdk/python/ap2/sdk/generated/...) is a plainEnum, not astr-mixed enum, sojson.dumpshas no way to serialize it and raises.Reproduction
Impact
Unlike a verification-time bug, this fails closed — the mandate simply cannot be signed, so no incorrectly-accepted mandate results from it. But it means
AgentRecurrence, one of the documented constraint types, cannot currently be exercised end-to-end through the SDK at all: no caller can construct a valid signedOpenPaymentMandatecarrying this constraint using the publiccreate()API. This also means the constraint's own evaluator (AgentRecurrenceEvaluator) cannot be tested against a real signed chain by downstream integrators — only against synthetic/hand-built payloads.Suggested fix
Use
payload.model_dump(by_alias=True, exclude_none=True, mode="json")indelegate_claims_from_modelso enum fields (and any other JSON-incompatible Python-mode types) are serialized to their JSON-safe representation before being handed to the disclosure-hashing code.Note
This is unrelated to #297, which concerns
AgentRecurrenceEvaluator's frequency/period semantics being underspecified (a design/spec gap in what the constraint enforces), not the constraint failing to serialize at signing time at all.