From 9d30939db9b38802b1dec0b40faecb0badde3541 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Wed, 5 Aug 2026 16:28:19 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260805=5F161319]=20Auto=20exec?= =?UTF-8?q?=20=C2=B7=20session=2020260805=5F161319?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 03_agents/qa/AGENT.md | 7 +- 05_deliverables_mvp/qa/regression/README.md | 11 ++- .../qa/regression/out/regression_run.json | 68 +++++++++---------- .../qa/regression/reglib/builder.py | 7 +- .../qa/regression/reglib/runner.py | 19 +++++- .../qa/regression/regression_gen.py | 9 ++- ci/check_regression.sh | 7 +- 7 files changed, 84 insertions(+), 44 deletions(-) diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index 9ac4507..9371e12 100644 --- a/03_agents/qa/AGENT.md +++ b/03_agents/qa/AGENT.md @@ -32,9 +32,14 @@ périmètre — un auditeur ne s'audite jamais. **conformité** normative des hand-off. ## Verdict agrégé courant (source `qa/regression/out/regression_run.json`) -**24 suites gated · 624 tests · 624 passés · 0 échec · 0 erreur → verdict `PASS`.** +**24 suites gated · 624 tests · 607 passés · 0 échec · 0 erreur → verdict `PASS`.** Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`, réutilisé sans duplication) — jamais compté à la main. +Les **17 tests restants sont ignorés** (`skipped`) : ce sont les *oracles optionnels* +`jsonschema` (`skipUnless`/`try…except ImportError`), absents du runner Gitea pip-less +(« stdlib pur, zéro pip »). La matrice est désormais exécutée sous `python -S` +(sans `site-packages`) → byte-identique quel que soit ce qui est `pip install`é +localement ; les validateurs maison couvrent les mêmes contrats que ces oracles. ## Deuxième étage QA : la batterie de gates statiques (`ci/*.sh`) Au-dessus des 4 modules d'audit ci-dessus, le mandat exécute une couche de **gates diff --git a/05_deliverables_mvp/qa/regression/README.md b/05_deliverables_mvp/qa/regression/README.md index 3dff79b..e1af09a 100644 --- a/05_deliverables_mvp/qa/regression/README.md +++ b/05_deliverables_mvp/qa/regression/README.md @@ -51,9 +51,16 @@ python3 regression_gen.py run # → out/regression_run.json (COMMIT ``` `run` renvoie un code de sortie ≠ 0 si **une seule** suite est rouge → utilisable -comme garde de release. Sa sortie est **byte-déterministe** — elle ne porte +comme garde de release. Sa sortie est **byte-déterministe** : (a) elle ne porte **aucun** horodatage / hôte / durée / chemin absolu (`path` relatif, compteurs -`ran/passed/failures` seuls) : deux `run` successifs sont **byte-identiques**. +`ran/passed/failures` seuls) ; (b) chaque suite est exécutée sous `python -S` +(sans `site-packages`), donc l'unique paquet tiers du corpus — l'oracle +**optionnel** `jsonschema` (toujours gardé par `skipUnless`) — est neutralisé, +exactement comme sur le runner Gitea pip-less. Sans (b), la matrice divergeait +selon que `jsonschema` était `pip install`é ou non (0 vs 17 `skipped`) → un +**faux-vert** : `check_regression` vert en local mais ROUGE sur le runner. Avec +les deux, deux `run` sont **byte-identiques** quel que soit l'environnement +(17 oracles ignorés ; les validateurs maison couvrent les mêmes contrats). `out/regression_run.json` est donc **commité** et sert de **baseline** au gate CI `ci/check_regression.sh`, qui régénère un `run` frais et exige l'**identité byte-for-byte** avec le fichier commité (+ verdict `PASS`) — c'est ce qui empêche diff --git a/05_deliverables_mvp/qa/regression/out/regression_run.json b/05_deliverables_mvp/qa/regression/out/regression_run.json index 3066f2b..d0cb8b3 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_run.json +++ b/05_deliverables_mvp/qa/regression/out/regression_run.json @@ -8,10 +8,10 @@ "path": "crm/commissions", "expected_methods": 25, "ran": 25, - "passed": 25, + "passed": 24, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -20,10 +20,10 @@ "path": "crm/dossier_vente", "expected_methods": 31, "ran": 31, - "passed": 31, + "passed": 30, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -44,10 +44,10 @@ "path": "crm/workflow_vente", "expected_methods": 25, "ran": 25, - "passed": 25, + "passed": 24, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -80,10 +80,10 @@ "path": "faisabilite/bancable", "expected_methods": 22, "ran": 22, - "passed": 22, + "passed": 21, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -92,10 +92,10 @@ "path": "faisabilite/generator", "expected_methods": 16, "ran": 16, - "passed": 16, + "passed": 15, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -104,10 +104,10 @@ "path": "fiscal/ecf_dgii", "expected_methods": 39, "ran": 39, - "passed": 39, + "passed": 38, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -128,10 +128,10 @@ "path": "frontend/portails", "expected_methods": 19, "ran": 19, - "passed": 19, + "passed": 18, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -140,10 +140,10 @@ "path": "legal/confotur", "expected_methods": 44, "ran": 44, - "passed": 44, + "passed": 43, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -164,10 +164,10 @@ "path": "pie/manifest", "expected_methods": 20, "ran": 20, - "passed": 20, + "passed": 19, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -176,10 +176,10 @@ "path": "publiciste", "expected_methods": 23, "ran": 23, - "passed": 23, + "passed": 21, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 2, "ok": true, "returncode": 0 }, @@ -224,10 +224,10 @@ "path": "rbac", "expected_methods": 10, "ran": 10, - "passed": 10, + "passed": 9, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -236,10 +236,10 @@ "path": "rbac/apply_plan", "expected_methods": 16, "ran": 16, - "passed": 16, + "passed": 15, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -248,10 +248,10 @@ "path": "rbac/fixtures_gen", "expected_methods": 11, "ran": 11, - "passed": 11, + "passed": 10, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -260,10 +260,10 @@ "path": "rbac/roleprofile_gen", "expected_methods": 11, "ran": 11, - "passed": 11, + "passed": 10, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -272,10 +272,10 @@ "path": "rbac/userperm_gen", "expected_methods": 12, "ran": 12, - "passed": 12, + "passed": 11, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 }, @@ -284,10 +284,10 @@ "path": "seo", "expected_methods": 36, "ran": 36, - "passed": 36, + "passed": 35, "failures": 0, "errors": 0, - "skipped": 0, + "skipped": 1, "ok": true, "returncode": 0 } @@ -297,10 +297,10 @@ "green": 24, "red": 0, "ran": 624, - "passed": 624, + "passed": 607, "failures": 0, "errors": 0, - "skipped": 0 + "skipped": 17 }, "verdict": "PASS" } diff --git a/05_deliverables_mvp/qa/regression/reglib/builder.py b/05_deliverables_mvp/qa/regression/reglib/builder.py index e08a0a2..d9a0a9e 100644 --- a/05_deliverables_mvp/qa/regression/reglib/builder.py +++ b/05_deliverables_mvp/qa/regression/reglib/builder.py @@ -69,8 +69,11 @@ def build_plan(spec: dict[str, Any]) -> dict[str, Any]: def run_matrix(spec: dict[str, Any], *, python: str | None = None) -> dict[str, Any]: """Exécute réellement toutes les suites du plan et agrège les compteurs. - Byte-déterministe (aucun horodatage/hôte/chemin absolu) → l'artefact - `regression_run.json` EST commité et byte-gaté par `ci/check_regression.sh`.""" + Byte-déterministe (aucun horodatage/hôte/chemin absolu ; suites lancées sous + `python -S` → oracle tiers optionnel `jsonschema` neutralisé, cf. + `runner.run_suite`) → l'artefact `regression_run.json` EST commité et + byte-gaté par `ci/check_regression.sh`, identique en local et sur le runner + Gitea pip-less.""" suites = discovery.discover_suites(spec) discover_cmd = list(spec["discover_cmd"]) # `python3 -m unittest ...` → on retire le binaire, on garde les args. diff --git a/05_deliverables_mvp/qa/regression/reglib/runner.py b/05_deliverables_mvp/qa/regression/reglib/runner.py index 98d70cd..4dfe684 100644 --- a/05_deliverables_mvp/qa/regression/reglib/runner.py +++ b/05_deliverables_mvp/qa/regression/reglib/runner.py @@ -10,6 +10,19 @@ des fichiers `tests/test_*.py` aux noms qui se recouvrent → un discover global in-process provoquerait des collisions de modules. Le subprocess reproduit fidèlement l'environnement du CI. +Déterminisme cross-environnement (`-S`) : chaque suite est lancée avec l'option +`-S` (interpréteur SANS `site` → aucun `site-packages`/`dist-packages` sur +`sys.path`). C'est le contrat exact du runner Gitea (`.gitea/workflows/ci.yml` : +« stdlib pur, zéro pip » — « L'oracle jsonschema est utilisé s'il est présent, +sinon ignoré »). Sans `-S`, le seul paquet tiers du corpus — l'oracle OPTIONNEL +`jsonschema`, toujours gardé par `skipUnless`/`try…except ImportError` — ferait +diverger la matrice : présent sur un poste dev (`skipped=0`), absent sur le +runner pip-less (`skipped=N`). L'artefact `regression_run.json` commité — que +`ci/check_regression.sh` exige byte-identique au run frais — devenait alors +un FAUX-VERT : vert en local, ROUGE sur le runner. `-S` neutralise l'oracle +partout → la matrice reflète la baseline garantie (stdlib + validateurs maison) +et redevient byte-identique quel que soit ce qui est `pip install`é localement. + `parse_unittest_output` est PUR (str → dict) donc testable sans exécuter quoi que ce soit — on lui injecte des sorties unittest synthétiques (OK / FAILED / skipped). """ @@ -70,7 +83,11 @@ def run_suite(abs_path: str, *, python: str | None = None, `abs_path` = répertoire du module (contenant `tests/`). Aucune sortie n'est imprimée ici — l'agrégateur décide de l'affichage.""" py = python or sys.executable - cmd = list(discover_cmd or ["-m", "unittest", "discover", "-s", "tests"]) + # `-S` : interpréteur sans `site` → pas de `site-packages` → l'oracle tiers + # optionnel `jsonschema` est absent, exactement comme sur le runner Gitea + # pip-less. Garantit une matrice byte-déterministe indépendante de ce qui est + # `pip install`é en local (cf. docstring du module). + cmd = ["-S", *list(discover_cmd or ["-m", "unittest", "discover", "-s", "tests"])] tests_dir = os.path.join(abs_path, "tests") if not os.path.isdir(tests_dir): return { diff --git a/05_deliverables_mvp/qa/regression/regression_gen.py b/05_deliverables_mvp/qa/regression/regression_gen.py index a27cb93..f3247f0 100644 --- a/05_deliverables_mvp/qa/regression/regression_gen.py +++ b/05_deliverables_mvp/qa/regression/regression_gen.py @@ -25,9 +25,12 @@ Sous-commandes : live + verdict ; sort en erreur (code ≠ 0) si une suite est rouge. Écrit `regression_run.json` : bien qu'il porte les compteurs réellement exécutés, il ne contient AUCUN - horodatage/hôte/chemin absolu → deux `run` sont - byte-identiques, donc l'artefact EST commité et byte-gaté - par `ci/check_regression.sh` (toute dérive = CI rouge). + horodatage/hôte/chemin absolu, ET chaque suite tourne sous + `python -S` (oracle tiers optionnel `jsonschema` neutralisé, + comme sur le runner pip-less · cf. `reglib/runner.py`) → + deux `run` sont byte-identiques quel que soit l'environnement, + donc l'artefact EST commité et byte-gaté par + `ci/check_regression.sh` (toute dérive = CI rouge). Toutes les sous-commandes productrices sont déterministes (tri stable, aucun horodatage) → artefacts diffables + commités + byte-gatés : `regression_plan.json` diff --git a/ci/check_regression.sh b/ci/check_regression.sh index a58ceac..5e5be09 100755 --- a/ci/check_regression.sh +++ b/ci/check_regression.sh @@ -22,7 +22,12 @@ # rend impossible à re-commettre. # # Déterminisme : `regression_run.json` ne contient AUCUN horodatage / hôte / -# chemin absolu — deux `run` successifs sont byte-identiques (vérifié). La +# chemin absolu, ET chaque suite est exécutée sous `python -S` (sans +# `site-packages`) par `reglib/runner.py` → l'oracle tiers OPTIONNEL `jsonschema` +# est neutralisé partout (comme sur le runner Gitea pip-less). Sans ce `-S`, la +# matrice divergeait selon que `jsonschema` était installé localement (0 vs 17 +# `skipped`) — un faux-vert VERT en local / ROUGE sur le runner. Avec, deux `run` +# successifs sont byte-identiques quel que soit l'environnement (vérifié). La # comparaison exacte est donc licite et constitue la preuve la plus forte. # # Contrat vérifié : `regression_gen.py run` régénéré vers un tmp DOIT produire un