docs: importer zacus_conversation_bundle_v3 et clôturer le blocage ZIP intake (#106)
* 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>
This commit was merged in pull request #106.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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/<date>/`.
|
||||
|
||||
@@ -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é.
|
||||
|
||||
@@ -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.
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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é.
|
||||
|
||||
@@ -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é
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Dependencies required by repository validators
|
||||
PyYAML>=6.0,<7
|
||||
Executable
+113
@@ -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())
|
||||
Executable
+187
@@ -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())
|
||||
Executable
+29
@@ -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"
|
||||
Reference in New Issue
Block a user