{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://torneo.ai/schemas/contest.v3",
  "title": "Frozen contest v3",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "contest_version",
    "category",
    "promise",
    "task",
    "dataset",
    "metrics",
    "participants",
    "budget",
    "window",
    "scoring",
    "design",
    "coverage",
    "freshness",
    "conflicts",
    "limits",
    "study_id",
    "season",
    "revision",
    "revision_of",
    "exclusions",
    "stopping_rule",
    "retry_rule",
    "seed_rule",
    "uncertainty",
    "cost_model",
    "sandbox_profile",
    "approvals",
    "contamination"
  ],
  "properties": {
    "contest_version": {
      "const": "contest.v3"
    },
    "category": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9_-]*$"
    },
    "promise": {
      "type": "string",
      "minLength": 1
    },
    "task": {
      "type": "string",
      "minLength": 1
    },
    "context": {
      "type": "string"
    },
    "objective_and_population": {
      "type": "string",
      "minLength": 1,
      "description": "One sentence: what is measured, on which frozen population, at which date. Projected verbatim as the FIRST field of result.v2 conclusion."
    },
    "dataset": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "description",
        "size",
        "frozen_at",
        "sha256",
        "contains_personal_data"
      ],
      "properties": {
        "description": {
          "type": "string",
          "minLength": 1
        },
        "size": {
          "type": "integer",
          "minimum": 1
        },
        "frozen_at": {
          "$ref": "#/$defs/utc_datetime"
        },
        "sha256": {
          "$ref": "#/$defs/sha256"
        },
        "contains_personal_data": {
          "type": "boolean"
        }
      }
    },
    "metrics": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "primary"
      ],
      "properties": {
        "primary": {
          "type": "string",
          "minLength": 1
        },
        "secondary": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "participants": {
      "type": "array",
      "minItems": 1,
      "description": "minItems is 1 in v2: a single-arm case study is a legitimate published result (C5), reported with conclusion.single_vendor true and no winner.",
      "items": {
        "$ref": "#/$defs/participant"
      }
    },
    "budget": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "cap_usd"
      ],
      "properties": {
        "cap_usd": {
          "type": "number",
          "minimum": 0
        },
        "per_participant_cap_usd": {
          "type": "number",
          "minimum": 0
        }
      }
    },
    "window": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "start",
        "end"
      ],
      "properties": {
        "start": {
          "$ref": "#/$defs/utc_datetime"
        },
        "end": {
          "$ref": "#/$defs/utc_datetime"
        }
      }
    },
    "scoring": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "min_n",
        "max_interval_width",
        "confidence",
        "min_blocks_for_rank"
      ],
      "properties": {
        "min_n": {
          "type": "integer",
          "minimum": 1
        },
        "max_interval_width": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 1
        },
        "confidence": {
          "type": "number",
          "exclusiveMinimum": 0,
          "exclusiveMaximum": 1
        },
        "min_blocks_for_rank": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "design": {
      "type": "object",
      "additionalProperties": false,
      "description": "The pairwise test, frozen before the run. All six parameters are required together; the schema refuses a partial design outright, and pairwise.py refuses to emit any per-pair flag if one is missing at runtime (C4).",
      "required": [
        "test",
        "alpha",
        "correction",
        "cluster_unit",
        "power",
        "mde"
      ],
      "properties": {
        "test": {
          "type": "string",
          "minLength": 1,
          "description": "Named statistical test, e.g. two-proportion z-test, cluster-robust."
        },
        "alpha": {
          "type": "number",
          "exclusiveMinimum": 0,
          "exclusiveMaximum": 1
        },
        "correction": {
          "type": "string",
          "minLength": 1,
          "description": "Multiplicity correction across pairs, e.g. holm, bonferroni, none_single_pair."
        },
        "cluster_unit": {
          "type": "string",
          "minLength": 1,
          "description": "Unit of independence used for clustering, e.g. contact, clip, company."
        },
        "power": {
          "type": "number",
          "exclusiveMinimum": 0,
          "exclusiveMaximum": 1
        },
        "mde": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Minimal detectable effect, absolute, commercially useful. Set before the run."
        }
      }
    },
    "coverage": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "must_include"
      ],
      "properties": {
        "must_include": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "description": "Vendors the category must cover to be honest about the market. Any entry absent from participants becomes a conclusion.coverage must_include_gap row with a reason from the shared enumeration."
        }
      }
    },
    "freshness": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "validity_days"
      ],
      "properties": {
        "validity_days": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "assignment": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "method",
        "seed"
      ],
      "properties": {
        "method": {
          "type": "string",
          "minLength": 1
        },
        "seed": {
          "type": [
            "integer",
            "string"
          ]
        }
      }
    },
    "applies_to": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "does_not_apply_to": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "conflicts": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "limits": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "runner": {
      "type": "string"
    },
    "funding": {
      "type": "string"
    },
    "study_id": {
      "type": "string",
      "minLength": 1
    },
    "season": {
      "type": "string",
      "minLength": 1
    },
    "revision": {
      "type": "integer",
      "minimum": 1
    },
    "revision_of": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "exclusions": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "minItems": 0
    },
    "stopping_rule": {
      "type": "string",
      "minLength": 1
    },
    "retry_rule": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "codes": {
          "type": "array",
          "items": {
            "enum": [
              "INFRA_KILL",
              "INFRA_UNAVAILABLE"
            ]
          },
          "minItems": 0
        },
        "max_retries": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10
        }
      },
      "required": [
        "codes",
        "max_retries"
      ]
    },
    "seed_rule": {
      "const": "sha256(config_id,unit_id,harness_id)"
    },
    "uncertainty": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "method": {
          "type": "string",
          "minLength": 1
        },
        "level": {
          "type": "number",
          "exclusiveMinimum": 0,
          "exclusiveMaximum": 1
        },
        "assumptions": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "minItems": 1
        }
      },
      "required": [
        "method",
        "level",
        "assumptions"
      ]
    },
    "cost_model": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "denominator": {
          "type": "string",
          "minLength": 1
        },
        "failures": {
          "type": "string",
          "minLength": 1
        },
        "currency": {
          "type": "string",
          "minLength": 1
        },
        "exchange_rate": {
          "type": "number",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "denominator",
        "failures",
        "currency",
        "exchange_rate"
      ]
    },
    "sandbox_profile": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "approvals": {
      "type": "array",
      "items": {
        "type": "object"
      },
      "minItems": 0
    },
    "contamination": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "cycle",
        "entities",
        "canary_units",
        "previous_contract",
        "max_exposures"
      ],
      "properties": {
        "cycle": {
          "type": "integer",
          "minimum": 1
        },
        "entities": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "uniqueItems": true,
          "minItems": 1
        },
        "canary_units": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "uniqueItems": true,
          "minItems": 1
        },
        "previous_contract": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            },
            {
              "type": "null"
            }
          ]
        },
        "max_exposures": {
          "type": "integer",
          "minimum": 1
        }
      }
    }
  },
  "$defs": {
    "utc_datetime": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$"
    },
    "sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "participant": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name"
      ],
      "properties": {
        "name": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9_-]*$"
        },
        "vendor": {
          "type": "string"
        },
        "tool_version": {
          "type": "string"
        },
        "excluded": {
          "type": "boolean"
        },
        "devlo_linked": {
          "type": "boolean"
        },
        "exclusion_reason": {
          "type": "string"
        },
        "blinded": {
          "type": "boolean",
          "description": "True when this participant appears in the public journal under an alias (e.g. arm_a) instead of its real vendor name. The alias is the value of `name` here. The alias\u2192vendor mapping is committed as `arm_map_sha256` at lock time and revealed only after `result_sha256` is set, via an `unblinding` event at the end of the journal. Decision: docs/decisions/L1-0003-unblinding.md"
        },
        "arm_map_sha256": {
          "$ref": "#/$defs/sha256",
          "description": "sha256 of the alias table held in the audit vault. Committed at lock time; must match the `arm_map_sha256` in the `unblinding` journal event. Required when `blinded: true`."
        },
        "access": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "api": {
              "type": "boolean"
            },
            "cli": {
              "type": "boolean"
            },
            "mcp": {
              "type": "boolean"
            },
            "auth": {
              "type": "string"
            },
            "setup_minutes": {
              "type": "number",
              "minimum": 0
            }
          }
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "devlo_linked": {
                "const": true
              }
            },
            "required": [
              "devlo_linked"
            ]
          },
          "then": {
            "required": [
              "excluded",
              "exclusion_reason"
            ],
            "properties": {
              "excluded": {
                "const": true
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "blinded": {
                "const": true
              }
            },
            "required": [
              "blinded"
            ]
          },
          "then": {
            "required": [
              "arm_map_sha256"
            ]
          }
        }
      ]
    }
  }
}
