[DTP-Worker 20260802_060518] Sprint 8 · buffer · Traçabilité : ancrage de la CITATION roadmap_ref VERBATIM (l.N + libellé) des 4 MANIFESTs sur la ligne réelle de la roadmap

Gate anti-dérive #6 : demo/scenarios · frontend/chat_otoia · frontend/portails ·
mobile/app_config publient un roadmap_ref « …l.A[-B] « <bullet> » » dans
out/MANIFEST.json (byte-gaté). check_artifacts ne prouve QUE spec==MANIFEST ;
INV11 n'ancre que les roadmap_line NUMÉRIQUES de qa/acceptance. Reformuler/
renuméroter la roadmap laissait les 4 citations périmées en silence (silent-green
prouvé : reword l.63 ⇒ 6 gates verts, chat_otoia cite toujours l'ancien texte).

check_readme_claims : découvre chaque MANIFEST à citation et exige (1) fichier ==
roadmap canonique, (2) libellé normalisé ⊆ lignes A..B réelles, (3) spec ==
MANIFEST ; 4 modules connus EXIGÉS présents (anti-évaporation). 5 checks verts ·
7 morsures (M1 silent-green reword roadmap · M2 renumber · M3 drift citation ·
M4 wrong file · M5 spec≠manifest · M6 évaporation · M7 hors-plage). Suite complète
exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-02 06:15:53 +00:00
parent 0d4fd0942c
commit 215c9e6f25
2 changed files with 148 additions and 0 deletions
+48
View File
@@ -1,5 +1,53 @@
# Activity log · 2026-08-02 · Claude Code DTP Worker # Activity log · 2026-08-02 · Claude Code DTP Worker
## Sprint 8 · buffer · Traçabilité roadmap — ancrage de la CITATION `roadmap_ref` VERBATIM (n° ligne + libellé) des artefacts sur la ligne RÉELLE de la roadmap
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention · toujours
vérifier sources archives »). Le pattern « citation vérifiée, pas déclarée » (INV11
de `qa/acceptance` ancre les `roadmap_line` NUMÉRIQUES de son artefact au bullet
DELIVERABLE réel) portait un angle mort : **quatre** modules — `demo/scenarios`,
`frontend/chat_otoia`, `frontend/portails`, `mobile/app_config` — publient dans leur
`out/MANIFEST.json` (byte-gaté) un champ `roadmap_ref` de forme
`« …ROADMAP… · Sprint N l.A[-B] « <libellé bullet> » »`, où le **libellé est une
transcription VERBATIM** du bullet de la roadmap à la ligne citée. Or **aucun gate**
ne vérifiait que la ligne A[-B] porte réellement ce texte : `check_artifacts` ne
prouve QUE `spec==MANIFEST` byte-for-byte (la MÊME citation recopiée des deux côtés,
jamais contredite) ; INV11 ne touche QUE les `roadmap_line` numériques de SON
artefact ; l'ancrage Expo mobile ne teste que « roadmap » + « Expo N » dans
`expo_sdk_source`, pas la citation complète.
**Le « vert trompeur »** — RÉORDONNER la roadmap (les n° de ligne glissent) ou
REFORMULER un bullet laisse ces 4 citations pointer une ligne qui dit AUTRE CHOSE :
un agent lisant le MANIFEST pour retrouver la promesse d'origine tomberait sur le
mauvais bullet. **Prouvé par mutation** : reformuler le bullet l.63 (`Chat OTOIA
embedded…`) laissait les **6 gates VERTS** pendant que `chat_otoia/out/MANIFEST.json`
citait encore « … Chat OTOIA embedded … » à l.63. Aucune suite `tests/` (FONCTIONS
de résolution, jamais l'ancre à la roadmap) ne mord.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Traçabilité roadmap », juste
avant `sys.exit`) — DÉCOUVRE tous les `out/MANIFEST.json` (glob 1 & 2 niveaux)
portant un `roadmap_ref` citant `(l.A[-B] «…»)` et exige, par module : **(1)** le
fichier cité == la roadmap canonique ; **(2)** le libellé, NORMALISÉ (markdown
`**`/`- ` retirés · espaces compressés · NFC), est SOUS-CHAÎNE des lignes A..B
réelles (robuste à la troncature du bullet et à la plage multi-lignes `l.56-57`) ;
**(3)** le spec du module porte le MÊME `roadmap_ref` (identité spec↔artefact publié).
Bornes de ligne hors-plage attrapées (renumérotation). Les **4 modules connus** sont
EXIGÉS présents (anti-évaporation #6 : retirer un `roadmap_ref` échoue) ; tout AUTRE
MANIFEST à citation est ancré en bonus (future-proof).
**Vérif** — 5 checks verts sur l'arbre propre (4 citations ancrées + recensement
« 4 modules attendus présents »). **7 morsures** adversariales, gate lancé SEUL,
reverts par `cp` ciblé (jamais `git checkout` large — cf. incident consigné) :
**M1 SILENT-GREEN** — reformuler le bullet l.63 (AUTORITÉ), MANIFEST inchangé ⇒
libellé ABSENT (preuve que l'autorité est la ROADMAP, pas la cohérence interne de
l'artefact) · M2 renuméroter le MANIFEST `l.63→l.62` (ligne existante mais autre
bullet) ⇒ ABSENT · M3 dériver le libellé du MANIFEST lui-même ⇒ ABSENT · M4 citer
un autre fichier roadmap ⇒ fichier ≠ canonique · M5 dériver la citation du SEUL spec
⇒ spec ≠ MANIFEST (artefact incohérent) · M6 retirer `roadmap_ref` du MANIFEST ⇒
ÉVAPORÉE ['frontend/chat_otoia'] · M7 citer `l.9999` ⇒ plage hors roadmap (1..92).
Suite complète re-verte : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
## Sprint 8 · buffer · RBAC SPEC §6 — ancrage de l'ÉNUMÉRATION des DocTypes `custom` sur le catalogue du contrat (2ᵉ surface prose) ## Sprint 8 · buffer · RBAC SPEC §6 — ancrage de l'ÉNUMÉRATION des DocTypes `custom` sur le catalogue du contrat (2ᵉ surface prose)
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le **Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le
+100
View File
@@ -8457,6 +8457,106 @@ if cmi_src is not None:
bad(f"Commissions invariants · {CMI_RD} — énumération prose {cmi_enum} " bad(f"Commissions invariants · {CMI_RD} — énumération prose {cmi_enum} "
f"≠ 1..{cmi_ninv} (item sauté/dupliqué) → régénérer le README") f"≠ 1..{cmi_ninv} (item sauté/dupliqué) → régénérer le README")
# ============================================================================
# Traçabilité roadmap · le `roadmap_ref` VERBATIM des artefacts — la CITATION
# (n° de ligne + « libellé bullet ») ANCRÉE sur la ligne RÉELLE de la roadmap,
# pas seulement déclarée.
# ----------------------------------------------------------------------------
# Quatre modules (demo/scenarios · frontend/chat_otoia · frontend/portails ·
# mobile/app_config) publient dans out/MANIFEST.json (byte-gaté par
# check_artifacts) un `roadmap_ref` de la forme :
# « 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md · Sprint N l.A[-B] « <libellé bullet> » »
# Ce libellé est une TRANSCRIPTION VERBATIM du bullet de la roadmap à la ligne
# citée — mais AUCUN gate ne vérifiait que la ligne A[-B] porte RÉELLEMENT ce
# texte : check_artifacts ne prouve QUE spec==MANIFEST (byte-for-byte, la MÊME
# citation recopiée des deux côtés — l'un ne contredit jamais l'autre) ;
# l'ancrage roadmap de qa/acceptance (INV11) ne touche QUE les `roadmap_line`
# NUMÉRIQUES de SON artefact, jamais le libellé cité par les autres specs ;
# l'ancrage Expo (mobile) ne vérifie que la présence de « roadmap » + « Expo N »
# dans `expo_sdk_source`, pas la citation complète. → REORDONNER la roadmap (les
# n° de ligne glissent) ou REFORMULER un bullet laisse ces 4 citations pointer
# une ligne qui dit AUTRE CHOSE : « vert trompeur » de traçabilité (#6)
# qu'aucune suite tests/ (FONCTIONS de résolution, jamais l'ancre) n'attrape.
# Prouvé par mutation : reformuler le bullet l.63 laissait les 6 gates VERTS
# pendant que chat_otoia citait encore « … Chat OTOIA embedded … » à l.63.
# On ANCRE : pour chaque MANIFEST portant un `roadmap_ref` citant (l.A[-B] «…»),
# (1) le fichier cité == la roadmap canonique ; (2) le libellé, NORMALISÉ
# (markdown `**`/`- ` retirés, espaces compressés, NFC), est SOUS-CHAÎNE des
# lignes A..B réelles ; (3) le spec du module porte le MÊME `roadmap_ref`
# (identité spec↔artefact publié). Les 4 modules connus sont EXIGÉS présents
# (anti-évaporation #6) ; tout AUTRE MANIFEST à citation est ancré en bonus.
# ============================================================================
import glob as _glob
ROADMAP_REL = "04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md"
RMREF_EXPECTED = {"demo/scenarios", "frontend/chat_otoia",
"frontend/portails", "mobile/app_config"}
def _rmn(s): # normalise une ligne markdown pour comparaison de libellé
s = unicodedata.normalize("NFC", str(s))
s = re.sub(r"^\s*[-*+]\s+", "", s) # marqueur de liste en tête de ligne
s = s.replace("*", "") # emphase **gras** / *ital*
return re.sub(r"\s+", " ", s).strip()
try:
_rm_lines = open(ROADMAP_REL, encoding="utf-8").read().splitlines()
except OSError as e:
bad(f"Traçabilité roadmap · {ROADMAP_REL} illisible : {e}"); _rm_lines = None
if _rm_lines is not None:
_man_paths = sorted(set(_glob.glob(f"{D}/*/*/out/MANIFEST.json") +
_glob.glob(f"{D}/*/out/MANIFEST.json")))
_rm_seen = set()
for _mp in _man_paths:
_mod = _mp[len(D) + 1:].rsplit("/out/", 1)[0] # ex. « demo/scenarios »
try:
_ref = json.load(open(_mp, encoding="utf-8")).get("roadmap_ref", "") or ""
except (OSError, ValueError) as e:
bad(f"Traçabilité roadmap · {_mp} illisible : {e}"); continue
_mln = re.search(r"l\.(\d+)(?:-(\d+))?", _ref)
_mq = re.search(r"«\s*(.+?)\s*»", _ref)
if not (_mln and _mq):
continue # pas une citation ligne+libellé → hors périmètre de ce gate
_rm_seen.add(_mod)
_path = _ref.split(" · ", 1)[0].strip()
_a = int(_mln.group(1)); _b = int(_mln.group(2) or _mln.group(1))
_quote = _mq.group(1)
# (1) fichier cité == roadmap canonique
if _path != ROADMAP_REL:
bad(f"Traçabilité roadmap · {_mod} — cite « {_path} » ≠ roadmap "
f"canonique « {ROADMAP_REL} »"); continue
# bornes de ligne valides (renumérotation hors-plage attrapée ici)
if not (1 <= _a <= _b <= len(_rm_lines)):
bad(f"Traçabilité roadmap · {_mod} — plage l.{_a}-{_b} hors roadmap "
f"(1..{len(_rm_lines)})"); continue
_hay = " ".join(_rmn(_rm_lines[_i - 1]) for _i in range(_a, _b + 1))
_rng = f"l.{_a}" + ("" if _a == _b else f"-{_b}")
# (2) libellé cité == sous-chaîne des lignes réelles A..B
if _rmn(_quote) not in _hay:
bad(f"Traçabilité roadmap · {_mod} — libellé cité « {_quote} » ABSENT "
f"des lignes {_rng} de la roadmap (qui disent « {_hay} ») → "
f"REFORMULATION/RENUMÉROTATION : aligner le roadmap_ref (ou la roadmap)")
continue
# (3) spec du module porte le MÊME roadmap_ref (identité spec↔artefact publié)
_specs = _glob.glob(f"{D}/{_mod}/*_spec.json")
_spec_ok = True
if len(_specs) == 1:
try:
_sref = json.load(open(_specs[0], encoding="utf-8")).get("roadmap_ref")
except (OSError, ValueError):
_sref = None
if _sref is not None and _sref != _ref:
_spec_ok = False
bad(f"Traçabilité roadmap · {_mod} — spec roadmap_ref « {_sref} » ≠ "
f"MANIFEST « {_ref} » (artefact interne incohérent)")
if _spec_ok:
good(f"Traçabilité roadmap · {_mod} — citation {_rng} « {_quote} » "
f"== roadmap")
# anti-évaporation : les 4 citations connues doivent TOUTES subsister
_rm_missing = sorted(RMREF_EXPECTED - _rm_seen)
if _rm_missing:
bad(f"Traçabilité roadmap · citation roadmap_ref ÉVAPORÉE pour {_rm_missing} "
f"(module attendu sans (l.N «…») dans out/MANIFEST.json · #6)")
elif _rm_seen:
good(f"Traçabilité roadmap · {len(_rm_seen)} citation(s) ancrée(s), "
f"4 modules attendus présents")
sys.exit(1 if FAIL else 0) sys.exit(1 if FAIL else 0)
PY PY
rc=$? rc=$?