{
  "contract_version": "gab_350_contract_v1",
  "gab_id": "GAB-350",
  "canonical_name": "SafetyFallbackLearningRecoveryPath",
  "module_owner": "EdTechSafetyFallbackLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "recovery_path_id",
    "previous_state",
    "recovery_choices",
    "action_order"
  ],
  "optional_fields": [
    "title",
    "prompt",
    "initial_state_badge",
    "loop_prevention",
    "progress_continuity",
    "source_notice"
  ],
  "field_types": {
    "previous_state": "object{kind,body,progress}",
    "prompt": "string",
    "recovery_choices": "array<{choice,label,icon,feedback,result_state,is_safe:boolean}>",
    "action_order": "array<string>  — ordonne les recovery_choices par leur clé 'choice'",
    "initial_state_badge": "string",
    "loop_prevention": "string",
    "progress_continuity": "string"
  },
  "constraints": [
    "previous_state.body obligatoire : l'état précédent est affiché, jamais perdu (progress_continuity).",
    "action_order doit référencer des clés 'choice' existantes ; ordre d'action clair imposé au rendu.",
    "Au moins un recovery_choice avec is_safe:true (la reprise sûre) doit exister — anti-bouclage.",
    "loop_prevention : ne jamais reboucler sur la même erreur ; alternative proposée sinon escalade vers GAB-346.",
    "primary action = boutons/items structurels (HTML), libellés et feedbacks venant du JSON.",
    "GAB SENSIBLE child-safety : aucun contenu inventé, tout extrait du HTML source."
  ],
  "blocked_conditions": [
    "previous_state absent ou sans body",
    "recovery_choices vides",
    "action_order absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "fallback_text"
  ],
  "child_safety": [
    "no_loop_on_same_error",
    "no_state_loss",
    "always_offer_safe_resume",
    "no_invented_content"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, choix dans action_order, 0 erreur" },
    { "case": "champ requis manquant", "expected": "BLOCKED listant le champ" },
    { "case": "recovery_choices vides", "expected": "BLOCKED known recovery_choices vides" },
    { "case": "choix reprise sûre (is_safe:true)", "expected": "panneau ok + badge 'recovered'" },
    { "case": "choix alternative", "expected": "panneau note + bascule sans bloquer, état conservé" },
    { "case": "instance externe injectée", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-350",
    "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)."
  }
}
