{
  "contract_version": "gab_297_contract_v1",
  "gab_id": "GAB-297",
  "canonical_name": "OralLearningStressSupport",
  "module_owner": "EdTechOralLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "stress_support_id",
    "support_goal",
    "support_steps",
    "non_medical_notice",
    "next_action"
  ],
  "optional_fields": [
    "card_icon",
    "card_tag",
    "title",
    "subtitle",
    "breathing_timer",
    "calm_mode",
    "skip_allowed",
    "trusted_adult_suggestion",
    "high_stress_fallback",
    "source_metadata"
  ],
  "field_types": {
    "stress_support_id": "string — identifiant unique de cet écran support",
    "support_goal": "string — objectif court (ex: 'respiration_calme_3_min')",
    "support_steps": "array<{step:number,verb:string,detail:string}> — min 1 étape",
    "non_medical_notice": "object{label,heading,body} — avertissement non-médical obligatoire",
    "next_action": "object{primary:{label,action}, secondary?:{label,action}, skip_feedback?:string}",
    "breathing_timer": "object{label,pattern,cycles,instruction,duration_seconds}",
    "calm_mode": "boolean — true = mode apaisé, animations réduites",
    "skip_allowed": "object{icon,heading,body} — bloc opt-out visible",
    "trusted_adult_suggestion": "string — exemples d'adultes de confiance",
    "high_stress_fallback": "object{label,heading,body,sos_notice} — bloc crise obligatoire si anxiété forte",
    "source_metadata": "object{reason_block_heading,reason_block_body} — pourquoi non-médical"
  },
  "constraints": [
    "non_medical_notice OBLIGATOIRE et non modifiable dans sa signification : l'app ne diagnostique pas, ne soigne pas, ne remplace pas un professionnel.",
    "skip_allowed doit être visible — l'exercice ne peut jamais être imposé.",
    "high_stress_fallback doit inclure un sos_notice avec des numéros utiles (3114, etc.).",
    "support_steps doit avoir au minimum 1 entrée.",
    "next_action.primary.action doit être une valeur machine (snake_case), jamais du HTML inline."
  ],
  "blocked_conditions": [
    "stress_support_id absent",
    "support_steps vide ou absent",
    "non_medical_notice absent",
    "next_action absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "breathing_animation_pausable",
    "opt_out_always_visible"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, 0 erreur" },
    { "case": "champ requis manquant (ex: non_medical_notice)", "expected": "BLOCKED listant le champ manquant" },
    { "case": "instance externe injectée", "expected": "le rendu change sans modifier le HTML" },
    { "case": "skip_allowed cliqué", "expected": "feedback skip_feedback affiché" },
    { "case": "bouton exercice cliqué", "expected": "timer breathing_timer.duration_seconds déclenché" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-297",
    "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). Contenu extrait du stage data-tpl='297' de INDEX-300-orallearning-GAB-296-300-PLAYABLE.html."
  }
}
