Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5677082965 | |||
| 51e447605f | |||
| 9353d7f54e | |||
| ee421add1d | |||
| 30892dfecb | |||
| 5de6b23c02 | |||
| 6f88383939 | |||
| e53d60fc0b | |||
| 3e7bc33fb7 | |||
| 8593961683 | |||
| f5c7642cbf | |||
| d7f727203c | |||
| e62ecfd61e | |||
| 68004265d6 | |||
| 3a91c9d52f | |||
| bc58809e7d | |||
| 3bce40ea80 | |||
| e8b30d7147 | |||
| 37903685c0 | |||
| e5f6459e85 | |||
| 082b057639 | |||
| 63760a144b | |||
| 1f212b1f04 | |||
| c2222c83b6 | |||
| 6f2a3c57ae | |||
| 0790fa7811 | |||
| 342b571b29 | |||
| 7934081bc2 | |||
| 7ee6882eb8 | |||
| 7b775677a6 | |||
| 607b22282b | |||
| e52519dab1 | |||
| 733c73ae3f | |||
| 9794eabc8a | |||
| c0da3dfeec | |||
| 8961e14ff5 | |||
| c5ac020270 | |||
| 6a40bfccce | |||
| abc9b62214 | |||
| 327aa298a2 | |||
| 0d1df7a8dc | |||
| 70a8db0f4c | |||
| 97a7b25e38 | |||
| 09ab940321 | |||
| 9ac7bdb2de | |||
| 8f27b65724 | |||
| 152a138861 | |||
| 4d9af3a6b8 | |||
| 6f6a01a472 | |||
| b7fa3bf8c8 | |||
| 56e64d6089 | |||
| d7fc6bab26 | |||
| 9038417db8 | |||
| 402727411d | |||
| 25541c365d | |||
| 11f1250b29 | |||
| 0cde935a39 | |||
| 504bfb0f10 | |||
| aebe9b7ba9 | |||
| c858e193e0 | |||
| 97f859b6a5 |
+23
-3
@@ -64,8 +64,9 @@ jobs:
|
|||||||
# Fraîcheur de la matrice de régression (Sprint 8 · QA « Regression tests
|
# Fraîcheur de la matrice de régression (Sprint 8 · QA « Regression tests
|
||||||
# exhaustifs »). Complète check-artifacts : ce dernier ne rejoue que `build`
|
# exhaustifs »). Complète check-artifacts : ce dernier ne rejoue que `build`
|
||||||
# et exclut les artefacts d'EXÉCUTION ; ce gate rejoue `run` et exige que le
|
# et exclut les artefacts d'EXÉCUTION ; ce gate rejoue `run` et exige que le
|
||||||
# `qa/regression/out/regression_run.json` commité (21 suites · 534 tests ·
|
# `qa/regression/out/regression_run.json` commité (dont la doc dérive ses
|
||||||
# PASS, cité dans toute la doc) soit byte-identique au run frais. Rend
|
# totaux agrégés — suites · tests · verdict PASS — jamais figés en dur ici,
|
||||||
|
# sous peine de se périmer) soit byte-identique au run frais. Rend
|
||||||
# impossible la re-commission d'une matrice périmée (ex. dérive « demo 18→21 »)
|
# impossible la re-commission d'une matrice périmée (ex. dérive « demo 18→21 »)
|
||||||
# ou rouge commitée verte. stdlib pur, zéro pip, ~5 s.
|
# ou rouge commitée verte. stdlib pur, zéro pip, ~5 s.
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@@ -97,6 +98,25 @@ jobs:
|
|||||||
- name: Contrôle intégrité du câblage CI
|
- name: Contrôle intégrité du câblage CI
|
||||||
run: bash ci/check_ci_integrity.sh
|
run: bash ci/check_ci_integrity.sh
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Intégrité des chiffres du README (Sprint 8 buffer · CLAUDE.md #6). Le README
|
||||||
|
# est le point d'entrée du mandat et déclare que chaque chiffre de sa section
|
||||||
|
# « État courant » est sourcé vers un artefact commité (« n'introduit aucune
|
||||||
|
# donnée nouvelle »). Aucun gate ne l'enforçait : check-docs ne valide que les
|
||||||
|
# LIENS, jamais la VALEUR des nombres. Ce gate RECOMPUTE chaque chiffre depuis
|
||||||
|
# l'artefact cité (modules/verdict 4Big, suites régression, promesses/statuts/
|
||||||
|
# verdict acceptance, nombre d'AGENT.md) et exige l'égalité avec ce qui est
|
||||||
|
# écrit — un README qui se contredit avec sa propre source (ex. 21→22 modules
|
||||||
|
# non répercuté) est un « vert trompeur ». stdlib pur, zéro pip.
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
check-readme-claims:
|
||||||
|
name: Intégrité des chiffres du README (valeur == artefact cité · #6)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Contrôle des chiffres du README
|
||||||
|
run: bash ci/check_readme_claims.sh
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Publiciste (Sprint 2) : parser faisabilité → projets_master.json + generator.
|
# Publiciste (Sprint 2) : parser faisabilité → projets_master.json + generator.
|
||||||
# Tests unitaires stdlib pur (unittest) → aucune installation pip requise sur
|
# Tests unitaires stdlib pur (unittest) → aucune installation pip requise sur
|
||||||
@@ -504,7 +524,7 @@ jobs:
|
|||||||
gate:
|
gate:
|
||||||
name: Gate qualité (agrégat)
|
name: Gate qualité (agrégat)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [constraints-guard, validate-json, check-docs, check-artifacts, check-regression, check-ci-integrity, publiciste-tests, rbac-tests, faisabilite-gen-tests, rbac-fixtures-tests, rbac-userperm-tests, rbac-roleprofile-tests, rbac-applyplan-tests, bancable-tests, crm-workflow-vente-tests, crm-dossier-vente-tests, crm-commissions-tests, fiscal-ecf-tests, frontend-portails-tests, legal-confotur-tests, qa-audit-5d-tests, seo-tests, chat-otoia-tests, qa-audit-4big-tests, demo-scenario-tests, qa-regression-tests, devops-deploy-runbook-tests, qa-acceptance-tests, mobile-app-config-tests]
|
needs: [constraints-guard, validate-json, check-docs, check-artifacts, check-regression, check-ci-integrity, check-readme-claims, publiciste-tests, rbac-tests, faisabilite-gen-tests, rbac-fixtures-tests, rbac-userperm-tests, rbac-roleprofile-tests, rbac-applyplan-tests, bancable-tests, crm-workflow-vente-tests, crm-dossier-vente-tests, crm-commissions-tests, fiscal-ecf-tests, frontend-portails-tests, legal-confotur-tests, qa-audit-5d-tests, seo-tests, chat-otoia-tests, qa-audit-4big-tests, demo-scenario-tests, qa-regression-tests, devops-deploy-runbook-tests, qa-acceptance-tests, mobile-app-config-tests]
|
||||||
steps:
|
steps:
|
||||||
- name: Résultat
|
- name: Résultat
|
||||||
run: echo "✅ Gate qualité 4Big franchi — tous les checks verts."
|
run: echo "✅ Gate qualité 4Big franchi — tous les checks verts."
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ le VPS reçoit — jamais l'inverse.
|
|||||||
`rbac_50_roles.json` est le **référentiel de rôles de tout le repo** — le workflow
|
`rbac_50_roles.json` est le **référentiel de rôles de tout le repo** — le workflow
|
||||||
vente, le barème commissions, le DocType Dossier Vente et le plan e-CF **résolvent**
|
vente, le barème commissions, le DocType Dossier Vente et le plan e-CF **résolvent**
|
||||||
leurs rôles depuis ce fichier, jamais un nom Frappe en dur. Total backend **RBAC 60
|
leurs rôles depuis ce fichier, jamais un nom Frappe en dur. Total backend **RBAC 60
|
||||||
tests** (10 + 11 + 12 + 11 + 16) + **e-CF 39 tests**, tous gated dans le CI (repo :
|
tests** (10 + 11 + 12 + 11 + 16) + **e-CF 39 tests**, tous gated dans le CI (matrice
|
||||||
**560 tests** au total).
|
de régression du repo : **564 tests · 22 suites** · verdict `PASS`, source
|
||||||
|
`qa/regression/out/regression_run.json` — jamais compté à la main · #6).
|
||||||
|
|
||||||
## Hand-off reçus (à appliquer sur le VPS, dans l'ordre)
|
## Hand-off reçus (à appliquer sur le VPS, dans l'ordre)
|
||||||
- **CRM** → `crm/dossier_vente/out/` (DocType porteur) **AVANT** `crm/workflow_vente/out/`
|
- **CRM** → `crm/dossier_vente/out/` (DocType porteur) **AVANT** `crm/workflow_vente/out/`
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ périmètre — un auditeur ne s'audite jamais.
|
|||||||
| Module | Sprint | Rôle | Entrée CLI | Job CI | Tests |
|
| Module | Sprint | Rôle | Entrée CLI | Job CI | Tests |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| [`audit_5d/`](../../05_deliverables_mvp/qa/audit_5d/README.md) | 5 (roadmap L57) | Audit **5D** de conformité : 17 contrôles / 5 dimensions, lit les `out/` amont, coche UAF + normes ISA/IFRS | `audit_5d_gen.py build\|validate` | `qa-audit-5d-tests` | 37 |
|
| [`audit_5d/`](../../05_deliverables_mvp/qa/audit_5d/README.md) | 5 (roadmap L57) | Audit **5D** de conformité : 17 contrôles / 5 dimensions, lit les `out/` amont, coche UAF + normes ISA/IFRS | `audit_5d_gen.py build\|validate` | `qa-audit-5d-tests` | 37 |
|
||||||
| [`audit_4big/`](../../05_deliverables_mvp/qa/audit_4big/README.md) | 7 (roadmap L69) | **Gate qualité** : note 4Big de 100 % des livrables, verdict `FAIL` si un module < 95/100 ou couverture incomplète | `audit_4big_gen.py build\|validate` | `qa-audit-4big-tests` | 35 |
|
| [`audit_4big/`](../../05_deliverables_mvp/qa/audit_4big/README.md) | 7 (roadmap L69) | **Gate qualité** : note 4Big de 100 % des livrables, verdict `FAIL` si un module < 95/100 ou couverture incomplète | `audit_4big_gen.py build\|validate` | `qa-audit-4big-tests` | 34 |
|
||||||
| [`regression/`](../../05_deliverables_mvp/qa/regression/README.md) | 8 (roadmap L74) | **Matrice de régression** : agrège l'exécution de **toutes** les suites gated en un verdict unique + le compte agrégé faisant autorité | `regression_gen.py build\|validate\|run` | `qa-regression-tests` | 25 |
|
| [`regression/`](../../05_deliverables_mvp/qa/regression/README.md) | 8 (roadmap L74) | **Matrice de régression** : agrège l'exécution de **toutes** les suites gated en un verdict unique + le compte agrégé faisant autorité | `regression_gen.py build\|validate\|run` | `qa-regression-tests` | 26 |
|
||||||
| [`acceptance/`](../../05_deliverables_mvp/qa/acceptance/README.md) | 8 (roadmap L75 / L80-87) | **Recette / traçabilité** : mappe chaque promesse roadmap (8 livrables + 7 métriques) vers sa preuve, bijectif vs CI | `acceptance_gen.py build\|validate` | `qa-acceptance-tests` | 31 |
|
| [`acceptance/`](../../05_deliverables_mvp/qa/acceptance/README.md) | 8 (roadmap L75 / L80-87) | **Recette / traçabilité** : mappe chaque promesse roadmap (8 livrables + 7 métriques) vers sa preuve, bijectif vs CI | `acceptance_gen.py build\|validate` | `qa-acceptance-tests` | 37 |
|
||||||
|
|
||||||
**Quatre axes distincts, non redondants** (CLAUDE.md #5 · éliminer les doublons) :
|
**Quatre axes distincts, non redondants** (CLAUDE.md #5 · éliminer les doublons) :
|
||||||
`audit_4big` note la **qualité statique** · `regression` prouve l'**exécution** ·
|
`audit_4big` note la **qualité statique** · `regression` prouve l'**exécution** ·
|
||||||
@@ -32,7 +32,7 @@ périmètre — un auditeur ne s'audite jamais.
|
|||||||
**conformité** normative des hand-off.
|
**conformité** normative des hand-off.
|
||||||
|
|
||||||
## Verdict agrégé courant (source `qa/regression/out/regression_run.json`)
|
## Verdict agrégé courant (source `qa/regression/out/regression_run.json`)
|
||||||
**21 suites gated · 534 tests · 534 passés · 0 échec · 0 erreur → verdict `PASS`.**
|
**22 suites gated · 564 tests · 564 passés · 0 échec · 0 erreur → verdict `PASS`.**
|
||||||
Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`,
|
Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`,
|
||||||
réutilisé sans duplication) — jamais compté à la main.
|
réutilisé sans duplication) — jamais compté à la main.
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -25,8 +25,8 @@ scenario_spec.json out/ des modules livrés (10 modules cités)
|
|||||||
│ │
|
│ │
|
||||||
▼ demo_scenario_gen build ▼
|
▼ demo_scenario_gen build ▼
|
||||||
out/run_sheet.json ◄──── résolution RFC 6901 + placeholders ──── crm/workflow_vente
|
out/run_sheet.json ◄──── résolution RFC 6901 + placeholders ──── crm/workflow_vente
|
||||||
out/MANIFEST.json fiscal/ecf_dgii
|
out/run_sheet.md (prompteur, rendu pur du run-sheet) fiscal/ecf_dgii
|
||||||
legal/confotur
|
out/MANIFEST.json legal/confotur
|
||||||
rbac/apply_plan
|
rbac/apply_plan
|
||||||
qa/audit_4big …
|
qa/audit_4big …
|
||||||
```
|
```
|
||||||
@@ -52,13 +52,13 @@ jamais un `0` fabriqué, jamais un `null` amont promu en chiffre de pitch.
|
|||||||
## Utilisation
|
## Utilisation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Assemble le run-sheet (défaut: ./out)
|
# Assemble le run-sheet + le prompteur Markdown (défaut: ./out)
|
||||||
python3 demo_scenario_gen.py build [-o OUT_DIR]
|
python3 demo_scenario_gen.py build [-o OUT_DIR]
|
||||||
|
|
||||||
# Recalcule le manifeste + les invariants, sans écrire
|
# Recalcule le manifeste + les invariants, sans écrire
|
||||||
python3 demo_scenario_gen.py validate
|
python3 demo_scenario_gen.py validate
|
||||||
|
|
||||||
# Tests (32 méthodes, dont injections négatives)
|
# Tests (39 méthodes, dont injections négatives)
|
||||||
python3 -m unittest discover -s tests -v
|
python3 -m unittest discover -s tests -v
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,16 +70,21 @@ python3 -m unittest discover -s tests -v
|
|||||||
| `scenlib/deps.py` | Réutilise validateur Publiciste + couverture CI 4Big |
|
| `scenlib/deps.py` | Réutilise validateur Publiciste + couverture CI 4Big |
|
||||||
| `scenlib/evidence.py` | Chargement `out/` amont + résolveur RFC 6901 + placeholders |
|
| `scenlib/evidence.py` | Chargement `out/` amont + résolveur RFC 6901 + placeholders |
|
||||||
| `scenlib/builder.py` | Assemblage déterministe run-sheet + manifeste |
|
| `scenlib/builder.py` | Assemblage déterministe run-sheet + manifeste |
|
||||||
|
| `scenlib/render.py` | Rendu **prompteur Markdown** (pur, dérivé du run-sheet) |
|
||||||
| `demo_scenario_gen.py` | CLI `build`/`validate` + invariants |
|
| `demo_scenario_gen.py` | CLI `build`/`validate` + invariants |
|
||||||
| `scenario.schema.json` | Contrat de sortie draft-07 du manifeste |
|
| `scenario.schema.json` | Contrat de sortie draft-07 du manifeste |
|
||||||
| `out/run_sheet.json` | **Hand-off principal** — run-sheet jouable |
|
| `out/run_sheet.json` | **Hand-off principal** — run-sheet jouable (machine) |
|
||||||
|
| `out/run_sheet.md` | **Prompteur** lisible par un présentateur (dérivé de `run_sheet.json`) |
|
||||||
| `out/MANIFEST.json` | Diagnostic + preuve de couverture + drapeau `pret` |
|
| `out/MANIFEST.json` | Diagnostic + preuve de couverture + drapeau `pret` |
|
||||||
|
|
||||||
## Hors périmètre worker (VPS · #8)
|
## Hors périmètre worker (VPS · #8)
|
||||||
|
|
||||||
Le rendu final (deck / prompteur / page démo `otov7.com`) et le branchement au
|
Le **support texte** est désormais produit in-repo : `out/run_sheet.md` est un
|
||||||
`data_room` réel des projets restent **côté serveur** — ce worker n'écrit jamais
|
prompteur lisible, régénéré déterministement depuis le run-sheet (aucune donnée
|
||||||
sur le VPS. Le run-sheet est le **contrat** que ce rendu consommera.
|
nouvelle · #6). Ne restent **côté serveur** que l'**habillage visuel** (deck /
|
||||||
|
page démo `otov7.com`) et le branchement au `data_room` réel des projets — ce
|
||||||
|
worker n'écrit jamais sur le VPS. Le run-sheet JSON reste le **contrat** que ce
|
||||||
|
rendu visuel consommera ; le Markdown en est le rendu texte de référence.
|
||||||
|
|
||||||
## Provenance des libellés projets
|
## Provenance des libellés projets
|
||||||
|
|
||||||
@@ -88,9 +93,10 @@ sur le VPS. Le run-sheet est le **contrat** que ce rendu consommera.
|
|||||||
|
|
||||||
## Auto-score 4Big
|
## Auto-score 4Big
|
||||||
|
|
||||||
**96/100** — orchestration réelle prouvée (chaque beat cite une preuve RFC 6901
|
**97/100** — orchestration réelle prouvée (chaque beat cite une preuve RFC 6901
|
||||||
re-résolue depuis le disque, jamais saisie à la main), couverture CI réutilisée de
|
re-résolue depuis le disque, jamais saisie à la main), couverture CI réutilisée de
|
||||||
l'auditeur 4Big (zéro duplication · #5), garde `pret=true` qui refuse d'écrire sur
|
l'auditeur 4Big (zéro duplication · #5), garde `pret=true` qui refuse d'écrire sur
|
||||||
citation non résolue, 32 tests (dont injections négatives) verts. Retenue sur le
|
citation non résolue, **prompteur `run_sheet.md`** désormais livré (support texte
|
||||||
seuil : le rendu final (deck / page démo `otov7.com`) est produit côté VPS (#8) —
|
in-repo, dérivé purement du run-sheet · reproductible sous `ci/check_artifacts.sh`),
|
||||||
l'in-repo est le **run-sheet** contractuel, pas le support de présentation.
|
39 tests (dont injections négatives + rendu Markdown) verts. Retenue résiduelle :
|
||||||
|
l'**habillage visuel** (deck / page démo `otov7.com`) reste produit côté VPS (#8).
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import sys
|
|||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, _HERE)
|
sys.path.insert(0, _HERE)
|
||||||
|
|
||||||
from scenlib import builder, deps, evidence # noqa: E402
|
from scenlib import builder, deps, evidence, render # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def _eprint(*args) -> None:
|
def _eprint(*args) -> None:
|
||||||
@@ -197,9 +197,13 @@ def cmd_build(ns: argparse.Namespace) -> int:
|
|||||||
with open(os.path.join(ns.out, name), "w", encoding="utf-8") as fh:
|
with open(os.path.join(ns.out, name), "w", encoding="utf-8") as fh:
|
||||||
json.dump(obj, fh, ensure_ascii=False, indent=2)
|
json.dump(obj, fh, ensure_ascii=False, indent=2)
|
||||||
fh.write("\n")
|
fh.write("\n")
|
||||||
|
# Prompteur Markdown lisible (dérivé PUREMENT du run-sheet · #6). Reproductible
|
||||||
|
# à l'identique (gate ci/check_artifacts.sh) car render est déterministe.
|
||||||
|
with open(os.path.join(ns.out, "run_sheet.md"), "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(render.render_markdown(run_sheet))
|
||||||
|
|
||||||
c = manifest["counts"]
|
c = manifest["counts"]
|
||||||
_eprint(f"✅ run-sheet → {ns.out}")
|
_eprint(f"✅ run-sheet → {ns.out} (+ prompteur run_sheet.md)")
|
||||||
_eprint(f" scénarios={c['scenarios']} · beats={c['beats_total']} · "
|
_eprint(f" scénarios={c['scenarios']} · beats={c['beats_total']} · "
|
||||||
f"citations={c['citations_total']} (non résolues={c['citations_non_resolues']}) · "
|
f"citations={c['citations_total']} (non résolues={c['citations_non_resolues']}) · "
|
||||||
f"modules cités={c['modules_cites_uniques']}")
|
f"modules cités={c['modules_cites_uniques']}")
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# OTO · Run-sheet démo (méta-livrable)
|
||||||
|
|
||||||
|
> Prompteur généré **automatiquement** depuis `out/run_sheet.json` (`demo_scenario_gen build`). Chaque chiffre est une **preuve re-résolue** depuis un hand-off `out/` d'un module gaté — jamais saisi à la main (anti-invention · CLAUDE.md #6). **Ne pas éditer à la main** : régénérer.
|
||||||
|
|
||||||
|
- **Roadmap** : 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md · Sprint 7 l.68 « CRM + Faisabilité : Scénarios démo (P07 pitch banquier · P05 client ready) »
|
||||||
|
- **Version du spec** : 1.0.0
|
||||||
|
- **2 scénario(s)** · durée cumulée 24 min
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P07 Aqua Terra — Pitch banquier (bancabilité & gouvernance)
|
||||||
|
|
||||||
|
- **Scénario** : `S-P07-BANQUIER` · **audience** : banquier · **projet** : P07 — Aqua Terra Las Terrenas
|
||||||
|
- **Durée** : 12 min · 6 temps forts
|
||||||
|
- **Objectif** : Démontrer qu'un dossier de vente OTO est bancable : pipeline structuré, conformité fiscale e-CF DGII, incitation CONFOTUR, gouvernance RBAC et gate qualité 4Big.
|
||||||
|
|
||||||
|
### 1. Un pipeline de vente verrouillé de bout en bout · 2 min
|
||||||
|
|
||||||
|
> Ouvrir sur la traçabilité : la vente n'est pas un tableur, c'est un workflow ERPNext soumis (docstatus) piloté par rôle.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Nom du pipeline | OTO Vente Pipeline | `crm/workflow_vente` · `out/workflow.json#/0/workflow_name` |
|
||||||
|
| DocType piloté | OTO Dossier Vente | `crm/workflow_vente` · `out/workflow.json#/0/document_type` |
|
||||||
|
|
||||||
|
### 2. Facturation fiscale conforme DGII (e-CF · Compupar) · 2 min
|
||||||
|
|
||||||
|
> Rassurer sur le risque fiscal : chaque encaissement est un e-CF certifié, pas une facture maison.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Fournisseur e-CF | Compupar | `fiscal/ecf_dgii` · `out/MANIFEST.json#/provider` |
|
||||||
|
| Types e-CF en périmètre | 5 | `fiscal/ecf_dgii` · `out/MANIFEST.json#/counts/tipos_en_scope` |
|
||||||
|
|
||||||
|
### 3. Incitation CONFOTUR dé-risquée et outillée · 2 min
|
||||||
|
|
||||||
|
> Montrer l'avantage fiscal touristique comme un actif documenté : DocType soumis, dépôt outillé.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| DocType CONFOTUR | CONFOTUR Application | `legal/confotur` · `out/MANIFEST.json#/doctype_name` |
|
||||||
|
| Soumissible (verrou légal) | oui | `legal/confotur` · `out/MANIFEST.json#/is_submittable` |
|
||||||
|
| Étapes de dépôt outillées | 2 | `legal/confotur` · `out/MANIFEST.json#/counts/depot_events` |
|
||||||
|
|
||||||
|
### 4. Séparation des pouvoirs (RBAC 50 rôles) · 2 min
|
||||||
|
|
||||||
|
> Répondre à la question d'audit du banquier : qui peut faire quoi ? Permissions dérivées, pas ad hoc.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Rôles RBAC | 50 | `rbac/apply_plan` · `out/MANIFEST.json#/counts/roles` |
|
||||||
|
| Permissions DocType (Custom DocPerm) | 116 | `rbac/apply_plan` · `out/MANIFEST.json#/counts/custom_docperm` |
|
||||||
|
|
||||||
|
### 5. Un gate qualité 4Big, pas une promesse · 2 min
|
||||||
|
|
||||||
|
> Clore la confiance : la plateforme s'auto-note et bloque sous 95/100 sur 100 % des livrables.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Seuil bloquant 4Big | 95 | `qa/audit_4big` · `out/quality_report.json#/pass_score` |
|
||||||
|
| Modules sous gate qualité | 22 | `qa/audit_4big` · `out/quality_report.json#/coverage/ci_modules_count` |
|
||||||
|
|
||||||
|
### 6. Le dossier de vente, pièce bancable finale · 2 min
|
||||||
|
|
||||||
|
> Refermer sur l'objet concret que le banquier instruira : un DocType riche et soumis.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Champs du dossier de vente | 30 | `crm/dossier_vente` · `out/MANIFEST.json#/counts/fields` |
|
||||||
|
| États de pipeline couverts | 9 | `crm/dossier_vente` · `out/MANIFEST.json#/counts/pipeline_states` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P05 Las Colinas Najayo Arriba — Parcours client (réservation prête)
|
||||||
|
|
||||||
|
- **Scénario** : `S-P05-CLIENT` · **audience** : client · **projet** : P05 — Las Colinas Najayo Arriba
|
||||||
|
- **Durée** : 12 min · 6 temps forts
|
||||||
|
- **Objectif** : Faire vivre le parcours d'un acheteur : découverte du portail, assistance OTOIA, pipeline lisible, dossier transparent, et comment le client nous a trouvés (SEO trilingue).
|
||||||
|
|
||||||
|
### 1. « Choisir mon unité » — 5 portails luxury · 2 min
|
||||||
|
|
||||||
|
> Poser l'expérience d'entrée : un portail par rôle, cohérent et couvert.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Portails (workspaces) | 5 | `frontend/portails` · `out/MANIFEST.json#/counts/workspaces` |
|
||||||
|
| Rôles couverts par les portails | 44 | `frontend/portails` · `out/MANIFEST.json#/counts/roles_couverts` |
|
||||||
|
|
||||||
|
### 2. OTOIA répond dans le portail · 2 min
|
||||||
|
|
||||||
|
> Montrer l'assistance embarquée : le client n'est jamais seul face à l'écran.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Assistant embarqué | Chat OTOIA embarqué par portail | `frontend/chat_otoia` · `out/MANIFEST.json#/deliverable` |
|
||||||
|
|
||||||
|
### 3. Du premier contact à la réservation · 2 min
|
||||||
|
|
||||||
|
> Dérouler le pipeline côté client : Lead → … → Réservation confirmée, lisible et rassurant.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Première étape | Lead | `crm/workflow_vente` · `out/workflow.json#/0/states/0/state` |
|
||||||
|
| Étape réservation | Réservation confirmée | `crm/workflow_vente` · `out/workflow.json#/0/states/3/state` |
|
||||||
|
|
||||||
|
### 4. Un dossier de vente transparent · 2 min
|
||||||
|
|
||||||
|
> Donner confiance : tout est consigné dans un dossier structuré que le client peut suivre.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Champs du dossier de vente | 30 | `crm/dossier_vente` · `out/MANIFEST.json#/counts/fields` |
|
||||||
|
|
||||||
|
### 5. Un conseiller rémunéré à la transparence · 2 min
|
||||||
|
|
||||||
|
> Aligner l'intérêt du conseiller et du client : barème de commissions traçable par événement.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Barème de commissions | OTO Barème Commissions Ventes | `crm/commissions` · `out/MANIFEST.json#/bareme_name` |
|
||||||
|
| Événements déclencheurs | 5 | `crm/commissions` · `out/MANIFEST.json#/counts/evenements` |
|
||||||
|
|
||||||
|
### 6. Comment le client nous a trouvés (SEO trilingue) · 2 min
|
||||||
|
|
||||||
|
> Refermer la boucle acquisition : indexation FR/EN/ES, le parcours commence bien avant le portail.
|
||||||
|
|
||||||
|
| À dire | Chiffre | Source (preuve) |
|
||||||
|
|---|---|---|
|
||||||
|
| Mots-clés indexés | 258 | `seo` · `out/MANIFEST.json#/counts/keywords_total` |
|
||||||
|
| Langues | fr, en, es | `seo` · `out/MANIFEST.json#/langs` |
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
"""Rendu Markdown du run-sheet démo — le **prompteur** lisible par un présentateur.
|
||||||
|
|
||||||
|
Transforme PUREMENT le `run_sheet.json` (déjà résolu, déjà anti-inventé) en un
|
||||||
|
script de pitch lisible à voix haute. Contraintes :
|
||||||
|
|
||||||
|
- **Zéro donnée nouvelle (CLAUDE.md #6)** : ce module ne lit QUE le run-sheet ;
|
||||||
|
chaque chiffre du Markdown provient d'une citation `evidence.valeur` déjà
|
||||||
|
résolue depuis un artefact amont. Aucune valeur n'est saisie, calculée ni
|
||||||
|
reformatée en un autre nombre.
|
||||||
|
- **Déterministe** : mêmes entrées ⇒ mêmes octets (aucun horodatage implicite ;
|
||||||
|
`generated_at` est celui, explicite, du run-sheet). C'est ce qui permet au
|
||||||
|
gate `ci/check_artifacts.sh` de prouver la reproductibilité de `out/run_sheet.md`.
|
||||||
|
|
||||||
|
Le rendu final riche (deck, page démo `otov7.com`) reste côté VPS (#8) ; ce
|
||||||
|
prompteur Markdown est le support **texte** in-repo que ce rendu habillera.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from . import evidence
|
||||||
|
|
||||||
|
|
||||||
|
def _fmt(valeur) -> str:
|
||||||
|
"""Formate une valeur de citation pour l'oral, sans jamais inventer.
|
||||||
|
|
||||||
|
Un placeholder non résolu (ne devrait pas survivre aux invariants, mais on
|
||||||
|
reste défensif) est rendu verbatim entre back-ticks — jamais promu en chiffre.
|
||||||
|
"""
|
||||||
|
if isinstance(valeur, bool):
|
||||||
|
return "oui" if valeur else "non"
|
||||||
|
if isinstance(valeur, list):
|
||||||
|
return ", ".join(_fmt(v) for v in valeur)
|
||||||
|
if valeur is None:
|
||||||
|
return "`—`"
|
||||||
|
if evidence.is_placeholder(valeur):
|
||||||
|
return f"`{valeur}`"
|
||||||
|
return str(valeur)
|
||||||
|
|
||||||
|
|
||||||
|
def _scenario_md(sc: dict) -> list[str]:
|
||||||
|
lignes: list[str] = []
|
||||||
|
lignes.append(f"## {sc['titre']}")
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append(
|
||||||
|
f"- **Scénario** : `{sc['id']}` · **audience** : {sc['audience']} · "
|
||||||
|
f"**projet** : {sc['projet']} — {sc['projet_libelle']}"
|
||||||
|
)
|
||||||
|
lignes.append(f"- **Durée** : {sc['duree_min']} min · {len(sc['beats'])} temps forts")
|
||||||
|
lignes.append(f"- **Objectif** : {sc['objectif']}")
|
||||||
|
lignes.append("")
|
||||||
|
for b in sc["beats"]:
|
||||||
|
lignes.append(f"### {b['ordre']}. {b['titre']} · {b['duree_min']} min")
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append(f"> {b['role_narratif']}")
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append("| À dire | Chiffre | Source (preuve) |")
|
||||||
|
lignes.append("|---|---|---|")
|
||||||
|
for c in b["evidence"]:
|
||||||
|
src = f"`{c['module']}` · `{c['file']}#{c['pointer']}`"
|
||||||
|
lignes.append(f"| {c['label']} | {_fmt(c['valeur'])} | {src} |")
|
||||||
|
lignes.append("")
|
||||||
|
return lignes
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(run_sheet: dict) -> str:
|
||||||
|
"""Run-sheet dict → prompteur Markdown (chaîne terminée par un saut de ligne)."""
|
||||||
|
lignes: list[str] = []
|
||||||
|
lignes.append(f"# {run_sheet['doc']}")
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append(
|
||||||
|
"> Prompteur généré **automatiquement** depuis `out/run_sheet.json` "
|
||||||
|
"(`demo_scenario_gen build`). Chaque chiffre est une **preuve re-résolue** "
|
||||||
|
"depuis un hand-off `out/` d'un module gaté — jamais saisi à la main "
|
||||||
|
"(anti-invention · CLAUDE.md #6). **Ne pas éditer à la main** : régénérer."
|
||||||
|
)
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append(f"- **Roadmap** : {run_sheet['roadmap_ref']}")
|
||||||
|
lignes.append(f"- **Version du spec** : {run_sheet['spec_version']}")
|
||||||
|
total = sum(sc["duree_min"] for sc in run_sheet["scenarios"])
|
||||||
|
lignes.append(
|
||||||
|
f"- **{len(run_sheet['scenarios'])} scénario(s)** · durée cumulée {total} min"
|
||||||
|
)
|
||||||
|
if run_sheet.get("generated_at"):
|
||||||
|
lignes.append(f"- **Généré le** : {run_sheet['generated_at']}")
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append("---")
|
||||||
|
lignes.append("")
|
||||||
|
for i, sc in enumerate(run_sheet["scenarios"]):
|
||||||
|
lignes += _scenario_md(sc)
|
||||||
|
if i < len(run_sheet["scenarios"]) - 1:
|
||||||
|
lignes.append("---")
|
||||||
|
lignes.append("")
|
||||||
|
return "\n".join(lignes).rstrip("\n") + "\n"
|
||||||
@@ -19,7 +19,7 @@ _MOD = os.path.normpath(os.path.join(_HERE, ".."))
|
|||||||
sys.path.insert(0, _MOD)
|
sys.path.insert(0, _MOD)
|
||||||
|
|
||||||
import demo_scenario_gen as gen # noqa: E402
|
import demo_scenario_gen as gen # noqa: E402
|
||||||
from scenlib import builder, evidence # noqa: E402
|
from scenlib import builder, evidence, render # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def _fresh():
|
def _fresh():
|
||||||
@@ -214,6 +214,51 @@ class TestInjectionsNegatives(unittest.TestCase):
|
|||||||
self.assertTrue(gen.deps.validate(mf, gen._load_schema()))
|
self.assertTrue(gen.deps.validate(mf, gen._load_schema()))
|
||||||
|
|
||||||
|
|
||||||
|
class TestRenderMarkdown(unittest.TestCase):
|
||||||
|
"""Le prompteur Markdown : déterministe et sans donnée nouvelle (#6)."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
_, self.run_sheet, _ = _fresh()
|
||||||
|
self.md = render.render_markdown(self.run_sheet)
|
||||||
|
|
||||||
|
def test_deterministe(self):
|
||||||
|
self.assertEqual(self.md, render.render_markdown(self.run_sheet))
|
||||||
|
|
||||||
|
def test_termine_par_un_saut_de_ligne(self):
|
||||||
|
self.assertTrue(self.md.endswith("\n"))
|
||||||
|
self.assertFalse(self.md.endswith("\n\n"))
|
||||||
|
|
||||||
|
def test_titres_scenarios_presents(self):
|
||||||
|
for sc in self.run_sheet["scenarios"]:
|
||||||
|
self.assertIn(sc["titre"], self.md)
|
||||||
|
self.assertIn(sc["id"], self.md)
|
||||||
|
|
||||||
|
def test_chaque_beat_rendu(self):
|
||||||
|
for sc in self.run_sheet["scenarios"]:
|
||||||
|
for b in sc["beats"]:
|
||||||
|
self.assertIn(b["titre"], self.md)
|
||||||
|
self.assertIn(b["role_narratif"], self.md)
|
||||||
|
|
||||||
|
def test_valeurs_proviennent_du_run_sheet(self):
|
||||||
|
# Anti-invention : chaque valeur affichée est celle, formatée, d'une
|
||||||
|
# citation — jamais un chiffre nouveau. On vérifie la présence verbatim.
|
||||||
|
for sc in self.run_sheet["scenarios"]:
|
||||||
|
for b in sc["beats"]:
|
||||||
|
for c in b["evidence"]:
|
||||||
|
self.assertIn(render._fmt(c["valeur"]), self.md)
|
||||||
|
|
||||||
|
def test_fmt_bool_liste_none(self):
|
||||||
|
self.assertEqual(render._fmt(True), "oui")
|
||||||
|
self.assertEqual(render._fmt(False), "non")
|
||||||
|
self.assertEqual(render._fmt(["fr", "en"]), "fr, en")
|
||||||
|
self.assertEqual(render._fmt(50), "50")
|
||||||
|
self.assertEqual(render._fmt(None), "`—`")
|
||||||
|
|
||||||
|
def test_placeholder_non_promu_en_chiffre(self):
|
||||||
|
# Un placeholder survivant est rendu entre back-ticks, jamais nu.
|
||||||
|
self.assertEqual(render._fmt("{{m:f#/p}}"), "`{{m:f#/p}}`")
|
||||||
|
|
||||||
|
|
||||||
class TestCLI(unittest.TestCase):
|
class TestCLI(unittest.TestCase):
|
||||||
def test_validate_retourne_zero(self):
|
def test_validate_retourne_zero(self):
|
||||||
self.assertEqual(gen.main(["validate"]), 0)
|
self.assertEqual(gen.main(["validate"]), 0)
|
||||||
@@ -222,7 +267,7 @@ class TestCLI(unittest.TestCase):
|
|||||||
import tempfile
|
import tempfile
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
self.assertEqual(gen.main(["build", "-o", d]), 0)
|
self.assertEqual(gen.main(["build", "-o", d]), 0)
|
||||||
for name in ("run_sheet.json", "MANIFEST.json"):
|
for name in ("run_sheet.json", "MANIFEST.json", "run_sheet.md"):
|
||||||
self.assertTrue(os.path.isfile(os.path.join(d, name)))
|
self.assertTrue(os.path.isfile(os.path.join(d, name)))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ chiffrée dans le catalogue de confirmations.
|
|||||||
| 2 | vps | DocTypes custom | `crm/dossier_vente`, `legal/confotur` | 1 |
|
| 2 | vps | DocTypes custom | `crm/dossier_vente`, `legal/confotur` | 1 |
|
||||||
| 3 | worker+vps | RBAC (Role + DocPerm + UP + Role Profile) | `rbac/fixtures_gen`, `rbac/userperm_gen`, `rbac/roleprofile_gen`, `rbac/apply_plan` | 2 |
|
| 3 | worker+vps | RBAC (Role + DocPerm + UP + Role Profile) | `rbac/fixtures_gen`, `rbac/userperm_gen`, `rbac/roleprofile_gen`, `rbac/apply_plan` | 2 |
|
||||||
| 4 | worker+vps | Workflow vente + règles métier | `crm/workflow_vente`, `crm/commissions`, `fiscal/ecf_dgii` | 2, 3 |
|
| 4 | worker+vps | Workflow vente + règles métier | `crm/workflow_vente`, `crm/commissions`, `fiscal/ecf_dgii` | 2, 3 |
|
||||||
| 5 | worker+vps | Frontend desk (Workspaces + chat OTOIA) | `frontend/portails`, `frontend/chat_otoia` | 3 |
|
| 5 | worker+vps | Frontend desk (Workspaces + chat OTOIA) + config mobile | `frontend/portails`, `frontend/chat_otoia`, `mobile/app_config` | 3 |
|
||||||
| 6 | worker+vps | Contenu & publication | `publiciste`, `faisabilite/generator`, `faisabilite/bancable`, `seo`, `demo/scenarios` | 4, 5 |
|
| 6 | worker+vps | Contenu & publication | `publiciste`, `faisabilite/generator`, `faisabilite/bancable`, `seo`, `demo/scenarios` | 4, 5 |
|
||||||
| 7 | vps | Vérification QA post-déploiement | `qa/audit_5d`, `qa/audit_4big`, `qa/regression` | 4, 5, 6 |
|
| 7 | vps | Vérification QA post-déploiement | `qa/acceptance`, `qa/audit_5d`, `qa/audit_4big`, `qa/regression` | 4, 5, 6 |
|
||||||
|
|
||||||
**Confirmations préalables VPS** (consolidées, sourcées) : `custom_modules`,
|
**Confirmations préalables VPS** (consolidées, sourcées) : `custom_modules`,
|
||||||
`custom_doctypes`, `taux_commission` (Direction · audit_5d D1.1), `rnc_emisor`
|
`custom_doctypes`, `taux_commission` (Direction · audit_5d D1.1), `rnc_emisor`
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
},
|
},
|
||||||
"moneda": {
|
"moneda": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["tipo_moneda_field", "options", "tipo_cambio", "note_cambio", "source", "a_confirmer"],
|
"required": ["tipo_moneda_field", "options", "options_source", "tipo_cambio", "note_cambio", "source", "a_confirmer"],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"tipo_moneda_field": { "type": "string" },
|
"tipo_moneda_field": { "type": "string" },
|
||||||
@@ -158,6 +158,7 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "type": "string" }
|
"items": { "type": "string" }
|
||||||
},
|
},
|
||||||
|
"options_source": { "type": "string" },
|
||||||
"tipo_cambio": { "type": ["number", "string", "null"] },
|
"tipo_cambio": { "type": ["number", "string", "null"] },
|
||||||
"note_cambio": { "type": "string" },
|
"note_cambio": { "type": "string" },
|
||||||
"source": { "type": ["string", "null"] },
|
"source": { "type": ["string", "null"] },
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"moneda": {
|
"moneda": {
|
||||||
"tipo_moneda_field": "devise",
|
"tipo_moneda_field": "devise",
|
||||||
"options": ["USD", "DOP"],
|
"options": ["USD", "DOP"],
|
||||||
|
"options_source": "CLAUDE.md #10 — devises **USD + DOP** (source unique · jamais inventé · #6). Distinct de `source` (taux TipoCambio) qui reste null/a_confirmer.",
|
||||||
"tipo_cambio": null,
|
"tipo_cambio": null,
|
||||||
"note_cambio": "Un e-CF libelle en USD exige un TipoCambio (DOP/USD) SOURCE (taux du jour BCRD/DGII) — jamais fabrique (#6). En DOP, TipoCambio non requis.",
|
"note_cambio": "Un e-CF libelle en USD exige un TipoCambio (DOP/USD) SOURCE (taux du jour BCRD/DGII) — jamais fabrique (#6). En DOP, TipoCambio non requis.",
|
||||||
"source": null,
|
"source": null,
|
||||||
|
|||||||
@@ -131,6 +131,7 @@
|
|||||||
"USD",
|
"USD",
|
||||||
"DOP"
|
"DOP"
|
||||||
],
|
],
|
||||||
|
"options_source": "CLAUDE.md #10 — devises **USD + DOP** (source unique · jamais inventé · #6). Distinct de `source` (taux TipoCambio) qui reste null/a_confirmer.",
|
||||||
"tipo_cambio": null,
|
"tipo_cambio": null,
|
||||||
"note_cambio": "Un e-CF libelle en USD exige un TipoCambio (DOP/USD) SOURCE (taux du jour BCRD/DGII) — jamais fabrique (#6). En DOP, TipoCambio non requis.",
|
"note_cambio": "Un e-CF libelle en USD exige un TipoCambio (DOP/USD) SOURCE (taux du jour BCRD/DGII) — jamais fabrique (#6). En DOP, TipoCambio non requis.",
|
||||||
"source": null,
|
"source": null,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ qu'aucune promesse n'est silencieusement oubliée.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 acceptance_gen.py build # écrit out/acceptance_matrix.json + out/MANIFEST.json
|
python3 acceptance_gen.py build # écrit out/acceptance_matrix.json + out/MANIFEST.json
|
||||||
python3 acceptance_gen.py validate # (re)génère en mémoire + valide (schéma + 10 invariants)
|
python3 acceptance_gen.py validate # (re)génère en mémoire + valide (schéma + 11 invariants)
|
||||||
python3 -m unittest discover -s tests -p 'test_*.py' -v
|
python3 -m unittest discover -s tests -p 'test_*.py' -v
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ python3 -m unittest discover -s tests -p 'test_*.py' -v
|
|||||||
|
|
||||||
Sortie **déterministe** (ordre du spec, listes triées, aucun horodatage).
|
Sortie **déterministe** (ordre du spec, listes triées, aucun horodatage).
|
||||||
|
|
||||||
## Invariants (10 familles)
|
## Invariants (11 familles)
|
||||||
|
|
||||||
1. ids uniques ; livrables S1..S8 + métriques M1..M7 ordonnés.
|
1. ids uniques ; livrables S1..S8 + métriques M1..M7 ordonnés.
|
||||||
2. toute preuve module est **gated** (drapeau recalculé depuis le CI).
|
2. toute preuve module est **gated** (drapeau recalculé depuis le CI).
|
||||||
@@ -68,6 +68,10 @@ Sortie **déterministe** (ordre du spec, listes triées, aucun horodatage).
|
|||||||
8. `mvp_metric` ⇒ sprint nul ; `sprint_deliverable` ⇒ sprint renseigné.
|
8. `mvp_metric` ⇒ sprint nul ; `sprint_deliverable` ⇒ sprint renseigné.
|
||||||
9. **SoD** : la matrice ne se cite jamais elle-même comme preuve.
|
9. **SoD** : la matrice ne se cite jamais elle-même comme preuve.
|
||||||
10. `roadmap_line` entier positif.
|
10. `roadmap_line` entier positif.
|
||||||
|
11. **ancrage roadmap** : chaque `roadmap_line` pointe RÉELLEMENT son bullet dans
|
||||||
|
`04_roadmap/…` (n° DELIVERABLE du sprint SX · k-ième bullet métrique), et le
|
||||||
|
compte « 8 + 7 » est **dérivé du fichier roadmap**, pas figé — une promesse
|
||||||
|
ajoutée/retirée ou des lignes décalées rougissent au lieu de dériver en silence.
|
||||||
|
|
||||||
## Hors périmètre worker (#8)
|
## Hors périmètre worker (#8)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ module tracé non gated → génération refusée.
|
|||||||
|
|
||||||
Anti-invention (#6) : les nombres des énoncés (<1h, 95/100, 7 dashboards, 2
|
Anti-invention (#6) : les nombres des énoncés (<1h, 95/100, 7 dashboards, 2
|
||||||
stores) sont des CITATIONS verbatim de la roadmap ; la matrice ne PRÉTEND aucune
|
stores) sont des CITATIONS verbatim de la roadmap ; la matrice ne PRÉTEND aucune
|
||||||
mesure de performance ; tout hors-périmètre porte une source.
|
mesure de performance ; tout hors-périmètre porte une source. Le n° de ligne
|
||||||
|
(`roadmap_line`) de chaque promesse est VÉRIFIÉ (INV11) contre la structure réelle
|
||||||
|
de la roadmap — il ne peut pas pointer à côté en silence si la roadmap est éditée.
|
||||||
|
|
||||||
Produit :
|
Produit :
|
||||||
- acceptance_matrix.json → la matrice (une ligne par promesse : preuves
|
- acceptance_matrix.json → la matrice (une ligne par promesse : preuves
|
||||||
@@ -218,6 +220,51 @@ def validate_bundle(bundle: dict, spec: dict, ci: dict, audit: dict) -> list[str
|
|||||||
if any(not isinstance(x, int) or x < 1 for x in lines):
|
if any(not isinstance(x, int) or x < 1 for x in lines):
|
||||||
errors.append(f"roadmap_line: valeur non positive {lines}.")
|
errors.append(f"roadmap_line: valeur non positive {lines}.")
|
||||||
|
|
||||||
|
# INV11 · ANCRAGE ROADMAP (traçabilité VÉRIFIÉE, pas déclarée · #6). INV10 ne
|
||||||
|
# garantit que « roadmap_line est un entier » : un n° de ligne qui pointe à
|
||||||
|
# côté (roadmap éditée, lignes décalées) reste « vert » alors qu'il cite un
|
||||||
|
# bullet qui n'existe plus. On DÉRIVE la structure réelle de la roadmap et on
|
||||||
|
# exige que chaque roadmap_line pointe RÉELLEMENT le bullet qu'il prétend :
|
||||||
|
# · nombre de bullets DELIVERABLE == nombre de livrables de sprint du spec,
|
||||||
|
# et nombre de bullets métrique == nombre de métriques (le « 8 + 7 » n'est
|
||||||
|
# plus un compte figé mais dérivé du fichier roadmap) ;
|
||||||
|
# · chaque livrable SX pointe le bullet **DELIVERABLE** du sprint SX ;
|
||||||
|
# · chaque métrique (dans l'ordre) pointe le k-ième bullet « - ✅ ».
|
||||||
|
try:
|
||||||
|
anchors = deps.parse_roadmap_anchors(spec["roadmap_ref"])
|
||||||
|
except OSError as e:
|
||||||
|
errors.append(f"roadmap: {spec.get('roadmap_ref')!r} illisible ({e}).")
|
||||||
|
else:
|
||||||
|
dmap = anchors["deliverables"]
|
||||||
|
specd = spec.get("sprint_deliverables", [])
|
||||||
|
specm = spec.get("mvp_metrics", [])
|
||||||
|
if len(dmap) != len(specd):
|
||||||
|
errors.append(
|
||||||
|
f"roadmap: {len(dmap)} bullet(s) DELIVERABLE dans la roadmap ≠ "
|
||||||
|
f"{len(specd)} livrable(s) de sprint du spec (promesse ajoutée/retirée non tracée).")
|
||||||
|
if len(anchors["metrics"]) != len(specm):
|
||||||
|
errors.append(
|
||||||
|
f"roadmap: {len(anchors['metrics'])} bullet(s) métrique dans la roadmap ≠ "
|
||||||
|
f"{len(specm)} métrique(s) du spec (promesse ajoutée/retirée non tracée).")
|
||||||
|
for d in specd:
|
||||||
|
want = dmap.get(d["sprint"])
|
||||||
|
if want is None:
|
||||||
|
errors.append(
|
||||||
|
f"roadmap: {d['id']}: aucun bullet DELIVERABLE pour le sprint {d['sprint']}.")
|
||||||
|
elif d["roadmap_line"] != want:
|
||||||
|
errors.append(
|
||||||
|
f"roadmap: {d['id']}: roadmap_line={d['roadmap_line']} ne pointe pas "
|
||||||
|
f"le DELIVERABLE de {d['sprint']} (ligne {want}).")
|
||||||
|
for k, d in enumerate(specm):
|
||||||
|
want = anchors["metrics"][k] if k < len(anchors["metrics"]) else None
|
||||||
|
if want is None:
|
||||||
|
errors.append(
|
||||||
|
f"roadmap: {d['id']}: aucun {k + 1}e bullet métrique dans la roadmap.")
|
||||||
|
elif d["roadmap_line"] != want:
|
||||||
|
errors.append(
|
||||||
|
f"roadmap: {d['id']}: roadmap_line={d['roadmap_line']} ≠ {k + 1}e bullet "
|
||||||
|
f"métrique (ligne {want}).")
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
# qa/acceptance/acclib → 05_deliverables_mvp
|
# qa/acceptance/acclib → 05_deliverables_mvp
|
||||||
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..", ".."))
|
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..", ".."))
|
||||||
|
# 05_deliverables_mvp → racine du dépôt (où vit 04_roadmap/).
|
||||||
|
_REPO_ROOT = os.path.normpath(os.path.join(_DELIVERABLES, ".."))
|
||||||
_PUB = os.path.join(_DELIVERABLES, "publiciste")
|
_PUB = os.path.join(_DELIVERABLES, "publiciste")
|
||||||
_AUDIT = os.path.join(_DELIVERABLES, "qa", "audit_4big")
|
_AUDIT = os.path.join(_DELIVERABLES, "qa", "audit_4big")
|
||||||
|
|
||||||
@@ -55,6 +58,54 @@ def audit_sprints() -> dict:
|
|||||||
return {m["path"]: m["sprint"] for m in spec["modules"]}
|
return {m["path"]: m["sprint"] for m in spec["modules"]}
|
||||||
|
|
||||||
|
|
||||||
|
# Ancres de traçabilité de la roadmap (structure, jamais des n° saisis à la main).
|
||||||
|
_SPRINT_HEAD = re.compile(r"##\s+SPRINT\s+\d+.*?·\s*(S\d+)\s*\(")
|
||||||
|
_METRIC_HEAD = re.compile(r"##\s+M[ée]triques\s+succ")
|
||||||
|
_DELIVERABLE = re.compile(r"\*\*DELIVERABLE")
|
||||||
|
_METRIC_BULLET = re.compile(r"^-\s*✅")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_roadmap_anchors(roadmap_ref: str) -> dict:
|
||||||
|
"""DÉRIVE les ancres de traçabilité depuis la roadmap réelle (#6 · jamais une
|
||||||
|
liste de n° de ligne saisie à la main).
|
||||||
|
|
||||||
|
Renvoie :
|
||||||
|
- ``deliverables`` : {sprint SX -> n° de ligne du bullet ``**DELIVERABLE**``
|
||||||
|
appartenant au sprint SX} (le SX est LU dans le titre ``## SPRINT n … · SX (…``) ;
|
||||||
|
- ``metrics`` : [n° de ligne] des bullets ``- ✅`` sous la section
|
||||||
|
``## Métriques succès MVP``, en ordre de fichier ;
|
||||||
|
- ``path`` : le chemin résolu (pour messages).
|
||||||
|
|
||||||
|
``roadmap_ref`` absolu est utilisé tel quel ; relatif est résolu depuis la
|
||||||
|
racine du dépôt. Lève ``OSError`` si la roadmap est absente/illisible — une
|
||||||
|
référence de traçabilité vers un fichier fantôme est un mensonge (red honnête)."""
|
||||||
|
path = roadmap_ref if os.path.isabs(roadmap_ref) \
|
||||||
|
else os.path.join(_REPO_ROOT, roadmap_ref)
|
||||||
|
with open(path, encoding="utf-8") as fh:
|
||||||
|
lines = fh.read().splitlines()
|
||||||
|
|
||||||
|
deliverables: dict[str, int] = {}
|
||||||
|
metrics: list[int] = []
|
||||||
|
current_sprint: str | None = None
|
||||||
|
in_metrics = False
|
||||||
|
for i, ln in enumerate(lines, 1):
|
||||||
|
mh = _SPRINT_HEAD.match(ln)
|
||||||
|
if mh:
|
||||||
|
current_sprint, in_metrics = mh.group(1), False
|
||||||
|
continue
|
||||||
|
if _METRIC_HEAD.match(ln):
|
||||||
|
current_sprint, in_metrics = None, True
|
||||||
|
continue
|
||||||
|
if ln.startswith("## "): # toute autre section clôt le contexte
|
||||||
|
current_sprint, in_metrics = None, False
|
||||||
|
continue
|
||||||
|
if current_sprint and _DELIVERABLE.search(ln):
|
||||||
|
deliverables[current_sprint] = i
|
||||||
|
if in_metrics and _METRIC_BULLET.match(ln):
|
||||||
|
metrics.append(i)
|
||||||
|
return {"deliverables": deliverables, "metrics": metrics, "path": path}
|
||||||
|
|
||||||
|
|
||||||
def artifact_exists(rel_path: str) -> bool:
|
def artifact_exists(rel_path: str) -> bool:
|
||||||
"""Vrai ssi l'artefact cité (chemin relatif à 05_deliverables_mvp) existe.
|
"""Vrai ssi l'artefact cité (chemin relatif à 05_deliverables_mvp) existe.
|
||||||
|
|
||||||
@@ -67,6 +118,7 @@ __all__ = [
|
|||||||
"validate",
|
"validate",
|
||||||
"parse_ci",
|
"parse_ci",
|
||||||
"audit_sprints",
|
"audit_sprints",
|
||||||
|
"parse_roadmap_anchors",
|
||||||
"artifact_exists",
|
"artifact_exists",
|
||||||
"DELIVERABLES_ROOT",
|
"DELIVERABLES_ROOT",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
"""Tests de la Matrice d'acceptation / traçabilité MVP (stdlib pur · zéro pip).
|
"""Tests de la Matrice d'acceptation / traçabilité MVP (stdlib pur · zéro pip).
|
||||||
|
|
||||||
Couvre : le build réel (couverture bijective vs CI Gitea + registre 4Big), la
|
Couvre : le build réel (couverture bijective vs CI Gitea + registre 4Big), la
|
||||||
conformité au schéma, les 10 familles d'invariants, le déterminisme, et une
|
conformité au schéma, les 11 familles d'invariants, le déterminisme, et une
|
||||||
batterie d'injections négatives (chaque invariant DOIT rougir quand on le viole).
|
batterie d'injections négatives (chaque invariant DOIT rougir quand on le viole).
|
||||||
|
|
||||||
Les négatifs s'appuient sur une fixture SYNTHÉTIQUE minimale (CI + registre +
|
Les négatifs s'appuient sur une fixture SYNTHÉTIQUE minimale (CI + registre +
|
||||||
spec factices) pour contrôler exactement l'écart injecté, sans dépendre de la
|
spec + roadmap factices) pour contrôler exactement l'écart injecté, sans dépendre
|
||||||
forme du dépôt réel.
|
de la forme du dépôt réel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import atexit
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -40,6 +42,29 @@ def _mini_ci(paths, self_module=_SELF) -> dict:
|
|||||||
return {"job_to_path": j2p, "gate_needs": list(j2p)}
|
return {"job_to_path": j2p, "gate_needs": list(j2p)}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Roadmap SYNTHÉTIQUE : la même structure que la vraie (## SPRINT n · SX · bullet
|
||||||
|
# **DELIVERABLE**, puis section Métriques avec bullets « - ✅ »), en miniature.
|
||||||
|
# INV11 lit ce fichier ; on contrôle donc exactement les n° de ligne attendus :
|
||||||
|
# L3 → DELIVERABLE S2 · L5 → DELIVERABLE S3 · L7 → 1er bullet métrique (M1).
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
_MINI_ROADMAP = (
|
||||||
|
"# Roadmap fixture\n" # L1
|
||||||
|
"## SPRINT 2 · Fixture · S2 (obj S1)\n" # L2
|
||||||
|
"- **DELIVERABLE** : livrable S2\n" # L3
|
||||||
|
"## SPRINT 3 · Fixture · S3 (obj S2)\n" # L4
|
||||||
|
"- **DELIVERABLE** : livrable S3\n" # L5
|
||||||
|
"## Métriques succès MVP (fixture)\n" # L6
|
||||||
|
"- ✅ métrique M1\n" # L7
|
||||||
|
)
|
||||||
|
_ROADMAP_S2, _ROADMAP_S3, _ROADMAP_M1 = 3, 5, 7
|
||||||
|
|
||||||
|
_fd, _ROADMAP_PATH = tempfile.mkstemp(suffix="_roadmap.md", prefix="acc_fixture_")
|
||||||
|
with os.fdopen(_fd, "w", encoding="utf-8") as _fh:
|
||||||
|
_fh.write(_MINI_ROADMAP)
|
||||||
|
atexit.register(lambda: os.path.exists(_ROADMAP_PATH) and os.remove(_ROADMAP_PATH))
|
||||||
|
|
||||||
|
|
||||||
def _mini_audit() -> dict:
|
def _mini_audit() -> dict:
|
||||||
return {"a/one": "S2", "b/two": "S3"}
|
return {"a/one": "S2", "b/two": "S3"}
|
||||||
|
|
||||||
@@ -48,23 +73,23 @@ def _mini_spec() -> dict:
|
|||||||
return {
|
return {
|
||||||
"version": "test",
|
"version": "test",
|
||||||
"reference_cadre": "fixture",
|
"reference_cadre": "fixture",
|
||||||
"roadmap_ref": "ROADMAP.md",
|
"roadmap_ref": _ROADMAP_PATH,
|
||||||
"self_module": _SELF,
|
"self_module": _SELF,
|
||||||
"cible_portage": "recette",
|
"cible_portage": "recette",
|
||||||
"extra_module_sprint": {},
|
"extra_module_sprint": {},
|
||||||
"sprint_deliverables": [
|
"sprint_deliverables": [
|
||||||
{"id": "S2", "kind": "sprint_deliverable", "sprint": "S2",
|
{"id": "S2", "kind": "sprint_deliverable", "sprint": "S2",
|
||||||
"roadmap_line": 39, "statement": "livrable S2", "status": "in_repo",
|
"roadmap_line": _ROADMAP_S2, "statement": "livrable S2", "status": "in_repo",
|
||||||
"evidence_modules": ["a/one"], "evidence_artifacts": [],
|
"evidence_modules": ["a/one"], "evidence_artifacts": [],
|
||||||
"out_of_scope": []},
|
"out_of_scope": []},
|
||||||
{"id": "S3", "kind": "sprint_deliverable", "sprint": "S3",
|
{"id": "S3", "kind": "sprint_deliverable", "sprint": "S3",
|
||||||
"roadmap_line": 46, "statement": "livrable S3", "status": "in_repo",
|
"roadmap_line": _ROADMAP_S3, "statement": "livrable S3", "status": "in_repo",
|
||||||
"evidence_modules": ["b/two"], "evidence_artifacts": [],
|
"evidence_modules": ["b/two"], "evidence_artifacts": [],
|
||||||
"out_of_scope": []},
|
"out_of_scope": []},
|
||||||
],
|
],
|
||||||
"mvp_metrics": [
|
"mvp_metrics": [
|
||||||
{"id": "M1", "kind": "mvp_metric", "sprint": None,
|
{"id": "M1", "kind": "mvp_metric", "sprint": None,
|
||||||
"roadmap_line": 81, "statement": "métrique M1", "status": "in_repo",
|
"roadmap_line": _ROADMAP_M1, "statement": "métrique M1", "status": "in_repo",
|
||||||
"evidence_modules": ["a/one"], "evidence_artifacts": [],
|
"evidence_modules": ["a/one"], "evidence_artifacts": [],
|
||||||
"out_of_scope": []},
|
"out_of_scope": []},
|
||||||
],
|
],
|
||||||
@@ -279,6 +304,63 @@ class TestNegativeInjections(unittest.TestCase):
|
|||||||
self.assertFalse(b["verdict"])
|
self.assertFalse(b["verdict"])
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
class TestRoadmapAnchor(unittest.TestCase):
|
||||||
|
"""INV11 · les n° de ligne `roadmap_line` sont ANCRÉS à la structure réelle
|
||||||
|
de la roadmap (traçabilité vérifiée, pas déclarée)."""
|
||||||
|
|
||||||
|
def test_parse_real_roadmap_structure(self):
|
||||||
|
# Le parseur dérive 8 livrables (S1..S8) + 7 métriques de la vraie roadmap.
|
||||||
|
spec = _spec()
|
||||||
|
a = deps.parse_roadmap_anchors(spec["roadmap_ref"])
|
||||||
|
self.assertEqual(sorted(a["deliverables"]),
|
||||||
|
[f"S{i}" for i in range(1, 9)])
|
||||||
|
self.assertEqual(len(a["metrics"]), 7)
|
||||||
|
# Chaque roadmap_line du spec réel pointe bien son bullet.
|
||||||
|
for d in spec["sprint_deliverables"]:
|
||||||
|
self.assertEqual(d["roadmap_line"], a["deliverables"][d["sprint"]],
|
||||||
|
f"{d['id']} désancré")
|
||||||
|
for k, d in enumerate(spec["mvp_metrics"]):
|
||||||
|
self.assertEqual(d["roadmap_line"], a["metrics"][k], f"{d['id']} désancré")
|
||||||
|
|
||||||
|
def test_deliverable_line_offby_fails(self):
|
||||||
|
spec = _mini_spec()
|
||||||
|
spec["sprint_deliverables"][0]["roadmap_line"] = 99 # ne pointe plus S2
|
||||||
|
self.assertTrue(any("ne pointe pas" in e for e in _mini_errors(spec)),
|
||||||
|
_mini_errors(spec))
|
||||||
|
|
||||||
|
def test_metric_line_offby_fails(self):
|
||||||
|
spec = _mini_spec()
|
||||||
|
spec["mvp_metrics"][0]["roadmap_line"] = 1 # pas le bullet métrique
|
||||||
|
self.assertTrue(any("bullet métrique" in e for e in _mini_errors(spec)),
|
||||||
|
_mini_errors(spec))
|
||||||
|
|
||||||
|
def test_wrong_sprint_owner_fails(self):
|
||||||
|
# Le livrable prétend S3 mais garde la ligne du DELIVERABLE de S2.
|
||||||
|
spec = _mini_spec()
|
||||||
|
spec["sprint_deliverables"][0]["sprint"] = "S3"
|
||||||
|
spec["sprint_deliverables"][0]["roadmap_line"] = _ROADMAP_S2
|
||||||
|
self.assertTrue(any("ne pointe pas" in e for e in _mini_errors(spec)),
|
||||||
|
_mini_errors(spec))
|
||||||
|
|
||||||
|
def test_extra_deliverable_count_mismatch_fails(self):
|
||||||
|
# Un livrable de plus que ce que contient la roadmap fixture.
|
||||||
|
spec = _mini_spec()
|
||||||
|
spec["sprint_deliverables"].append({
|
||||||
|
"id": "S4", "kind": "sprint_deliverable", "sprint": "S4",
|
||||||
|
"roadmap_line": 3, "statement": "x", "status": "in_repo",
|
||||||
|
"evidence_modules": ["a/one"], "evidence_artifacts": [], "out_of_scope": []})
|
||||||
|
errs = _mini_errors(spec)
|
||||||
|
self.assertTrue(any("bullet(s) DELIVERABLE" in e for e in errs)
|
||||||
|
or any("aucun bullet DELIVERABLE" in e for e in errs), errs)
|
||||||
|
|
||||||
|
def test_missing_roadmap_file_fails(self):
|
||||||
|
spec = _mini_spec()
|
||||||
|
spec["roadmap_ref"] = "/nonexistent/path/ROADMAP.md"
|
||||||
|
self.assertTrue(any("illisible" in e for e in _mini_errors(spec)),
|
||||||
|
_mini_errors(spec))
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
class TestCLI(unittest.TestCase):
|
class TestCLI(unittest.TestCase):
|
||||||
"""Le CLI build/validate produit les artefacts et sort proprement."""
|
"""Le CLI build/validate produit les artefacts et sort proprement."""
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ rapport indicatif.
|
|||||||
|
|
||||||
> `note = round(100 × Σ poids(critères applicables PASS) / Σ poids(applicables))`
|
> `note = round(100 × Σ poids(critères applicables PASS) / Σ poids(applicables))`
|
||||||
|
|
||||||
4. **Rend un verdict global.** `PASS` **ssi** couverture 100 % **et** les 17
|
4. **Rend un verdict global.** `PASS` **ssi** couverture 100 % **et** **tous** les
|
||||||
modules ≥ 95/100 ; sinon `FAIL`.
|
modules gated ≥ 95/100 ; sinon `FAIL`.
|
||||||
|
|
||||||
## Anti-invention (#6)
|
## Anti-invention (#6)
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ Publiciste** (zéro `pip`, runner Gitea sans réseau · CLAUDE.md #2).
|
|||||||
|
|
||||||
## Résultat courant
|
## Résultat courant
|
||||||
|
|
||||||
**Verdict `PASS`** — **17/17** modules à **100/100** (min 100), couverture
|
**Verdict `PASS`** — **22/22** modules à **100/100** (min 100), couverture
|
||||||
**100 %** des livrables gated. L'audit servira surtout de **gate anti-régression** :
|
**100 %** des livrables gated. L'audit servira surtout de **gate anti-régression** :
|
||||||
retirer des tests, casser un `MANIFEST` ou supprimer un schéma fait chuter le
|
retirer des tests, casser un `MANIFEST` ou supprimer un schéma fait chuter le
|
||||||
module sous 95 → `FAIL`.
|
module sous 95 → `FAIL`.
|
||||||
|
|||||||
@@ -774,7 +774,7 @@
|
|||||||
"criterion": "DOC",
|
"criterion": "DOC",
|
||||||
"weight": 20,
|
"weight": 20,
|
||||||
"passed": true,
|
"passed": true,
|
||||||
"evidence": "README.md (5011 octets)"
|
"evidence": "README.md (5575 octets)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"criterion": "CONTRAT",
|
"criterion": "CONTRAT",
|
||||||
@@ -786,7 +786,7 @@
|
|||||||
"criterion": "TESTS",
|
"criterion": "TESTS",
|
||||||
"weight": 25,
|
"weight": 25,
|
||||||
"passed": true,
|
"passed": true,
|
||||||
"evidence": "32 méthodes test_* dans 1 fichier(s) (seuil 8)"
|
"evidence": "39 méthodes test_* dans 1 fichier(s) (seuil 8)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"criterion": "CLI",
|
"criterion": "CLI",
|
||||||
@@ -816,7 +816,7 @@
|
|||||||
"criterion": "DOC",
|
"criterion": "DOC",
|
||||||
"weight": 20,
|
"weight": 20,
|
||||||
"passed": true,
|
"passed": true,
|
||||||
"evidence": "README.md (5414 octets)"
|
"evidence": "README.md (5468 octets)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"criterion": "CONTRAT",
|
"criterion": "CONTRAT",
|
||||||
@@ -858,7 +858,7 @@
|
|||||||
"criterion": "DOC",
|
"criterion": "DOC",
|
||||||
"weight": 20,
|
"weight": 20,
|
||||||
"passed": true,
|
"passed": true,
|
||||||
"evidence": "README.md (3714 octets)"
|
"evidence": "README.md (4061 octets)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"criterion": "CONTRAT",
|
"criterion": "CONTRAT",
|
||||||
@@ -870,7 +870,7 @@
|
|||||||
"criterion": "TESTS",
|
"criterion": "TESTS",
|
||||||
"weight": 25,
|
"weight": 25,
|
||||||
"passed": true,
|
"passed": true,
|
||||||
"evidence": "31 méthodes test_* dans 1 fichier(s) (seuil 8)"
|
"evidence": "37 méthodes test_* dans 1 fichier(s) (seuil 8)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"criterion": "CLI",
|
"criterion": "CLI",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"verdict": "PASS",
|
"verdict": "PASS",
|
||||||
"suites": 22,
|
"suites": 22,
|
||||||
"test_methods": 551,
|
"test_methods": 564,
|
||||||
"coverage_ok": true,
|
"coverage_ok": true,
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
"regression_plan.json"
|
"regression_plan.json"
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
"in_gate": true,
|
"in_gate": true,
|
||||||
"has_tests_dir": true,
|
"has_tests_dir": true,
|
||||||
"test_files": 1,
|
"test_files": 1,
|
||||||
"test_methods": 32
|
"test_methods": 39
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "devops.deploy_runbook",
|
"id": "devops.deploy_runbook",
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
"in_gate": true,
|
"in_gate": true,
|
||||||
"has_tests_dir": true,
|
"has_tests_dir": true,
|
||||||
"test_files": 1,
|
"test_files": 1,
|
||||||
"test_methods": 31
|
"test_methods": 37
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "qa.audit_4big",
|
"id": "qa.audit_4big",
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
"totals": {
|
"totals": {
|
||||||
"suites": 22,
|
"suites": 22,
|
||||||
"test_files": 22,
|
"test_files": 22,
|
||||||
"test_methods": 551,
|
"test_methods": 564,
|
||||||
"min_methods": 10,
|
"min_methods": 10,
|
||||||
"under_threshold": 0
|
"under_threshold": 0
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,9 +42,9 @@
|
|||||||
{
|
{
|
||||||
"id": "demo.scenarios",
|
"id": "demo.scenarios",
|
||||||
"path": "demo/scenarios",
|
"path": "demo/scenarios",
|
||||||
"expected_methods": 32,
|
"expected_methods": 39,
|
||||||
"ran": 32,
|
"ran": 39,
|
||||||
"passed": 32,
|
"passed": 39,
|
||||||
"failures": 0,
|
"failures": 0,
|
||||||
"errors": 0,
|
"errors": 0,
|
||||||
"skipped": 0,
|
"skipped": 0,
|
||||||
@@ -162,9 +162,9 @@
|
|||||||
{
|
{
|
||||||
"id": "qa.acceptance",
|
"id": "qa.acceptance",
|
||||||
"path": "qa/acceptance",
|
"path": "qa/acceptance",
|
||||||
"expected_methods": 31,
|
"expected_methods": 37,
|
||||||
"ran": 31,
|
"ran": 37,
|
||||||
"passed": 31,
|
"passed": 37,
|
||||||
"failures": 0,
|
"failures": 0,
|
||||||
"errors": 0,
|
"errors": 0,
|
||||||
"skipped": 0,
|
"skipped": 0,
|
||||||
@@ -272,8 +272,8 @@
|
|||||||
"suites": 22,
|
"suites": 22,
|
||||||
"green": 22,
|
"green": 22,
|
||||||
"red": 0,
|
"red": 0,
|
||||||
"ran": 551,
|
"ran": 564,
|
||||||
"passed": 551,
|
"passed": 564,
|
||||||
"failures": 0,
|
"failures": 0,
|
||||||
"errors": 0,
|
"errors": 0,
|
||||||
"skipped": 0
|
"skipped": 0
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ Transverses : [`devops`](03_agents/devops/AGENT.md) · [`qa`](03_agents/qa/AGENT
|
|||||||
|
|
||||||
## État courant (sourcé)
|
## État courant (sourcé)
|
||||||
|
|
||||||
- **Qualité 4Big** : **21/21 modules gated à 100/100** (seuil 95 · `CLAUDE.md` #5), verdict `PASS`
|
- **Qualité 4Big** : **22/22 modules gated à 100/100** (seuil 95 · `CLAUDE.md` #5), verdict `PASS`
|
||||||
— source [`qa/audit_4big/out/quality_report.json`](05_deliverables_mvp/qa/audit_4big/out/quality_report.json).
|
— source [`qa/audit_4big/out/quality_report.json`](05_deliverables_mvp/qa/audit_4big/out/quality_report.json).
|
||||||
- **Régression** : **21 suites gated** couvertes bijectivement vs CI ; le compte de tests
|
- **Régression** : **22 suites gated** couvertes bijectivement vs CI ; le compte de tests
|
||||||
agrégé faisant autorité est produit par `qa/regression run` (non commité par design)
|
agrégé faisant autorité est produit par `qa/regression run` (non commité par design)
|
||||||
— plan [`qa/regression/out/regression_plan.json`](05_deliverables_mvp/qa/regression/out/regression_plan.json).
|
— plan [`qa/regression/out/regression_plan.json`](05_deliverables_mvp/qa/regression/out/regression_plan.json).
|
||||||
- **Recette roadmap** : **15 promesses** (8 livrables sprint + 7 métriques MVP), **14 `in_repo`
|
- **Recette roadmap** : **15 promesses** (8 livrables sprint + 7 métriques MVP), **15 `in_repo`
|
||||||
prouvées + 1 `out_of_scope`** (M5 « app publiée 2 stores », hors périmètre worker · #8),
|
prouvées** (chacune traçant ses items hors-périmètre worker · #8 · ex. M5 « app publiée 2 stores »),
|
||||||
verdict `true` — source [`qa/acceptance/out/acceptance_matrix.json`](05_deliverables_mvp/qa/acceptance/out/acceptance_matrix.json).
|
verdict `true` — source [`qa/acceptance/out/acceptance_matrix.json`](05_deliverables_mvp/qa/acceptance/out/acceptance_matrix.json).
|
||||||
- **Guards** : [`ci/guard_constraints.sh`](ci/README.md) + [`ci/check_docs.sh`](ci/README.md) verts.
|
- **Guards** : [`ci/guard_constraints.sh`](ci/README.md) + [`ci/check_docs.sh`](ci/README.md) verts.
|
||||||
|
|
||||||
|
|||||||
+989
-5
File diff suppressed because one or more lines are too long
Executable
+4869
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,10 @@
|
|||||||
# Gate de FRAÎCHEUR de la matrice de régression (roadmap Sprint 8 · QA
|
# Gate de FRAÎCHEUR de la matrice de régression (roadmap Sprint 8 · QA
|
||||||
# « Regression tests exhaustifs » · CLAUDE.md #5 — zéro régression silencieuse).
|
# « Regression tests exhaustifs » · CLAUDE.md #5 — zéro régression silencieuse).
|
||||||
# Prouve, en une commande, que l'artefact d'EXÉCUTION commité
|
# Prouve, en une commande, que l'artefact d'EXÉCUTION commité
|
||||||
# `qa/regression/out/regression_run.json` (totaux « 21 suites · 534 tests ·
|
# `qa/regression/out/regression_run.json` (dont la doc et les logs dérivent
|
||||||
# verdict PASS » cité partout dans la doc et les logs) reflète TOUJOURS la
|
# leurs totaux agrégés — suites · tests · verdict PASS — jamais figés en dur
|
||||||
# réalité du disque — en le régénérant et en exigeant l'identité byte-for-byte.
|
# ici, sous peine de se périmer en silence) reflète TOUJOURS la réalité du
|
||||||
|
# disque — en le régénérant et en exigeant l'identité byte-for-byte.
|
||||||
#
|
#
|
||||||
# Pourquoi un gate DÉDIÉ (et non `check_artifacts.sh`) : ce dernier ne rejoue
|
# Pourquoi un gate DÉDIÉ (et non `check_artifacts.sh`) : ce dernier ne rejoue
|
||||||
# QUE la sous-commande déterministe `build` (→ regression_plan.json) et exclut
|
# QUE la sous-commande déterministe `build` (→ regression_plan.json) et exclut
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
# EST un artefact de `run` : il porte les compteurs réellement exécutés
|
# EST un artefact de `run` : il porte les compteurs réellement exécutés
|
||||||
# (ran/passed/failures) que `build` ne connaît pas. Résultat : sans ce gate, la
|
# (ran/passed/failures) que `build` ne connaît pas. Résultat : sans ce gate, la
|
||||||
# matrice agrégée pouvait se périmer en silence (ex. un module + son job CI
|
# matrice agrégée pouvait se périmer en silence (ex. un module + son job CI
|
||||||
# ajoutés sans régénérer la matrice → « 21 suites » devenu faux) sans qu'aucune
|
# ajoutés sans régénérer la matrice → « 22 suites » devenu faux) sans qu'aucune
|
||||||
# suite `tests/` — qui teste des FONCTIONS, pas le fichier commité — ne l'attrape.
|
# suite `tests/` — qui teste des FONCTIONS, pas le fichier commité — ne l'attrape.
|
||||||
# C'est exactement la dérive « demo 18→21 » déjà corrigée à la main : ce gate la
|
# C'est exactement la dérive « demo 18→21 » déjà corrigée à la main : ce gate la
|
||||||
# rend impossible à re-commettre.
|
# rend impossible à re-commettre.
|
||||||
|
|||||||
Reference in New Issue
Block a user