{
  "contract_version": "gab_291_contract_v1",
  "gab_id": "GAB-291",
  "canonical_name": "OralLearningIntro",
  "module_owner": "EdTechOralLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "oral_id",
    "title",
    "oral_goal",
    "oral_type",
    "expected_output",
    "start_cta"
  ],
  "optional_fields": [
    "subtitle",
    "estimated_duration",
    "rubric_preview",
    "micro_policy",
    "recording_policy",
    "privacy_notice",
    "fallback_no_micro",
    "source_metadata"
  ],
  "field_types": {
    "oral_id": "string — identifiant unique de la session orale",
    "title": "string — titre affiché dans le hero",
    "oral_goal": "string — objectif pédagogique (ex: 'clarté + plan')",
    "oral_type": "enum['présentation_orale_3min','présentation_orale_libre','exposé_jury','lecture_expressive','improvisation'] — type de production orale attendue",
    "expected_output": "string — description de la production attendue",
    "start_cta": "object{primary:{label,action}, secondary?:{label,action}} — boutons de démarrage",
    "subtitle": "string — sous-titre affiché sous le titre hero",
    "estimated_duration": "string — durée cible affichée (ex: '3 min')",
    "rubric_preview": "array<{criterion:string, detail:string}> — critères d'évaluation prévisualisés",
    "micro_policy": "object{label:string, toggles:array<{id,label,default:boolean}>} — politique opt-in micro",
    "recording_policy": "object{default_record:boolean, rationale:string} — politique d'enregistrement",
    "privacy_notice": "string — notice de confidentialité affichée à l'élève",
    "fallback_no_micro": "object{label:string, text:string} — texte fallback si élève sans micro",
    "source_metadata": "object{level?,subject?,context?} — métadonnées disciplinaires (non pédagogiques)"
  },
  "constraints": [
    "start_cta.primary est obligatoire et structurel (bouton principal de l'écran).",
    "micro_policy.toggles[*].default doit être false par défaut — doctrine opt-in strict.",
    "recording_policy.default_record doit être false — non-enregistrement par défaut.",
    "fallback_no_micro obligatoire si micro_policy présent — tout élève doit avoir une voie sans micro.",
    "oral_type : valeur dans l'enum, jamais libre-form pour garantir la cohérence inter-gabarits.",
    "rubric_preview : tableau de critères, jamais de notes ni scores — prévisualisation uniquement."
  ],
  "blocked_conditions": [
    "oral_id absent",
    "title absent",
    "oral_goal absent",
    "oral_type absent",
    "expected_output absent",
    "start_cta absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_pressed_on_toggles",
    "aria_label_on_switches"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, hero + toggles + fallback + rubric + CTAs, 0 erreur" },
    { "case": "champ requis manquant (oral_id)", "expected": "BLOCKED listant oral_id absent" },
    { "case": "champ requis manquant (start_cta)", "expected": "BLOCKED listant start_cta absent" },
    { "case": "rubric_preview vide ou absent", "expected": "bloc rubric masqué (hidden), pas d'erreur" },
    { "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 empilés sur mobile" },
    { "case": "toggle switch actionné", "expected": "classe .on bascule, aria-pressed mis à jour" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-291",
    "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). Doctrine transverse EdTechOralLearning : micro opt-in, non-enregistrement par défaut, fallback sans micro obligatoire, sécurité psychologique avant la performance."
  }
}
