{
  "contract_version": "gab_072_contract_v1",
  "gab_id": "GAB-072",
  "canonical_name": "StoryCastCharacterCard",
  "module_owner": "EdTechStoryLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "cast_card_id",
    "title",
    "members"
  ],
  "optional_fields": [
    "cta_label",
    "cta_action"
  ],
  "field_types": {
    "cast_card_id": "string — identifiant unique de la carte cast (slug)",
    "title": "string — titre affiché en en-tête de la carte",
    "members": "array<{id, avatar, name, tag, role_description, member_type}> — min 2 membres",
    "members[].id": "string — identifiant unique du personnage",
    "members[].avatar": "string — emoji représentant le personnage",
    "members[].name": "string — nom affiché",
    "members[].tag": "string — libellé du badge de rôle (ex: 'Guide IA', 'Personnage fictif')",
    "members[].role_description": "string — description du rôle pédagogique du personnage",
    "members[].member_type": "enum['ai_guide','fictional'] — 'ai_guide' = Ketty ou IA ; 'fictional' = personnage narratif",
    "cta_label": "string — libellé du bouton d'action principale",
    "cta_action": "string — identifiant de l'action déclenchée par le CTA"
  },
  "constraints": [
    "members doit contenir au moins 2 personnages (sinon → GAB-053 pour personnage unique).",
    "Au moins 1 membre de type 'ai_guide' est recommandé pour distinguer Ketty des persos fictifs.",
    "Pas de contenu pédagogique inventé : avatar, nom, tag et role_description doivent venir du script Story.",
    "cta_label : si absent, le renderer affiche un libellé par défaut 'Continuer'."
  ],
  "blocked_conditions": [
    "members absent ou vide",
    "members contient moins de 2 entrées",
    "cast_card_id absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "alt_text_via_role_description"
  ],
  "qa_cases": [
    { "case": "instance conforme (3 membres)", "expected": "rendu complet, 3 cartes, 0 erreur" },
    { "case": "members absent", "expected": "BLOCKED listant le champ" },
    { "case": "members.length < 2", "expected": "BLOCKED — usage 1 seul personnage → GAB-053" },
    { "case": "cast_card_id absent", "expected": "BLOCKED" },
    { "case": "instance externe injectée (init(ext))", "expected": "le rendu change sans modifier le HTML" },
    { "case": "member_type = 'ai_guide'", "expected": "couleur de fond magenta/violet" },
    { "case": "member_type = 'fictional'", "expected": "couleur de fond gold/coral ou sky selon position" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "external_refs": {
    "dependencies": [
      "GAB-053 (SingleCharacterCard) — à utiliser si 1 seul personnage",
      "GAB-047 (DialogueBubble) — à utiliser si dialogue en cours",
      "GAB-045→073 — périmètre complet EdTechStoryLearning"
    ]
  },
  "traceability": {
    "derived_from_core_gab": "GAB-072",
    "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)."
  }
}
