cac65386ab
Orchestrateur méta-niveau : compose les hand-offs out/ des 10 modules déjà livrés en un run-sheet de démo jouable, via pointeurs RFC 6901 résolus à la construction (zéro chiffre en dur · #6). Réutilise le validateur Publiciste et la preuve de couverture CI de l'auditeur 4Big (#5) — une démo ne s'appuie que sur des modules gated. Enregistre demo/scenarios dans l'audit 4Big (17→18, verdict PASS 18/18 à 100). 32 tests (9 injections négatives) · régression 474 tests verts · guards CI verts · builds déterministes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
"""Assemblage déterministe du run-sheet démo + du manifeste (hand-off).
|
|
|
|
Le builder est PUR : mêmes entrées (spec + artefacts `out/` sur disque) ⇒ même
|
|
sortie. Aucun horodatage implicite, aucun ordre non déterministe. L'ordre des
|
|
beats est dérivé de la position dans le spec (jamais d'un champ `ordre` en dur,
|
|
pour éviter toute dérive). Les modules cités sont recoupés avec l'ensemble des
|
|
modules gated par le CI (via `deps.ci_module_paths`) — une démo ne s'appuie que
|
|
sur du validé.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from . import deps, evidence
|
|
|
|
SELF_MODULE = "demo/scenarios"
|
|
|
|
|
|
def _build_beat(loader: evidence.Loader, beat: dict, ordre: int) -> dict:
|
|
cites = []
|
|
for ev in beat["evidence"]:
|
|
module, file, pointer = ev["module"], ev["file"], ev["pointer"]
|
|
valeur, resolu = evidence.resolve(loader, module, file, pointer)
|
|
cites.append({
|
|
"module": module,
|
|
"file": file,
|
|
"pointer": pointer,
|
|
"label": ev["label"],
|
|
"valeur": valeur,
|
|
"resolu": resolu,
|
|
})
|
|
return {
|
|
"ordre": ordre,
|
|
"id": beat["id"],
|
|
"titre": beat["titre"],
|
|
"role_narratif": beat["role_narratif"],
|
|
"duree_min": beat["duree_min"],
|
|
"evidence": cites,
|
|
}
|
|
|
|
|
|
def _build_scenario(loader: evidence.Loader, spec: dict, sc: dict) -> dict:
|
|
beats = [_build_beat(loader, b, i + 1) for i, b in enumerate(sc["beats"])]
|
|
modules = sorted({c["module"] for b in beats for c in b["evidence"]})
|
|
duree = sum(b["duree_min"] for b in beats)
|
|
return {
|
|
"id": sc["id"],
|
|
"projet": sc["projet"],
|
|
"projet_libelle": spec["projets"][sc["projet"]],
|
|
"audience": sc["audience"],
|
|
"titre": sc["titre"],
|
|
"objectif": sc["objectif"],
|
|
"duree_min": duree,
|
|
"modules_cites": modules,
|
|
"beats": beats,
|
|
}
|
|
|
|
|
|
def build_run_sheet(spec: dict, generated_at: str | None = None) -> dict:
|
|
"""Construit le run-sheet complet (le hand-off principal)."""
|
|
loader = evidence.Loader()
|
|
scenarios = [_build_scenario(loader, spec, sc) for sc in spec["scenarios"]]
|
|
return {
|
|
"doc": "OTO · Run-sheet démo (méta-livrable)",
|
|
"spec_version": spec["spec_version"],
|
|
"roadmap_ref": spec["roadmap_ref"],
|
|
"generated_at": generated_at,
|
|
"scenarios": scenarios,
|
|
}
|
|
|
|
|
|
def _all_citations(run_sheet: dict) -> list[dict]:
|
|
return [c for sc in run_sheet["scenarios"] for b in sc["beats"] for c in b["evidence"]]
|
|
|
|
|
|
def build_manifest(spec: dict, run_sheet: dict) -> dict:
|
|
"""Manifeste diagnostic + preuve de couverture CI + drapeau `pret`."""
|
|
cites = _all_citations(run_sheet)
|
|
non_resolues = [c for c in cites if not c["resolu"]]
|
|
modules_cites = sorted({c["module"] for c in cites})
|
|
|
|
gated = deps.ci_module_paths() # modules réellement gated par le CI
|
|
non_gated = sorted(m for m in modules_cites if m not in gated)
|
|
|
|
scen_diag = []
|
|
for sc in run_sheet["scenarios"]:
|
|
sc_cites = [c for b in sc["beats"] for c in b["evidence"]]
|
|
scen_diag.append({
|
|
"id": sc["id"],
|
|
"projet": sc["projet"],
|
|
"audience": sc["audience"],
|
|
"beats": len(sc["beats"]),
|
|
"citations": len(sc_cites),
|
|
"citations_resolues": sum(1 for c in sc_cites if c["resolu"]),
|
|
"duree_min": sc["duree_min"],
|
|
"modules_cites": sc["modules_cites"],
|
|
})
|
|
|
|
couverture_ok = not non_gated
|
|
pret = (not non_resolues) and couverture_ok
|
|
|
|
return {
|
|
"deliverable": "Scénarios démo · run-sheet de pitch",
|
|
"sprint": "Sprint 7 · CRM + Faisabilité",
|
|
"roadmap_ref": spec["roadmap_ref"],
|
|
"generated_from": "scenario_spec.json",
|
|
"deliverables_root": "05_deliverables_mvp",
|
|
"self_module": SELF_MODULE,
|
|
"generated_at": run_sheet["generated_at"],
|
|
"scenarios": scen_diag,
|
|
"counts": {
|
|
"scenarios": len(run_sheet["scenarios"]),
|
|
"beats_total": sum(len(sc["beats"]) for sc in run_sheet["scenarios"]),
|
|
"citations_total": len(cites),
|
|
"citations_non_resolues": len(non_resolues),
|
|
"modules_cites_uniques": len(modules_cites),
|
|
},
|
|
"modules_cites": modules_cites,
|
|
"couverture_ci": {
|
|
"ok": couverture_ok,
|
|
"modules_cites_non_gated": non_gated,
|
|
},
|
|
"pret": pret,
|
|
}
|