[DTP-Worker] Sprint 8 · buffer L75 · Traçabilité non vérifiée : les n° de ligne roadmap_line de la matrice d'acceptation pouvaient pointer à côté en silence (roadmap éditée) → INV11 ancrage roadmap (dérive 8+7 du fichier · #6) + parse_roadmap_anchors + 6 tests ; régénéré 558→564
INV10 ne garantissait que « roadmap_line est un entier positif ». Ajout de parse_roadmap_anchors (deps) qui DÉRIVE la structure réelle de la roadmap, et d'INV11 qui exige que chaque roadmap_line pointe RÉELLEMENT son bullet (DELIVERABLE du sprint SX · k-ième bullet métrique) et que le « 8 + 7 » soit dérivé du fichier, pas figé. Morsure prouvée sur le spec réel (S1=999, M3=200). Régénéré consommateurs : regression 558→564 (run/plan/MANIFEST), quality_report (acceptance 31→37 méthodes, 100/100 inchangé), fiches QA + Backend, README acceptance (10→11 invariants). 7 gates verts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
"""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
|
||||
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).
|
||||
|
||||
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
|
||||
forme du dépôt réel.
|
||||
spec + roadmap factices) pour contrôler exactement l'écart injecté, sans dépendre
|
||||
de la forme du dépôt réel.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
_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)}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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:
|
||||
return {"a/one": "S2", "b/two": "S3"}
|
||||
|
||||
@@ -48,23 +73,23 @@ def _mini_spec() -> dict:
|
||||
return {
|
||||
"version": "test",
|
||||
"reference_cadre": "fixture",
|
||||
"roadmap_ref": "ROADMAP.md",
|
||||
"roadmap_ref": _ROADMAP_PATH,
|
||||
"self_module": _SELF,
|
||||
"cible_portage": "recette",
|
||||
"extra_module_sprint": {},
|
||||
"sprint_deliverables": [
|
||||
{"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": [],
|
||||
"out_of_scope": []},
|
||||
{"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": [],
|
||||
"out_of_scope": []},
|
||||
],
|
||||
"mvp_metrics": [
|
||||
{"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": [],
|
||||
"out_of_scope": []},
|
||||
],
|
||||
@@ -279,6 +304,63 @@ class TestNegativeInjections(unittest.TestCase):
|
||||
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):
|
||||
"""Le CLI build/validate produit les artefacts et sort proprement."""
|
||||
|
||||
Reference in New Issue
Block a user