{
  "contract_version": "gab_321_contract_v1",
  "gab_id": "GAB-321",
  "canonical_name": "ResearchLearningIntro",
  "module_owner": "EdTechResearchLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "research_intro_id",
    "doc_kind",
    "doc_title",
    "doc_body",
    "primary_cta",
    "contract_items"
  ],
  "optional_fields": [
    "secondary_cta",
    "contract_success_message",
    "anti_plagiarism_message"
  ],
  "field_types": {
    "doc_kind": "string — catégorie affichée dans le doc-paper (ex: 'Recherche · contrat de fiabilité')",
    "doc_title": "string — sujet affiché dans le doc-paper",
    "doc_body": "string — corps introductif du doc-paper",
    "primary_cta": "object{label:string, action:string}",
    "secondary_cta": "object{label:string, action:string}",
    "contract_items": "array<{icon:string, label:string, detail:string}> — 1 item minimum",
    "contract_success_message": "string — message panel ok après activation du contrat",
    "anti_plagiarism_message": "string — message panel warn pour la règle anti-plagiat"
  },
  "constraints": [
    "contract_items doit contenir au moins 1 item.",
    "primary_cta.label est le libellé HTML du bouton principal — vient du JSON, jamais en dur.",
    "secondary_cta optionnel : si absent, le bouton ghost n'est pas rendu.",
    "contract_success_message : affiché en panel ok après clic primary_cta.",
    "anti_plagiarism_message : affiché en panel warn après clic secondary_cta."
  ],
  "blocked_conditions": [
    "research_intro_id absent",
    "contract_items vide ou absent",
    "primary_cta absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "fallback_text"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, 0 erreur" },
    { "case": "champ requis manquant", "expected": "BLOCKED listant le champ" },
    { "case": "contract_items vide", "expected": "BLOCKED — contrat non rendu" },
    { "case": "primary_cta absent", "expected": "BLOCKED — bouton principal non rendu" },
    { "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" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-321",
    "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)."
  }
}
