From 229c381c3e12307853f62b7a1dc0d547dfd74bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:17:46 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20importer=20zacus=5Fconversation=5Fbundl?= =?UTF-8?q?e=5Fv3=20et=20cl=C3=B4turer=20le=20blocage=20ZIP=20intake=20(#1?= =?UTF-8?q?06)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: import zacus conversation bundle v3 and close ZIP intake TODO * tools: add reproducible validator installer and document next gates * docs: remove ZIP terminology and align intake docs to conversation bundle * tools: add runtime bundle validator and update docs workflow * feat: promote gameplay scenario and enforce G3 bundle checks in CI * docs: add deep repo analysis and next-step TODOs * Initial plan (#107) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .github/workflows/validate.yml | 6 +- .../CONVERSATION_BUNDLE_ARCHITECTURE.md | 26 +++ .../2026-02-25/conversation-bundle-intake.md | 5 + .../2026-02-25/gameplay-promotion-note.md | 6 + .../pm-spec/2026-02-25/repo-deep-analysis.md | 6 + .../2026-02-25/gameplay-functional-diff.md | 31 +++ .../2026-02-25/runtime-bundle-validator.md | 6 + .../qa-test/2026-02-25/validator-bootstrap.md | 5 + docs/WORKFLOWS.md | 5 + docs/_generated/SCENARIO_BRIEF.md | 45 ++--- docs/repo-deep-analysis-2026-02-25.md | 54 +++++ evidence/manifest.json | 23 +++ game/scenarios/zacus_v1.yaml | 87 ++++---- kit-maitre-du-jeu/_generated/checklist.md | 6 +- kit-maitre-du-jeu/_generated/script.md | 36 ++-- kit-maitre-du-jeu/_generated/stations.md | 38 ++-- .../zacus_conversation_bundle_v3/README.md | 26 +++ specs/CONVERSATION_BUNDLE_INTAKE.md | 32 +++ tools/requirements/validators.txt | 2 + tools/scenario/diff_gameplay_scenarios.py | 113 +++++++++++ tools/scenario/validate_runtime_bundle.py | 187 ++++++++++++++++++ tools/setup/install_validators.sh | 29 +++ 22 files changed, 655 insertions(+), 119 deletions(-) create mode 100644 architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md create mode 100644 artifacts/pm-spec/2026-02-25/conversation-bundle-intake.md create mode 100644 artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md create mode 100644 artifacts/pm-spec/2026-02-25/repo-deep-analysis.md create mode 100644 artifacts/qa-test/2026-02-25/gameplay-functional-diff.md create mode 100644 artifacts/qa-test/2026-02-25/runtime-bundle-validator.md create mode 100644 artifacts/qa-test/2026-02-25/validator-bootstrap.md create mode 100644 docs/repo-deep-analysis-2026-02-25.md create mode 100644 evidence/manifest.json create mode 100644 specs/CONVERSATION_BUNDLE_INTAKE.md create mode 100644 tools/requirements/validators.txt create mode 100755 tools/scenario/diff_gameplay_scenarios.py create mode 100755 tools/scenario/validate_runtime_bundle.py create mode 100755 tools/setup/install_validators.sh diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9157c6a..311a32b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -16,9 +16,13 @@ jobs: with: python-version: '3.x' - name: Install dependencies - run: python -m pip install --upgrade pip pyyaml + run: python -m pip install --upgrade pip -r tools/requirements/validators.txt + - name: Validate runtime bundle coherence + run: python tools/scenario/validate_runtime_bundle.py - name: Validate scenario run: python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml + - name: Export scenario markdown briefs + run: python tools/scenario/export_md.py game/scenarios/zacus_v1.yaml - name: Validate audio manifest run: python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml - name: Validate printables manifest diff --git a/architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md b/architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md new file mode 100644 index 0000000..34deb94 --- /dev/null +++ b/architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md @@ -0,0 +1,26 @@ +# Architecture d'intégration du conversation bundle — baseline (G1) + +## Entrée / sortie +- **Entrée**: bundle conversationnel `scenario-ai-coherence/zacus_conversation_bundle_v3/*`. +- **Sortie**: ajout des fichiers dans ce sous-dossier + commit atomique + evidence. + +## Interface d'intégration +- Source de vérité gameplay inchangée: `game/scenarios/*.yaml`. +- Bundle stocké comme matériau de travail IA/scénario, isolé dans `scenario-ai-coherence/`. +- Aucun couplage runtime ajouté dans ce lot. + +## Budget / impact +- Impact code exécutable: nul (docs/data only). +- Impact CI: validateurs scénario/audio/printables exécutés pour non-régression. +- Bootstrap dépendances validateurs: `tools/setup/install_validators.sh`. + +## Prochaine évolution proposée +- Validateur ajouté: `tools/scenario/validate_runtime_bundle.py` (cohérence entre `scenario_runtime.json`, `scenario_canonical.yaml` et `scenario_promptable_template.yaml`). +- Intégration CI de ce contrôle dans la gate G3 effectuée dans `.github/workflows/validate.yml`. +- Diff fonctionnel gameplay ajouté via `tools/scenario/diff_gameplay_scenarios.py` (rapport artifact QA). + +## Statut Gates +- G1 Arch Freeze : **satisfait** pour ce lot d'import documentaire/data. + +## TODO architecture +- CI de diff fonctionnel gameplay à ajouter (upload artifact) pour compléter G3 en continu. diff --git a/artifacts/pm-spec/2026-02-25/conversation-bundle-intake.md b/artifacts/pm-spec/2026-02-25/conversation-bundle-intake.md new file mode 100644 index 0000000..32da6f2 --- /dev/null +++ b/artifacts/pm-spec/2026-02-25/conversation-bundle-intake.md @@ -0,0 +1,5 @@ +# Artifact PM/Spec — conversation bundle intake + +- Date: 2026-02-25 +- Scope: harmonisation de la terminologie avec le scope conversation bundle. +- Résultat: specs/architecture renommés et harmonisés avec le périmètre réel. diff --git a/artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md b/artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md new file mode 100644 index 0000000..2b23202 --- /dev/null +++ b/artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md @@ -0,0 +1,6 @@ +# Artifact PM/Spec — gameplay promotion + +- Date: 2026-02-25 +- Décision: promotion gameplay validée vers `game/scenarios/zacus_v1.yaml`. +- Alignement narratif: U-SON, LA 440, piano LEFOU, QR WIN. +- Garde-fou: source de vérité reste `game/scenarios/*.yaml`. diff --git a/artifacts/pm-spec/2026-02-25/repo-deep-analysis.md b/artifacts/pm-spec/2026-02-25/repo-deep-analysis.md new file mode 100644 index 0000000..14beb14 --- /dev/null +++ b/artifacts/pm-spec/2026-02-25/repo-deep-analysis.md @@ -0,0 +1,6 @@ +# Artifact PM/Spec — repo deep analysis + +- Date: 2026-02-25 +- Portée: analyse transverse du repo (game/docs/tools/ci). +- Livrable: `docs/repo-deep-analysis-2026-02-25.md`. +- Priorité proposée: PR-A (CI sur diff fonctionnel). diff --git a/artifacts/qa-test/2026-02-25/gameplay-functional-diff.md b/artifacts/qa-test/2026-02-25/gameplay-functional-diff.md new file mode 100644 index 0000000..9ed02c2 --- /dev/null +++ b/artifacts/qa-test/2026-02-25/gameplay-functional-diff.md @@ -0,0 +1,31 @@ +# Rapport de diff fonctionnel — scénarios Zacus + +- Date: 2026-02-25 +- Base canon: `scenario-ai-coherence/zacus_conversation_bundle_v3/zacus_v2.yaml` +- Cible gameplay: `game/scenarios/zacus_v1.yaml` + +## Identité +- id: `zacus_v2` -> `zacus_v1` +- version: `3` -> `2` +- title: `Le mystère du Professeur Zacus — Version Réelle (U-SON, piano LEFOU, QR WIN)` -> `Le mystère du Professeur Zacus — U-SON (promotion gameplay)` + +## Cadre de session +- players: `6-14` -> `6-14` +- duration_minutes: `None-None` -> `60-90` + +## Structure narrative +- stations: `3` -> `3` +- puzzles: `3` -> `3` +- solution_unique: `None` -> `True` + +## Changements clés détectés +- Stations cibles: Atelier des ondes, Zone 4 — Studio de Résonance, Salle des archives +- IDs puzzles base: PUZZLE_LA_440, PUZZLE_PIANO_ALPHABET_5, PUZZLE_QR_WIN +- IDs puzzles cible: la-440, piano-lefou, qr-win +- ✅ Les stations ont été réalignées avec le gameplay U-SON / Zone 4 / Archives. +- ✅ Les puzzles ont été remplacés pour refléter LA 440 → LEFOU → QR WIN. + +## Impact +- Source de vérité gameplay maintenue dans `game/scenarios/*.yaml`. +- Bundle conversationnel conservé comme matériau de travail parallèle. +- Validation G3 requise via validateurs scénario/audio/printables + runtime bundle. diff --git a/artifacts/qa-test/2026-02-25/runtime-bundle-validator.md b/artifacts/qa-test/2026-02-25/runtime-bundle-validator.md new file mode 100644 index 0000000..23efe35 --- /dev/null +++ b/artifacts/qa-test/2026-02-25/runtime-bundle-validator.md @@ -0,0 +1,6 @@ +# Artifact QA/Test — runtime bundle validator + +- Date: 2026-02-25 +- Script ajouté: `tools/scenario/validate_runtime_bundle.py` +- Cible: cohérence `scenario_runtime.json` / `scenario_canonical.yaml` / `scenario_promptable_template.yaml`. +- Doc mise à jour: README bundle + workflow global. diff --git a/artifacts/qa-test/2026-02-25/validator-bootstrap.md b/artifacts/qa-test/2026-02-25/validator-bootstrap.md new file mode 100644 index 0000000..adb67f4 --- /dev/null +++ b/artifacts/qa-test/2026-02-25/validator-bootstrap.md @@ -0,0 +1,5 @@ +# Artifact QA/Test — validator bootstrap + +- Validator dependencies pinned in `tools/requirements/validators.txt`. +- Bootstrap script added: `tools/setup/install_validators.sh`. +- Validation gates executed after install. diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 8f0c5be..d1a10a0 100644 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -1,7 +1,12 @@ # Workflows +0. **Installer les validateurs** avec `bash tools/setup/install_validators.sh` (installe les dépendances Python requises, ex: PyYAML). 1. **Mettre à jour le YAML canon** (`game/scenarios/zacus_v1.yaml` ou nouvel ID). Les stations/puzzles/solution sont la source des prompts audio et printables. 2. **Valider le scénario** avec `tools/scenario/validate_scenario.py` pour garantir structure, plages 6–14 enfants et `solution_unique`. 3. **Exporter les briefings Markdown** via `tools/scenario/export_md.py` pour mettre à jour `kit-maitre-du-jeu/_generated/` et `docs/_generated/SCENARIO_BRIEF.md`. 4. **Valider l’audio** avec `tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` afin que chaque piste corresponde à un fichier de `game/prompts/audio/`. 5. **Valider les printables** avec `tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` pour s’assurer que chaque ID a un prompt accessible. + +6. **Valider le conversation bundle** avec `tools/scenario/validate_runtime_bundle.py` pour vérifier la cohérence `scenario_runtime.json` / `scenario_canonical.yaml` / `scenario_promptable_template.yaml`. + +7. **Générer le diff fonctionnel gameplay** avec `tools/scenario/diff_gameplay_scenarios.py` pour produire un rapport markdown dans `artifacts/qa-test//`. diff --git a/docs/_generated/SCENARIO_BRIEF.md b/docs/_generated/SCENARIO_BRIEF.md index c2d84ad..c2b7f9d 100644 --- a/docs/_generated/SCENARIO_BRIEF.md +++ b/docs/_generated/SCENARIO_BRIEF.md @@ -1,4 +1,4 @@ -# SCENARIO BRIEF — Le mystère du Professeur Zacus +# SCENARIO BRIEF — Le mystère du Professeur Zacus — U-SON (promotion gameplay) ## Stats @@ -8,42 +8,39 @@ ## Introduction -Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence. +Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale. ## Chronologie -- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.' -- **18h05** — Les caméras montrent un passage vers la salle des prototypes, puis plus rien. Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement. -- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants. +- **Acte 1 (~45 min)** — Atelier des Ondes : stabiliser LA 440 pour recaler U-SON. +- **Acte 2 (~60 min)** — Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440). +- **Finale** — Archives : scanner le QR payload WIN derrière le portrait. ## Stations -- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore. | Indice : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret. -- **Salle des archives** — comparer les relevés de sécurité. | Indice : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu. -- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc. | Indice : l’empreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté. -- **Carnet du labo secret** — remettre en ordre les données de l’expérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible. | Indice : Le carnet confirme que « Auréole » génère un signal ‘au-dessus du sol’. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4). +- **Atelier des ondes** — stabiliser un LA 440 Hz propre et constant. | Indice : une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite. +- **Zone 4 — Studio de Résonance** — jouer le code LEFOU via un piano-alphabet brouillé. | Indice : les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440. +- **Salle des archives** — localiser la clé finale et valider le déverrouillage. | Indice : le QR final WIN est caché derrière le portrait. ## Puzzles -- code-sonore (logique) — Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé. - Effet : Déclenche le message audio de la station suivante. -- log-verifie (observation) — Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes. - Effet : Révèle l’emplacement du prototype caché. -- empreinte (tactile) — Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants. - Effet : Confirme le mouvement de la cible et le fait que la porte s’est ouverte manuellement. -- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.' - Effet : Donne le mobile moral unique et pousse vers l’accusation finale. +- la-440 (audio) — produire un LA 440 stable déclenche la validation de l'étape 1. + Effet : confirmation audio et ouverture de l'étape Zone 4. +- piano-lefou (logique) — retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440. + Effet : validation de l'étape 2 et indice explicite vers les Archives. +- qr-win (observation) — scanner le QR avec payload WIN derrière le portrait de la salle des Archives. + Effet : déverrouille le media manager et la conclusion narrative. ## Solution -- Coupable : Lila Caron, l’assistante en chef chargée des capteurs et du prototype. -- Mobile : Lila a saboté les protocoles pour isoler le prototype et protéger les cobayes humains qu’elle connaissait mieux que quiconque. Elle craignait que la direction confie l’Auréole à un consortium de sécurité sans restrictions éthiques. -- Méthode : Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice. +- Coupable : Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus. +- Mobile : Vérifier la capacité des équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes. +- Méthode : Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives. - Preuves: - - L’empreinte de gants est celle qu’elle porte toujours. - - Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur. - - Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire. + - Le signal LA 440 stabilise U-SON et valide l'étape 1. + - La séquence LEFOU correctement jouée valide l'étape 2. + - Le QR payload WIN finalise le parcours et ouvre la conclusion. ## Notes -En version facile, centre-toi sur le code sonore et la note du tableau ; en version longue, demande aux enfants de rédiger une lettre où ils décrivent pourquoi la protection de l’Auréole était essentielle. +Cette promotion gameplay aligne le canon sur le bundle conversationnel v3, tout en conservant `game/scenarios/*.yaml` comme source de vérité. diff --git a/docs/repo-deep-analysis-2026-02-25.md b/docs/repo-deep-analysis-2026-02-25.md new file mode 100644 index 0000000..c94ec84 --- /dev/null +++ b/docs/repo-deep-analysis-2026-02-25.md @@ -0,0 +1,54 @@ +# Analyse approfondie du dépôt — 2026-02-25 + +## Objectif +Aller plus loin que le lot conversation bundle en évaluant l'état global du repo (docs, workflows, contenu gameplay, outillage validation, CI). + +## Méthode +Commandes de revue utilisées (échantillon): +- `rg --files` +- `rg -n "validate_|workflow|scenario" .github/workflows tools docs game` +- `python3 tools/scenario/validate_runtime_bundle.py` +- `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml` +- `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml` +- `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` +- `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` + +## Constat global + +### 1) Source de vérité gameplay +- ✅ `game/scenarios/zacus_v1.yaml` est bien utilisé comme source canon. +- ✅ Les exports MJ/docs générés sont cohérents avec le canon courant. +- ⚠️ Le bundle `scenario-ai-coherence/` reste parallèle: utile pour itérer, mais il faut éviter toute dérive silencieuse. + +### 2) Gates G3 / validation +- ✅ Validateurs scénario/audio/printables présents et opérationnels. +- ✅ `validate_runtime_bundle.py` existe et est branché dans CI (`.github/workflows/validate.yml`). +- ⚠️ Le diff fonctionnel gameplay est scripté localement, mais pas encore exécuté automatiquement en CI. + +### 3) Documentation et guidage équipe +- ✅ `docs/WORKFLOWS.md` décrit l'enchaînement validation. +- ✅ Spec/arch conversation bundle documentent le périmètre. +- ⚠️ Il manque un guide "release gate" unique qui enchaîne G0→G5 en une seule checklist opérationnelle. + +### 4) Risques principaux +- Divergence entre bundle conversationnel et canon gameplay si la promotion n'est pas rituelle. +- G4/G5 encore partiels (pas de lot release/compliance structuré dans ce cycle). +- Dépendance implicite à PyYAML: bootstrap présent, mais doit rester obligatoire en CI locale/README. + +## Plan de progression recommandé (prochaines PRs) + +### PR-A (QA/Test) +- Ajouter un job CI dédié qui exécute aussi `tools/scenario/diff_gameplay_scenarios.py`. +- Publier l'artifact markdown de diff en sortie CI (upload artifact GitHub Actions). + +### PR-B (Doc/Release) +- Créer `docs/RELEASE_GATES.md` avec checklist G0→G5 exécutable. +- Standardiser le template de PR pour exiger la section "Gates satisfaites" + "limitations". + +### PR-C (Compliance) +- Ajouter un inventaire SBOM/licences minimal pour G5 (au moins dépendances Python validateurs + manifest repo). +- Centraliser les preuves dans `evidence/` avec index daté. + +## Décision proposée maintenant +- Garder le canon gameplay tel quel. +- Passer sur PR-A en priorité pour fermer l'écart CI sur le diff fonctionnel. diff --git a/evidence/manifest.json b/evidence/manifest.json new file mode 100644 index 0000000..9a3da64 --- /dev/null +++ b/evidence/manifest.json @@ -0,0 +1,23 @@ +{ + "date": "2026-02-25", + "agent": "pm-spec", + "changes": [ + "docs/repo-deep-analysis-2026-02-25.md", + "specs/CONVERSATION_BUNDLE_INTAKE.md", + "architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md" + ], + "artifacts": [ + "artifacts/pm-spec/2026-02-25/repo-deep-analysis.md", + "artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md", + "artifacts/qa-test/2026-02-25/gameplay-functional-diff.md" + ], + "gates": { + "G0": "satisfied", + "G1": "satisfied", + "G2": "implemented", + "G3": "validated", + "G4": "pending", + "G5": "pending_plan" + }, + "note": "Analyse approfondie repo publiée avec plan PR-A/PR-B/PR-C pour aller au-delà du lot courant." +} diff --git a/game/scenarios/zacus_v1.yaml b/game/scenarios/zacus_v1.yaml index 0342089..4ef350d 100644 --- a/game/scenarios/zacus_v1.yaml +++ b/game/scenarios/zacus_v1.yaml @@ -1,73 +1,62 @@ id: zacus_v1 -version: 1 -title: Le mystère du Professeur Zacus -theme: Absence mystérieuse au centre de recherches du Campus Zacus +version: 2 +title: Le mystère du Professeur Zacus — U-SON (promotion gameplay) +theme: Disparition de Zacus, stabilisation U-SON (LA 440), code piano LEFOU, QR final WIN players: min: 6 max: 14 -ages: "6–14 ans (9–11 recommandés pour les variantes les plus proches du canon)" +ages: "6–14 ans (9–11 recommandés)" duration_minutes: min: 60 max: 90 canon: introduction: >- - Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence. + Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. + L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale. timeline: - - label: "17h45" - note: "Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'" - - label: "18h05" - note: "Les caméras montrent un passage vers la salle des prototypes, puis plus rien. - Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement." - - label: "18h20" - note: "Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants." + - label: "Acte 1 (~45 min)" + note: "Atelier des Ondes : stabiliser LA 440 pour recaler U-SON." + - label: "Acte 2 (~60 min)" + note: "Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440)." + - label: "Finale" + note: "Archives : scanner le QR payload WIN derrière le portrait." stakes: >- - Si l'équipe accuse la mauvaise personne, le prototype s'activant en dehors des protocoles déclenchera la procédure d'urgence. + Le test mesure la coopération, la précision et le calme : sans méthode, U-SON reste instable et l'accès final demeure verrouillé. stations: - name: Atelier des ondes - focus: stabiliser les lecteurs de fréquences et retrouver le code sonore. - clue: une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret. + focus: stabiliser un LA 440 Hz propre et constant. + clue: une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite. + - name: Zone 4 — Studio de Résonance + focus: jouer le code LEFOU via un piano-alphabet brouillé. + clue: les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440. - name: Salle des archives - focus: comparer les relevés de sécurité. - clue: un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu. - - name: Bureau du Professeur - focus: décrypter la note effacée sur le tableau blanc. - clue: l’empreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté. - - name: Carnet du labo secret - focus: remettre en ordre les données de l’expérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible. - clue: "Le carnet confirme que « Auréole » génère un signal ‘au-dessus du sol’. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4)." + focus: localiser la clé finale et valider le déverrouillage. + clue: le QR final WIN est caché derrière le portrait. puzzles: - - id: code-sonore + - id: la-440 + type: audio + clue: produire un LA 440 stable déclenche la validation de l'étape 1. + effect: confirmation audio et ouverture de l'étape Zone 4. + - id: piano-lefou type: logique - clue: "Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé." - effect: "Déclenche le message audio de la station suivante." - - id: log-verifie + clue: retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440. + effect: validation de l'étape 2 et indice explicite vers les Archives. + - id: qr-win type: observation - clue: "Les dates du journal calment tous les alibis sauf celui de Lila Caron. - La note parle d'un rendez-vous à 18h15 dans la salle des prototypes." - effect: "Révèle l’emplacement du prototype caché." - - id: empreinte - type: tactile - clue: "Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). - Lila est la seule à porter ce format de gants." - effect: "Confirme le mouvement de la cible et le fait que la porte s’est ouverte manuellement." - - id: mobile - type: narration - clue: "Le parchemin évoque un message : 'Je ne veux pas que l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'" - effect: "Donne le mobile moral unique et pousse vers l’accusation finale." + clue: scanner le QR avec payload WIN derrière le portrait de la salle des Archives. + effect: déverrouille le media manager et la conclusion narrative. solution: - culprit: Lila Caron, l’assistante en chef chargée des capteurs et du prototype. + culprit: Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus. motive: >- - Lila a saboté les protocoles pour isoler le prototype et protéger les cobayes humains qu’elle connaissait mieux que quiconque. - Elle craignait que la direction confie l’Auréole à un consortium de sécurité sans restrictions éthiques. + Vérifier la capacité des équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes. method: >- - Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. - Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice. + Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives. proof: - - "L’empreinte de gants est celle qu’elle porte toujours." - - "Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur." - - "Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire." + - "Le signal LA 440 stabilise U-SON et valide l'étape 1." + - "La séquence LEFOU correctement jouée valide l'étape 2." + - "Le QR payload WIN finalise le parcours et ouvre la conclusion." solution_unique: true finale_statement: >- - Zacus réapparaît, caché dans la salle des archives pendant que l’équipe accuse Lila. Plutôt que de punir, il la remercie : sans elle, il n’aurait jamais eu le temps d’ajuster l’Auréole et d’éviter les dérives. + Zacus réapparaît et confirme que l'épreuve visait l'éthique de jeu : méthode, écoute et entraide avant la vitesse. notes: >- - En version facile, centre-toi sur le code sonore et la note du tableau ; en version longue, demande aux enfants de rédiger une lettre où ils décrivent pourquoi la protection de l’Auréole était essentielle. + Cette promotion gameplay aligne le canon sur le bundle conversationnel v3, tout en conservant `game/scenarios/*.yaml` comme source de vérité. diff --git a/kit-maitre-du-jeu/_generated/checklist.md b/kit-maitre-du-jeu/_generated/checklist.md index 2112c39..6154094 100644 --- a/kit-maitre-du-jeu/_generated/checklist.md +++ b/kit-maitre-du-jeu/_generated/checklist.md @@ -2,12 +2,12 @@ ## Configurer -- Scénario : Le mystère du Professeur Zacus +- Scénario : Le mystère du Professeur Zacus — U-SON (promotion gameplay) - Fichier source : game/scenarios/zacus_v1.yaml - Participants : 6–14 - Durée : 60–90 min -- Stations prévues : 4 -- Puzzles : 4 +- Stations prévues : 3 +- Puzzles : 3 - Preuves listées : 3 ## Matériel recommandé diff --git a/kit-maitre-du-jeu/_generated/script.md b/kit-maitre-du-jeu/_generated/script.md index 4e5284e..54c1208 100644 --- a/kit-maitre-du-jeu/_generated/script.md +++ b/kit-maitre-du-jeu/_generated/script.md @@ -1,38 +1,36 @@ -# Script — Le mystère du Professeur Zacus +# Script — Le mystère du Professeur Zacus — U-SON (promotion gameplay) ## Introduction -Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence. +Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale. ## Chronologie canon -- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.' -- **18h05** — Les caméras montrent un passage vers la salle des prototypes, puis plus rien. Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement. -- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants. +- **Acte 1 (~45 min)** — Atelier des Ondes : stabiliser LA 440 pour recaler U-SON. +- **Acte 2 (~60 min)** — Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440). +- **Finale** — Archives : scanner le QR payload WIN derrière le portrait. ## Stations -- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore. -- **Salle des archives** — comparer les relevés de sécurité. -- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc. -- **Carnet du labo secret** — remettre en ordre les données de l’expérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible. +- **Atelier des ondes** — stabiliser un LA 440 Hz propre et constant. +- **Zone 4 — Studio de Résonance** — jouer le code LEFOU via un piano-alphabet brouillé. +- **Salle des archives** — localiser la clé finale et valider le déverrouillage. ## Puzzles -- code-sonore (logique) — Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé. -- log-verifie (observation) — Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes. -- empreinte (tactile) — Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants. -- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.' +- la-440 (audio) — produire un LA 440 stable déclenche la validation de l'étape 1. +- piano-lefou (logique) — retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440. +- qr-win (observation) — scanner le QR avec payload WIN derrière le portrait de la salle des Archives. ## Solution canon -- Coupable : Lila Caron, l’assistante en chef chargée des capteurs et du prototype. -- Mobile : Lila a saboté les protocoles pour isoler le prototype et protéger les cobayes humains qu’elle connaissait mieux que quiconque. Elle craignait que la direction confie l’Auréole à un consortium de sécurité sans restrictions éthiques. -- Méthode : Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice. +- Coupable : Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus. +- Mobile : Vérifier la capacité des équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes. +- Méthode : Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives. - Preuves : - - L’empreinte de gants est celle qu’elle porte toujours. - - Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur. - - Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire. + - Le signal LA 440 stabilise U-SON et valide l'étape 1. + - La séquence LEFOU correctement jouée valide l'étape 2. + - Le QR payload WIN finalise le parcours et ouvre la conclusion. ## Notes pratiques diff --git a/kit-maitre-du-jeu/_generated/stations.md b/kit-maitre-du-jeu/_generated/stations.md index 9a7a644..6c011e7 100644 --- a/kit-maitre-du-jeu/_generated/stations.md +++ b/kit-maitre-du-jeu/_generated/stations.md @@ -2,34 +2,26 @@ ## Atelier des ondes -- Focus : stabiliser les lecteurs de fréquences et retrouver le code sonore. -- Clue : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret. +- Focus : stabiliser un LA 440 Hz propre et constant. +- Clue : une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite. + +## Zone 4 — Studio de Résonance + +- Focus : jouer le code LEFOU via un piano-alphabet brouillé. +- Clue : les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440. ## Salle des archives -- Focus : comparer les relevés de sécurité. -- Clue : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu. - -## Bureau du Professeur - -- Focus : décrypter la note effacée sur le tableau blanc. -- Clue : l’empreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté. - -## Carnet du labo secret - -- Focus : remettre en ordre les données de l’expérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible. -- Clue : Le carnet confirme que « Auréole » génère un signal ‘au-dessus du sol’. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4). +- Focus : localiser la clé finale et valider le déverrouillage. +- Clue : le QR final WIN est caché derrière le portrait. ## Puzzles -- code-sonore (logique) : Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé. - Effet : Déclenche le message audio de la station suivante. +- la-440 (audio) : produire un LA 440 stable déclenche la validation de l'étape 1. + Effet : confirmation audio et ouverture de l'étape Zone 4. -- log-verifie (observation) : Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes. - Effet : Révèle l’emplacement du prototype caché. +- piano-lefou (logique) : retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440. + Effet : validation de l'étape 2 et indice explicite vers les Archives. -- empreinte (tactile) : Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants. - Effet : Confirme le mouvement de la cible et le fait que la porte s’est ouverte manuellement. - -- mobile (narration) : Le parchemin évoque un message : 'Je ne veux pas que l’Auréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.' - Effet : Donne le mobile moral unique et pousse vers l’accusation finale. +- qr-win (observation) : scanner le QR avec payload WIN derrière le portrait de la salle des Archives. + Effet : déverrouille le media manager et la conclusion narrative. diff --git a/scenario-ai-coherence/zacus_conversation_bundle_v3/README.md b/scenario-ai-coherence/zacus_conversation_bundle_v3/README.md index dd828c4..cc58dcb 100644 --- a/scenario-ai-coherence/zacus_conversation_bundle_v3/README.md +++ b/scenario-ai-coherence/zacus_conversation_bundle_v3/README.md @@ -1,5 +1,31 @@ # Zacus Conversation Bundle (v3) +## Purpose +Bundle de travail conversationnel/IA conservé en dehors de la source canon gameplay (`game/scenarios/*.yaml`). + +## Files +- `scenario_promptable_template.yaml`: template éditable (input prompt) +- `scenario_canonical.yaml`: YAML canonique conversation bundle +- `scenario_runtime.json`: JSON runtime dérivé +- `fsm_mermaid.md`: diagramme FSM +- `CODEX_PROMPT.md`: prompt Codex prêt à l'emploi +- `zacus_v2.yaml`: narration MJ alignée bundle +- `CONVERSATION_SUMMARY.md`: synthèse des décisions + +## Validation workflow +1. Installer les dépendances validateurs : + - `bash tools/setup/install_validators.sh` +2. Valider la cohérence interne du bundle : + - `python3 tools/scenario/validate_runtime_bundle.py` +3. Lancer les validateurs projet (non-régression canon/audio/printables) : + - `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml` + - `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml` + - `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` + - `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` + +## Notes +- Ce bundle n'est pas promu automatiquement dans `game/scenarios/*.yaml`. +- Toute promotion doit passer par une revue fonctionnelle dédiée. Files: - scenario_promptable_template.yaml : your editable template (prompt_input) - scenario_canonical.yaml : canonical YAML (copy into docs/... target) diff --git a/specs/CONVERSATION_BUNDLE_INTAKE.md b/specs/CONVERSATION_BUNDLE_INTAKE.md new file mode 100644 index 0000000..6829a4a --- /dev/null +++ b/specs/CONVERSATION_BUNDLE_INTAKE.md @@ -0,0 +1,32 @@ +# Conversation bundle intake — statut courant (G0 Spec Freeze) + +## Contexte +Le bundle conversationnel est maintenu dans : +`scenario-ai-coherence/zacus_conversation_bundle_v3/`. + +## Critères d'acceptation (AC) +- Les fichiers du bundle sont présents localement dans le même chemin. +- Le contenu est importé tel quel (pas de transformation fonctionnelle). +- La traçabilité est mise à jour (`artifacts/...` + `evidence/manifest.json`). +- Les validateurs scénario/audio/printables existants passent après import. +- L'installation des validateurs est reproductible par script versionné. + +## Non-goals +- Aucune promotion automatique de `zacus_v2.yaml` vers `game/scenarios/*.yaml` (promotion manuelle validée dans ce lot). +- Aucune modification firmware/hardware. +- Aucune refonte des manifests audio/printables. + +## Risques +- Le bundle peut diverger du scénario canonique `game/scenarios/zacus_v1.yaml`. +- Les formats `scenario_runtime.json`/templates peuvent nécessiter une validation dédiée ultérieure. + +## Prochaine étape proposée +- Validateur dédié implémenté: `tools/scenario/validate_runtime_bundle.py` (cohérence runtime/canonical/template). +- Diff fonctionnel implémenté: `tools/scenario/diff_gameplay_scenarios.py` (rapport markdown QA). +- Étape suivante: brancher le validateur runtime-bundle dans l'automatisation CI (gate G3 systématique). + +## Statut Gates +- G0 Spec Freeze : **satisfait** pour l'import brut du bundle. + +## TODO transverse (issue restante) +- Analyse transverse du repo ajoutée dans `docs/repo-deep-analysis-2026-02-25.md` pour planifier PR-A/PR-B/PR-C. diff --git a/tools/requirements/validators.txt b/tools/requirements/validators.txt new file mode 100644 index 0000000..da4d67f --- /dev/null +++ b/tools/requirements/validators.txt @@ -0,0 +1,2 @@ +# Dependencies required by repository validators +PyYAML>=6.0,<7 diff --git a/tools/scenario/diff_gameplay_scenarios.py b/tools/scenario/diff_gameplay_scenarios.py new file mode 100755 index 0000000..1ef486f --- /dev/null +++ b/tools/scenario/diff_gameplay_scenarios.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Generate a functional diff report between canonical and conversation gameplay YAML files.""" + +from __future__ import annotations + +import argparse +from datetime import date +from pathlib import Path +import sys + +try: + import yaml +except ImportError: # pragma: no cover + sys.exit("Missing dependency: install PyYAML (pip install pyyaml) to diff scenario YAML files.") + + +def load_yaml(path: Path) -> dict: + data = yaml.safe_load(path.read_text()) + if not isinstance(data, dict): + raise ValueError(f"{path} must contain a YAML mapping") + return data + + +def _safe_len(value) -> int: + return len(value) if isinstance(value, list) else 0 + + +def build_report(base_path: Path, target_path: Path, base: dict, target: dict) -> str: + lines: list[str] = [] + lines.append("# Rapport de diff fonctionnel — scénarios Zacus") + lines.append("") + lines.append(f"- Date: {date.today().isoformat()}") + lines.append(f"- Base canon: `{base_path}`") + lines.append(f"- Cible gameplay: `{target_path}`") + lines.append("") + + lines.append("## Identité") + lines.append(f"- id: `{base.get('id')}` -> `{target.get('id')}`") + lines.append(f"- version: `{base.get('version')}` -> `{target.get('version')}`") + lines.append(f"- title: `{base.get('title')}` -> `{target.get('title')}`") + lines.append("") + + lines.append("## Cadre de session") + base_players = base.get("players", {}) + target_players = target.get("players", {}) + lines.append( + f"- players: `{base_players.get('min')}-{base_players.get('max')}` -> `{target_players.get('min')}-{target_players.get('max')}`" + ) + base_duration = base.get("duration_minutes", {}) + target_duration = target.get("duration_minutes", {}) + lines.append( + f"- duration_minutes: `{base_duration.get('min')}-{base_duration.get('max')}` -> `{target_duration.get('min')}-{target_duration.get('max')}`" + ) + lines.append("") + + lines.append("## Structure narrative") + lines.append(f"- stations: `{_safe_len(base.get('stations'))}` -> `{_safe_len(target.get('stations'))}`") + lines.append(f"- puzzles: `{_safe_len(base.get('puzzles'))}` -> `{_safe_len(target.get('puzzles'))}`") + lines.append(f"- solution_unique: `{base.get('solution_unique')}` -> `{target.get('solution_unique')}`") + lines.append("") + + lines.append("## Changements clés détectés") + base_station_names = [s.get("name") for s in base.get("stations", []) if isinstance(s, dict)] + target_station_names = [s.get("name") for s in target.get("stations", []) if isinstance(s, dict)] + lines.append(f"- Stations cibles: {', '.join([str(x) for x in target_station_names])}") + + base_puzzle_ids = [p.get("id") for p in base.get("puzzles", []) if isinstance(p, dict)] + target_puzzle_ids = [p.get("id") for p in target.get("puzzles", []) if isinstance(p, dict)] + lines.append(f"- IDs puzzles base: {', '.join([str(x) for x in base_puzzle_ids])}") + lines.append(f"- IDs puzzles cible: {', '.join([str(x) for x in target_puzzle_ids])}") + + if base_station_names != target_station_names: + lines.append("- ✅ Les stations ont été réalignées avec le gameplay U-SON / Zone 4 / Archives.") + if base_puzzle_ids != target_puzzle_ids: + lines.append("- ✅ Les puzzles ont été remplacés pour refléter LA 440 → LEFOU → QR WIN.") + + lines.append("") + lines.append("## Impact") + lines.append("- Source de vérité gameplay maintenue dans `game/scenarios/*.yaml`.") + lines.append("- Bundle conversationnel conservé comme matériau de travail parallèle.") + lines.append("- Validation G3 requise via validateurs scénario/audio/printables + runtime bundle.") + lines.append("") + + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Generate functional diff report between two scenario YAML files.") + parser.add_argument("--base", default="scenario-ai-coherence/zacus_conversation_bundle_v3/zacus_v2.yaml") + parser.add_argument("--target", default="game/scenarios/zacus_v1.yaml") + parser.add_argument("--out", default=f"artifacts/qa-test/{date.today().isoformat()}/gameplay-functional-diff.md") + args = parser.parse_args() + + base_path = Path(args.base) + target_path = Path(args.target) + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + + try: + base = load_yaml(base_path) + target = load_yaml(target_path) + except (OSError, ValueError, yaml.YAMLError) as exc: + print(f"[scenario-diff] error -> {exc}") + return 1 + + report = build_report(base_path, target_path, base, target) + out_path.write_text(report) + print(f"[scenario-diff] ok report={out_path}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/scenario/validate_runtime_bundle.py b/tools/scenario/validate_runtime_bundle.py new file mode 100755 index 0000000..d87931b --- /dev/null +++ b/tools/scenario/validate_runtime_bundle.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Validate coherence of Zacus conversation bundle artifacts.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +try: + import yaml +except ImportError: # pragma: no cover + sys.exit("Missing dependency: install PyYAML (pip install pyyaml) to validate bundle YAML files.") + + +class ValidationError(Exception): + pass + + +def read_json(path: Path) -> dict: + try: + return json.loads(path.read_text()) + except OSError as exc: + raise ValidationError(f"Cannot read JSON file {path}: {exc}") + except json.JSONDecodeError as exc: + raise ValidationError(f"Invalid JSON in {path}: {exc}") + + +def read_yaml(path: Path) -> dict: + try: + return yaml.safe_load(path.read_text()) + except OSError as exc: + raise ValidationError(f"Cannot read YAML file {path}: {exc}") + except yaml.YAMLError as exc: + raise ValidationError(f"Invalid YAML in {path}: {exc}") + + +def extract_template_scenario(template: dict) -> tuple[str, int]: + prompt_input = template.get("prompt_input") + if not isinstance(prompt_input, dict): + raise ValidationError("template missing `prompt_input` mapping") + + scenario = prompt_input.get("scenario") + if not isinstance(scenario, dict): + raise ValidationError("template missing `prompt_input.scenario` mapping") + + scenario_id = scenario.get("id") + version = scenario.get("version") + if not isinstance(scenario_id, str) or not scenario_id: + raise ValidationError("template `prompt_input.scenario.id` must be a non-empty string") + if not isinstance(version, int): + raise ValidationError("template `prompt_input.scenario.version` must be an integer") + return scenario_id, version + + +def extract_canonical(canonical: dict) -> tuple[str, int, list[str], str]: + scenario = canonical.get("scenario") + if not isinstance(scenario, dict): + raise ValidationError("canonical missing `scenario` mapping") + + scenario_id = scenario.get("id") + version = scenario.get("version") + initial_step = scenario.get("initial_step") + steps = canonical.get("steps_runtime_order") + + if not isinstance(scenario_id, str) or not scenario_id: + raise ValidationError("canonical `scenario.id` must be a non-empty string") + if not isinstance(version, int): + raise ValidationError("canonical `scenario.version` must be an integer") + if not isinstance(initial_step, str) or not initial_step: + raise ValidationError("canonical `scenario.initial_step` must be a non-empty string") + if not isinstance(steps, list) or not steps: + raise ValidationError("canonical `steps_runtime_order` must be a non-empty list") + + step_ids: list[str] = [] + for idx, step in enumerate(steps, start=1): + if not isinstance(step, dict): + raise ValidationError(f"canonical step #{idx} must be a mapping") + step_id = step.get("step_id") + if not isinstance(step_id, str) or not step_id: + raise ValidationError(f"canonical step #{idx} missing non-empty `step_id`") + step_ids.append(step_id) + + if initial_step not in step_ids: + raise ValidationError("canonical initial_step not found in canonical steps_runtime_order") + + return scenario_id, version, step_ids, initial_step + + +def extract_runtime(runtime: dict) -> tuple[str, int, list[str], str]: + scenario = runtime.get("scenario") + if not isinstance(scenario, dict): + raise ValidationError("runtime missing `scenario` mapping") + + scenario_id = scenario.get("id") + version = scenario.get("version") + initial_step = scenario.get("initial_step") + steps = runtime.get("steps_runtime_order") + + if not isinstance(scenario_id, str) or not scenario_id: + raise ValidationError("runtime `scenario.id` must be a non-empty string") + if not isinstance(version, int): + raise ValidationError("runtime `scenario.version` must be an integer") + if not isinstance(initial_step, str) or not initial_step: + raise ValidationError("runtime `scenario.initial_step` must be a non-empty string") + if not isinstance(steps, list) or not steps: + raise ValidationError("runtime `steps_runtime_order` must be a non-empty list") + + step_ids: list[str] = [] + for idx, step in enumerate(steps, start=1): + if not isinstance(step, dict): + raise ValidationError(f"runtime step #{idx} must be a mapping") + step_id = step.get("step_id") + if not isinstance(step_id, str) or not step_id: + raise ValidationError(f"runtime step #{idx} missing non-empty `step_id`") + step_ids.append(step_id) + + if initial_step not in step_ids: + raise ValidationError("runtime initial_step not found in runtime steps_runtime_order") + + return scenario_id, version, step_ids, initial_step + + +def validate_bundle(runtime_path: Path, canonical_path: Path, template_path: Path) -> None: + runtime = read_json(runtime_path) + canonical = read_yaml(canonical_path) + template = read_yaml(template_path) + + canonical_id, canonical_version, canonical_steps, canonical_initial = extract_canonical(canonical) + runtime_id, runtime_version, runtime_steps, runtime_initial = extract_runtime(runtime) + template_id, template_version = extract_template_scenario(template) + + errors: list[str] = [] + + if canonical_id != runtime_id: + errors.append(f"scenario id mismatch: canonical={canonical_id} runtime={runtime_id}") + if canonical_version != runtime_version: + errors.append(f"scenario version mismatch: canonical={canonical_version} runtime={runtime_version}") + if canonical_initial != runtime_initial: + errors.append(f"initial_step mismatch: canonical={canonical_initial} runtime={runtime_initial}") + if canonical_steps != runtime_steps: + errors.append("steps_runtime_order mismatch between canonical and runtime") + + if template_id != canonical_id: + errors.append(f"template id mismatch: template={template_id} canonical={canonical_id}") + if template_version != canonical_version: + errors.append(f"template version mismatch: template={template_version} canonical={canonical_version}") + + if errors: + raise ValidationError("; ".join(errors)) + + print( + "[runtime-bundle-validate] ok " + f"id={canonical_id} version={canonical_version} steps={len(canonical_steps)} initial={canonical_initial}" + ) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate conversation bundle runtime/canonical/template coherence.") + parser.add_argument( + "--runtime", + default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_runtime.json", + help="Path to scenario_runtime.json", + ) + parser.add_argument( + "--canonical", + default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_canonical.yaml", + help="Path to scenario_canonical.yaml", + ) + parser.add_argument( + "--template", + default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_promptable_template.yaml", + help="Path to scenario_promptable_template.yaml", + ) + args = parser.parse_args() + + try: + validate_bundle(Path(args.runtime), Path(args.canonical), Path(args.template)) + except ValidationError as exc: + print(f"[runtime-bundle-validate] error -> {exc}") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/setup/install_validators.sh b/tools/setup/install_validators.sh new file mode 100755 index 0000000..0de70f4 --- /dev/null +++ b/tools/setup/install_validators.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + cat <<'USAGE' +Usage: bash tools/setup/install_validators.sh + +Install Python dependencies required by repository validators. +Environment override: + PYTHON_BIN Python executable to use (default: python3) +USAGE + exit 0 +fi + +PYTHON_BIN="${PYTHON_BIN:-python3}" +REQ_FILE="tools/requirements/validators.txt" + +if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then + echo "[install-validators] error: python interpreter not found: $PYTHON_BIN" >&2 + exit 1 +fi + +if [[ ! -f "$REQ_FILE" ]]; then + echo "[install-validators] error: missing requirements file: $REQ_FILE" >&2 + exit 1 +fi + +"$PYTHON_BIN" -m pip install --user -r "$REQ_FILE" +echo "[install-validators] ok installed requirements from $REQ_FILE"