{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://geodesix.com/schemas/product-evidence/1/product-evidence.schema.json",
  "title": "Product evidence extension",
  "description": "A product_evidence object that rides in a retailer's ACP or UCP product feed. It carries third-party citations about the product from a named evidence provider, plus an attribution token from a named attribution network. Field names are provider-neutral so any evidence provider or network can use the same slot. Structural rules only; word counts, token verification, and tier rules live in the validator. Minor version 1.1 adds the optional compares_to on a citation. Unknown fields are allowed within a major version and are reported as warnings, never rejected.",
  "type": "object",
  "required": [
    "schema_version",
    "provider",
    "canonical_product_id",
    "evidence_set_id",
    "attribution",
    "citations"
  ],
  "properties": {
    "schema_version": {
      "type": "string",
      "pattern": "^1(\\.[0-9]+)?$",
      "description": "Major.minor. This schema covers major version 1. Readers reject any other major."
    },
    "provider": {
      "type": "object",
      "description": "Who assembled the evidence set and assigned the ids in this block.",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_-]*$"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "url": {
          "type": "string",
          "format": "uri"
        }
      }
    },
    "canonical_product_id": {
      "$ref": "#/$defs/opaqueId",
      "description": "The provider's canonical id for this product. The join key across retailers and for route C."
    },
    "evidence_set_id": {
      "$ref": "#/$defs/opaqueId",
      "description": "Id of this exact set of citations. Changes when the set changes, which also changes the attribution token."
    },
    "attribution": {
      "type": "object",
      "description": "How an order placed from this evidence is attributed. The token is minted by the network, readable by anyone, and passes through checkout unchanged.",
      "required": [
        "network",
        "agent_referral_id"
      ],
      "properties": {
        "network": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_-]*$",
          "description": "Attribution network id, for example impact."
        },
        "agent_referral_id": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
          "description": "Signed token: <version>.<base64url payload>.<base64url signature>. Same value as the url_parameter in the buy URL."
        },
        "url_parameter": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_]+$",
          "description": "Query parameter that carries the token on the buy URL, for example irarid. Defaults to irarid when omitted."
        }
      }
    },
    "summary": {
      "type": "object",
      "description": "One-line consensus and verdict counts for agents that will not parse citations.",
      "required": [
        "consensus",
        "verdicts",
        "as_of"
      ],
      "properties": {
        "consensus": {
          "type": "string",
          "minLength": 1
        },
        "verdicts": {
          "type": "object",
          "required": [
            "recommend",
            "conditional",
            "avoid"
          ],
          "properties": {
            "recommend": {
              "type": "integer",
              "minimum": 0
            },
            "conditional": {
              "type": "integer",
              "minimum": 0
            },
            "avoid": {
              "type": "integer",
              "minimum": 0
            }
          }
        },
        "as_of": {
          "type": "string",
          "format": "date"
        }
      }
    },
    "citations": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/citation"
      }
    }
  },
  "$defs": {
    "opaqueId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9_.:-]+$",
      "description": "Provider-assigned id. Opaque to readers."
    },
    "citation": {
      "type": "object",
      "required": [
        "citation_id",
        "source",
        "url",
        "criterion",
        "polarity",
        "claim",
        "quote",
        "license_status"
      ],
      "properties": {
        "citation_id": {
          "$ref": "#/$defs/opaqueId"
        },
        "source": {
          "$ref": "#/$defs/source"
        },
        "author": {
          "type": "string",
          "minLength": 1
        },
        "published_at": {
          "type": "string",
          "format": "date",
          "description": "Publication date of the source. Omit when unknown rather than guessing."
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "Link to the source. For video, may carry a timestamp parameter so the link lands on the quoted moment."
        },
        "criterion": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*$",
          "description": "Category criterion slug. Unknown criteria are shown under other."
        },
        "polarity": {
          "type": "string",
          "enum": [
            "positive",
            "negative",
            "neutral",
            "conditional"
          ]
        },
        "claim": {
          "type": "string",
          "minLength": 1,
          "description": "The distilled claim in plain words, written by the provider's pipeline."
        },
        "quote": {
          "type": "string",
          "minLength": 1,
          "description": "The supporting span from the source. Must be under 15 words; the validator drops the citation otherwise."
        },
        "product_version": {
          "type": "string",
          "minLength": 1
        },
        "license_status": {
          "type": "string",
          "enum": [
            "licensed",
            "fair_use_excerpt",
            "withdrawn"
          ]
        },
        "scope": {
          "type": "string",
          "enum": [
            "product",
            "product_line",
            "brand"
          ],
          "default": "product",
          "description": "What the source was talking about. product is this product; product_line is the retailer's line this product belongs to; brand is the retailer in general. The app shows the scope so a line-level quote is never presented as a product test."
        },
        "compares_to": {
          "type": "array",
          "description": "Added in 1.1. Products this citation compares the product with, by the provider's canonical id. The app builds its alternatives graph only from these; it never infers a comparison.",
          "items": {
            "type": "object",
            "required": [
              "canonical_product_id",
              "outcome"
            ],
            "properties": {
              "canonical_product_id": {
                "$ref": "#/$defs/opaqueId"
              },
              "outcome": {
                "type": "string",
                "enum": [
                  "wins",
                  "loses",
                  "mixed"
                ],
                "description": "How this product fared against the compared product in the source's judgment."
              },
              "criteria": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^[a-z][a-z0-9_]*$"
                },
                "description": "Criteria the comparison turned on, when the source says."
              }
            }
          }
        }
      }
    },
    "source": {
      "type": "object",
      "required": [
        "id",
        "name",
        "type",
        "tier",
        "test_method",
        "sponsorship_disclosed"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/opaqueId"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "type": {
          "type": "string",
          "enum": [
            "publisher",
            "creator",
            "community",
            "merchant"
          ]
        },
        "tier": {
          "type": "integer",
          "minimum": 1,
          "maximum": 4,
          "description": "1 independent lab, 2 independent hands-on, 3 creator with disclosed sponsorship or owner review, 4 merchant or brand."
        },
        "test_method": {
          "type": "string",
          "enum": [
            "lab",
            "hands_on",
            "long_term",
            "owner",
            "unknown"
          ]
        },
        "sponsorship_disclosed": {
          "type": "boolean"
        }
      }
    }
  }
}
