{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://suffect.com/schemas/0.2/consent-record.schema.json",
  "title": "Suffect consent record 0.2",
  "description": "The signed serialization of a mandate. Canonicalized with RFC 8785 before hashing and signing. See docs/05 section 5 and docs/04 v2 section 6.",
  "type": "object",
  "required": [
    "type",
    "version",
    "id",
    "issued_at",
    "issuer",
    "subject",
    "approver",
    "agent",
    "provider",
    "scope",
    "terms",
    "commercial",
    "budget",
    "data_shared",
    "assent_text",
    "notices_shown",
    "proofs",
    "prev"
  ],
  "properties": {
    "type": {
      "const": "SuffectConsent"
    },
    "version": {
      "const": "0.2"
    },
    "id": {
      "type": "string",
      "pattern": "^cns_[0-9A-Z]{26}$"
    },
    "issued_at": {
      "type": "string",
      "format": "date-time"
    },
    "issuer": {
      "type": "string",
      "format": "uri"
    },
    "subject": {
      "type": "object",
      "required": ["kind", "id", "name", "country", "representations"],
      "properties": {
        "kind": {
          "type": "string",
          "enum": ["user", "org"]
        },
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "country": {
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        },
        "representations": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/representation"
          }
        }
      }
    },
    "approver": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "object",
          "required": ["id", "name", "representations"],
          "properties": {
            "id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "representations": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/representation"
              }
            }
          }
        }
      ]
    },
    "agent": {
      "type": "object",
      "required": ["client_id", "runtime", "display"],
      "properties": {
        "client_id": {
          "type": "string"
        },
        "runtime": {
          "type": "string"
        },
        "display": {
          "type": "string"
        }
      }
    },
    "provider": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "object",
          "required": ["name", "domain"],
          "properties": {
            "name": {
              "type": "string"
            },
            "domain": {
              "type": "string"
            }
          }
        }
      ]
    },
    "scope": {
      "$ref": "#/$defs/scope"
    },
    "terms": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["kind", "url", "version", "sha256"],
        "properties": {
          "kind": {
            "$ref": "#/$defs/terms_kind"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "version": {
            "type": "string"
          },
          "sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "fetched_at": {
            "type": "string",
            "format": "date-time"
          },
          "summary_sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "Present when the sheet showed a model-written summary of this document: the SHA-256 of the canonical JSON of the summary entry as shown (sha256, generated_at, model, prompt_version, sections)."
          }
        }
      },
      "description": "Every terms snapshot this record accepts. Empty for a standing mandate; each provider's terms are then accepted in a follow-on record chained through prev."
    },
    "commercial": {
      "type": "object",
      "required": ["plan", "price", "currency", "period", "renewal"],
      "properties": {
        "plan": {
          "type": ["string", "null"]
        },
        "price": {
          "type": ["number", "null"]
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$"
        },
        "period": {
          "type": ["string", "null"],
          "enum": ["month", "week", "day", "once", null]
        },
        "renewal": {
          "type": "string",
          "enum": ["auto", "manual", "none"]
        }
      }
    },
    "budget": {
      "type": "object",
      "required": ["amount", "currency", "period"],
      "properties": {
        "amount": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$"
        },
        "period": {
          "type": "string",
          "enum": ["month", "week", "day", "once"]
        }
      }
    },
    "data_shared": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": ["name", "alias_email", "email", "country", "card"]
      },
      "uniqueItems": true
    },
    "assent_text": {
      "type": "string",
      "minLength": 20
    },
    "notices_shown": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "clauses_shown": {
      "type": "array",
      "description": "The clauses the sheet printed under its notices, each keyed to a document in terms by kind and URL, so the quote is checkable against that document's sha256; what the human read is what they signed.",
      "items": {
        "type": "object",
        "required": ["kind", "url", "notice", "quote"],
        "additionalProperties": false,
        "properties": {
          "kind": {
            "$ref": "#/$defs/terms_kind"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "notice": {
            "type": "string",
            "minLength": 1
          },
          "section": {
            "type": "string",
            "maxLength": 40
          },
          "quote": {
            "type": "string",
            "minLength": 1,
            "maxLength": 600
          }
        }
      }
    },
    "proofs": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "type",
              "credential_id",
              "client_data",
              "authenticator_data",
              "signature",
              "public_key_jwk"
            ],
            "properties": {
              "type": {
                "const": "webauthn"
              },
              "credential_id": {
                "type": "string"
              },
              "client_data": {
                "type": "string"
              },
              "authenticator_data": {
                "type": "string"
              },
              "signature": {
                "type": "string"
              },
              "public_key_jwk": {
                "type": "object"
              }
            }
          },
          {
            "type": "object",
            "required": ["type", "kid", "signature"],
            "properties": {
              "type": {
                "const": "ed25519"
              },
              "kid": {
                "type": "string"
              },
              "signature": {
                "type": "string"
              }
            }
          }
        ]
      }
    },
    "prev": {
      "type": ["string", "null"],
      "pattern": "^[a-f0-9]{64}$"
    }
  },
  "$defs": {
    "representation": {
      "type": "string",
      "enum": ["age_18", "authority", "residency"]
    },
    "scope": {
      "type": "object",
      "description": "The fixed predicate set, docs/04 v2 section 6.1.",
      "properties": {
        "resources": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["provider"],
            "properties": {
              "provider": {
                "type": "string"
              },
              "kind": {
                "$ref": "#/$defs/resource_kind"
              },
              "id": {
                "type": "string"
              },
              "actions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        },
        "categories": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "providers_excluded": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "max_price_per_call": {
          "$ref": "#/$defs/money"
        },
        "rails": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/rail"
          },
          "uniqueItems": true
        },
        "agents": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "anyOf": [
        {
          "required": ["resources"],
          "properties": {
            "resources": {
              "type": "array",
              "minItems": 1
            }
          }
        },
        {
          "required": ["categories"],
          "properties": {
            "categories": {
              "type": "array",
              "minItems": 1
            }
          }
        }
      ]
    },
    "resource_kind": {
      "type": "string",
      "enum": ["account", "plan", "endpoint", "tool", "connection"]
    },
    "rail": {
      "type": "string",
      "enum": ["card", "x402", "mpp", "balance"]
    },
    "money": {
      "type": "object",
      "required": ["amount", "currency"],
      "properties": {
        "amount": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$"
        }
      }
    },
    "terms_kind": {
      "type": "string",
      "enum": ["terms_of_use", "privacy_policy", "acceptable_use", "other"]
    }
  }
}
