tools: add runtime bundle validator and update docs workflow

This commit is contained in:
Clément SAILLANT
2026-02-25 18:03:40 +01:00
parent 62fbcbad3d
commit 9ac84cd893
7 changed files with 235 additions and 22 deletions
@@ -1,4 +1,4 @@
# Architecture d'intégration ZIP — implémentation minimale (G1)
# Architecture d'intégration du conversation bundle — baseline (G1)
## Entrée / sortie
- **Entrée**: bundle conversationnel `scenario-ai-coherence/zacus_conversation_bundle_v3/*`.
@@ -15,7 +15,8 @@
- Bootstrap dépendances validateurs: `tools/setup/install_validators.sh`.
## Prochaine évolution proposée
- Ajouter `tools/scenario/validate_runtime_bundle.py` pour vérifier la cohérence entre `scenario_runtime.json`, `scenario_canonical.yaml` et `scenario_promptable_template.yaml`.
- Validateur ajouté: `tools/scenario/validate_runtime_bundle.py` (cohérence entre `scenario_runtime.json`, `scenario_canonical.yaml` et `scenario_promptable_template.yaml`).
- Prochaine itération: intégration CI de ce contrôle dans la gate G3.
## Statut Gates
- G1 Arch Freeze : **satisfait** pour ce lot d'import documentaire/data.
@@ -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.
+2
View File
@@ -6,3 +6,5 @@
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 laudio** 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 sassurer 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`.
+8 -5
View File
@@ -1,16 +1,19 @@
{
"date": "2026-02-25",
"agent": "pm-spec",
"agent": "qa-test",
"changes": [
"tools/scenario/validate_runtime_bundle.py",
"scenario-ai-coherence/zacus_conversation_bundle_v3/README.md",
"docs/WORKFLOWS.md",
"specs/CONVERSATION_BUNDLE_INTAKE.md",
"architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md",
"tools/requirements/validators.txt",
"tools/setup/install_validators.sh",
"docs/WORKFLOWS.md"
"tools/setup/install_validators.sh"
],
"artifacts": [
"artifacts/pm-spec/2026-02-25/conversation-bundle-intake.md",
"artifacts/qa-test/2026-02-25/validator-bootstrap.md"
"artifacts/qa-test/2026-02-25/validator-bootstrap.md",
"artifacts/qa-test/2026-02-25/runtime-bundle-validator.md"
],
"gates": {
"G0": "satisfied",
@@ -20,5 +23,5 @@
"G4": "pending",
"G5": "partial"
},
"note": "Terminologie harmonisée autour du conversation bundle; validateurs installables et exécutés."
"note": "Validateur runtime bundle ajouté + documentation mise à jour; contrôles projet exécutés."
}
@@ -1,17 +1,30 @@
# Zacus Conversation Bundle (v3)
Files:
- scenario_promptable_template.yaml : your editable template (prompt_input)
- scenario_canonical.yaml : canonical YAML (copy into docs/... target)
- scenario_runtime.json : runtime JSON (copy into data/story/scenarios/DEFAULT.json)
- fsm_mermaid.md : FSM diagram
- CODEX_PROMPT.md : ready-to-paste Codex prompt
- zacus_v2.yaml : narrative scenario aligned with runtime + gameplay reality
- CONVERSATION_SUMMARY.md : what changed / key decisions
## Purpose
Bundle de travail conversationnel/IA conservé en dehors de la source canon gameplay (`game/scenarios/*.yaml`).
Typical flow:
1) Update prompt_input in scenario_promptable_template.yaml if needed.
2) Use canonical/runtime files to update repo targets.
3) Use zacus_v2.yaml as the printed narrative / MJ reference for the real game.
## 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.
Generated: 2026-02-25
+4 -3
View File
@@ -1,7 +1,7 @@
# ZIP intake — statut après réception bundle (G0 Spec Freeze)
# Conversation bundle intake — statut courant (G0 Spec Freeze)
## Contexte
Le bundle annoncé par l'utilisateur est désormais disponible sous :
Le bundle conversationnel est maintenu dans :
`scenario-ai-coherence/zacus_conversation_bundle_v3/`.
## Critères d'acceptation (AC)
@@ -21,7 +21,8 @@ Le bundle annoncé par l'utilisateur est désormais disponible sous :
- Les formats `scenario_runtime.json`/templates peuvent nécessiter une validation dédiée ultérieure.
## Prochaine étape proposée
- Ajouter un validateur dédié pour `scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_runtime.json` et son template promptable, puis l'inclure dans les gates G3.
- Validateur dédié implémenté: `tools/scenario/validate_runtime_bundle.py` (cohérence runtime/canonical/template).
- Étape suivante: brancher ce validateur dans l'automatisation CI (gate G3 systématique).
## Statut Gates
- G0 Spec Freeze : **satisfait** pour l'import brut du bundle.
+187
View File
@@ -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())