{
  "contract_version": "gab_293_contract_v1",
  "gab_id": "GAB-293",
  "canonical_name": "OralLearningPracticeTimer",
  "module_owner": "EdTechOralLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "timer_id",
    "practice_ref",
    "duration_target",
    "timer_mode",
    "completion_policy"
  ],
  "optional_fields": [
    "title",
    "subtitle",
    "mode_label",
    "target_label",
    "pause_allowed",
    "warning_marks",
    "grace_period",
    "grace_items",
    "completion_policy_title",
    "completion_policy_text",
    "reason_title",
    "reason_text",
    "primary_cta",
    "stress_support_ref",
    "accessibility_adjustment",
    "source_metadata"
  ],
  "field_types": {
    "duration_target": "integer — durée cible en secondes (ex: 180 = 3 min)",
    "timer_mode": "enum['non_stressant','standard','strict'] — pilote la tolérance affichée",
    "completion_policy": "enum['anti_penalite','strict','progressive'] — règle de fin",
    "pause_allowed": "boolean — true = pause sans pénalité affichée",
    "warning_marks": "array<{at_seconds:integer, label:string, style:string}> — marques temporelles sur la barre",
    "grace_period": "integer — secondes de grâce après duration_target",
    "grace_items": "array<{label:string, detail:string}> — items liste policy GRACE",
    "primary_cta": "object{label,action,duration_seconds,timer_label}",
    "source_metadata": "object libre — contexte pédagogique (non rendu)"
  },
  "constraints": [
    "duration_target > 0 et exprimé en secondes.",
    "grace_period >= 0 ; si absent, défaut 0 (pas de grâce).",
    "warning_marks : at_seconds < duration_target (marque AVANT la fin).",
    "primary_cta.duration_seconds doit être cohérent avec duration_target.",
    "timer_mode 'non_stressant' impose pause_allowed:true implicitement."
  ],
  "blocked_conditions": [
    "timer_id absent (BLOCKED)",
    "duration_target absent ou <= 0 (BLOCKED)",
    "timer_mode absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "mode_silencieux_optionnel"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, timer démarrable, 0 erreur" },
    { "case": "timer_id absent", "expected": "BLOCKED listé dans le panel d'erreur" },
    { "case": "duration_target absent", "expected": "BLOCKED listé dans le panel d'erreur" },
    { "case": "timer_mode absent", "expected": "BLOCKED listé dans le panel d'erreur" },
    { "case": "instance externe injectée", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" },
    { "case": "prefers_reduced_motion", "expected": "transitions réduites, 0 animation agressive" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-293",
    "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)."
  }
}
