{
  "contract_version": "gab_142_contract_v1",
  "gab_id": "GAB-142",
  "canonical_name": "InteractiveBuildDiagram",
  "module_owner": "EdTechInteractiveLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "diagram_id",
    "tokens",
    "slots",
    "instruction"
  ],
  "optional_fields": [
    "title",
    "status_idle",
    "status_armed_tpl",
    "arrow",
    "feedback_partial",
    "feedback_success",
    "feedback_partial_error"
  ],
  "field_types": {
    "diagram_id": "string — identifiant unique du diagramme",
    "tokens": "array<{id:string, label:string, best_slot:string}> — éléments à placer",
    "slots": "array<{id:string, label:string}> — cases du schéma, dans l'ordre gauche→droite",
    "instruction": "string — consigne affichée au-dessus du pool",
    "status_idle": "string — message d'état initial (patron texte libre)",
    "status_armed_tpl": "string — patron avec {token} substitué dynamiquement",
    "arrow": "string — séparateur entre cases (défaut '→')",
    "feedback_partial": "string — patron avec {placed} et {total}",
    "feedback_success": "string — message quand tous les tokens sont bien placés",
    "feedback_partial_error": "string — patron avec {good} et {total}"
  },
  "constraints": [
    "Chaque token.best_slot doit référencer un id présent dans slots.",
    "Le nombre de tokens peut être différent du nombre de slots (tokens supplémentaires = distracteurs possibles).",
    "status_armed_tpl doit contenir {token} pour la substitution dynamique.",
    "feedback_partial doit contenir {placed} et {total}.",
    "feedback_partial_error doit contenir {good} et {total}.",
    "arrow : valeur display seule, pas de logique de scoring."
  ],
  "blocked_conditions": [
    "diagram_id absent",
    "tokens vides",
    "slots vides",
    "instruction absente"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_labels_on_slots",
    "role_button_on_slots"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet pool + schéma, 0 erreur" },
    { "case": "champ requis manquant (diagram_id)", "expected": "BLOCKED listant le champ" },
    { "case": "tokens vides", "expected": "BLOCKED tokens vides" },
    { "case": "slots vides", "expected": "BLOCKED slots vides" },
    { "case": "token placé sur mauvaise case", "expected": "feedback_partial_error avec bon/total" },
    { "case": "tous tokens bien placés", "expected": "feedback_success affiché" },
    { "case": "clic token déjà placé", "expected": "token retiré de la case, reposé dans pool" },
    { "case": "instance externe injectée", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement, flèches en rotation 90° mobile" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-142",
    "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)."
  }
}
