{
  "contract_version": "gab_146_contract_v1",
  "gab_id": "GAB-146",
  "canonical_name": "InteractiveSortLab",
  "module_owner": "EdTechInteractiveLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "sort_lab_id",
    "instruction",
    "items",
    "feedback",
    "categories"
  ],
  "optional_fields": [
    "title",
    "hint",
    "status_idle",
    "status_idle_after",
    "status_armed_prefix",
    "primary_cta"
  ],
  "field_types": {
    "sort_lab_id": "string — identifiant unique du lab (slug)",
    "instruction": "string — consigne affichée en kicker mono uppercase",
    "hint": "string — texte d'aide affiché dans le bandeau violet-soft",
    "items": "array<{id:string, label:string, category_id:string}> — tokens à classer",
    "categories": "array<{id:string, label:string, color_key:enum['mint','violet','sky','gold','coral']}> — zones de dépôt",
    "feedback": "object{in_progress:string, all_correct:string, partial:string} — messages progressifs (placeholders {placed}/{total}/{good} interpolés)",
    "primary_cta": "object{label:string, action:string}"
  },
  "constraints": [
    "items[].category_id doit référencer un id présent dans categories[].",
    "Minimum 2 catégories, minimum 2 items par catégorie recommandé.",
    "feedback.in_progress supporte les placeholders {placed} et {total}.",
    "feedback.partial supporte les placeholders {good} et {total}.",
    "color_key parmi : mint, violet, sky, gold, coral (palette DS V2).",
    "Pas de scoring officiel — le lab est exploratoire, jamais noté."
  ],
  "blocked_conditions": [
    "items absent ou vide",
    "categories absent ou vide",
    "feedback absent",
    "instruction absente"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_labels_on_tokens",
    "aria_labels_on_drop_zones"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, tokens cliquables, drop zones actives, 0 erreur" },
    { "case": "champ requis manquant (items vide)", "expected": "BLOCKED listant le champ manquant" },
    { "case": "categories absentes", "expected": "BLOCKED listant le champ manquant" },
    { "case": "tous les tokens placés correctement", "expected": "feedback all_correct affiché" },
    { "case": "placement partiel", "expected": "feedback in_progress jusqu'au dernier, puis partial si erreurs" },
    { "case": "instance externe injectée via init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "board en colonne sur mobile, 3 colonnes sur desktop" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-146",
    "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). Archétype : tri exploratoire sans scoring officiel, pattern B armé (sélection token → activation zones)."
  }
}
