{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://wickfeed.com/schema/envelope.v1.json",
  "title": "Wickfeed event envelope v1",
  "description": "One envelope for all four event types. Additive only: a field once published is never removed and never changes meaning. Consumers MUST tolerate unknown fields and unknown enum values, because adding a book, venue, source or event type does not bump the version.",
  "type": "object",
  "required": [
    "type",
    "wick_id",
    "id",
    "book",
    "venue",
    "event_time",
    "emit_time",
    "mid"
  ],
  "additionalProperties": true,
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "wickfeed.moved.v1",
        "wickfeed.mouth.v1",
        "wickfeed.print.v1",
        "wickfeed.lit.v1"
      ],
      "description": "wickfeed.<kind>.v<n>"
    },
    "wick_id": {
      "type": "string",
      "description": "Deterministic and stable across redelivery. Receiving the same wick_id twice is safe to discard."
    },
    "prev_wick_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "Previous wick for this identifier. Forms a chain per contract."
    },
    "id": {
      "type": "string",
      "pattern": "^wf:[a-z0-9-]+$",
      "description": "The canonical wf: identifier. Permanent, never reused. See /ids.json"
    },
    "book": {
      "type": "string",
      "enum": [
        "midterms",
        "fed",
        "sports",
        "geopol",
        "crypto"
      ],
      "description": "New books may be added without a version bump."
    },
    "venue": {
      "type": "string"
    },
    "venue_ref": {
      "type": [
        "string",
        "null"
      ],
      "description": "That venue own identifier for the contract."
    },
    "event_time": {
      "type": "string",
      "format": "date-time",
      "description": "The source timestamp. Windows are ordered by this, never by arrival time."
    },
    "emit_time": {
      "type": "string",
      "format": "date-time"
    },
    "late": {
      "type": "boolean",
      "description": "True if this arrived after its watermark closed."
    },
    "mid": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    },
    "delta": {
      "type": "number",
      "description": "Signed change across the window."
    },
    "window_s": {
      "type": "integer"
    },
    "threshold": {
      "type": "number",
      "description": "The adaptive threshold in force for this contract when it fired."
    },
    "verdict": {
      "type": [
        "object",
        "null"
      ],
      "description": "Present on wickfeed.lit only. The three fields that carry the product are driver, attribution and reaction.",
      "required": [
        "driver",
        "attribution",
        "reaction"
      ],
      "additionalProperties": true,
      "properties": {
        "driver": {
          "type": "string",
          "enum": [
            "information",
            "liquidity",
            "unknown"
          ]
        },
        "attribution": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Confidence the named cause produced the move. Below 0.3 the driver is reported as unknown."
        },
        "reaction": {
          "type": "string",
          "enum": [
            "under",
            "proportionate",
            "over",
            "unknown"
          ]
        },
        "reaction_gap": {
          "type": [
            "number",
            "null"
          ],
          "description": "Distance from proportionate, in probability points."
        },
        "confidence": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "cause": {
          "type": [
            "object",
            "null"
          ],
          "description": "Null when the move could not be explained. A cause is never guessed.",
          "additionalProperties": true,
          "properties": {
            "kind": {
              "type": "string",
              "enum": [
                "mouth",
                "print",
                "flow"
              ]
            },
            "source": {
              "type": "string",
              "description": "Only a source actually observed, archived and scored."
            },
            "source_class": {
              "type": "string",
              "enum": [
                "candidate",
                "official",
                "agency",
                "pundit",
                "crowd"
              ]
            },
            "authority": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ]
            },
            "relevance": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            },
            "novelty": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100,
              "description": "First candidate reporting a fact in a 24h window scores 100. Retellings decay."
            },
            "stance": {
              "type": "string",
              "enum": [
                "pro_yes",
                "pro_no",
                "unclear"
              ],
              "description": "Measured against the contract resolution criteria. This is NOT a sentiment score; no sentiment field exists at any version."
            },
            "lag_s": {
              "type": "integer",
              "description": "Seconds between cause and move. Negative means the cause followed the move."
            },
            "ref": {
              "type": [
                "string",
                "null"
              ],
              "format": "uri"
            }
          }
        }
      }
    },
    "corroboration": {
      "type": "array",
      "description": "Later, less novel candidates reporting the same fact. They never produce their own wick.",
      "items": {
        "type": "object",
        "additionalProperties": true
      }
    }
  }
}