{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.wmo-hulp.com/spec/handforge-episode-schema.json",
  "title": "Handforge episode record",
  "description": "One attempt at one task by one named operator in one instrumented environment. Accompanies the media and parquet sidecars in episodes/<episode_id>/.",
  "type": "object",
  "required": ["episode_id", "task", "outcome", "operator", "environment", "capture", "licence"],
  "additionalProperties": false,
  "properties": {
    "episode_id": { "type": "string", "pattern": "^ep_[0-9]{7}$" },
    "task": {
      "type": "object",
      "required": ["id", "taxonomy", "criterion"],
      "properties": {
        "id": { "type": "string", "description": "Task identifier within the taxonomy, e.g. TOWEL_FOLD." },
        "taxonomy": { "type": "string", "description": "Versioned task taxonomy the id belongs to." },
        "criterion": { "type": "string", "description": "The written acceptance criterion this attempt was scored against." },
        "variant": { "type": ["string", "null"], "description": "Named variation, e.g. linen weight or starting configuration." }
      }
    },
    "outcome": {
      "type": "object",
      "required": ["accepted", "scored_by"],
      "properties": {
        "accepted": { "type": "boolean", "description": "Pass or fail against task.criterion. Human-scored." },
        "failure_class": {
          "type": ["string", "null"],
          "enum": ["grasp_failure", "alignment", "dropped", "incomplete", "unsafe", "occluded_capture", "out_of_scope", null],
          "description": "Populated when accepted is false."
        },
        "scored_by": { "type": "string", "description": "Stable reviewer identifier." },
        "scored_at": { "type": "string", "format": "date-time" }
      }
    },
    "operator": {
      "type": "object",
      "required": ["id", "role", "years_in_role", "consent_ref"],
      "properties": {
        "id": { "type": "string", "description": "Stable pseudonymous identifier. Never a legal name." },
        "role": { "type": "string" },
        "years_in_role": { "type": "number", "minimum": 0 },
        "consent_ref": { "type": "string", "description": "Reference to the written, revocable consent record covering this episode." }
      }
    },
    "environment": {
      "type": "object",
      "required": ["site", "class", "held_out"],
      "properties": {
        "site": { "type": "string", "description": "Site code. Held-out sites never appear in a licensed corpus." },
        "class": { "type": "string", "description": "Environment class, e.g. hotel_guest_room." },
        "held_out": { "type": "boolean", "description": "Always false in a licensed drop. True only inside evaluation records." },
        "randomisation_seed": { "type": ["integer", "null"] }
      }
    },
    "capture": {
      "type": "object",
      "required": ["cameras", "fps", "sync_drift_ms", "duration_s", "hand_hz", "force"],
      "properties": {
        "cameras": { "type": "integer", "minimum": 1 },
        "fps": { "type": "number" },
        "sync_drift_ms": { "type": "number", "maximum": 2, "description": "Measured hardware sync drift. Episodes above 2 ms are rejected." },
        "duration_s": { "type": "number" },
        "hand_hz": { "type": "number", "description": "Hand skeleton sample rate." },
        "force": { "type": "boolean", "description": "True when instrumented gloves were fitted; adds per-fingertip normal force to hand.parquet." },
        "depth": { "type": "string", "enum": ["stereo", "stereo+active", "none"] }
      }
    },
    "licence": {
      "type": "string",
      "enum": ["non-exclusive-perpetual", "exclusive-12m", "exclusive-18m", "exclusive-24m", "evaluation-only"]
    }
  }
}
