{
  "contract_version": "gab_123_contract_v1",
  "gab_id": "GAB-123",
  "canonical_name": "ExerciseMatching",
  "module_owner": "EdTechExerciseLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "matching_id",
    "question",
    "col_left_header",
    "col_right_header",
    "pairs"
  ],
  "optional_fields": [
    "title",
    "instruction_emoji",
    "sub_instruction",
    "feedback_ok",
    "feedback_ok_detail",
    "cta_label"
  ],
  "field_types": {
    "matching_id": "string — identifiant unique de l'instance",
    "question": "string — énoncé principal affiché en titre de l'exercice",
    "col_left_header": "string — libellé de la colonne gauche",
    "col_right_header": "string — libellé de la colonne droite",
    "pairs": "array<{pair_key:string, left:string, right:string}> — min 2 paires, les items droits sont shufflés par le moteur",
    "instruction_emoji": "string — préfixe emoji + verbe court (ex: '🔗 Associe')",
    "sub_instruction": "string — consigne détaillée sous la question",
    "feedback_ok": "string — message principal si toutes les paires sont justes",
    "feedback_ok_detail": "string — détail complémentaire du feedback succès",
    "cta_label": "string — libellé du bouton d'action structurel (non utilisé dans le moteur auto, prévu pour usage futur)"
  },
  "constraints": [
    "pairs doit contenir au minimum 2 éléments.",
    "Chaque pair_key doit être unique dans le tableau pairs.",
    "Les items de col_right sont mélangés (shuffled) par le moteur au rendu — jamais en dur dans le HTML.",
    "Le moteur valide paire par paire via pair_key, sans texte de réponse correcte en dur dans le HTML."
  ],
  "blocked_conditions": [
    "pairs absent ou vide (BLOCKED)",
    "col_left_header absent (BLOCKED)",
    "col_right_header absent (BLOCKED)",
    "question absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "arming_feedback_textuel"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet — grille 2 colonnes, paires shufflées, 0 erreur" },
    { "case": "champ requis manquant (question)", "expected": "BLOCKED listant le champ absent" },
    { "case": "pairs vide", "expected": "BLOCKED — pairs absent ou vide" },
    { "case": "paire correcte sélectionnée", "expected": "les 2 items passent en .matched, compteur incrémenté" },
    { "case": "paire incorrecte sélectionnée", "expected": "animation .wrong sur les 2 items, pas de compteur" },
    { "case": "toutes les paires trouvées", "expected": "feedback_ok + feedback_ok_detail affichés" },
    { "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-123",
    "note": "Ce schema VALIDE l'instance. Le contrat pédagogique complet (input_contract/validation_logic/feedback_scoring_logic) vit dans le CORE-GAB officiel, pas ici (évite la duplication)."
  }
}
