{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://travel.agenthaven.dev/contracts/schemas/mandate-ap2.schema.json",
  "title": "The mandate in AP2 v0.2 form: the open content of the two roots the authority issues",
  "description": "What contracts/mandate-ap2-profile.md calls the AP2 v0.2 form of one approval: an open Checkout Mandate and an open Payment Mandate, each a Delegate SD-JWT root signed by the authority's issuing key (header {alg: ES256, typ: example+sd-jwt, kid: <thumbprint>}, the typ the reference SDK emits), carried as `<issuer JWT>~<disclosure>~...~` with every disclosure. This schema describes the OPEN CONTENT: the object the reference resolves as delegate_payload[0] once the disclosures are applied (ap2.sdk.sdjwt.sd_jwt.verify), which is the reference's own OpenCheckoutMandate / OpenPaymentMandate plus this profile's claims. The reference's schemas for those types are vendored under schemas/ap2/ with their $id kept; this file is the profile over them, not a replacement. The buyer's closing hops are `$defs.closed_checkout` and `$defs.closed_payment`: the content of `delegate_payload[0]` of a hop with typ kb+sd-jwt and NO kid (its key is the root's cnf.jwk), whose top level carries iat, aud (the seller's base URL), nonce (the quote's jti) and sd_hash (base64url SHA-256 over the root with its disclosures and trailing `~`). On the wire a chain is `<root without trailing ~>~~<hop>`, as the reference SDK's present() joins it. Receipts: the seller answers a Checkout Receipt and a Payment Receipt (reference's types, typ checkout-receipt+jwt / payment-receipt+jwt under our header policy) whose `reference` is the base64url SHA-256 of the closed-mandate JWT alone, as the reference's get_closed_mandate_jwt defines it. Status: IMPLEMENTED by agent-authority (issuance, since 2026-09-13; vectors mandate-ap2-*.json) and by the seller (checks 4 to 10 and receipts, src/ap2.mjs, since 2026-09-13, against the reference-built chain vector mandate-ap2-chain.json); the buyer's own hop encoding is C5.",
  "oneOf": [
    {
      "$ref": "#/$defs/open_checkout"
    },
    {
      "$ref": "#/$defs/open_payment"
    }
  ],
  "$defs": {
    "profile_claims": {
      "description": "What this profile adds to the reference's open content, all visible (SD-JWT-VC allows any claim). The same five words mean the same things as in mandate.schema.json.",
      "type": "object",
      "required": [
        "iss",
        "sub",
        "act",
        "jti",
        "policy_version",
        "cnf",
        "iat",
        "exp"
      ],
      "properties": {
        "iss": {
          "type": "string",
          "format": "uri",
          "description": "The authority's issuer URL; its key set is at <iss>/.well-known/jwks.json."
        },
        "sub": {
          "type": "string",
          "minLength": 1,
          "description": "The person's opaque id at the authority; never a phone number."
        },
        "act": {
          "type": "object",
          "required": [
            "sub"
          ],
          "additionalProperties": false,
          "properties": {
            "sub": {
              "type": "string",
              "pattern": "^spiffe://"
            }
          },
          "description": "The buyer instance that will close the mandate. No act.act: redelegation is refused (R-12)."
        },
        "jti": {
          "type": "string",
          "description": "One id shared by the checkout and the payment root of one approval: the handle of GET <iss>/mandates/<jti> (check 10) and of a revocation."
        },
        "policy_version": {
          "type": "string"
        },
        "cnf": {
          "type": "object",
          "required": [
            "jwk"
          ],
          "additionalProperties": false,
          "properties": {
            "jwk": {
              "$ref": "#/$defs/jwk_p256"
            }
          },
          "description": "RFC 7800: the buyer's stable public key, whole. The closing hop is signed by this key; its thumbprint is the mandate+jwt's cnf.jkt."
        },
        "iat": {
          "type": "integer",
          "description": "Seconds since the epoch; the approval time."
        },
        "exp": {
          "type": "integer",
          "description": "iat plus the mandate window (300 s in the POC)."
        }
      }
    },
    "jwk_p256": {
      "type": "object",
      "required": [
        "kty",
        "crv",
        "x",
        "y"
      ],
      "additionalProperties": false,
      "properties": {
        "kty": {
          "const": "EC"
        },
        "crv": {
          "const": "P-256"
        },
        "x": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{43}$"
        },
        "y": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{43}$"
        }
      }
    },
    "merchant": {
      "type": "object",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "Equal to the quote's merchant_id."
        },
        "name": {
          "type": "string"
        },
        "website": {
          "type": "string",
          "format": "uri",
          "description": "Equal to the quote's iss."
        }
      },
      "description": "The reference's Merchant; matched by id first (ap2.sdk.constraints.merchant_matches)."
    },
    "open_checkout": {
      "allOf": [
        {
          "$ref": "#/$defs/profile_claims"
        },
        {
          "type": "object",
          "required": [
            "vct",
            "constraints"
          ],
          "properties": {
            "vct": {
              "const": "mandate.checkout.open.1"
            },
            "constraints": {
              "type": "array",
              "minItems": 2,
              "maxItems": 2,
              "description": "Exactly the two the POC issues, already narrowed to the approved quote: one merchant, one item. An open mandate with any other constraint type is refused as unresolved_constraint.",
              "items": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "type",
                      "allowed"
                    ],
                    "properties": {
                      "type": {
                        "const": "checkout.allowed_merchants"
                      },
                      "allowed": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 1,
                        "items": {
                          "$ref": "#/$defs/merchant"
                        }
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "type",
                      "items"
                    ],
                    "properties": {
                      "type": {
                        "const": "checkout.line_items"
                      },
                      "items": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 1,
                        "items": {
                          "type": "object",
                          "required": [
                            "id",
                            "acceptable_items",
                            "quantity"
                          ],
                          "properties": {
                            "id": {
                              "const": "line_1"
                            },
                            "acceptable_items": {
                              "type": "array",
                              "minItems": 1,
                              "maxItems": 1,
                              "items": {
                                "type": "object",
                                "required": [
                                  "id",
                                  "title"
                                ],
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "description": "Equal to the quote's offer_id and to line_items[0].item.id."
                                  },
                                  "title": {
                                    "type": "string",
                                    "description": "Equal to the quote's line_items[0].item.title."
                                  }
                                }
                              }
                            },
                            "quantity": {
                              "const": 1
                            }
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    },
    "open_payment": {
      "allOf": [
        {
          "$ref": "#/$defs/profile_claims"
        },
        {
          "type": "object",
          "required": [
            "vct",
            "constraints",
            "payee",
            "payment_amount"
          ],
          "properties": {
            "vct": {
              "const": "mandate.payment.open.1"
            },
            "payee": {
              "$ref": "#/$defs/merchant",
              "description": "Preset: the quote's merchant. The closing hop must repeat it unchanged (check_preset_payment_claims)."
            },
            "payment_amount": {
              "type": "object",
              "required": [
                "amount",
                "currency"
              ],
              "additionalProperties": false,
              "properties": {
                "amount": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "The quote's total, minor units."
                },
                "currency": {
                  "type": "string",
                  "pattern": "^[A-Z]{3}$"
                }
              },
              "description": "Preset: the quote's total and currency, so the amount is fixed at approval, as the cart mandate fixes it."
            },
            "constraints": {
              "type": "array",
              "minItems": 1,
              "maxItems": 1,
              "items": {
                "type": "object",
                "required": [
                  "type",
                  "conditional_transaction_id"
                ],
                "properties": {
                  "type": {
                    "const": "payment.reference"
                  },
                  "conditional_transaction_id": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$",
                    "description": "The checkout root's sd_hash: base64url SHA-256 over the ASCII of that root with its disclosures and trailing `~` (RFC 9901 §4.3), as ap2.sdk.sdjwt.compute_sd_hash and the reference merchant compute it. Binds this payment to that one checkout."
                  }
                }
              },
              "description": "Exactly the reference binding. pisp, agent_recurrence, budget, execution_date, allowed_pisps and allowed_payment_instruments are not issued; an open mandate carrying them is refused as unresolved_constraint."
            }
          }
        }
      ]
    },
    "closed_checkout": {
      "type": "object",
      "required": [
        "vct",
        "checkout_hash",
        "checkout_jwt"
      ],
      "properties": {
        "vct": {
          "const": "mandate.checkout.1"
        },
        "checkout_hash": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{43}$",
          "description": "base64url SHA-256 over the ASCII of checkout_jwt. The seller recomputes it over the quote JWS it stored."
        },
        "checkout_jwt": {
          "type": "string",
          "description": "The seller's quote+jwt, byte for byte (the reference discloses it as a field-level disclosure inside the item). The seller compares it with the stored JWS, so quote_hash holds too."
        }
      },
      "description": "What the buyer's checkout hop discloses. No cnf: a terminal hop names no further key."
    },
    "closed_payment": {
      "type": "object",
      "required": [
        "vct",
        "transaction_id",
        "payee",
        "payment_amount",
        "payment_instrument"
      ],
      "properties": {
        "vct": {
          "const": "mandate.payment.1"
        },
        "transaction_id": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{43}$",
          "description": "Equal to the checkout hop's checkout_hash."
        },
        "payee": {
          "$ref": "#/$defs/merchant",
          "description": "Repeats the open root's preset payee (id match)."
        },
        "payment_amount": {
          "type": "object",
          "required": [
            "amount",
            "currency"
          ],
          "properties": {
            "amount": {
              "type": "integer"
            },
            "currency": {
              "type": "string",
              "pattern": "^[A-Z]{3}$"
            }
          },
          "description": "Repeats the open root's preset amount exactly."
        },
        "payment_instrument": {
          "type": "object",
          "required": [
            "id",
            "type"
          ],
          "properties": {
            "id": {
              "type": "string"
            },
            "type": {
              "type": "string"
            },
            "description": {
              "type": "string"
            }
          },
          "description": "Opaque in this POC: the merchant's sandbox rail charges its own test card."
        }
      },
      "description": "What the buyer's payment hop discloses."
    }
  }
}
