{
  "contract_version": "gab_125_contract_v1",
  "gab_id": "GAB-125",
  "canonical_name": "ExerciseOpenExplain",
  "module_owner": "EdTechExerciseLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "exercise_id",
    "instruction",
    "question",
    "cta_label",
    "criteria"
  ],
  "optional_fields": [
    "title",
    "answer_placeholder",
    "word_count_hint",
    "criteria_header",
    "ai_feedback_note",
    "accessibility",
    "child_safety"
  ],
  "field_types": {
    "exercise_id": "string — identifiant unique de l'exercice",
    "instruction": "string — libellé court affiché en badge avant la question (ex : 'Rédige')",
    "question": "string — consigne complète affichée à l'élève",
    "answer_placeholder": "string — texte indicatif dans le textarea",
    "word_count_hint": "string — conseil de longueur affiché dans la barre d'outil",
    "cta_label": "string — libellé du bouton de révélation de la grille",
    "criteria_header": "string — titre affiché au-dessus de la grille de critères",
    "criteria": "array<string> — liste ordonnée de critères à cocher (auto-évaluation)",
    "ai_feedback_note": "string — note sur l'intervention IA Ketty (optionnelle)"
  },
  "constraints": [
    "criteria doit contenir au moins 1 entrée si présent.",
    "cta_label est le libellé du bouton structurel — vient exclusivement du JSON.",
    "instruction affiché en badge préfixant la question — doit rester court (1-3 mots).",
    "La grille de critères est une auto-évaluation : aucun scoring automatique imposé."
  ],
  "blocked_conditions": [
    "question absent",
    "instruction absent",
    "criteria absent ou tableau vide"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "textarea_labelled"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, question affichée, bouton CTA actif" },
    { "case": "champ requis manquant (question)", "expected": "BLOCKED indiquant le champ manquant" },
    { "case": "criteria vide []", "expected": "BLOCKED — grille non affichable" },
    { "case": "clic CTA avant rédaction", "expected": "grille révélée même si textarea vide (auto-évaluation)" },
    { "case": "instance externe injectée via init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-125",
    "note": "Contenu extrait de INDEX-300-exerciselearning-GAB-121-125-PLAYABLE.html (stage data-tpl=125, handlers oeCount/oeReveal/oeTick). Le contrat pédagogique complet vit dans le CORE-GAB officiel."
  }
}
