{
  "contract_version": "gab_298_contract_v1",
  "gab_id": "GAB-298",
  "canonical_name": "OralLearningImprovisationPrompt",
  "module_owner": "EdTechOralLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "impro_prompt_id",
    "prompt",
    "preparation_policy",
    "response_frame",
    "success_criteria"
  ],
  "optional_fields": [
    "title",
    "subtitle",
    "difficulty",
    "difficulty_max",
    "timebox",
    "hint_allowed",
    "starter_phrases",
    "fallback_prompt",
    "rationale",
    "primary_cta",
    "source_metadata"
  ],
  "field_types": {
    "impro_prompt_id": "string — identifiant unique du prompt d'improvisation",
    "prompt": "string — question d'improvisation posée à l'élève",
    "preparation_policy": "object{duration_s:number, instruction:string} — politique de préparation avant de parler",
    "response_frame": "array<{phase:string, duration_s:number|null, instruction:string}> — cadre structuré de la réponse (PREP/SPEAK/PAUSE)",
    "success_criteria": "string — critères de réussite observables",
    "difficulty": "number(1..3) — niveau de difficulté",
    "difficulty_max": "number — maximum de l'échelle de difficulté",
    "timebox": "number — durée maximale de la prise de parole en secondes",
    "hint_allowed": "string — contenu du hint affiché si l'élève en a besoin",
    "starter_phrases": "array<string> — 3 amorces proposées à l'élève pour débuter",
    "fallback_prompt": "string — phrase à dire si l'élève est totalement bloqué",
    "rationale": "string — explication pédagogique pourquoi le cadre aide l'improvisation",
    "primary_cta": "object{label:string, action:string, timer_s?:number, timer_label?:string}"
  },
  "constraints": [
    "prompt : non vide, question ouverte posée à l'élève.",
    "preparation_policy.duration_s : entier positif en secondes (30 recommandé).",
    "response_frame : au moins 1 phase SPEAK avec instruction non vide.",
    "starter_phrases : 3 amorces recommandées (non inventées, extraites du source).",
    "fallback_prompt : formulation bienveillante — jamais culpabilisante.",
    "difficulty dans [1,3] si présent.",
    "ANTI-PANIQUE : fallback_prompt est obligatoire en pratique même s'il est optionnel schema (soft warning)."
  ],
  "blocked_conditions": [
    "prompt absent ou vide (BLOCKED)",
    "preparation_policy absent (BLOCKED)",
    "response_frame absent ou vide (BLOCKED)",
    "success_criteria absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "fallback_text (fallback_prompt lisible sans JS)"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, timer 30s opérationnel, hint et fallback affichés, 0 erreur" },
    { "case": "prompt absent", "expected": "BLOCKED listant 'prompt absent'" },
    { "case": "preparation_policy absent", "expected": "BLOCKED listant 'preparation_policy absent'" },
    { "case": "response_frame vide", "expected": "BLOCKED listant 'response_frame absent ou vide'" },
    { "case": "instance externe injectée via init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, phases lisibles" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-298",
    "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)."
  }
}
