{
  "contract_version": "gab_325_contract_v1",
  "gab_id": "GAB-325",
  "canonical_name": "ResearchLearningSourceReliabilityCheck",
  "module_owner": "EdTechResearchLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "reliability_check_id",
    "title",
    "criteria",
    "verdicts",
    "primary_cta"
  ],
  "optional_fields": [
    "intro_hint",
    "partial_threshold",
    "progress_label_tpl",
    "use_when",
    "do_not_use_when"
  ],
  "field_types": {
    "criteria": "array<{id:string, label:string}> — liste des critères à cocher, min 1 élément",
    "verdicts": "object{all_checked:{cls,state,message}, partial:{cls,state,message_tpl}, insufficient:{cls,state,message_tpl}}",
    "partial_threshold": "integer — nombre minimum de critères cochés pour le verdict 'partial' (défaut: 2)",
    "primary_cta": "object{label:string, action:string}",
    "progress_label_tpl": "string — template avec {done} et {total} comme placeholders"
  },
  "constraints": [
    "criteria doit contenir au moins 1 entrée sinon BLOCKED.",
    "verdicts.all_checked, verdicts.partial et verdicts.insufficient doivent tous être présents.",
    "Le verdict n'est jamais binaire : 3 niveaux (usable / caution / cross_check_needed).",
    "primary_cta.label est le libellé du bouton côté HTML.",
    "partial_threshold doit être < criteria.length si présent."
  ],
  "blocked_conditions": [
    "criteria absent ou vide (BLOCKED)",
    "verdicts absent (BLOCKED)",
    "primary_cta absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_live_region_for_progress"
  ],
  "qa_cases": [
    { "case": "instance conforme, 0 critère coché → CTA → verdict insufficient", "expected": "panel warn, state cross_check_needed" },
    { "case": "instance conforme, partial_threshold critères cochés → CTA → verdict partial", "expected": "panel note, state caution" },
    { "case": "instance conforme, tous critères cochés → CTA → verdict all_checked", "expected": "panel ok, state usable" },
    { "case": "criteria vide ou absent", "expected": "BLOCKED listé dans le panel d'erreur" },
    { "case": "instance externe injectée via init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-325",
    "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).",
    "external_refs": {
      "dependencies": "GAB-324 (sélection sources) — GAB-325 est l'étape suivante dans le flow recherche. Le verdict nuancé interdit le jugement binaire (voir use_when/do_not_use_when)."
    }
  }
}
