{
  "contract_version": "gab_260_contract_v1",
  "gab_id": "GAB-260",
  "canonical_name": "CollaborationLearningTeamChallenge",
  "module_owner": "EdTechCollaborationLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "challenge_id",
    "items",
    "team_size",
    "primary_cta"
  ],
  "optional_fields": [
    "title",
    "subtitle",
    "challenge_format",
    "challenge_format_enum",
    "challenge_count",
    "estimated_duration",
    "scoring_rule",
    "safety_note",
    "challenge_variants",
    "secondary_cta",
    "next_action",
    "use_when",
    "do_not_use_when",
    "accessibility",
    "child_safety"
  ],
  "field_types": {
    "challenge_id": "string — identifiant unique de l'instance défi",
    "items": "array<{step:number, label:string, body:string}> — étapes du sprint, ordonnées",
    "team_size": "number — nombre d'élèves dans l'équipe (3 ou 4 recommandé)",
    "challenge_count": "number — nombre de défis à résoudre",
    "estimated_duration": "string — durée affichée (ex : '10 min')",
    "challenge_format": "enum['sprint_team','escape_team','quiz_team','project_team']",
    "challenge_format_enum": "array<string> — valeurs enum affichées dans le sélecteur",
    "challenge_variants": "object<format_key,{title,subtitle,primary_cta}> — variantes pilotées par challenge_format",
    "scoring_rule": "string — règle de score affichée au clic 'Voir les règles'",
    "safety_note": "string — note garde-fou gamif (V1_X : score collectif anonyme, pas de classement public)",
    "primary_cta": "object{label:string, action:string}",
    "secondary_cta": "object{label:string, action:string}",
    "child_safety": "object{public_ranking:boolean, score_visibility:string, anonymous_score:boolean, safety_flag:string}"
  },
  "constraints": [
    "items doit contenir au moins 1 étape (step, label, body tous requis).",
    "team_size : entier ≥ 2. Recommandé 3-4.",
    "challenge_format si présent doit être dans challenge_format_enum.",
    "child_safety.public_ranking DOIT être false (garde-fou V1_X).",
    "primary_cta : bouton structurel HTML, libellé venant du JSON.",
    "challenge_variants : si présent, la clé active est challenge_format ; sinon fallback title/subtitle de l'instance."
  ],
  "blocked_conditions": [
    "items vides ou absents",
    "challenge_id absent",
    "team_size absent",
    "primary_cta absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "responsive_375_768_1024"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, 0 erreur" },
    { "case": "champ requis manquant (ex: items vide)", "expected": "BLOCKED listant le champ" },
    { "case": "clic enum 'escape_team'", "expected": "titre et CTA changent sans modifier le HTML" },
    { "case": "instance externe injectée via ENGINE.init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "team-grid passe en 2 colonnes sur mobile, 0 débordement" },
    { "case": "clic primary CTA", "expected": "panel ok 'Défi lancé'" },
    { "case": "clic secondary CTA", "expected": "panel note avec scoring_rule" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-260",
    "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). Chaîne : GAB-256 ParentChild → 257 Explain → 258 Correction → 259 Oral → 260 TeamChallenge. Référence post-défi : GAB-255 GroupReflection."
  }
}
