{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://kiosk.tech/spec/schemas/problem.schema.json",
  "title": "Kiosk problem document",
  "description": "Every error response is an RFC 9457 problem document, served as application/problem+json. Success responses carry no wrapper at all -- the body is the verb's result, described by the verb's own output_schema -- so this is the only response schema the protocol defines.",
  "type": "object",
  "required": ["type", "title", "status", "code"],
  "properties": {
    "type": {
      "type": "string",
      "format": "uri",
      "description": "RFC 9457 problem type: https://kiosk.tech/problems/<code>, one URI per vocabulary entry. An identifier, not a document locator -- the normative documentation for every code is the error vocabulary in the specification. Clients MUST NOT parse it to recover the code; read `code`.",
      "pattern": "^https://kiosk\\.tech/problems/[a-z_]+$"
    },
    "title": {
      "type": "string",
      "description": "RFC 9457 title: a short human-readable summary of the problem TYPE. Constant per type -- it does not change from occurrence to occurrence. The incident-specific sentence is `detail`."
    },
    "status": {
      "type": "integer",
      "description": "RFC 9457 status: the HTTP status code, restated in the body. The status is a CONSTANT OF THE CODE, never a per-incident choice: the `allOf` below binds each of the seventeen vocabulary entries to exactly one status, so a document whose `code` and `status` disagree is invalid here and not merely unusual. That mapping is the one the specification's error-vocabulary table publishes, and it is the machine-readable copy an implementation's own code table is derived from -- before this existed the table lived in hand-kept copies on both sides of the repository boundary with nothing comparing them. Four codes share 403, three share 402 and two share 404, which is why `code` and not the status is the field to branch on.",
      "minimum": 100,
      "maximum": 599
    },
    "detail": {
      "type": "string",
      "description": "RFC 9457 detail: the human-readable, incident-specific message. Dropped when there is nothing to say."
    },
    "code": {
      "description": "Extension member, and THE CONTRACT: the closed, stable error vocabulary. An AI assistant branches on this, never on the HTTP status alone (four codes share 403, three share 402, and two share 404) and never on the presence of a response header. The two 404s are the split of Section 9: verb_not_found means no verb by that NAME is registered here (re-read the catalogue), not_found means the verb exists and an argument ADDRESSED something absent (stop and say so). module_not_served is the third of that family and is 501, because the published path is correct and the whole capability is absent.",
      "enum": [
        "bad_request",
        "unauthenticated",
        "forbidden",
        "rls_denied",
        "kyc_required",
        "spending_cap_exceeded",
        "verb_not_found",
        "not_found",
        "method_not_allowed",
        "conflict",
        "pow_required",
        "payment_setup_required",
        "payment_failed",
        "quota_exceeded",
        "action_failed",
        "internal_error",
        "module_not_served"
      ]
    },
    "hint": {
      "type": "string",
      "description": "Extension member: a remediation pointer (e.g. the registered verb names on a verb_not_found, the method a verb wanted on a method_not_allowed). On kyc_required and payment_failed it is the field that says what to do next."
    },
    "challenges": {
      "type": "array",
      "description": "Extension member, present ONLY on pow_required: the list of PoW challenges to solve.",
      "items": { "$ref": "https://kiosk.tech/spec/schemas/pow.schema.json#/$defs/challenge" }
    }
  },
  "allOf": [
    { "if": { "required": ["code"], "properties": { "code": { "const": "bad_request" } } },
      "then": { "properties": { "status": { "const": 400 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "unauthenticated" } } },
      "then": { "properties": { "status": { "const": 401 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "pow_required" } } },
      "then": { "properties": { "status": { "const": 402 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "payment_setup_required" } } },
      "then": { "properties": { "status": { "const": 402 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "payment_failed" } } },
      "then": { "properties": { "status": { "const": 402 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "forbidden" } } },
      "then": { "properties": { "status": { "const": 403 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "rls_denied" } } },
      "then": { "properties": { "status": { "const": 403 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "spending_cap_exceeded" } } },
      "then": { "properties": { "status": { "const": 403 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "kyc_required" } } },
      "then": { "properties": { "status": { "const": 403 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "verb_not_found" } } },
      "then": { "properties": { "status": { "const": 404 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "not_found" } } },
      "then": { "properties": { "status": { "const": 404 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "method_not_allowed" } } },
      "then": { "properties": { "status": { "const": 405 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "conflict" } } },
      "then": { "properties": { "status": { "const": 409 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "quota_exceeded" } } },
      "then": { "properties": { "status": { "const": 429 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "action_failed" } } },
      "then": { "properties": { "status": { "const": 500 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "internal_error" } } },
      "then": { "properties": { "status": { "const": 500 } } } },
    { "if": { "required": ["code"], "properties": { "code": { "const": "module_not_served" } } },
      "then": { "properties": { "status": { "const": 501 } } } }
  ]
}
