{
  "contract_version": "gab_303_observe_zones_v1",
  "renderer_key": "observe_zones",
  "gab_id": "GAB-303",
  "canonical_name": "DocumentLearningObserveDescribe",
  "family_code": "document_analysis",
  "purpose": "Valide une instance d'observation guidée d'un document (zones cliquables + faits observés, séparation stricte observer/interpréter). Le renderer consomme l'instance validée; ne lit aucun registre au runtime.",
  "pipeline": "registre GAB -> schema.json -> valide instance -> renderer.html (moteur observe_zones)",

  "required_fields": ["gab_id", "renderer_key", "instance"],
  "optional_fields": ["canonical_name", "family_code", "site", "source_notice", "accessibility", "child_safety"],

  "field_types": {
    "gab_id": "string ('GAB-303')",
    "renderer_key": "string ('observe_zones')",
    "site": "string (ex 'allobrevet.fr')",
    "instance": "object { title, document_ref, observation_prompt, modes[], observable_zones[], observe_order[], description_rules }",
    "instance.document_ref": "object { src:string, kind:enum['image','document'], alt:string }",
    "instance.modes": "array<{ id:enum['obs','int'], label:string, active:boolean }> — toujours 2 modes : observer (obs) et interpréter (int)",
    "instance.observable_zones": "array<{ id:string, emoji:string, fact:string, pos:{ top, left|right, width, height } }> — fact = UNIQUEMENT ce qu'on VOIT, jamais une interprétation",
    "instance.observe_order": "array<string> — ids des zones dans l'ordre d'observation suggéré",
    "instance.description_rules": "object { observe_feedback:string, interpret_block:string, all_observed:string }"
  },

  "constraints": [
    "instance.observable_zones non vide (sinon BLOCKED).",
    "instance.modes déclaré avec au moins le mode 'obs' (sinon BLOCKED).",
    "instance.document_ref présent avec src (sinon BLOCKED).",
    "Chaque zone.fact décrit UNIQUEMENT ce qui est visible (fait), jamais une interprétation — l'interprétation est renvoyée à GAB-305.",
    "Le mode 'int' bloque l'observation et affiche un rappel pédagogique (pas d'échec dur).",
    "observe_order référence des ids présents dans observable_zones."
  ],
  "blocked_conditions": [
    "instance.observable_zones absent ou vide",
    "instance.modes absent ou sans mode 'obs'",
    "instance.document_ref absent ou sans src",
    "une zone.fact manquante ou vide"
  ],
  "edge_cases": [
    { "case": "clic en mode interpréter", "expected": "afficher le rappel interpret_block (mode panel warn), ne pas marquer la zone observée" },
    { "case": "toutes les zones observées", "expected": "afficher all_observed + passer l'état à 'ready' (prêt pour 304)" },
    { "case": "document_ref cassé", "expected": "les zones restent cliquables (faits textuels), pas d'écran vide" }
  ]
}
