{
  "contract_version": "gab_229_contract_v1",
  "gab_id": "GAB-229",
  "canonical_name": "SmartSelectSessionMix",
  "module_owner": "EdTechSmartSelect",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "session_mix_id",
    "title",
    "summary",
    "items",
    "next_action"
  ],
  "optional_fields": [
    "mix_type",
    "mix_type_enum",
    "reason",
    "estimated_duration",
    "block_count",
    "difficulty",
    "distinction_note"
  ],
  "field_types": {
    "session_mix_id": "string — identifiant unique de la session mix",
    "title": "string — titre affiché en hero (ex: 'Session construite pour toi · Pythagore')",
    "summary": "string — sous-titre court (ex: '3 blocs courts orchestrés en pipeline')",
    "mix_type": "enum['learn_practice_check','review_memory_check','audio_practice_feedback','visual_interactive_exercise','story_exercise_recap'] — type actif",
    "mix_type_enum": "array<string> — liste des valeurs mix_type proposées à l'utilisateur",
    "reason": "string — explication pédagogique de la composition (affiché dans le bloc reason)",
    "estimated_duration": "string — durée estimée totale (ex: '~ 15 min total')",
    "block_count": "number — nombre de blocs dans le pipeline",
    "difficulty": "string — niveau de difficulté (ex: '⭐⭐ niveau guidé')",
    "items": "array<{step:number, role:string, role_label:string, title:string, description:string, module_type:string, module_chip:string}> — blocs du pipeline ordonné",
    "distinction_note": "string — note de distinction GAB-229 vs GAB-202",
    "next_action": "object{primary_label:string, primary_action:string, secondary_label:string, secondary_action:string} — libellés et actions des CTAs"
  },
  "constraints": [
    "items doit contenir au moins 1 élément (sinon BLOCKED).",
    "items[].step doit être un entier croissant (1, 2, 3...).",
    "mix_type doit correspondre à une valeur de mix_type_enum si ce champ est présent.",
    "next_action.primary_label vient du JSON — jamais hardcodé dans le HTML.",
    "block_count : cohérent avec length(items) si fourni.",
    "Distinction 229 ≠ 202 : SmartSelectSessionMix CHOISIT la composition ; GAB-202 (BlockStack) EXÉCUTE. Ces deux GABs ne sont pas substituables."
  ],
  "blocked_conditions": [
    "title absent",
    "summary absent",
    "items vides",
    "next_action absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "enum_chips_tab_accessible"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet pipeline 3 blocs, 0 erreur" },
    { "case": "champ requis manquant (ex: items vide)", "expected": "BLOCKED listant le champ" },
    { "case": "mix_type_enum présent", "expected": "chips affichées, chip active = mix_type, clic change mix-chip" },
    { "case": "instance externe injectée via ENGINE.init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, CTAs en colonne sur mobile" },
    { "case": "clic CTA primaire", "expected": "panel ok avec le label primary_label" },
    { "case": "clic CTA secondaire", "expected": "panel note avec le label secondary_label" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-229",
    "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)."
  }
}
