{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://trenches.party/schema/report.json",
  "title": "trenches report (schema_version 1.0)",
  "description": "Canonical data contract for one trenches report. Every public representation (HTML, Markdown, plain text, JSON, feed entry) is generated from a document that validates against this schema. Timestamps are explicit UTC (RFC 3339 with a trailing Z). Unknown or unavailable values are null, never zero. Observed facts (`observed`, `evidence`, `metrics`, `checks`) are kept separate from interpretation (`inferred`, `relative_strength`, `confidence`).",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "report_id",
    "revision",
    "revises",
    "revision_note",
    "previous_report_id",
    "published_at",
    "title",
    "generated_by",
    "observation_window",
    "coverage",
    "summary",
    "changes",
    "narratives",
    "tokens",
    "actors",
    "evidence",
    "sources",
    "watch",
    "limitations"
  ],
  "properties": {
    "schema_version": {
      "description": "Version of this contract. Bumped when fields change incompatibly.",
      "type": "string",
      "enum": ["1.0"]
    },
    "report_id": {
      "description": "Stable identifier, derived from the publication minute in UTC (YYYY-MM-DDTHHMMZ). Revisions append -r<revision>. Several reports per day are allowed.",
      "$ref": "#/$defs/report_id"
    },
    "revision": {
      "description": "1 for an original report; 2+ for a correction that revises an earlier report.",
      "type": "integer",
      "minimum": 1
    },
    "revises": {
      "description": "report_id of the report this revision corrects, or null for an original report. Archived reports are never overwritten; corrections are published as new revisions.",
      "anyOf": [{ "$ref": "#/$defs/report_id" }, { "type": "null" }]
    },
    "revision_note": {
      "description": "What was corrected and why. Required (non-null) when revises is set.",
      "$ref": "#/$defs/nullable_text"
    },
    "previous_report_id": {
      "description": "report_id of the report this one compares against, or null for the first report. With null, `changes.items` must be empty: no previous report means no comparison.",
      "anyOf": [{ "$ref": "#/$defs/report_id" }, { "type": "null" }]
    },
    "published_at": {
      "description": "Publication time (UTC). Must not be earlier than observation_window.end.",
      "$ref": "#/$defs/utc_datetime"
    },
    "title": {
      "description": "Optional headline override. When null, the summary headline is used.",
      "$ref": "#/$defs/nullable_short_text"
    },
    "generated_by": {
      "description": "How the report was produced (workflow name, version, free-form note). Never include credentials or raw private tool responses.",
      "anyOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["name", "version", "note"],
          "properties": {
            "name": { "$ref": "#/$defs/short_text" },
            "version": { "$ref": "#/$defs/nullable_short_text" },
            "note": { "$ref": "#/$defs/nullable_text" }
          }
        },
        { "type": "null" }
      ]
    },
    "observation_window": {
      "description": "The period the observations cover. Distinct from published_at.",
      "type": "object",
      "additionalProperties": false,
      "required": ["start", "end", "note"],
      "properties": {
        "start": { "$ref": "#/$defs/utc_datetime" },
        "end": { "$ref": "#/$defs/utc_datetime" },
        "note": { "$ref": "#/$defs/nullable_text" }
      }
    },
    "coverage": {
      "description": "What was actually observed in this report: chains/networks with their sources and scope, plus known gaps. List only chains that were really covered.",
      "type": "object",
      "additionalProperties": false,
      "required": ["chains", "gaps"],
      "properties": {
        "chains": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "name", "network", "status", "scope", "sources"],
            "properties": {
              "id": {
                "description": "CAIP-2 style chain identifier, e.g. eip155:8453 (Base mainnet), eip155:56 (BNB Smart Chain), solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana mainnet), eip155:84532 (Base Sepolia testnet).",
                "$ref": "#/$defs/chain_id"
              },
              "name": { "$ref": "#/$defs/short_text" },
              "network": { "type": "string", "enum": ["mainnet", "testnet", "devnet", "other"] },
              "status": {
                "description": "covered: the stated scope was observed; partial: some sources failed or the scope was incomplete.",
                "type": "string",
                "enum": ["covered", "partial"]
              },
              "scope": {
                "description": "What was observed on this chain (e.g. launchpad feed, trending pools, holder checks for listed tokens).",
                "$ref": "#/$defs/text"
              },
              "sources": {
                "description": "Source ids used for this chain.",
                "$ref": "#/$defs/id_list"
              }
            }
          }
        },
        "gaps": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["text", "impact"],
            "properties": {
              "text": { "$ref": "#/$defs/text" },
              "impact": { "$ref": "#/$defs/confidence" }
            }
          }
        }
      }
    },
    "summary": {
      "type": "object",
      "additionalProperties": false,
      "required": ["headline", "body"],
      "properties": {
        "headline": { "$ref": "#/$defs/short_text" },
        "body": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/text" } }
      }
    },
    "changes": {
      "description": "What changed relative to previous_report_id. Must be empty when previous_report_id is null.",
      "type": "object",
      "additionalProperties": false,
      "required": ["items"],
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["kind", "text", "narrative_id", "token_ids", "evidence"],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "narrative-emerged",
                  "narrative-persisting",
                  "narrative-cooling",
                  "narrative-dormant",
                  "token-added",
                  "token-dropped",
                  "coverage-changed",
                  "correction",
                  "other"
                ]
              },
              "text": { "$ref": "#/$defs/text" },
              "narrative_id": { "anyOf": [{ "$ref": "#/$defs/slug" }, { "type": "null" }] },
              "token_ids": { "$ref": "#/$defs/id_list" },
              "evidence": { "$ref": "#/$defs/id_list" }
            }
          }
        }
      }
    },
    "narratives": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name",
          "description",
          "lifecycle",
          "confidence",
          "catalysts",
          "tokens",
          "observed",
          "inferred",
          "contradictions",
          "relative_strength",
          "evidence"
        ],
        "properties": {
          "id": {
            "description": "Stable slug used for anchors (#narrative-<id>) and cross-report comparison. Keep it constant across reports.",
            "$ref": "#/$defs/slug"
          },
          "name": { "$ref": "#/$defs/short_text" },
          "description": { "$ref": "#/$defs/text" },
          "lifecycle": {
            "type": "string",
            "enum": ["emerging", "persisting", "cooling", "dormant", "unknown"]
          },
          "confidence": {
            "description": "Confidence that this narrative is real and correctly characterised.",
            "$ref": "#/$defs/confidence"
          },
          "catalysts": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["text", "observed_at", "evidence"],
              "properties": {
                "text": { "$ref": "#/$defs/text" },
                "observed_at": { "$ref": "#/$defs/nullable_utc_datetime" },
                "evidence": { "$ref": "#/$defs/id_list" }
              }
            }
          },
          "tokens": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["token_id", "role", "note"],
              "properties": {
                "token_id": { "$ref": "#/$defs/ref_id" },
                "role": {
                  "description": "copycat-suspected must be backed by evidence or marked low/unknown confidence in the narrative text.",
                  "type": "string",
                  "enum": ["leader", "member", "derivative", "copycat-suspected", "unknown"]
                },
                "note": { "$ref": "#/$defs/nullable_text" }
              }
            }
          },
          "observed": {
            "description": "Observed facts, each tied to evidence.",
            "type": "array",
            "items": { "$ref": "#/$defs/observation" }
          },
          "inferred": {
            "description": "Interpretations, each with its own confidence.",
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["text", "confidence", "basis"],
              "properties": {
                "text": { "$ref": "#/$defs/text" },
                "confidence": { "$ref": "#/$defs/confidence" },
                "basis": { "$ref": "#/$defs/nullable_text" }
              }
            }
          },
          "contradictions": {
            "description": "Observations that cut against the narrative.",
            "type": "array",
            "items": { "$ref": "#/$defs/observation" }
          },
          "relative_strength": {
            "description": "Which tokens are holding up relative to narrative peers, and why.",
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["token_id", "text", "confidence", "evidence"],
              "properties": {
                "token_id": { "$ref": "#/$defs/ref_id" },
                "text": { "$ref": "#/$defs/text" },
                "confidence": { "$ref": "#/$defs/confidence" },
                "evidence": { "$ref": "#/$defs/id_list" }
              }
            }
          },
          "evidence": { "$ref": "#/$defs/id_list" }
        }
      }
    },
    "tokens": {
      "description": "Tokens are identified by chain id plus contract or mint address, never by ticker alone.",
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "chain",
          "address",
          "address_format",
          "symbol",
          "name",
          "launchpad",
          "launched_at",
          "deployer",
          "metrics",
          "checks",
          "notes",
          "evidence"
        ],
        "properties": {
          "id": {
            "description": "Report-local key referenced by narratives, actors, changes and watch items. Convention: <chain-short>:<address>.",
            "$ref": "#/$defs/ref_id"
          },
          "chain": { "$ref": "#/$defs/chain_id" },
          "address": {
            "description": "Contract or mint address exactly as observed. EVM hex addresses keep their case (checksum casing is not normalised); Solana base58 is case-sensitive.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "address_format": {
            "type": "string",
            "enum": ["evm-hex", "solana-base58", "other"]
          },
          "symbol": { "$ref": "#/$defs/nullable_short_text" },
          "name": { "$ref": "#/$defs/nullable_short_text" },
          "launchpad": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": ["name", "url"],
                "properties": {
                  "name": { "$ref": "#/$defs/short_text" },
                  "url": { "$ref": "#/$defs/nullable_http_url" }
                }
              },
              { "type": "null" }
            ]
          },
          "launched_at": { "$ref": "#/$defs/nullable_utc_datetime" },
          "deployer": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": ["address", "note"],
                "properties": {
                  "address": { "type": "string", "minLength": 1, "maxLength": 128 },
                  "note": { "$ref": "#/$defs/nullable_text" }
                }
              },
              { "type": "null" }
            ]
          },
          "metrics": { "type": "array", "items": { "$ref": "#/$defs/metric" } },
          "checks": {
            "description": "Specific checks performed on the token. Describe what was observed or not observed; never summarise as safe or clean.",
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["name", "result", "detail", "observed_at", "source"],
              "properties": {
                "name": { "$ref": "#/$defs/short_text" },
                "result": { "type": "string", "enum": ["observed", "not-observed", "unknown"] },
                "detail": { "$ref": "#/$defs/nullable_text" },
                "observed_at": { "$ref": "#/$defs/nullable_utc_datetime" },
                "source": { "anyOf": [{ "$ref": "#/$defs/ref_id" }, { "type": "null" }] }
              }
            }
          },
          "notes": { "type": "array", "items": { "$ref": "#/$defs/text" } },
          "evidence": { "$ref": "#/$defs/id_list" }
        }
      }
    },
    "actors": {
      "description": "Who is launching, promoting, building or migrating what. Claims about relationships or promotion need evidence or an explicit low/unknown confidence.",
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "name", "kind", "handles", "activity"],
        "properties": {
          "id": { "$ref": "#/$defs/slug" },
          "name": { "$ref": "#/$defs/short_text" },
          "kind": {
            "type": "string",
            "enum": ["deployer", "team", "builder", "promoter", "kol", "launchpad", "protocol", "unknown"]
          },
          "handles": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["platform", "handle", "url"],
              "properties": {
                "platform": { "$ref": "#/$defs/short_text" },
                "handle": { "$ref": "#/$defs/short_text" },
                "url": { "$ref": "#/$defs/nullable_http_url" }
              }
            }
          },
          "activity": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["action", "text", "token_ids", "narrative_ids", "observed_at", "confidence", "evidence"],
              "properties": {
                "action": {
                  "type": "string",
                  "enum": ["launching", "promoting", "building", "migrating", "other"]
                },
                "text": { "$ref": "#/$defs/text" },
                "token_ids": { "$ref": "#/$defs/id_list" },
                "narrative_ids": { "type": "array", "items": { "$ref": "#/$defs/slug" }, "uniqueItems": true },
                "observed_at": { "$ref": "#/$defs/nullable_utc_datetime" },
                "confidence": { "$ref": "#/$defs/confidence" },
                "evidence": { "$ref": "#/$defs/id_list" }
              }
            }
          }
        }
      }
    },
    "evidence": {
      "description": "Citable evidence items. Excerpts are untrusted source text and are always escaped when rendered.",
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "kind", "title", "url", "source", "observed_at", "excerpt", "note"],
        "properties": {
          "id": { "$ref": "#/$defs/ref_id" },
          "kind": {
            "type": "string",
            "enum": ["onchain", "market-data", "social-post", "webpage", "tool-output", "other"]
          },
          "title": { "$ref": "#/$defs/short_text" },
          "url": { "$ref": "#/$defs/nullable_http_url" },
          "source": { "$ref": "#/$defs/ref_id" },
          "observed_at": { "$ref": "#/$defs/utc_datetime" },
          "excerpt": { "anyOf": [{ "type": "string", "maxLength": 600 }, { "type": "null" }] },
          "note": { "$ref": "#/$defs/nullable_text" }
        }
      }
    },
    "sources": {
      "description": "Every data source consulted, including ones that failed. tool_id is the Frames catalog id when a Frames tool was used.",
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "name", "kind", "tool_id", "url", "accessed_at", "status", "note"],
        "properties": {
          "id": { "$ref": "#/$defs/ref_id" },
          "name": { "$ref": "#/$defs/short_text" },
          "kind": {
            "type": "string",
            "enum": ["frames-tool", "api", "onchain-rpc", "webpage", "social", "manual", "other"]
          },
          "tool_id": { "$ref": "#/$defs/nullable_short_text" },
          "url": { "$ref": "#/$defs/nullable_http_url" },
          "accessed_at": { "$ref": "#/$defs/nullable_utc_datetime" },
          "status": { "type": "string", "enum": ["ok", "partial", "failed", "not-used"] },
          "note": { "$ref": "#/$defs/nullable_text" }
        }
      }
    },
    "watch": {
      "description": "What to watch next.",
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["text", "narrative_id", "token_ids", "trigger"],
        "properties": {
          "text": { "$ref": "#/$defs/text" },
          "narrative_id": { "anyOf": [{ "$ref": "#/$defs/slug" }, { "type": "null" }] },
          "token_ids": { "$ref": "#/$defs/id_list" },
          "trigger": { "$ref": "#/$defs/nullable_text" }
        }
      }
    },
    "limitations": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/$defs/text" }
    }
  },
  "$defs": {
    "utc_datetime": {
      "type": "string",
      "format": "date-time",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?Z$"
    },
    "nullable_utc_datetime": {
      "anyOf": [{ "$ref": "#/$defs/utc_datetime" }, { "type": "null" }]
    },
    "report_id": {
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{4}Z(-r[0-9]+)?$"
    },
    "slug": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" },
    "ref_id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:._-]{0,119}$" },
    "chain_id": { "type": "string", "pattern": "^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$" },
    "http_url": { "type": "string", "pattern": "^https?://[^\\s]+$", "maxLength": 2048 },
    "nullable_http_url": { "anyOf": [{ "$ref": "#/$defs/http_url" }, { "type": "null" }] },
    "confidence": { "type": "string", "enum": ["high", "moderate", "low", "unknown"] },
    "text": { "type": "string", "minLength": 1, "maxLength": 4000 },
    "nullable_text": { "anyOf": [{ "$ref": "#/$defs/text" }, { "type": "null" }] },
    "short_text": { "type": "string", "minLength": 1, "maxLength": 300 },
    "nullable_short_text": { "anyOf": [{ "$ref": "#/$defs/short_text" }, { "type": "null" }] },
    "id_list": { "type": "array", "items": { "$ref": "#/$defs/ref_id" }, "uniqueItems": true },
    "observation": {
      "type": "object",
      "additionalProperties": false,
      "required": ["text", "evidence"],
      "properties": {
        "text": { "$ref": "#/$defs/text" },
        "evidence": { "$ref": "#/$defs/id_list" }
      }
    },
    "metric_window": {
      "description": "Measurement window. null on a metric means a point-in-time snapshot at observed_at.",
      "type": "object",
      "additionalProperties": false,
      "required": ["label", "start", "end"],
      "properties": {
        "label": { "$ref": "#/$defs/short_text" },
        "start": { "$ref": "#/$defs/nullable_utc_datetime" },
        "end": { "$ref": "#/$defs/nullable_utc_datetime" }
      }
    },
    "metric": {
      "description": "One measurement: value and unit, observation time, window, source, and (only when a change is reported) a baseline. Market cap and FDV are distinct names. A missing value is null, never 0.",
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "label", "value", "unit", "unit_label", "observed_at", "window", "source", "baseline", "note"],
      "properties": {
        "name": {
          "type": "string",
          "enum": [
            "price_usd",
            "market_cap_usd",
            "fdv_usd",
            "liquidity_usd",
            "volume_usd",
            "holders",
            "transactions",
            "buys",
            "sells",
            "social_mentions",
            "bonding_curve_progress_pct",
            "other"
          ]
        },
        "label": { "$ref": "#/$defs/nullable_short_text" },
        "value": { "anyOf": [{ "type": "number" }, { "type": "null" }] },
        "unit": { "type": "string", "enum": ["usd", "count", "percent", "native", "ratio", "other"] },
        "unit_label": {
          "description": "Symbol for native/other units, e.g. SOL or ETH.",
          "$ref": "#/$defs/nullable_short_text"
        },
        "observed_at": { "$ref": "#/$defs/utc_datetime" },
        "window": { "anyOf": [{ "$ref": "#/$defs/metric_window" }, { "type": "null" }] },
        "source": { "$ref": "#/$defs/ref_id" },
        "baseline": {
          "anyOf": [
            {
              "type": "object",
              "additionalProperties": false,
              "required": ["value", "observed_at", "window", "source", "report_id"],
              "properties": {
                "value": { "anyOf": [{ "type": "number" }, { "type": "null" }] },
                "observed_at": { "$ref": "#/$defs/utc_datetime" },
                "window": { "anyOf": [{ "$ref": "#/$defs/metric_window" }, { "type": "null" }] },
                "source": { "$ref": "#/$defs/ref_id" },
                "report_id": { "anyOf": [{ "$ref": "#/$defs/report_id" }, { "type": "null" }] }
              }
            },
            { "type": "null" }
          ]
        },
        "note": { "$ref": "#/$defs/nullable_text" }
      }
    }
  }
}
