97d3b64780
Correction de VÉRITÉ (PAS un gate #5, PAS un chiffre inventé #6).
Constat : README.md:44-46 déclarait le compte agrégé « produit par qa/regression
run (non commité par design) ». Or regression_run.json EST commité (git ls-files le
confirme) et sert de baseline byte-identique au gate ci/check_regression.sh (mécanisme
introduit par 76fb0e7) ; le README du module (qa/regression/README.md:50,57-61) ET le
commentaire du gate lui-même (ci/check_readme_claims.sh:264,269 « commité · déterministe
· compte agrégé faisant autorité ») le disent. Le README racine reflétait l'ANCIEN
design pré-76fb0e7.
Angle mort : check_readme_claims ancre le nombre « 22 suites gated » (recompté de
regression_plan.totals.suites) mais PAS le fait-en-clair « (non commité par design) » ;
check_docs ne valide que les liens. La dérive vivait dans l'interstice.
Fix : puce réécrite vers la réalité — regression_run.json désigné artefact « commité et
byte-gaté », lien ajouté ; token gaté **22 suites gated** conservé verbatim ; lien
regression_plan.json préservé ; « non commité par design » supprimé. Aucun 564 hardcodé
(pas de surface non gatée nouvelle · fidèle à « ce README n'introduit aucune donnée nouvelle »).
Vérif : run_ci.sh 30 PASS / 0 FAIL / 0 SKIP (check_docs résout le nouveau lien commité ·
check_readme_claims retrouve 22 suites gated · guard_constraints vert). Zéro gate ajouté,
zéro fichier de code touché, aucune commande VPS.
Note : dir scratch untracked 05_deliverables_mvp/frontend/portails/out_fresh/ (byte-identique
à out/ commité, référencé par aucun code) laissé en place — authorship non confirmé, non
supprimé par prudence (jamais git clean · interdit #1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1813 lines
125 KiB
Markdown
1813 lines
125 KiB
Markdown
# Activity log · 2026-08-02 · Claude Code DTP Worker
|
||
|
||
## Session `20260802_183604` · Sprint 8 · buffer · Dérive doc-vs-réalité (README racine) : la ligne « Régression » affirmait `regression_run.json` « non commité par design » — FAUX depuis la refonte baseline byte-gatée (`76fb0e7`) → correction de vérité (PAS un gate #5, PAS un chiffre inventé #6)
|
||
|
||
**Nature — dérive réelle non gatée, corrigée par la vérité (pas un gate de plus).**
|
||
Ouverture : arbre propre, `run_ci.sh` **30 PASS / 0 FAIL / 0 SKIP** (7 gates statiques +
|
||
23 suites), campagne de gates **saturée**. La file de fiches est close (13/13) et un
|
||
**audit doc-vs-réalité frais** (fan-out d'exploration sur les 13 `03_agents/*/AGENT.md`
|
||
et les README de module) n'a remonté **aucune** dérive de fiche. Plutôt que d'empiler un
|
||
gate redondant (#5, explicitement déconseillé par la mémoire *verify-uncovered-before-gating*)
|
||
ou de ré-adosser le `daily_report` (canal 2 déjà à jour — le dernier commit `3ff51d9` **était**
|
||
ce rapport), j'ai poussé l'audit vers une surface non couverte : le **README racine**, point
|
||
d'entrée du mandat.
|
||
|
||
**Constat — un fait de commit-status périmé.** `README.md:44-46` (section « État courant »,
|
||
puce **Régression**) déclarait : *« le compte de tests agrégé faisant autorité est produit
|
||
par `qa/regression run` (**non commité par design**) — plan `…/regression_plan.json` »*.
|
||
Or c'est **faux depuis la refonte** :
|
||
- Le README **du module** régression (`05_deliverables_mvp/qa/regression/README.md:50,57-61`)
|
||
énonce sans ambiguïté que `out/regression_run.json` est **COMMITÉ** et sert de **baseline
|
||
byte-identique** au gate `ci/check_regression.sh`, qui régénère un `run` frais et exige
|
||
l'**identité byte-for-byte** (+ verdict `PASS`) — mécanisme introduit par le commit
|
||
**`76fb0e7`** (« *fix gate check_regression … run baseline commité* »).
|
||
- `git ls-files 05_deliverables_mvp/qa/regression/out/` confirme les **trois** artefacts
|
||
suivis, dont `regression_run.json`.
|
||
- Le **commentaire d'en-tête du gate lui-même** (`ci/check_readme_claims.sh:264,269`) qualifie
|
||
`regression_run.json` de « **commité · déterministe** » et de « **compte agrégé faisant
|
||
autorité** ».
|
||
Le README racine reflétait donc l'**ANCIEN design** (pré-`76fb0e7`, run non commité) — dérive
|
||
classique « la doc de tête retarde d'une refonte ».
|
||
|
||
**Pourquoi aucun gate ne l'attrapait (angle mort, PAS à re-gater).** `check_readme_claims.sh`
|
||
ancre la partie **numérique** de cette puce — le motif `\*\*(\d+) suites gated\*\*` recompté
|
||
depuis `regression_plan.json::totals.suites` (=22) — mais le **fait en clair** « (non commité
|
||
par design) » est une affirmation de **statut de commit**, hors du périmètre des recomputes de
|
||
compteurs. `check_docs.sh` ne valide que les **cibles de liens** (le lien `regression_plan.json`
|
||
était valide). La dérive vivait précisément dans l'interstice entre « chiffre gaté » et « lien
|
||
valide ».
|
||
|
||
**2 solutions (workflow #4) — la bonne = corriger la doc, pas gater le statut.**
|
||
- ❌ Ajouter un gate « statut de commit de `regression_run.json` » : redondant (#5) — le fait
|
||
« commité » est déjà **structurellement** prouvé par `check_regression` (qui EXIGE le fichier
|
||
commité pour comparer byte-for-byte ; sans lui le gate échouerait). Un checker de prose serait
|
||
un doublon de la vérité déjà enforced.
|
||
- ✅ **Corriger la puce vers la réalité.** Nouvelle rédaction (`README.md:44-48`) :
|
||
*« Le **compte de tests agrégé faisant autorité** vit dans `…/regression_run.json`, artefact
|
||
**commité et byte-gaté** : `ci/check_regression.sh` régénère un `run` frais et exige l'identité
|
||
**byte-for-byte** avec la baseline commitée (verdict `PASS`). Recensement déterministe des
|
||
suites : `…/regression_plan.json`. »* — le token gaté **`**22 suites gated**`** est **conservé
|
||
verbatim** (le gate le recompte toujours), le lien `regression_plan.json` est **préservé**, un
|
||
lien vers `regression_run.json` (commité) est **ajouté**, et le faux « non commité par design »
|
||
est **supprimé**. **Aucun chiffre nouveau saisi** (#6) : je n'ai PAS hardcodé le total « 564 »
|
||
dans une surface non gatée — je désigne l'artefact commité comme source, fidèle à la promesse
|
||
du README (« ce README n'introduit aucune donnée nouvelle »).
|
||
|
||
**Vérif** (en-repo, zéro contact VPS). `run_ci.sh --static` → **7 PASS / 0 FAIL** :
|
||
`check_docs` résout le nouveau lien `regression_run.json` (fichier commité) · `check_readme_claims`
|
||
retrouve toujours `**22 suites gated**` (== `regression_plan.totals.suites`) · `guard_constraints`
|
||
vert. `run_ci.sh` complet → **30 PASS / 0 FAIL / 0 SKIP**. `git status --porcelain` = seuls
|
||
`README.md` (+ ce log) modifiés. **Zéro gate ajouté, zéro chiffre inventé, zéro fichier de code
|
||
touché, aucune commande VPS.**
|
||
|
||
## Session `20260802_180604` · Sprint 8 · buffer · Reporting stakeholder (canal 2) — rattrapage du snapshot `daily_reports/2026-08-02` sur les 3 sessions de retouches doc/outillage depuis la clôture `160601` (4ᵉ addendum sourcé · ZÉRO gate ajouté #5)
|
||
|
||
**Nature — réconciliation de reporting, PAS un gate de plus (CLAUDE.md #5).** Ouverture :
|
||
arbre propre, `run_ci.sh` **30 PASS / 0 FAIL / 0 SKIP** (7 gates statiques + 23 suites
|
||
module), file substantielle saturée. Un **audit doc-vs-réalité relancé** cette session
|
||
(fan-out d'exploration sur les 13 fiches `03_agents/*/AGENT.md`, les README de module
|
||
`05_deliverables_mvp/`, et leurs références de chemin internes) n'a remonté **aucune
|
||
dérive** : liens Markdown valides, chemins existants, capacités décrites conformes au code.
|
||
La campagne de gates reste **saturée** (mémoire *verify-uncovered-before-gating* : ajouter
|
||
un gate N+1 sans blind-spot prouvé serait redondant #5). La seule tâche à valeur
|
||
non-redondante restante est celle que le `daily_report` lui-même désigne en **« Prochaine
|
||
tâche » #1** : tenir le **canal 2** (snapshot stakeholder) à jour à chaque session buffer.
|
||
|
||
**Constat de staleness.** Le dernier addendum du `daily_report` datait de la clôture
|
||
`160601` (commit `4f390b4`). **Trois** sessions ont commité **après**, absentes de
|
||
l'instantané direction : `59eb4dc` (`163601`, fiche `devops/AGENT.md` documente
|
||
`run_ci.sh`) · `68f8c1a` (`170601`, `run_ci.sh` `-h|--help` + en-tête `--static`) ·
|
||
`d2ab269` (`173603`, `ci/README.md` retrait des comptes figés `7 gates / 23 suites`).
|
||
Les trois sont des **retouches de doc/outillage** (aucun nouveau module, aucun gate) dans
|
||
la lignée directe de `run_ci.sh` — leur trace fine existe au canal 1 (ce journal) mais
|
||
aucune synthèse consolidée sourcée côté stakeholder.
|
||
|
||
**Réalisé — 4ᵉ addendum ADDITIF** (corps `110539` + 3 addenda `120544`/`143555`/`160601`
|
||
laissés intacts — record historique préservé) consolidant les 3 sessions. Chaque chiffre
|
||
**re-vérifié par recompute `python3` indépendant sur l'artefact commité** (#6) :
|
||
- Qualité 4Big **22/22 modules** (scores distincts = {100} · seuil 95 · verdict `PASS`)
|
||
← `qa/audit_4big/out/quality_report.json` ;
|
||
- Régression **22 suites · 564 ran · 564 passés · 0 échec · 0 erreur · 0 skip** (`PASS`)
|
||
← `qa/regression/out/regression_run.json::totals` ;
|
||
- Recette **15/15** promesses `status=in_repo` (verdict `true`)
|
||
← `qa/acceptance/out/acceptance_matrix.json::matrix` ;
|
||
- Fiches **13/13** ← `03_agents/*/AGENT.md` ; Câblage CI `gate.needs` = **30 jobs**
|
||
(7 gates `ci/*.sh` + 23 suites · dérivé par `run_ci.sh --list`). État **inchangé, vert**.
|
||
|
||
**Vérif** — les gates touchant la prose/les liens restent **exit 0** APRÈS l'édition
|
||
(l'addendum ajoute des liens Markdown internes que `check_docs` valide) : `check_docs` ·
|
||
`check_readme_claims` · `guard_constraints` tous verts. `git status --porcelain` = seul le
|
||
`daily_report` (+ ce log) modifié. **Zéro gate ajouté, zéro chiffre inventé, zéro fichier
|
||
de code touché, aucune commande VPS.** Le canal 2 est de nouveau à jour (4 addenda ·
|
||
7 sessions couvertes).
|
||
|
||
## Session `20260802_173603` · Sprint 8 · buffer · DevOps doc — `ci/README.md` : retrait des comptes CI figés `7 gates / 23 suites` (même fix que `dcb463e`, un cran plus haut · anti-invention #6, PAS un gate #5)
|
||
|
||
**Contexte — le décompte figé oublié un niveau au-dessus du lanceur.** Ouverture :
|
||
arbre propre, `run_ci.sh` **30 PASS / 0 FAIL** (7 gates statiques + 23 suites de
|
||
module), file substantielle saturée (22/22 qualité à 100 · 22 suites / 564 méthodes ·
|
||
recette 15/15 `in_repo` · 13 fiches · RBAC 50) — tous re-vérifiés indépendamment en
|
||
`python3`. Le commit `dcb463e` avait **dérivé** le décompte `30/23/7` dans `run_ci.sh`
|
||
(retrait des comptes figés du **commentaire** du script) précisément parce qu'ils se
|
||
périment en silence quand un module est ajouté/retiré à `ci.yml`. Mais la **doc du
|
||
lanceur**, `ci/README.md` §« Rejouer le gate EN LOCAL », re-transcrivait toujours à la
|
||
main les MÊMES nombres : bloc d'usage L34-35 « `tout gate.needs (7 gates statiques + 23
|
||
suites de module)` » / « `seulement les 7 gates ci/*.sh` », et checklist L1601 « les **7
|
||
gates statiques** apparaissent ». Même « vert trompeur », un cran au-dessus de l'endroit
|
||
déjà corrigé.
|
||
|
||
**Vérif d'exposition (mémoire *verify-uncovered-before-gating*).** Mutation-test :
|
||
`23→24` + `7→8` sur ces deux lignes → `check_readme_claims.sh` **exit 0** et
|
||
`check_docs.sh` **exit 0** (les deux VERTS) — surface **réellement non gatée**, aucune
|
||
protection existante ne mord. Dérivation indépendante de `gate.needs` de `ci.yml`
|
||
(classification `bash ci/` == statique, celle de `run_ci.sh::is_static`) confirme
|
||
`30 = 7 gates + 23 suites`, EXACTEMENT ce que le README figeait.
|
||
|
||
**Décision — FIXER, pas gater** (mémoire *fix-vs-gate-a-transitively-protected-constant*
|
||
+ *local-ci-runner*). `ci/README.md:51-54` déclare lui-même que `run_ci.sh` « n'a pas à
|
||
être re-gaté (#5) » car son exactitude est **structurelle** ; ajouter un gate N+1 sur la
|
||
prose qui DÉCRIT ce lanceur trahirait cette posture et prendrait la mauvaise autorité.
|
||
La réponse cohérente est celle que `dcb463e` a déjà appliquée un cran plus bas :
|
||
**retirer les nombres figés**, laisser `run_ci.sh` seul propriétaire du décompte (dérivé
|
||
de `gate.needs`, imprimé à l'exécution + `--list`).
|
||
|
||
**Réalisé.**
|
||
- `ci/README.md` bloc d'usage L34-37 : « 7 gates statiques + 23 suites » → « gates
|
||
statiques `ci/*.sh` + suites de module » (qualitatif, non chiffré) ; `--list` annoté
|
||
« + décompte dérivé ».
|
||
- Ajout d'un paragraphe : le **décompte exact `N jobs = G gates + S suites` est dérivé
|
||
de `gate.needs` et imprimé à l'exécution / `--list`, jamais figé ici** (même raison #6
|
||
qui l'a retiré du commentaire de `run_ci.sh`).
|
||
- Checklist §5 L1601 : « les **7 gates statiques** » → « les gates statiques `ci/*.sh` ».
|
||
- **Intactes** : les mentions « 7 gates re-verts » du **changelog** (§ sessions de
|
||
mutation-test, L1387-1542) — narratif historique d'états passés VRAIS, pas une
|
||
affirmation de composition VIVE ; les figer serait réécrire l'histoire.
|
||
|
||
**Vérifs (en-repo, zéro contact VPS).** `run_ci.sh --list` imprime toujours «`30 jobs …
|
||
: 7 gates statiques ci/*.sh + 23 suites de module`». `run_ci.sh` complet : **30 PASS · 0
|
||
FAIL · 0 SKIP**. `grep` des surfaces vives figées → **aucune restante**. Zéro module,
|
||
zéro gate ajouté (#5), zéro chiffre inventé (#6).
|
||
|
||
## Session `20260802_153601` · Sprint 8 · buffer · DevOps outillage — `run_ci.sh` : décompte `30/23/7` DÉRIVÉ à l'exécution (retrait des comptes figés du commentaire · anti-invention #6, PAS un gate #5)
|
||
|
||
**Contexte — dernière surface figée dans un outil « anti-dérive par construction ».**
|
||
Ouverture : arbre propre, **7 gates verts**, **30/30 jobs `gate.needs`** (`run_ci.sh`).
|
||
La file substantielle reste **saturée** (22/22 qualité · 564/564 tests · recette
|
||
15/15 · 13 fiches). Balayage des comptes numériques encore susceptibles de dériver :
|
||
`run_ci.sh` (créé la session précédente, commit `49123ec`) DÉRIVE dynamiquement la
|
||
liste des checks de `gate.needs` (« zéro liste en dur ») — mais son **commentaire
|
||
d'en-tête** re-transcrivait à la main « **30 jobs · 23 suites · 7 gates statiques** ».
|
||
Or ce sont exactement les quantités que le script calcule déjà : si un job module
|
||
est ajouté à `ci.yml`, le script s'adapte tout seul (il exécuterait 31 jobs) tandis
|
||
que son commentaire continuerait d'affirmer « 30 · 23 » — un **« vert trompeur »**
|
||
de la même classe que celle que l'outil combat, logé dans sa propre doc d'intention.
|
||
|
||
**Décision — FIXER (éliminer le nombre figé), pas gater** (mémoire *fix-vs-gate-a-
|
||
transitively-protected-constant*). Un gate N+1 sur un commentaire de lanceur de
|
||
commodité (root ∉ `ci/*.sh`) serait redondant (#5) et prendrait la mauvaise autorité.
|
||
La bonne réponse : **rendre le décompte inderivable-à-la-main** — le script le calcule
|
||
et l'imprime, le commentaire ne l'affirme plus.
|
||
|
||
**Livraison — `run_ci.sh` (+20/−4)** :
|
||
1. **Ventilation dérivée** des mêmes `ROWS` que l'exécution : `n_gates` (cmd `bash
|
||
ci/*`) vs `n_suites` (le reste avec commande) → `n_jobs`. Ajouter un job à
|
||
`gate.needs` le reclasse ici automatiquement, zéro édition.
|
||
2. **En-tête d'exécution** : nouvelle ligne `gate.needs dérivé : 30 jobs = 7 gates
|
||
ci/*.sh + 23 suites de module` (toujours exacte, imprimée par `all` et `--static`).
|
||
3. **Pied de `--list`** : `-- 30 jobs dérivés de gate.needs : 7 gates statiques
|
||
ci/*.sh + 23 suites de module.`
|
||
4. **Commentaire d'en-tête reformulé** : retrait des littéraux « 30 / 23 / 7 »
|
||
(remplacés par la description QUALITATIVE `bash ci/*.sh` / `python3 -m unittest`
|
||
— le point étant « stdlib pur, aucun pip », pas le nombre) + note explicite « le
|
||
décompte exact est DÉRIVÉ et imprimé à l'exécution, jamais figé ici (#6) ». Ligne
|
||
d'usage `--static` : « les gates statiques » (sans « 7 »).
|
||
|
||
**Vérif** — `--list` → pied `30 = 7 + 23` ; `--static` → en-tête `30 = 7 + 23` +
|
||
**7 PASS / 0 FAIL** ; `run_ci.sh` complet → **30 PASS / 0 FAIL / 0 SKIP**. Les **7
|
||
gates re-joués → exit 0** (guard_constraints · validate_json · check_docs ·
|
||
check_artifacts · check_regression · check_ci_integrity · check_readme_claims).
|
||
Aucun gate ne scanne `run_ci.sh` (surface hors périmètre des gates, cf. son en-tête).
|
||
Aucune commande touchant au VPS.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · DevOps outillage — `run_ci.sh` : miroir LOCAL du gate CI, dérivé de `gate.needs` (zéro liste en dur · PAS un gate #5)
|
||
|
||
**Contexte — friction récurrente, pas une surface à gater.** Ouverture : arbre
|
||
propre, **7 gates statiques verts**, **30/30 jobs `gate.needs` verts** (7 gates
|
||
`ci/*.sh` + 23 suites de module · 564/564 tests), campagne de gates **saturée**
|
||
(mémoires *verify-uncovered* / *guard-flags-usage-not-mention* : les surfaces
|
||
restantes sont mono-sourcées ou déjà mordues). Ajouter un gate N+1 serait
|
||
redondant (#5). La vraie friction non-adressée est **opérationnelle** : chaque
|
||
session rejouait à la main une boucle ad-hoc `for g in guard_constraints … ; do
|
||
bash ci/$g.sh` (cf. logs des sessions précédentes « 7 gates re-joués »). Une
|
||
telle liste écrite à la main **dérive en silence** de ce que la CI enforce (un
|
||
gate ajouté/retiré dans `ci.yml` n'y est jamais répercuté) — même classe de
|
||
« vert trompeur » que les gates traquent, appliquée au **lanceur** lui-même.
|
||
|
||
**Livraison — `run_ci.sh` à la racine du dépôt.** Un lanceur de commodité qui
|
||
reproduit EN LOCAL, en une commande, l'intégralité du gate de merge :
|
||
- `bash run_ci.sh` → tout `gate.needs` (30 jobs) · `--static` → les 7 gates
|
||
`ci/*.sh` seuls (rapide) · `--list` → plan sans exécution.
|
||
- **Aucune liste en dur** : le script PARSE `gate.needs` de
|
||
`.gitea/workflows/ci.yml` (la source de vérité que `check_ci_integrity`
|
||
verrouille déjà) et exécute, par job, la commande `run:` déclarée dans son
|
||
`working-directory:`. Ajouter/retirer un gate en CI se répercute
|
||
**automatiquement**, zéro édition — anti-dérive par construction.
|
||
- Par périmètre `gate.needs`, exclut à dessein les jobs manuels
|
||
`workflow_dispatch` (`e2e-baseline`/`npm test`, qui exige Node) et le job
|
||
`gate`. → stdlib pur (bash/awk/git/python3), zéro pip, **zéro commande VPS**.
|
||
- Code de sortie **0 ssi tous les jobs passent** (même sémantique que le gate) ;
|
||
sur échec, dump des 12 dernières lignes du log fautif + liste des jobs rouges.
|
||
|
||
**Ce n'est PAS un gate** (aucun nouveau `ci/*.sh`, aucune nouvelle assertion) :
|
||
lanceur hors du glob `ci/*.sh`, donc **hors [INV-B]** de `check_ci_integrity` ;
|
||
son exactitude est **structurelle** (exécute ce que ci.yml déclare) et n'a pas à
|
||
être re-gatée (#5). Placé à la racine (pas dans `ci/`) précisément pour ne pas
|
||
déclencher INV-B qui exigerait de le câbler comme job.
|
||
|
||
**Robustesse prouvée.** Parseur awk **anti-commentaire** (un `working-directory:`
|
||
ou `bash ci/…` cité en COMMENTAIRE de ci.yml était capturé par une 1ʳᵉ version →
|
||
corrigé par `sub(/^[[:space:]]*#.*$/,"")`, cas réel sur le bloc `check-regression`).
|
||
Tests : `--list` = 30 jobs dans l'ordre de `gate.needs` (e2e-baseline/gate
|
||
correctement absents) ; `run_ci.sh` = **30 PASS / 0 FAIL** ; **chemin d'échec
|
||
vérifié** sur un dépôt-jouet synthétique (`bad-job` → FAIL rendu + dump `boom
|
||
detail` + **EXIT=1**) ; hors checkout git → EXIT 3 honnête.
|
||
|
||
**Doc.** Ligne de nav `README.md` + section « Rejouer le gate EN LOCAL » dans
|
||
`ci/README.md` (usage + garantie anti-dérive + rappel « pas un gate #5 »).
|
||
Chemins en backticks/liens Markdown vers cibles existantes (`check_docs` vert).
|
||
|
||
**Vérif finale.** `bash run_ci.sh --static` avec les nouveaux fichiers stagés →
|
||
**7 PASS / 0 FAIL** (guard_constraints · validate_json · check_docs ·
|
||
check_artifacts · check_regression · check_ci_integrity · check_readme_claims).
|
||
Aucun gate perturbé par l'ajout (root ∉ `ci/*.sh` ; nav sans chiffre ; liens
|
||
valides). Aucune commande touchant le VPS.
|
||
|
||
## Sprint 8 · buffer · Reporting stakeholder (canal 2) — rattrapage du snapshot `daily_reports/2026-08-02` sur les 4 sessions buffer intercalées depuis la clôture `120544` (2ᵉ addendum sourcé · ZÉRO gate ajouté #5)
|
||
|
||
**Nature — réconciliation de reporting, PAS un gate de plus (CLAUDE.md #5).**
|
||
Ouverture de session : arbre propre, **7 gates verts**, fiche-audit **close** (13/13),
|
||
campagne de gates **saturée** (dernier commit `bd3572b` = audit d'efficacité 7/7 ROUGE).
|
||
Aucune surface non couverte évidente ; ajouter un gate serait redondant (#5). La tâche
|
||
à VRAIE valeur non-redondante restante est celle que le `daily_report` lui-même désignait
|
||
en **« Prochaine tâche » #1** (l.84) : **tenir le canal 2 (snapshot stakeholder) à jour
|
||
à chaque session buffer**. Il était **périmé de 4 sessions** — le dernier addendum datait
|
||
de la clôture `120544` (commit `2e71c5f`), mais `cb8d931` · `8dcedb0` · `576e78b` ·
|
||
`bd3572b` ont commité **après**, absents de l'instantané direction (leur trace fine
|
||
existe au **canal 1**, ce journal, mais aucune synthèse consolidée sourcée).
|
||
|
||
**Faux positif écarté avant action (mémoire `verify-uncovered-before-gating`).**
|
||
Un agent d'exploration a remonté `CLAUDE.md:16` (« repo `michel/otov7-platform` ») comme
|
||
dérive vs `CLAUDE.md:62` (« Repo mandat DTP : `michel/oto-enterprise-os-dtp` »). **Non
|
||
retenu** : `otov7-platform`/`otov7_platform` est une entité RÉELLE et DISTINCTE — l'app
|
||
ERPNext plateforme OTOV7 (`AGENTS_EXISTING_ASSETS.md:13` cite `/opt/oto/claude_code_work/
|
||
otov7-platform/` comme chemin réel · roadmap S1 `otov7_platform` crm/api). La #2 nomme le
|
||
repo **plateforme produit** phare ; le pied de page nomme le repo **mandat** — deux repos
|
||
sur la même Gitea, pas une contradiction. **Éditer la constitution `CLAUDE.md` sur une
|
||
inférence aurait risqué une invention (#6)** → écarté, aucune modification de `CLAUDE.md`.
|
||
|
||
**Réalisé — 2ᵉ addendum ADDITIF** (corps `110539` + addendum `120544` laissés intacts —
|
||
record historique préservé, comme le 1ᵉ addendum) consolidant les 4 sessions, chaque
|
||
chiffre **re-vérifié par recompute `python3` indépendant sur l'artefact commité** (#6) :
|
||
- Qualité 4Big **22/22 modules** (scores distincts = {100} · seuil `pass_score` 95 ·
|
||
verdict `PASS`) ← `qa/audit_4big/out/quality_report.json` ;
|
||
- Régression **22 suites · 564 ran · 564 passés · 0 échec · 0 erreur** (verdict `PASS`)
|
||
← `qa/regression/out/regression_run.json` ;
|
||
- Recette **15/15** promesses `status=in_repo` (verdict `true`)
|
||
← `qa/acceptance/out/acceptance_matrix.json` ;
|
||
- Fiches **13/13** ← `03_agents/*/` ; Câblage CI `gate.needs` = **30 jobs**, **1** job
|
||
manuel (`e2e-baseline`) ← `.gitea/workflows/ci.yml` (chiffre nouvellement sourcé, tiré
|
||
de l'audit d'efficacité `bd3572b`). État **inchangé et vert**.
|
||
|
||
**Vérif** — les **7 gates CI** restent **exit 0** APRÈS l'édition (le rapport ajoute des
|
||
liens Markdown internes que `check_docs` valide → tous résolus) : `check_docs` ·
|
||
`check_readme_claims` · `check_artifacts` · `check_regression` · `guard_constraints` ·
|
||
`check_ci_integrity` · `validate_json`. `git status --porcelain` propre hors ce log +
|
||
le `daily_report`. **Zéro gate ajouté, zéro chiffre inventé, zéro fichier de code touché,
|
||
aucune commande VPS.** Le canal 2 est de nouveau à jour (2 addenda · 5 sessions couvertes).
|
||
|
||
## Sprint 8 · buffer · Audit d'EFFICACITÉ des gates anti-dérive : mutation-testing du suite complet → 7 mutations, 7 RED prouvés (« vert » ≠ « faux-vert ») + zéro gate ajouté (#5)
|
||
|
||
**Nature — assurance de phase launch, PAS un gate de plus (CLAUDE.md #5).**
|
||
La série récente a saturé la chasse aux *surfaces non couvertes* (mémoire
|
||
`verify-uncovered-before-gating`) : elle prouve qu'une dérive **serait attrapée SI**
|
||
un gate mordait. Angle complémentaire jamais fait explicitement en buffer : **les
|
||
gates verts mordent-ils réellement ?** Un gate cassé/neutralisé reste vert et donne
|
||
une fausse assurance — risque direct en phase de bascule launch (Sprint 8). J'ai donc
|
||
**mutation-testé le suite**, chaque mutation immédiatement révertée (`git checkout --`,
|
||
aucune trace ; arbre propre re-vérifié `git status --porcelain` = vide).
|
||
|
||
**Protocole — 7 mutations ciblées sur 4 gates, résultat attendu = ROUGE :**
|
||
|
||
| # | Gate | Mutation injectée | Verdict |
|
||
|---|---|---|---|
|
||
| M1 | `check_ci_integrity` | job fantôme `ghost-job` ajouté à `gate.needs` (DANGLING) | RED ✓ |
|
||
| M2 | `check_ci_integrity` | `mobile-app-config-tests` retiré de `gate.needs` (MISSING) | RED ✓ |
|
||
| M3 | `check_artifacts` | octet parasite ajouté à `crm/commissions/out/MANIFEST.json` (repro byte) | RED ✓ |
|
||
| M4b | `check_readme_claims` | README « 22/22 modules » → « 23/22 » | RED ✓ |
|
||
| M4c | `check_readme_claims` | README « 15 promesses » → « 16 » | RED ✓ |
|
||
| M5b | `guard_constraints` | URL `https://github.com/acme/thing` (usage réel — cité ici, `ci-allow`) | RED ✓ |
|
||
| M5c | `guard_constraints` | `stripe payments` (paiement interdit CLAUDE.md #10) | RED ✓ |
|
||
|
||
**7/7 → les 4 gates échantillonnés MORDENT.** Chaque ROUGE a été confirmé par le
|
||
message de violation exact (ex. M4b : *« Qualité 4Big · modules gated — README dit
|
||
23/22/100 MAIS artefact dit 22 »* ; M4c : *« Recette · promesses — README dit 16/8/7
|
||
MAIS artefact… »* ; M5b : *« Interdit détecté — GitHub »*). Les 3 autres gates
|
||
(`check_docs`, `check_regression`, `validate_json`) restent couverts par la campagne
|
||
« surfaces » antérieure ; échantillon centré ici sur les gates à **oracle recomputé**
|
||
(les plus exposés à un faux-vert par régression de logique).
|
||
|
||
**Non-finding documenté (piège évité) — `guard_constraints` ne flague PAS le mot nu
|
||
« GitHub », À DESSEIN.** Une 1ʳᵉ mutation (« GitHub is great. ») est restée VERTE ;
|
||
avant de crier au trou, lecture de `ci/guard_constraints.sh:60` : le terme interdit
|
||
est l'**usage** (`github\.com|git@github|github\.io`), jamais la simple **mention** —
|
||
même principe que le suppresseur `PROHIBITION` (l.25) qui ignore les lignes-rappels
|
||
de règle (« JAMAIS GitHub » de `CLAUDE.md` ne doit pas s'auto-flaguer). Le « trou »
|
||
était donc ma mutation mal conçue, pas une faille : re-testé avec un **vrai** usage
|
||
(M5b) → ROUGE. Leçon actée en mémoire (`guard-constraints-flags-usage-not-mention`)
|
||
pour qu'une session future ne re-panique pas sur ce faux-positif.
|
||
|
||
**Bilan.** Le suite anti-dérive n'est pas décoratif : **vert = réellement conforme**.
|
||
Zéro gate ajouté (aucune surface non couverte prouvée · #5), zéro fichier de code/doc
|
||
touché hors ce log, arbre propre. État inchangé et vert : 7 gates re-joués → exit 0,
|
||
câblage CI intègre (`gate.needs` = 30 jobs · `e2e-baseline` seul job manuel, vérifié).
|
||
Aucune commande touchant au VPS.
|
||
|
||
|
||
## Sprint 8 · buffer · Dérive doc-vs-réalité : 11 fiches AGENT.md pointaient un canal `handoffs/` INEXISTANT — correction vers le canal de hand-off RÉEL (livrables `out/` commités)
|
||
|
||
**Nature — dérive réelle, pas un gate de plus (CLAUDE.md #5 · interdit « documenter sans vérifier existence courante »).**
|
||
Audit de complétude plutôt qu'empilage : la série récente a saturé les surfaces
|
||
driftables numériques ; j'ai cherché une dérive doc-vs-réalité échappant aux gates.
|
||
|
||
**Constat.** Le bloc boilerplate « ## Communication inter-agents » (identique dans
|
||
**11** des 13 fiches `03_agents/*/AGENT.md`) affichait :
|
||
`- Handoffs formalisés dans \`05_deliverables_mvp/handoffs/\``. Or ce répertoire
|
||
**n'existe pas** — `find -iname '*handoff*'` = 0 fichier, 0 consommateur (code/gate),
|
||
0 test. Fausse promesse de canal. Les 2 autres lignes du bloc sont, elles, vraies
|
||
(`daily_reports/` existe · WhatsApp conforme CLAUDE.md).
|
||
|
||
**Pourquoi les gates l'ont manqué (angle mort documenté, pas à re-gater).**
|
||
`check_docs.sh` §1 valide les cibles de liens `[texte](cible)` mais **neutralise
|
||
délibérément les code-spans backtick** (l.28 `sed 's/\`[^\`]*\`//g'`) : un chemin
|
||
entre backticks est traité comme *exemple de syntaxe* (ex. `gen.py build|validate`
|
||
n'est pas un chemin réel). `\`…/handoffs/\`` étant un code-span, il échappe. **Gater
|
||
tous les backticks créerait des faux positifs massifs** (les fiches en regorgent :
|
||
entrées CLI, extensions) → j'ai **choisi de NE PAS élargir le gate** (anti-sur-gating,
|
||
mémoire `verify-uncovered-before-gating`). Le correctif est un correctif de vérité,
|
||
pas un nouveau gate.
|
||
|
||
**2 solutions (workflow #4) — la bonne = corriger la doc, pas créer le dossier.**
|
||
- ❌ Créer `handoffs/` + README index : inventerait du contenu (#6) et **dupliquerait**
|
||
le mécanisme de hand-off déjà réel (les `out/` commités), violant #5.
|
||
- ✅ **Corriger les 11 lignes** vers le canal qui existe déjà, décrit richement dans
|
||
chaque fiche (« Chaque générateur transforme un spec en fixtures `out/` commitées —
|
||
le hand-off direct »). Nouvelle ligne, universellement vraie pour les 11 agents
|
||
(chacun produit au moins un doc/spec/fixture commité, y compris bim/ifc_speckle/
|
||
rendu/erpnext_backend qui n'ont pas de `out/` mais ont une fiche/SPEC) :
|
||
`- Hand-off inter-agents = livrables déterministes commités in-repo (fixtures/specs \`out/\`, SPEC, README), consommés directement par l'agent destinataire`.
|
||
|
||
**Exécution.** Remplacement littéral exact (script Python, assertion `count==11`,
|
||
1 occurrence/fichier) sur les 11 fiches : bim · crm · devops · erpnext_backend ·
|
||
frontend_console · ifc_speckle · mobile · onapi_legal · qa · rendu · seo.
|
||
|
||
**Vérif.** (1) `grep -rn handoffs` sur `03_agents/ 05_deliverables_mvp/ ci/ README.md`
|
||
→ **0 résidu**. (2) Nouvelle ligne présente (échantillon crm l.68, bim l.74). (3)
|
||
**7 gates verts** : check_ci_integrity · check_artifacts · check_docs ·
|
||
check_readme_claims · check_regression · guard_constraints · validate_json (tous exit 0).
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Démo/scenarios — ancrage des MINIMA D'ORCHESTRATION (« ≥ 2 modules distincts · ≥ 3 beats ») sur la garde `check_invariants` (CODE-autorité)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Classe « seuil data-derived recopié dans une garde de validation +
|
||
sa prose, jamais ancré » (déjà couverte pour les planchers `min_doc_bytes` /
|
||
`min_test_methods` de l'auditeur 4Big). Un candidat restait NON gaté (grep :
|
||
absent de tous les `ci/*.sh`) au **Livrable Sprint 7** (`demo/scenarios`, run-sheet
|
||
de pitch P07/P05) : la **garantie #3** du README (« chaque scénario tisse **≥ 2
|
||
modules distincts** et **≥ 3 beats** ») énonce deux planchers structurels
|
||
RÉELLEMENT imposés par la garde `check_invariants` bloc (c) de
|
||
`demo_scenario_gen.py` — la SEULE autorité d'exécution : `len(sc["beats"]) < 3`
|
||
refuse un scénario sous 3 beats, `len(mods) < 2` sous 2 modules distincts. Les
|
||
deux nombres sont recopiés À LA MAIN à **3 surfaces**, aucune gatée : (1) la
|
||
comparaison `< N`/`< M` (le plancher EFFECTIF) · (2) le message d'erreur
|
||
« (min 3) »/« (min 2 · orchestration) » (bornes annoncées au caller) · (3) la
|
||
prose README.
|
||
|
||
**Le « vert trompeur » — PROUVÉ ungate avant ajout** (mémoire
|
||
`verify-uncovered-before-gating`) : muter « ≥ 3 beats »→« ≥ 4 »/« ≥ 2 modules »→
|
||
« ≥ 5 » dans le README laissait `check_readme_claims` **exit 0**. Resserrer la
|
||
règle (démo plus riche : `< 2`→`< 3` modules) sans toucher la prose ⇒ README
|
||
PÉRIMÉ en silence, documentant un plancher que le code n'applique plus — dérive
|
||
qu'aucune suite `tests/` (exerce la FONCTION `check_invariants`, jamais la prose)
|
||
n'attrape, invisible à `check_artifacts` (`.py` ≠ `out/*.json`).
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Démo · run-sheet · MINIMA
|
||
D'ORCHESTRATION », juste après le bloc « modules cités ») — RE-DÉRIVE les 2 minima
|
||
depuis la SEULE comparaison `<` de la garde (source unique · zéro duplication #5)
|
||
et exige : (1) autorité = `<` de `check_invariants` ; (2) le message d'erreur
|
||
« (min N) »/« (min M · orchestration) » == plancher effectif ; (3) la prose #3 du
|
||
README == plancher effectif. Une surface INTROUVABLE échoue AUSSI (ancre morte ·
|
||
anti-évaporation #6).
|
||
|
||
**Vérif** — 3 checks verts sur l'arbre propre (≥ 2 modules · ≥ 3 beats). **7
|
||
morsures** adversariales, gate lancé SEUL, reverts par `cp` ciblé (jamais
|
||
`git checkout` large — cf. incident consigné) : **M1** README `≥3 beats`→`≥4` (3) ·
|
||
**M2** README `≥2 modules`→`≥5` (3) · **M3 SILENT-GREEN** garde `<3`→`<4` beats
|
||
(code resserré, prose+message figés) ⇒ **message ET README mordent** (preuve que
|
||
l'autorité est le CODE, pas la cohérence interne de la prose) · **M4** message
|
||
`(min 3)`→`(min 4)` désync de `<` ⇒ message mord, README reste OK · **M5** garde
|
||
`<2`→`<3` modules ⇒ message ET README mordent · **M6** prose #3 supprimée ⇒
|
||
INTROUVABLE · **M7** comparaison `<` de la garde retirée ⇒ autorité INTROUVABLE.
|
||
Arbre restauré (`git status` propre, seule l'addition au gate subsiste). Suite
|
||
unittest re-verte : **39 tests OK** (générateur inchangé). Suite CI complète
|
||
re-verte : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||
|
||
## Sprint 8 · buffer · CRM/workflow_vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` (9ᵉ instance du ledger · dialecte `# Invariant N ·`)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles
|
||
CI depuis le registre-source des marqueurs numérotés de la fonction de validation,
|
||
jamais une liste à la main) était consignée « close à 8 générateurs » (Chat OTOIA ·
|
||
SEO · Fiscal e-CF · Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM
|
||
Commissions · Mobile) + 2 (deploy_runbook/acceptance, dialecte `# INVn`). La mémoire
|
||
`invariant-ledger-count-gate` avertit précisément : « CLOSED a été prouvé FAUX DEUX
|
||
FOIS ; re-scanner TOUS les dialectes de marqueur AVANT de croire la clôture ». Un
|
||
**3ᵉ oubli** a été exhumé : `crm/workflow_vente/workflow_vente_gen.py` (Livrable
|
||
Sprint 4 l.50 · workflow de vente Frappe lead→…→CONFOTUR) porte un ledger de **9
|
||
invariants métier de graphe** dans `_validate_bundle`, marqueurs `# Invariant N ·`
|
||
(style Mobile), phrasé « schéma + 9 invariants ». Le nombre « 9 » est recopié À LA
|
||
MAIN à **4 surfaces vives**, aucune gatée : générateur l.223 (message CLI de succès)
|
||
· README l.78 (bloc de commande) · README l.114 (prose de vérification) · README
|
||
l.125 (prose d'auto-score).
|
||
|
||
**Le « vert trompeur » — PROUVÉ ungate avant ajout** (mémoire
|
||
`verify-uncovered-before-gating`) : muter « 9 invariants »→« 10 » dans le README
|
||
laissait `check_readme_claims` **exit 0**. Ni `check_artifacts` (le `.py` source
|
||
n'est pas un `out/*.json`) ni les suites `tests/` (FONCTIONS de validation, jamais
|
||
la prose) n'attrapent la dérive. Ajouter un contrôle `# Invariant 10 ·` à
|
||
`_validate_bundle` sans toucher les 4 chaînes ⇒ README + self-report CLI mentent en
|
||
silence pendant que la CI applique 10 contrôles.
|
||
|
||
**Particularités du registre** (vs les 8 blocs `# N ·`) — style `# Invariant N ·` +
|
||
fonction `_validate_bundle` (comme Mobile ; les blocs ancrés `# N ·`/`def _validate\b`
|
||
ne le voient pas). Ledger PROPRE 1..9 compté À PART du schéma (« schéma + N
|
||
invariants » · pas de schéma-#1, pas de split a/b ; filet doublon nu conservé). Un
|
||
SECOND nombre distinct — « **25 tests** » (suite unittest, déjà gatée) — présent au
|
||
README : l'ancre sur le mot « invariants » l'exclut nativement ⇒ **aucun faux
|
||
positif**. Le README N'A NI titre « ## Les N invariants » (son titre « ## Invariants
|
||
vérifiés par le CLI » ne porte aucun chiffre) NI énumération 1:1 avec le ledger — sa
|
||
liste compte le SCHÉMA en item 1 puis **FUSIONNE les invariants 8 & 9** dans son item
|
||
9 (« Maîtres … ; comptes du manifeste cohérents ») : elle N'EST PAS le ledger, la
|
||
gater comme contiguë==ledger serait un faux positif fragile (présentation éditoriale).
|
||
On gate donc UNIQUEMENT (a)(b)(d), comme Audit 5D.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « CRM · workflow_vente · IDENTITÉ
|
||
du COMPTE D'INVARIANTS », juste après le bloc Mobile) — RECOMPUTE le ledger depuis
|
||
le SEUL registre-source (les `# Invariant N ·` de `_validate_bundle`, corps extrait
|
||
jusqu'au `def` suivant) et exige : **(a)** ledger CONTIGU 1..N (lacune OU doublon nu
|
||
= vrai défaut de registre) ; **(b)** toute mention « N invariants » du `.py` ==
|
||
|ledger| ; **(d)** toute mention « N invariants » du README == |ledger|. Mention ou
|
||
registre absent = échec (anti-évaporation #6).
|
||
|
||
**Vérif** — 3 checks verts sur l'arbre propre (ledger 1..9 = 9 marqueurs `# Invariant
|
||
N ·`, schéma compté à part). **7 morsures** adversariales, gate lancé SEUL, reverts
|
||
par `cp` ciblé (jamais `git checkout` large — cf. incident consigné) : **M1** `.py`
|
||
9→10 (b) · **M2** README 9→10 métier (d) · **M3 SILENT-GREEN** `# Invariant 10 ·`
|
||
injecté ⇒ ledger→10 pendant que la prose reste 9 (mord `.py` ET README, preuve que
|
||
l'autorité est le CODE) · **M4** `# Invariant 5 ·` supprimé ⇒ ledger non contigu (a) +
|
||
self-reports désync · **M5** doublon nu `# Invariant 3 ·` ⇒ vrai doublon (a) · **M6**
|
||
toutes les mentions « 9 invariants » retirées du README ⇒ self-report ÉVAPORÉ · **M7
|
||
no-false-positive** : muter « 25 tests »→« 26 » ⇒ le gate ledger reste SILENCIEUX
|
||
(l'ancre « invariants » l'exclut ; le rouge vient du gate test-count préexistant, pas
|
||
du mien). Arbre restauré (`git status` propre, seule l'addition +113 subsiste). Suite
|
||
unittest re-verte : **25 tests OK** (générateur inchangé). Suite CI complète re-verte :
|
||
`check_artifacts` · `check_docs` · `guard_constraints` · `check_ci_integrity` ·
|
||
`check_regression` · `check_readme_claims` tous exit 0. **La classe ledger passe à 9
|
||
générateurs (+2 dialecte `# INVn`) ; re-scanner tout NOUVEAU dialecte AVANT de
|
||
re-déclarer close — ce 3ᵉ oubli confirme l'avertissement de la mémoire.**
|
||
|
||
## Sprint 8 · buffer · Faisabilité/bancable — ÉLIMINATION du taux `0.52` codé en dur dans l'oracle anti-invention (source unique CANONICAL · #6/#5)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres » · workflow #5 « zéro duplication »). En balayant les copies MANUELLES
|
||
du paramètre canonique #9 « point d'équilibre 52 % » restées HORS de la source
|
||
unique, une a été trouvée au **cœur de l'oracle anti-invention** du générateur
|
||
bancable : `faisabilite/bancable/bancable_gen.py::_check_derived_arithmetic`
|
||
(l.131) recopiait le taux en **littéral `0.52`** (`exp_pe = math.ceil(0.52 *
|
||
exp_total)`) pour re-calculer indépendamment le point d'équilibre et prouver
|
||
qu'« aucune valeur n'a été posée ». Paradoxe : le module PRÊCHE l'inverse — son
|
||
`deps.py` (« une seule source de vérité pour tout le mandat ») importe
|
||
`CANONICAL = genlib.model.CANONICAL` (gaté sur CLAUDE.md #9/#10) précisément
|
||
pour NE PAS dupliquer `3 % / 8.5 % / 52 %`, et `finance._pct` porte le docstring
|
||
explicite « **jamais codé en dur ici** — évite toute dérive ». Le `0.52` de
|
||
l'oracle était le SEUL survivant en dur (grep : aucun autre `0.03/0.085/0.52`
|
||
littéral dans le code du module ; `finance.py`/`report.py` passent tous par
|
||
`CANONICAL`).
|
||
|
||
**Pourquoi un FIX et pas un gate** — mutation-test préalable (mémoire
|
||
`verify-uncovered-before-gating`) : AUCUNE mutation ne produit un « vert
|
||
trompeur ». Le `0.52` est **transitivement protégé** — l'invariant du module
|
||
compare l'oracle à la figure `finance` (issue de CANONICAL) au moment de la
|
||
génération, et le gate bancable (`check_readme_claims.sh`) LANCE cette génération
|
||
(`bancable_gen validate`) ; toute divergence `0.52`≠CANONICAL casse `validate`
|
||
→ gate ROUGE. Un gate d'ancrage du littéral serait donc **redondant** (interdit
|
||
#5). MAIS le `0.52` en dur est un **BUG LATENT** de sens inverse : un changement
|
||
LÉGITIME du mandat (#9 52 %→55 %, propagé à CANONICAL par les gates model/
|
||
template) laisserait l'oracle à 21 pendant que `finance` produit 22 ⇒ le
|
||
générateur **REFUSERAIT en silence un dossier bancable VALIDE** (invariant
|
||
« manifeste 22 != recalcul 21 »), avec un message cryptique « génération réelle
|
||
échoue » côté CI. La bonne réponse n'est pas de gater la copie, mais de la
|
||
**supprimer** — dériver le taux de la source unique, comme le fait déjà
|
||
`finance.py`.
|
||
|
||
**Correctif** (`bancable_gen.py`, `_check_derived_arithmetic`) — `0.52` remplacé
|
||
par `pe_pct = finance._pct(deps.CANONICAL["point_equilibre_pct"])` (RÉUTILISE le
|
||
parseur `%→fraction` déjà livré · zéro nouvelle logique) + garde `pe_pct is not
|
||
None`. Docstring l.117 réécrit : le taux est « lu à la SOURCE UNIQUE (CANONICAL ·
|
||
CLAUDE.md #9, gaté), jamais recopié en dur ici ». L'indépendance de l'oracle est
|
||
préservée là où elle compte (Σ quantités · × prix · `ceil`) ; le TAUX, lui, est
|
||
une constante de mandat déjà ancrée en amont (CANONICAL gaté sur CLAUDE.md #9),
|
||
donc le re-dériver de cette source n'affaiblit rien.
|
||
|
||
**Vérif** — `validate fixtures/brief_bancable.json` byte-identique à 52 %
|
||
(`point_equilibre_unites = 21 = ⌈52 % × 40⌉`). **22 tests** unittest verts
|
||
(inchangés · oracles fixture self-consistants). **Preuve du bug levé** : en
|
||
simulant la source à 55 % (`deps.CANONICAL["point_equilibre_pct"]="55 %"`), les
|
||
invariants restent **OK** et `point_equilibre_unites` passe à **22 = ⌈55 % × 40⌉**
|
||
(AVANT le fix : oracle figé à 21 ⇒ invariant cassé ⇒ dossier valide rejeté).
|
||
Suite CI complète re-verte : `check_artifacts` · `check_docs` ·
|
||
`guard_constraints` · `check_ci_integrity` · `check_regression` ·
|
||
`check_readme_claims` tous exit 0. **Leçon** : toute copie manuelle d'une
|
||
constante de mandat n'appelle pas un gate N+1 — quand elle est déjà
|
||
transitivement protégée, la SUPPRIMER (source unique) vaut mieux qu'un checker
|
||
redondant (#5), et lève au passage le bug-miroir « le mandat change → le module
|
||
casse ».
|
||
|
||
## Sprint 8 · buffer · Ledger d'invariants — 2ᵉ FAMILLE (`# INVn ·` / `validate_bundle` / « N familles d'invariants ») : ancrage du COMPTE de `deploy_runbook` (11) + `acceptance` (11)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles CI
|
||
depuis le registre-source des marqueurs numérotés de la fonction `_validate`, jamais
|
||
une liste à la main) avait été **déclarée close à 8 générateurs** — mais sur le SEUL
|
||
dialecte `# N ·` / « N invariants » / `_validate`(`_validate_bundle`). La mémoire
|
||
`invariant-ledger-count-gate` consigne précisément le risque : « un prior *CLOSED
|
||
après 7* était FAUX — un style de marqueur différent avait glissé ; re-scanner LES
|
||
DEUX styles avant de croire la clôture ». Un **re-scan systématique** a exhumé une
|
||
**DEUXIÈME famille** entièrement HORS gate : marqueurs `# INVn ·` (préfixe `INV`)
|
||
dans `validate_bundle`, phrasé « N **familles d'**invariants ». DEUX générateurs la
|
||
portent :
|
||
- `devops/deploy_runbook/deploy_runbook_gen.py` — ledger `# INV1..INV11` dans
|
||
`validate_bundle`, compte **« 11 familles d'invariants »** recopié à **3 surfaces
|
||
vives** : générateur l.272 (message CLI de succès) · README l.73 (bloc de commande)
|
||
· suite tests l.4 (docstring) ;
|
||
- `qa/acceptance/acceptance_gen.py` — ledger `# INV1..INV11` dans `validate_bundle`,
|
||
compte à **2 surfaces vives** : README l.46 « 11 invariants » · suite tests l.4
|
||
« 11 familles d'invariants ». (Le `.py` acceptance ne porte AUCUN compte numéroté
|
||
→ surface non exigée.)
|
||
|
||
**Le « vert trompeur »** — ajouter un contrôle `# INV12 ·` à `validate_bundle` sans
|
||
toucher ces chaînes ⇒ README + self-reports + docstrings de test mentent en silence
|
||
pendant que la CI applique 12 contrôles. Ni `check_artifacts` (le `.py` source n'est
|
||
pas un `out/*.json`) ni les suites `tests/` (FONCTIONS de validation, jamais la prose)
|
||
n'attrapent la dérive. **Prouvé (M4 SILENT-GREEN)** : injecter `# INV12 ·` dans
|
||
`validate_bundle` (ledger→12, toute la prose reste 11) ⇒ les **3 surfaces
|
||
deploy_runbook mordent** « [11] MAIS ledger en compte 12 » — preuve que l'autorité
|
||
est le **CODE-registre**, pas la cohérence interne de la prose.
|
||
|
||
**Particularités** — Ledger PROPRE 1..N compté À PART du schéma (« schéma + N
|
||
invariants »). Les mentions NON numérotées de « invariants » (docstrings « invariants
|
||
de déploiement/recette », README « Un invariant refuse … ») ne sont pas ancrées par
|
||
un chiffre → **aucun faux positif**. Le compte-regex accepte les DEUX formes
|
||
(« N invariants » ET « N familles d'invariants ») via un préfixe optionnel
|
||
`(?:familles d')?`. Les `daily_reports`/`activity_log` (snapshots datés — ex.
|
||
acceptance « 10 » d'avant la croissance 10→11) ne sont PAS des surfaces vives et ne
|
||
sont volontairement pas ancrés (aucune dérive à surveiller sur un log figé).
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « DevOps deploy_runbook + QA
|
||
acceptance · IDENTITÉ du COMPTE d'INVARIANTS de `validate_bundle` », juste après le
|
||
bloc Mobile) — **paramétré sur les 2 modules** (zéro duplication). RECOMPUTE le
|
||
ledger depuis le SEUL registre-source (`# INVn ·` de `validate_bundle`, corps extrait
|
||
jusqu'au `def` suivant) et exige : **(a)** ledger CONTIGU 1..N (lacune OU doublon nu
|
||
= vrai défaut de registre) ; **(b)** CHAQUE surface déclarée porte ≥1 mention
|
||
« N [familles d']invariants » toutes == |ledger|. Une surface sans mention = self-report
|
||
ÉVAPORÉ (échec · anti-évaporation #6).
|
||
|
||
**Vérif** — 7 checks verts sur l'arbre propre (2 ledgers 1..11 + 5 surfaces).
|
||
**8 morsures** adversariales, gate lancé SEUL, reverts par `cp` ciblé (jamais
|
||
`git checkout` large — cf. incident consigné) : **M1** deploy `.py` 11→12 (b générateur)
|
||
· **M2** deploy README 11→12 (b) · **M3** deploy test 11→12 (b) · **M4 SILENT-GREEN**
|
||
`# INV12 ·` injecté ⇒ 3 surfaces deploy mordent (autorité = CODE) · **M5** `# INV7 ·`
|
||
supprimé ⇒ ledger non contigu (a) + 3 surfaces désync · **M6** acceptance README 11→12
|
||
(b) · **M7** acceptance test 11→12 (b) · **M8** compte retiré du README acceptance ⇒
|
||
self-report ÉVAPORÉ. Arbre restauré (`git status` propre). Suites unittest re-vertes :
|
||
deploy_runbook **29 tests OK** · acceptance **37 tests OK** (générateurs inchangés).
|
||
Suite complète re-verte : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||
**Les DEUX familles de ledger sont désormais gatées ; re-scanner tout NOUVEAU
|
||
dialecte de marqueur (préfixe/phrasé) avant de re-déclarer la classe close.**
|
||
|
||
## Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'encadré « Défense en profondeur » (NOM du rôle détenteur de `set_user_permissions` · scope · famille) sur l'artefact byte-gaté
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le §2
|
||
« Modèle RBAC » de `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` ferme sur un
|
||
encadré de sécurité « **Défense en profondeur** » : le pouvoir `set_user_permissions`
|
||
(élévation de privilège) est « **réservé** au SEUL rôle `OTO Plateforme RBAC Admin`
|
||
(`scope_donnees = groupe`, `famille = plateforme`) — séparation des pouvoirs vérifiée
|
||
par test automatisé ». La SPEC se réclame donc d'un **fait de sécurité tiré du
|
||
contrat** `rbac_50_roles.json`, sur **trois attributs** : NOM du détenteur · scope ·
|
||
famille. Or le test cité (`test_rbac::test_set_user_permissions_reserve_admin`) impose
|
||
bien scope==groupe ET famille==plateforme à TOUT détenteur — MAIS **jamais le NOM du
|
||
rôle** (`OTO Plateforme RBAC Admin`) NI cette prose §2. L'**UNICITÉ** (« au SEUL rôle »)
|
||
et le NOM étaient DÉJÀ gatés côté **artefact** (bloc « RBAC fixtures », singleton
|
||
`len(fx_sup)==1` depuis `custom_docperm.json` byte-gaté + prose du **README fixtures**
|
||
« émis que pour le rôle **NAME** ») — mais **PAS la 3ᵉ surface prose, le §2 de la SPEC**.
|
||
|
||
**Le « vert trompeur »** — RENOMMER le détenteur dans le contrat (+régénérer
|
||
fixtures/out + audit 4Big) laisse le §2 citer un rôle **DISPARU** en silence :
|
||
`check_artifacts` ne voit qu'un **HASH de TAILLE** du doc (agnostique au nom · la SPEC
|
||
n'est pas un `out/*.json`), les 10 tests restent verts (scope/famille inchangés).
|
||
**Prouvé (M6 SILENT-GREEN)** : renommer `…RBAC Admin`→`…Securite Admin` dans le
|
||
contrat + régénérer les 4 générateurs RBAC & l'audit ⇒ **`check_artifacts` VERT**
|
||
pendant que le nouveau gate mord « §2 nomme ['OTO Plateforme RBAC Admin'] MAIS
|
||
set_user_permissions réservé à 'OTO Plateforme Securite Admin' » — preuve que
|
||
l'autorité est l'**ARTEFACT byte-gaté**, pas la cohérence interne de la SPEC.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, **extension** du bloc « RBAC fixtures »,
|
||
points 4 & 5 — pas un bloc neuf) — RÉUTILISE `fx_sup` (le singleton déjà byte-gaté
|
||
ci-dessus · **zéro re-dérivation de l'unicité**, interdit #5 « jamais de doublon »)
|
||
comme **NOM-autorité** et `src_roles` (contrat byte-gaté) pour scope/famille. Capte
|
||
l'encadré `**Défense en profondeur** … vérifiée par test automatisé` (spec_rd
|
||
whitespace-normalisé) et exige : le pouvoir `set_user_permissions` y est **nommé** ;
|
||
**(a)** le NOM du détenteur cité (accents/casse neutralisés) == le seul porteur du
|
||
flag ; **(b)/(c)** les `scope_donnees = …` / `famille = …` cités == attributs du
|
||
détenteur dans `rbac_50_roles.json` ; **(5)** §6 (row « Séparation des pouvoirs »)
|
||
cite bien `set_user_permissions` réservé (anti-évaporation). Encadré/row INTROUVABLE =
|
||
échec (#6). La glose « (gestion des habilitations) » N'est PAS gatée (éditoriale).
|
||
|
||
**Note process** (interdit #5) — un premier jet ajoutait un bloc §2 autonome
|
||
RECALCULANT le singleton depuis `src_roles` : **doublon** de la vérification d'unicité
|
||
déjà portée par le bloc fixtures (via `custom_docperm.json` byte-gaté, équivalent par
|
||
`check_artifacts`). Écarté ; les 4 checks réécrits en **extension** du bloc existant,
|
||
sans ré-affirmer l'invariant d'unicité.
|
||
|
||
**Vérif** — 4 checks verts sur l'arbre propre. **6 morsures** adversariales, gate
|
||
lancé seul, reverts par `cp`/`tar` ciblés (jamais `git checkout` large — cf. incident
|
||
consigné) : **M1** §2 renomme le détenteur (prose) ⇒ NOM ≠ artefact · **M2** §2
|
||
scope `groupe`→`entite` ⇒ (b) ≠ détenteur · **M3** §2 famille `plateforme`→`ventes`
|
||
⇒ (c) ≠ détenteur · **M4** encadré supprimé ⇒ INTROUVABLE · **M5** §6 retire
|
||
`set_user_permissions` ⇒ ancre évaporée · **M6 SILENT-GREEN** (ci-dessus) contrat
|
||
renommé + artefacts régénérés, `check_artifacts` VERT ⇒ §2 (a) mord. 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 · 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)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le
|
||
générateur `rbac_fixtures_gen` émet `MANIFEST.custom_doctypes_a_confirmer` =
|
||
l'ensemble des DocTypes `custom: true` du contrat `rbac_50_roles.json` (les DocTypes
|
||
DTP **à créer** côté VPS avant import — `Faisabilité`, `Publiciste Log`, `CONFOTUR
|
||
Application`, `API Access`). Cet ensemble data-derived est transcrit À LA MAIN à
|
||
**deux** surfaces prose : (1) `rbac/fixtures_gen/README.md` — **déjà gatée** (bloc
|
||
« RBAC fixtures · catalogue custom ») ; (2) `RBAC_50_ROLES_SPEC.md §6` l.117
|
||
(« **DocTypes `custom: true`** (`Faisabilité`, `Publiciste Log`, `CONFOTUR
|
||
Application`, `API Access`) ») — **NON gatée**. Le bloc `apply_plan` ne gate que le
|
||
COMPTE (`len(custom_doctypes)`), aveugle aux NOMS ; `check_artifacts` est aveugle
|
||
(la SPEC n'est pas un `out/*.json`).
|
||
|
||
**Le « vert trompeur »** — AJOUTER un DocType `custom` au contrat (le MANIFEST
|
||
rebâtit l'entrée) ou RENOMMER/ÉCHANGER l'un des 4 laisse §6 à ses 4 noms périmés en
|
||
silence : la cible d'application VPS énumérée par la SPEC ne correspond plus aux
|
||
DocTypes que l'agent ERPNext doit réellement créer. Vérifié par mutation : dropper
|
||
`Publiciste Log` de §6 laissait le gate à **exit 0**. Même patron que la 2ᵉ surface
|
||
CONFOTUR/entités (README gaté, fiche non).
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, extension du bloc « RBAC fixtures »,
|
||
point 3) — RÉUTILISE `fx_cdt` (= `MANIFEST.custom_doctypes_a_confirmer`, byte-gaté,
|
||
déjà dérivé par le point 1) et le normaliseur d'accents `_fx_acc` (zéro duplication
|
||
du contrat) : capte l'énumération §6 (`DocTypes `custom: true`** (…)` sur le texte
|
||
whitespace-normalisé, robuste au retour-ligne 117↔118), extrait les tokens
|
||
backtickés, et exige set-diff EXACT == catalogue MANIFEST (absent ET en trop, casse/
|
||
accents neutralisés). Énumération INTROUVABLE = échec (anti-évaporation #6).
|
||
|
||
**Vérif** — 1 check vert sur l'arbre propre (les 4 == MANIFEST). **5 morsures**
|
||
adversariales : A dropper `Publiciste Log` ⇒ absent · B renommer `API Access`→`API
|
||
Acces` ⇒ absent+en-trop · C ajouter `Ghost DocType` fantôme ⇒ en-trop · **D
|
||
SILENT-GREEN : le MANIFEST GAGNE un 5ᵉ DocType custom (§6 inchangé) ⇒ mord « absents=
|
||
[new custom dt] » — preuve que l'autorité est l'ARTEFACT, pas la cohérence interne
|
||
de la SPEC** · E énumération §6 supprimée ⇒ INTROUVABLE. Reverts par chemin ciblé
|
||
(jamais `git checkout` large — cf. incident consigné). Suite complète re-verte :
|
||
`check_artifacts` · `check_docs` · `guard_constraints` · `check_ci_integrity` ·
|
||
`check_regression` · `check_readme_claims` tous exit 0.
|
||
|
||
**Note process** — un candidat initial (ancrage des attributs de rôle des fiches
|
||
`mobile`/`rendu`/`ifc_speckle`) s'est révélé DÉJÀ couvert par les blocs existants
|
||
« Fiche Mobile » / « Fiches Faisabilité » : la ligne d'index `MEMORY.md` était
|
||
périmée vs le corps détaillé de la mémoire (classe close le 2026-08-01). Doublon
|
||
écarté (interdit #5 « jamais accumuler doublons »), index mémoire corrigé.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'ÉNUMÉRATION des `scope_donnees` (ligne Row-level) sur l'ENUM du schéma
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). La table §2 « Modèle RBAC » de
|
||
`05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` ouvre par « trois niveaux, **tous
|
||
couverts par le schéma** » puis sa 3ᵉ ligne (« **Row-level** (User Permission) |
|
||
`scope_donnees` | … ») ÉNUMÈRE à la main l'ensemble EXHAUSTIF des portées
|
||
row-level : `own` · `equipe` · `entite` (Company) · `groupe` (consolidé). Par sa
|
||
phrase d'intro, cet ensemble EST l'`enum` `scope_donnees` de `rbac.schema.json`
|
||
(la DÉFINITION du modèle). Or **aucun gate ne le touchait** : §3 compte les rôles
|
||
par portail, §4 les entités, §5 le résumé, et le bloc `userperm` gate la table
|
||
`scope → mécanisme` d'un **autre** artefact (`userperm_gen/out`) — tous aveugles à
|
||
l'énumération du §2. Même classe que §4 (colonne ancrée sur une source-de-vérité
|
||
structurée), côté schéma cette fois.
|
||
|
||
**Le « vert trompeur »** — si le schéma AJOUTE une portée (`region` pour un besoin
|
||
ops réel) ou en RETIRE une (`own` supprimé, schéma resserré), le §2 reste PÉRIMÉ en
|
||
silence : il documente un modèle qui CONTREDIT le schéma tout en se réclamant
|
||
« couvert par le schéma ». Ni `check_artifacts` (la SPEC n'est pas un `out/*.json`)
|
||
ni `tests/` (FONCTIONS de validation, jamais l'ancre SPEC↔schéma) ne mordent.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1octies », juste après le §5)
|
||
— charge l'`enum` `scope_donnees` depuis `rbac.schema.json` (source de vérité du
|
||
modèle) puis exige : **(a)** ENSEMBLE des jetons `scope` backtickés de la ligne §2
|
||
== enum (set-diff absent ET fantôme) ; **(b)** ANCRAGE — la colonne du milieu NOMME
|
||
bien `scope_donnees` (sinon on gate la mauvaise ligne) ; **(c)** sanity — toute
|
||
portée RÉELLEMENT exercée par le contrat ∈ enum (une portée en usage jamais dans
|
||
l'enum = schéma/contrat incohérents). Les gloses parenthétiques `(Company)`/
|
||
`(consolidé)` NE sont PAS gatées (éditoriales · #6). Ligne/section/enum absents =
|
||
échec (traçabilité #6).
|
||
|
||
**Vérif** — 3 checks verts sur l'arbre propre (4 portées `own/equipe/entite/groupe`
|
||
== enum). **6 morsures** adversariales : M1 §2 retire `own` ⇒ manquant (a) · M2 §2
|
||
ajoute fantôme `region` ⇒ fantôme (a) · **M3 SILENT-GREEN : le schéma GAGNE `region`,
|
||
§2 inchangé ⇒ (a) mord `manquant region` — preuve que l'autorité est le SCHÉMA, pas
|
||
la cohérence interne de la SPEC** · M4 colonne du milieu renommée `scope_donnees`→
|
||
`scope` ⇒ ancre perdue (b) · M5 ligne Row-level supprimée ⇒ INTROUVABLE · M6 le
|
||
schéma RETIRE `own` (encore exercé par 2 rôles) ⇒ (a) fantôme + (c) portée hors
|
||
enum. Gates re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Faisabilité — ancrage du DOMAINE DES CODES PROJET (garde d'entrée `_validate_brief` + 2 schémas JSON) sur CLAUDE.md §Projets
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). La classe « constante constitutionnelle recopiée dans une GARDE DE
|
||
VALIDATION en code, jamais ancrée » (déjà couverte pour le CONTRAT DE FORMAT
|
||
e-NCF de `ncf.py`) portait un candidat resté NON gaté et paradoxalement au **cœur
|
||
du Livrable Sprint 3** (« P07 Aqua Terra <1h ») : le **DOMAINE des codes projet
|
||
valides** du générateur 4 volets. Ce domaine EST exactement l'ensemble
|
||
`CLAUDE.md §Projets` (P01..P09), recopié À LA MAIN à **QUATRE surfaces**, aucune
|
||
ancrée, toutes invisibles à `check_artifacts` (ni `.py` ni schémas ne sont des
|
||
`out/*.json`) :
|
||
1. `faisabilite/generator/faisabilite_gen.py::_validate_brief` — la GARDE runtime
|
||
`code[:2] == "P0" and code[2] in "123456789"` (le domaine EFFECTIF) + le message
|
||
`« … invalide (attendu P01..P09) »` (les bornes) ;
|
||
2. `faisabilite/version.schema.json` — `properties.projet.pattern "^P0[1-9]$"`
|
||
(validation du `version.json` par projet) + sa `description` ;
|
||
3. `faisabilite/generator/brief.schema.json` — même `pattern "^P0[1-9]$"`
|
||
(validation du brief d'entrée) + sa `description` (qui n'en avait **aucune**).
|
||
|
||
**Le « vert trompeur »** — si CLAUDE.md §Projets AJOUTE un 10ᵉ projet (`P10 …`,
|
||
évènement de portefeuille réel), la garde ET les deux patterns REJETTENT `P10`
|
||
comme « invalide (attendu P01..P09) » : le générateur **REFUSE de scaffolder un
|
||
projet LÉGITIMEMENT mandaté** (bug d'exécution) pendant que la suite `tests/`
|
||
reste VERTE (oracles P01/P02 self-consistants, jamais comparés à §Projets).
|
||
Symétriquement, RETIRER un projet laisse la garde trop PERMISSIVE (accepte un code
|
||
démobilisé). Ni `check_artifacts` (schémas/`.py` ≠ `out/*.json`) ni `tests/`
|
||
(teste la FONCTION `_validate_brief`, pas son ANCRAGE à §Projets) ne mordent.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Faisabilité/generator ·
|
||
DOMAINE DES CODES PROJET », juste après « Fiches #10 ») — RE-DÉRIVE le catalogue
|
||
de codes depuis `CLAUDE.md §Projets` (`\bP\d{2,}\b` sur la ligne `## Projets` ·
|
||
source unique) puis exige que chaque surface accepte **EXACTEMENT** cet ensemble :
|
||
(a) la garde `_validate_brief` — reconstruit l'ensemble ACCEPTÉ `{préfixe+chiffre}`
|
||
depuis le code source et set-diff == §Projets ; le message cite les bornes
|
||
min..max ; (b)/(c) les 2 patterns JSON — **test COMPORTEMENTAL** (compile le
|
||
pattern, l'applique à une sonde `§Projets ∪ {P00,P10,P99,P0,PXX,P1,P010,Q01}`,
|
||
exige accepté == §Projets · robuste à la forme de la classe de caractères) + la
|
||
`description` DÉCLARE l'ancrage « CLAUDE.md §Projets » et cite les bornes. Une
|
||
surface INTROUVABLE échoue AUSSI (ancre morte · anti-évaporation #6). Deux
|
||
descriptions de schéma enrichies pour porter l'ancrage (brief.schema en gagne une).
|
||
|
||
**Vérif** — 6 checks verts sur l'arbre propre. **6 morsures** adversariales :
|
||
M1 garde retire le chiffre `7` (validateur trop restrictif) ⇒ set ≠ · M2 message
|
||
`P01..P09→P01..P08` ⇒ bornes ≠ · M3 pattern version élargi `^P0[0-9]$` ⇒ accepte
|
||
`P00` en trop · M4 brief.schema perd l'ancrage §Projets ⇒ ne DÉCLARE plus · **M5
|
||
SILENT-GREEN : CLAUDE.md §Projets ajoute `P10` SEUL (les 4 surfaces inchangées) ⇒
|
||
CASCADE sur les 6 lignes (garde accepte≠, 2 patterns manquent P10, message +2
|
||
descriptions bornes P09≠P10) — preuve que l'autorité est §Projets, pas la
|
||
cohérence interne** · M6 version.schema perd les bornes de sa description. Les
|
||
**16 tests unittest** du générateur restent verts (garde inchangée). Gates
|
||
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · RBAC SPEC §3 — ancrage de la colonne « Familles rattachées » PAR portail sur le contrat
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). La table §3 « Cartographie portails ↔ familles » de
|
||
`05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` porte DEUX colonnes data-derived
|
||
du contrat `rbac_50_roles.json` : « Nb rôles » (déjà gatée, bloc §3 count) et —
|
||
juste à sa gauche — « **Familles rattachées** » : pour chaque portail, l'ENSEMBLE
|
||
des `famille` distinctes de ses rôles (ex. `direction` → {direction, finance,
|
||
faisabilite, legal}). Cette 2ᵉ colonne était **NON gatée** : le bloc §3 count la
|
||
capture avec `[^|]*` (il ne lit QUE le compte), le §5 « résumé » traite volontairement
|
||
les regroupements comme ÉDITORIAUX (pas de mapping famille↔groupe, cf. memory
|
||
`rbac-spec-portail-count-gate`), et `check_artifacts` est aveugle (la SPEC n'est
|
||
pas un `out/*.json`). Même classe que « colonne SET gatée seulement sur son COUNT
|
||
voisin » (roleprofile per-profil count vs quels rôles).
|
||
|
||
**Le « vert trompeur »** — reclasser la `famille` d'un rôle À L'INTÉRIEUR d'un
|
||
portail change le SET de familles **sans** changer le « Nb rôles » du portail → la
|
||
cellule §3 ment en silence pendant que le count-gate reste VERT. Prouvé (M4) :
|
||
reclasser les DEUX rôles `legal` de `direction` (Directeur Juridique + Agent ONAPI)
|
||
en `finance` laisse le compte à **9==9 VERT** tandis que le nouveau gate mord
|
||
**FANTÔME {legal}** (la SPEC liste encore `legal`, le contrat ne l'a plus).
|
||
Ajouter/retirer une famille (rôle d'une famille inédite) laisse aussi la cellule
|
||
périmée. Aucune suite `tests/` (FONCTIONS de validation, pas l'ancre SPEC↔contrat)
|
||
ne mord.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1quater-bis » juste après le
|
||
§3 count-gate, RÉUTILISE `src_roles`/`spec_rd` déjà chargés) — RECOMPUTE le SET de
|
||
familles distinctes PAR portail depuis `rbac_50_roles.json[roles][].famille` et
|
||
exige, par ENSEMBLE (absent ET fantôme), l'égalité avec les jetons `famille` de la
|
||
cellule. On ne gate QUE les jetons `famille` bruts (identifiant de tête de chaque
|
||
item séparé par `,`, via `(?:^|,)\s*([a-z]+)`), **PAS** les parenthèses ÉDITORIALES
|
||
(« finance (CFO) », « faisabilite (analyste/rendu/IFC/économiste) » — paraphrase,
|
||
pas contrat, #6). Ordre NON gaté (réordonner les rôles dans le contrat byte-gaté
|
||
est sémantiquement neutre pour l'appartenance). Un portail sans cellule échoue AUSSI
|
||
(INTROUVABLE = régression de traçabilité #6).
|
||
|
||
**Vérif** — 6 checks verts sur l'arbre propre (un par portail). **5 morsures**
|
||
adversariales : M1 SPEC retire `legal` de `direction` ⇒ MANQUE {legal} · M2 SPEC
|
||
ajoute `rh` à `achat` ⇒ FANTÔME {rh} · M3 SPEC `finance`→`financ` sur `compta` ⇒
|
||
MANQUE {finance} · FANTÔME {financ} · **M4 SILENT-GREEN : contrat reclasse les 2
|
||
rôles `legal`→`finance` dans `direction`, compte inchangé (count-gate 9==9 VERT) ⇒
|
||
familles-gate mord FANTÔME {legal} — preuve que l'autorité est le CONTRAT, pas la
|
||
cohérence interne de la SPEC** · M5 SPEC supprime la ligne `direction` ⇒ cellule
|
||
INTROUVABLE. Gates re-verts : `check_artifacts` · `check_ci_integrity` ·
|
||
`check_docs` · `check_readme_claims` · `check_regression` · `guard_constraints`
|
||
tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Faisabilité — ancrage du TEMPLATE CANONIQUE v1.0 (5 surfaces #9/#10) sur CLAUDE.md
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le pattern « constante canonique CLAUDE.md recopiée à la main dans
|
||
une surface data-derived » (re-dérive la valeur depuis la contrainte, jamais la
|
||
cohérence interne) couvre déjà le générateur (`model.py`/README/renderer/scorer/
|
||
oracle-test), les fiches ERPNext/CRM (« Fiches #10 »), le module fiscal (`moneda`),
|
||
la marque SEO §Entités, la persona Chat OTOIA, les tokens branding #4, etc. Un
|
||
fichier restait NON gaté (grep : absent de tous les `ci/*.sh`) et c'est
|
||
paradoxalement **le plus normatif** : `05_deliverables_mvp/faisabilite/
|
||
TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md`, le DOCUMENT-MODÈLE « CANONIQUE » (Livrable
|
||
Sprint 1) que **toute faisabilité en production DOIT reproduire**, et que le
|
||
générateur 4 volets + le Publiciste + la régénération batch S4 sont censés suivre.
|
||
|
||
Ce template recopie **à la main les 6 valeurs de CLAUDE.md #9/#10 à CINQ surfaces
|
||
structurées**, toutes se réclamant de « contrainte #9 »/« #10 », **aucune gatée** :
|
||
1. **l.28** intro §0 « frais **3 %** · marketing **8.5 %** · point d'équilibre
|
||
**52 %** » (3 pourcentages #9, associés par mot-clé) ;
|
||
2. **l.144-146** §5.2 — 3 lignes de table « **N %** (fixe · contrainte #9) » ;
|
||
3. **l.29** intro §0 « **USD + DOP** · format **Letter US** · paiements **Cardnet** » ;
|
||
4. **l.149-151** §5.2 — 3 lignes « **valeur** (… contrainte #10) » (la ligne
|
||
paiement citant l'exclusion « ❌ pas-X ») ;
|
||
5. **l.196** §7 checklist « 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet
|
||
présents » (énumération des 6 marqueurs).
|
||
|
||
**Le « vert trompeur »** — si CLAUDE.md #9 passe **52 %→55 %** (ou #10 renomme le
|
||
prestataire) et que les modules s'alignent, ce TEMPLATE — la RÉFÉRENCE de toutes
|
||
les faisabilités — reste PÉRIMÉ en silence, imposant à chaque projet un paramètre
|
||
qui CONTREDIT le mandat pendant qu'il se réclame « contrainte #9/#10 ». Ni
|
||
`check_artifacts` (ce n'est pas un `out/*.json`) ni `check_docs`/`guard_constraints`
|
||
ni aucune suite `tests/` (FONCTIONS, jamais l'ancre à CLAUDE.md) ne mordent.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « TEMPLATE FAISABILITÉ CANONIQUE
|
||
v1.0 · PARAMÈTRES #9/#10 », juste après « Fiches #10 ») — RÉUTILISE `exp` (les 6
|
||
valeurs déjà recomputées des lignes #9/#10 de CLAUDE.md · zéro re-dérivation) +
|
||
`exp_excl` (prestataire exclu du `(pas X)`). Exige que chacune des 5 surfaces ==
|
||
CLAUDE.md : (1) 3 pcts associés par mot-clé ; (2) SET des 3 lignes « (fixe ·
|
||
contrainte #9) » == {3 %,8.5 %,52 %} (mord aussi ligne ajoutée/retirée) ; (3)/(4)
|
||
chaque valeur #10 présente en gras dans sa région ancrée + l'exclusion ❌ ; (5)
|
||
énumération des 6 marqueurs == ensemble EXACT (set-diff absent ET en trop). Une
|
||
surface INTROUVABLE échoue AUSSI (recensement évaporé = régression #6).
|
||
|
||
**Vérif** — 5 checks verts sur l'arbre propre. **11 morsures** adversariales :
|
||
M1 intro#9 52→55 · M2 ligne#9 8.5→9 · M3 intro#10 Cardnet→Azul · M4 ligne#10
|
||
Letter US→A4 · M5 exclusion ❌ Stripe→PayPal · M6 checklist perd Cardnet · M7
|
||
checklist 52→60 · M8 intro#9 supprimée (INTROUVABLE) · M9 checklist « présents »→
|
||
« présent » (INTROUVABLE) · **M10 CLAUDE.md #9 52→55 SEUL ⇒ cascade sur 3 régions
|
||
(1)(2)(5)** · **M11 CLAUDE.md #10 Cardnet→Azul SEUL ⇒ cascade sur 3 régions
|
||
(3)(4)(5)** — les deux preuves que l'AUTORITÉ est CLAUDE.md, pas la cohérence
|
||
interne du template. Un piège corrigé en route : le groupe capturé est le NOMBRE nu
|
||
(sans `%`), donc `_pct()` (qui exige un `%`) renvoyait `None` → `float()` direct.
|
||
Gates re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Fiscal e-CF — ancrage du CONTRAT DE FORMAT e-NCF dans le COMPOSEUR `ncf.py` (CODE) + son ORACLE de test
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le bloc « Fiscal · e-CF DGII · CONTRAT DE FORMAT » existant ancre le
|
||
format de l'identifiant fiscal DGII (`E + tipoeCF(2) + secuencia(10)` = 13,
|
||
Ley 32-23) sur le SEUL bloc `e_ncf` byte-gaté de `out/ecf_plan.json` (dérivé de
|
||
`ecf_spec.json`) et couvre **4 surfaces de PROSE** : README ×3 + fiche
|
||
`erpnext_backend`. Mais le **composeur d'exécution lui-même** — `ecflib/ncf.py`,
|
||
le code Frappe qui VALIDE et COMPOSE réellement le numéro e-NCF côté VPS/Compupar
|
||
— porte **TROIS copies INDÉPENDANTES du format, AUCUNE dérivée de l'artefact**, +
|
||
un oracle de test qui les bénit, tous **NON gatés** :
|
||
|
||
1. `ENCF_RE = re.compile(r"^E([0-9]{2})([0-9]{10})$")` (l.26) — la **regex de
|
||
VALIDATION** (cœur de `is_valid_encf`/`parse_encf`) ;
|
||
2. `FORMULE = "E + tipoeCF(2) + secuencia(10)"` (l.27) — la chaîne **publiée par
|
||
`compose_encf`** (renvoyée au caller VPS) ;
|
||
3. docstring l.8 « `"E" + tipoeCF (2 chiffres) + secuencia (10 chiffres) → 13
|
||
caracteres` » + commentaire l.25 « Ley 32-23 : E + tipo(2) + secuencia(10) =
|
||
13 » (deux proses-en-code) ;
|
||
4. `tests/test_ecf_dgii.py:246` — oracle hardcodé `assertEqual(…["formule"],
|
||
"E + tipoeCF(2) + secuencia(10)")`.
|
||
|
||
**Le « vert trompeur »** — un rango DGII à 12 chiffres (évènement ops réel, cité
|
||
dans le commentaire du gate existant) régénère `ecf_spec→ecf_plan`, donc les 4
|
||
proses gatées SUIVENT. Mais `ENCF_RE` reste `{2}{10}` : `compose_encf` zfill à 10
|
||
et `is_valid_encf` **REJETTE** un e-NCF désormais valide → **bug d'exécution du
|
||
composeur fiscal**, pendant que la suite `tests/` reste **VERTE** (son oracle ==
|
||
`FORMULE` périmée, self-consistante). Ni `check_artifacts` (`ncf.py` n'est pas un
|
||
`out/*.json`) ni `tests/` (teste les FONCTIONS, pas leur ANCRAGE à l'artefact) ne
|
||
mordent. Même patron que l'oracle/formateur du Publiciste déjà gaté.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, extension du bloc e-NCF · surfaces
|
||
5→9) — RÉUTILISE `prefix/tipo_len/seq_len/total` déjà dérivés du bloc `e_ncf`
|
||
byte-gaté + le helper `ecf_check` (zéro re-dérivation du format #6). Ancre les 5
|
||
surfaces CODE/test au SEUL artefact : (5) `ENCF_RE`, (6) `FORMULE`, (7) docstring,
|
||
(8) commentaire, (9) oracle de test. Une surface absente échoue AUSSI (INTROUVABLE
|
||
· anti-évaporation #6).
|
||
|
||
**Vérif** — 5 checks verts sur l'arbre propre. **7 morsures** adversariales :
|
||
M1 `ENCF_RE {10}→{12}` · M2 `FORMULE secuencia(10)→(12)` · M3 docstring `10→12
|
||
chiffres` · M4 commentaire `secuencia(10)→(12)` · M5 oracle test `(10)→(12)` ·
|
||
**M6 SILENT-GREEN — l'ARTEFACT passe à seq=12 pendant que `ncf.py` reste à 10 ⇒
|
||
les 5 surfaces mordent TOUTES (preuve que l'autorité est l'artefact byte-gaté, pas
|
||
la cohérence interne du composeur)** · M7 `FORMULE` renommée ⇒ INTROUVABLE. Les
|
||
**39 tests unittest** e-CF restent verts (composeur inchangé). Gates re-verts :
|
||
`check_artifacts` · `check_ci_integrity` · `check_docs` · `check_readme_claims` ·
|
||
`check_regression` · `guard_constraints` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · CRM Commissions — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate` (7ᵉ instance du ledger)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
|
||
CI depuis le registre-source `# N ·` de `_validate`, jamais une liste à la main)
|
||
a déjà couvert **Chat OTOIA**, **SEO**, **Fiscal e-CF**, **Legal CONFOTUR**,
|
||
**CRM Dossier Vente** et **QA Audit 5D**. Le **7ᵉ générateur** — dernier candidat
|
||
restant recensé au ledger (`crm/commissions` ·10) — était NON gaté :
|
||
`crm/commissions/commissions_gen.py` (barème de commissions vendeurs · roadmap l.51).
|
||
|
||
Le compte **« 10 invariants »** y est transcrit **à la main à CINQ endroits**,
|
||
aucun gaté, tous invisibles à `check_artifacts` (le `.py` source n'est pas un
|
||
`out/*.json`) :
|
||
1. message de succès CLI `validate` « … schéma + 10 invariants verts. » (l.209) ;
|
||
2. aide du sous-parseur `validate` « schéma + 10 invariants » (l.221) ;
|
||
3. README bloc de commande « schéma + 10 invariants » (l.60) ;
|
||
4. README titre de section « ## Les 10 invariants » (l.64) ;
|
||
5. README prose de score « 10 invariants de cross-cohérence » (l.92).
|
||
|
||
Ajouter un contrôle `# 11 ·` à `_validate` sans toucher les 5 chaînes ⇒ le README
|
||
ET les self-reports du `.py` mentent en silence pendant que la CI applique 11
|
||
contrôles. Aucune suite `tests/` (qui teste des FONCTIONS de validation, pas la
|
||
prose) n'attrape ce « vert trompeur ».
|
||
|
||
**Particularité du registre Commissions** (≈ Dossier Vente) : le ledger est
|
||
**PROPRE 1..10** — le **schéma est compté À PART** (phrasé « schéma + N
|
||
invariants »), donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la
|
||
détection de doublon nu comme filet. Le module porte AUSSI un **SECOND nombre** —
|
||
« **25 tests** » (la suite unittest, déjà gatée par ailleurs) présent dans le
|
||
README : l'ancre sur le mot « invariants » l'exclut nativement (« 25 tests » ≠
|
||
« invariants ») ⇒ aucun faux positif. Comme Dossier Vente, ce README A un titre
|
||
« ## Les N invariants » ET une énumération numérotée du ledger → on gate (a)(b)(c)(d)(e).
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « CRM · Commissions · IDENTITÉ
|
||
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
|
||
(`# N ·` de `_validate`) et exige :
|
||
- **(a)** ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
|
||
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
|
||
- **(c)** l'annonce du titre README « ## Les N invariants » == |ledger| ;
|
||
- **(d)** toute mention « N invariants » du README == |ledger| ;
|
||
- **(e)** l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N.
|
||
Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).
|
||
|
||
**Vérif** — 5 checks verts sur l'arbre propre (ledger 1..10 = 10 marqueurs
|
||
`# N ·`, schéma compté à part). **7 morsures** adversariales : M1 self-report
|
||
`.py` 10→11 (b) · M2 titre README 10→11 (c+d) · M3 prose README 10→11 (d) · **M4
|
||
ajout marqueur `# 11 ·` ⇒ ledger→11 pendant que la prose reste 10 (mord `.py` ET
|
||
README titre/mention ET l'énumération, preuve que l'autorité est le CODE)** · M5
|
||
suppression `# 7 ·` ⇒ gap non-contigu (a) · M6 doublon nu `# 5 ·` ⇒ détection de
|
||
vrai doublon (a) · M7 énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Le distinct
|
||
« 25 tests » reste INVISIBLE au gate (ancre « invariants »). Gates re-verts :
|
||
`check_artifacts` · `check_docs` · `guard_constraints` · `check_ci_integrity` ·
|
||
`check_regression` · `check_readme_claims` tous exit 0. **Série ledger close : les
|
||
7 générateurs portant le nombre magique d'invariants sont désormais tous gatés.**
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · QA Audit 5D — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate` (6ᵉ instance du ledger)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
|
||
CI depuis le registre-source `# N ·` de `_validate`, jamais une liste à la main)
|
||
a déjà couvert **Chat OTOIA**, **SEO**, **Fiscal e-CF**, **Legal CONFOTUR** et
|
||
**CRM Dossier Vente**. Le **6ᵉ générateur** portant le même nombre magique restait
|
||
NON gaté : `qa/audit_5d/audit_5d_gen.py` (candidat restant recensé : `crm/commissions`
|
||
·10, ledger propre non gaté).
|
||
|
||
Le compte **« 15 invariants »** y est transcrit **à la main à SIX endroits**,
|
||
aucun gaté, tous invisibles à `check_artifacts` (le `.py` source n'est pas un
|
||
`out/*.json`) :
|
||
1. docstring sous-commande module (l.19 « valide schéma + 15 invariants ») ;
|
||
2. docstring `_validate` (l.80 « 15 invariants de cohérence audit/contrôles ») ;
|
||
3. message de succès CLI `validate` « … 15 invariants — verdict … » (l.218) ;
|
||
4. aide du sous-parseur `validate` « valide schéma + 15 invariants » (l.229) ;
|
||
5. README bloc de commande « schéma + 15 invariants » (l.66) ;
|
||
6. README prose d'architecture « CLI build/validate + **15 invariants** » (l.81).
|
||
|
||
Ajouter un contrôle `# 16 ·` à `_validate` sans toucher les 6 chaînes ⇒ le README
|
||
ET les self-reports du `.py` mentent en silence pendant que la CI applique 16
|
||
contrôles. Aucune suite `tests/` (qui teste des FONCTIONS de validation, pas la
|
||
prose) n'attrape ce « vert trompeur ».
|
||
|
||
**Particularité du registre Audit 5D** (≈ Dossier Vente, ≠ Fiscal) : le ledger est
|
||
**PROPRE 1..15** — le **schéma est compté À PART** (phrasé « schéma + N invariants »),
|
||
donc **pas de schéma-#1 non marqué** et **pas de split a/b** ; on garde la détection
|
||
de doublon nu comme filet. MAIS le module porte un **SECOND nombre distinct** —
|
||
« **17 contrôles** » (le compte de fonctions-contrôle de `qalib/controls.py`, déjà
|
||
gaté par ailleurs), présent dans le `.py` ET le README : un `\d+ invariants` ancré
|
||
sur le mot « invariants » l'**exclut nativement** (« 17 contrôles » ≠ mot
|
||
« invariants ») ⇒ **aucun faux positif** sur l'arbre propre. Contrairement à
|
||
Dossier Vente/Fiscal, ce README n'a **NI titre « ## Les N invariants » NI
|
||
énumération numérotée** du ledger → on ne gate que (a)(b)(d).
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « QA · Audit 5D · IDENTITÉ du
|
||
COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
|
||
(`# N ·` de `_validate`) et exige :
|
||
- **(a)** ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
|
||
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
|
||
- **(d)** toute mention « N invariants » du README == |ledger|.
|
||
Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).
|
||
|
||
**Vérif** — 3 checks verts sur l'arbre propre (ledger 1..15 = 15 marqueurs `# N ·`,
|
||
schéma compté à part). **6 morsures** adversariales : M1 self-report `.py` 15→16
|
||
(b) · M2 mention README 15→16 (d) · **M3 ajout marqueur `# 16 ·` ⇒ ledger→16 pendant
|
||
que la prose reste 15 (mord `.py` ET README, preuve que l'autorité est le CODE)** ·
|
||
M4 suppression `# 7 ·` ⇒ gap non-contigu (a) · M5 doublon nu `# 5 ·` ⇒ détection de
|
||
vrai doublon (a) · **M6 no-false-positive** : le distinct « 17 contrôles » reste
|
||
INVISIBLE au gate (l'ancre « invariants » l'exclut, arbre propre VERT). Gates
|
||
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · CRM Dossier Vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate` (5ᵉ instance du ledger)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
|
||
CI depuis le registre-source `# N ·` de `_validate`, jamais une liste à la main)
|
||
a déjà couvert **Chat OTOIA**, **SEO**, **Fiscal e-CF** et **Legal CONFOTUR**. Le
|
||
**5ᵉ générateur** portant le même nombre magique restait NON gaté :
|
||
`crm/dossier_vente/doctype_dossier_vente_gen.py` (candidats restants recensés :
|
||
`qa/audit_5d` ·15, `crm/commissions` ·10, tous ledgers propres non gatés).
|
||
|
||
Le compte **« 12 invariants »** y est transcrit **à la main à SEPT endroits**,
|
||
aucun gaté, tous invisibles à `check_artifacts` (le `.py` source n'est pas un
|
||
`out/*.json`) :
|
||
1. docstring module (l.14 « schéma + 12 invariants de ») ;
|
||
2. docstring `_validate` (l.77 « 12 invariants de cross-cohérence ») ;
|
||
3. message de succès CLI `validate` « … schéma + 12 invariants verts. » (l.218) ;
|
||
4. aide du sous-parseur `validate` (l.230) ;
|
||
5. README bloc de commande « schéma + 12 invariants » (l.56) ;
|
||
6. README titre de section « ## Les 12 invariants » (l.60) ;
|
||
7. README prose de score « 12 invariants de cross-cohérence » (l.83).
|
||
|
||
Ajouter un contrôle `# 13 ·` à `_validate` sans toucher les 7 chaînes ⇒ le README
|
||
ET les self-reports du `.py` mentent en silence pendant que la CI applique 13
|
||
contrôles. Aucune suite `tests/` (qui teste des FONCTIONS de validation, pas la
|
||
prose) n'attrape ce « vert trompeur ».
|
||
|
||
**Particularité du registre Dossier Vente** (≈ CONFOTUR, ≠ Fiscal) : le ledger est
|
||
**PROPRE 1..12** — le **schéma est compté À PART** (phrasé « schéma + N invariants »),
|
||
donc **pas de schéma-#1 non marqué** et **pas de split a/b**. On garde néanmoins la
|
||
détection de doublon nu comme filet. Le README porte AUSSI une **énumération
|
||
numérotée** sous le titre de section (item 1 = conformité au schéma, puis les
|
||
contrôles), gatée en contiguïté 1..N.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « CRM · Dossier Vente · IDENTITÉ
|
||
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
|
||
(`# N ·` de `_validate`) et exige :
|
||
- **(a)** ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
|
||
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
|
||
- **(c)** l'annonce du titre README « ## Les N invariants » == |ledger| ;
|
||
- **(d)** toute mention « N invariants » du README == |ledger| ;
|
||
- **(e)** l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N.
|
||
Une mention ou le registre absent échoue AUSSI (traçabilité #6).
|
||
|
||
**Vérif** — 5 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs `# N ·`,
|
||
schéma compté à part). **7 morsures** adversariales : M1 self-report `.py` 12→13
|
||
(b) · M2 titre README 12→13 (c+d) · M3 prose README 12→13 (d) · **M4 ajout marqueur
|
||
`# 13 ·` ⇒ ledger→13 pendant que la prose reste 12 (mord `.py` ET README ET
|
||
l'énumération, preuve que l'autorité est le CODE)** · M5 suppression `# 7 ·` ⇒ gap
|
||
non-contigu (a) · M6 doublon nu `# 5 ·` ⇒ détection de vrai doublon (a) · M7
|
||
énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Gates re-verts : `check_artifacts`
|
||
· `check_docs` · `guard_constraints` · `check_ci_integrity` · `check_regression` ·
|
||
`check_readme_claims` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Legal CONFOTUR — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate` (4ᵉ instance du ledger)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
|
||
CI depuis le registre-source `# N ·` de `_validate`, jamais une liste à la main)
|
||
a déjà couvert **Chat OTOIA**, **SEO** et **Fiscal e-CF**. Le **4ᵉ générateur**
|
||
portant le même nombre magique restait NON gaté :
|
||
`legal/confotur/confotur_application_gen.py`.
|
||
|
||
Le compte **« 14 invariants »** y est transcrit **à la main à SIX endroits**,
|
||
aucun gaté, tous invisibles à `check_artifacts` (le `.py` source n'est pas un
|
||
`out/*.json`) :
|
||
1. docstring module (l.24 « schéma + 14 invariants de ») ;
|
||
2. docstring `_validate` (l.87 « 14 invariants de cross-coherence ») ;
|
||
3. message de succès CLI `validate` « … schéma + 14 invariants verts. » (l.283) ;
|
||
4. aide du sous-parseur `validate` (l.295) ;
|
||
5. README bloc de commande « schéma + 14 invariants » (l.56) ;
|
||
6. README commentaire d'arborescence « CLI build/validate · 14 invariants » (l.82).
|
||
|
||
Ajouter un contrôle `# 15 ·` à `_validate` sans toucher les 6 chaînes ⇒ le README
|
||
ET les self-reports du `.py` mentent en silence pendant que la CI applique 15
|
||
contrôles. Aucune suite `tests/` (qui teste des FONCTIONS de validation, pas la
|
||
prose) n'attrape ce « vert trompeur ».
|
||
|
||
**Particularité du registre CONFOTUR** (≠ Fiscal) : le ledger est **PROPRE 1..14**
|
||
(pas de schéma-#1 non marqué, pas de split a/b). MAIS le README porte AUSSI un
|
||
compte de **SOUS-ENSEMBLE éditorial** — « **2 invariants** refusent tout champ
|
||
monétaire » (l.92) — à NE PAS confondre avec le total. Un `\d+ invariants` naïf
|
||
mordrait donc l'arbre PROPRE (il verrait `{2, 14}`). Le gate exclut le
|
||
sous-ensemble par **lookahead négatif** `(\d+)\s+invariants(?!\s+refusent)`,
|
||
appliqué au `.py` comme au README.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Legal · CONFOTUR · IDENTITÉ
|
||
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
|
||
(`# N ·` de `_validate`) et exige :
|
||
- **(a)** ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
|
||
- **(b)** toute mention-total « N invariants » du `.py` (hors « refusent ») == |ledger| ;
|
||
- **(c)** toute mention-total du README (hors « refusent ») == |ledger|.
|
||
Une mention ou le registre absent échoue AUSSI (disparition d'un chiffre =
|
||
régression de traçabilité #6).
|
||
|
||
**Vérif** — 3 checks verts sur l'arbre propre (ledger 1..14 = 14 marqueurs `# N ·`).
|
||
**6 morsures** adversariales : M1 self-report `.py` 14→15 (b) · M2 README commande
|
||
14→15 (c) · M3 README arborescence 14→13 (c) · **M4 ajout marqueur `# 15 ·` ⇒
|
||
ledger→15 pendant que la prose reste 14 (mord `.py` ET README, preuve que
|
||
l'autorité est le CODE)** · M5 suppression `# 7 ·` ⇒ gap non-contigu (a) · M6
|
||
doublon nu `# 5 ·` ⇒ détection de vrai doublon (a). **M7 no-false-positive** :
|
||
sous-ensemble « 2 → 3 invariants refusent » reste VERT (le lookahead exclut bien
|
||
le sous-ensemble). Gates re-verts : `check_artifacts` · `check_docs` ·
|
||
`guard_constraints` · `check_ci_integrity` · `check_regression` ·
|
||
`check_readme_claims` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Publiciste — gate d'ANCRAGE des DEVISES du site public sur CLAUDE.md #10
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le bloc `Publiciste/branding` existant ancre les **4 tokens DESIGN**
|
||
de CLAUDE.md #4 (2 couleurs + 2 typographies) sur leurs 4 copies. Mais le MÊME
|
||
module publiciste porte une **2ᵉ constante constitutionnelle** — les **devises
|
||
CLAUDE.md #10 (« USD + DOP »)** — recopiée à 4 endroits data-derived, **aucun
|
||
gaté**, tous invisibles à `check_artifacts` (pas un `out/*.json`) :
|
||
|
||
1. `lib/branding.py` `DEVISE_PRIMAIRE`/`DEVISE_SECONDAIRE` — constantes auto-
|
||
étiquetées « contrainte #10 · USD + DOP », sans gate ;
|
||
2. `lib/generator.py` `_fmt_usd`/`_fmt_dop` — les **formateurs qui IMPRIMENT le
|
||
code devise sur la grille de prix du site public** `vente.otov7.com` (sortie
|
||
face-public : une devise hors #10 = mandat trahi en silence) ;
|
||
3. `lib/generator.py` footer HTML « Prix … en USD + DOP (Cardnet) » ;
|
||
4. `tests/test_publiciste.py` — l'oracle hardcodé `assertIn("USD 150 000", …)`,
|
||
une copie de plus jamais comparée à CLAUDE.md.
|
||
|
||
Aucune suite `tests/` (qui teste des **fonctions** de rendu, pas l'ancre à
|
||
CLAUDE.md) n'attrape la dérive : renommer la devise #10 et aligner le module
|
||
reste vert alors que le site émettrait la mauvaise devise ; ne rien bouger reste
|
||
vert alors que `branding.py` se dit « #10 » en mentant.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Publiciste/generator+branding
|
||
· DEVISES CANONIQUES ») — RE-PARSE les 2 devises depuis la ligne #10 de CLAUDE.md
|
||
(indépendamment de `exp` amont · aucun skip hérité) puis exige :
|
||
- **(a)** `branding.py` DEVISE_PRIMAIRE/SECONDAIRE == #10 (ordre inclus) ;
|
||
- **(b)** l'ensemble des codes émis par les formateurs `generator.py` == #10 ;
|
||
- **(c)** le footer HTML cite les 2 devises dans l'ordre + `(Cardnet)` ;
|
||
- **(d)** l'oracle du test == devise primaire #10.
|
||
Un claim absent échoue AUSSI (traçabilité #6 · anti-évaporation).
|
||
|
||
**Vérif** — 5 checks verts sur l'arbre propre. **6 morsures** adversariales :
|
||
M1 branding primaire USD→EUR · M2 formateur `_fmt_usd`→EUR (hors #10) · M3 footer
|
||
USD+DOP→USD+EUR · M4 oracle test → EUR · **M5 CLAUDE.md #10 USD→EUR = cascade sur
|
||
les 4 copies simultanément** (preuve que l'ancre est CLAUDE.md, pas la cohérence
|
||
interne) · M6 footer supprimé → INTROUVABLE. Gates re-verts :
|
||
`check_readme_claims` · `check_docs` · `guard_constraints` · `check_ci_integrity`
|
||
tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Fiscal e-CF DGII : gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate`
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). Le pattern « ledger d'invariants » a déjà été appliqué à **Chat
|
||
OTOIA** et **SEO** (recompute le nombre d'invariants CI depuis le registre-source
|
||
`# N ·` de `_validate`, jamais une liste à la main). Le **3ᵉ générateur** portant
|
||
le même nombre magique restait NON couvert : `fiscal/ecf_dgii/ecf_dgii_gen.py`.
|
||
|
||
Le compte **« 12 invariants »** y est transcrit **à la main à SEPT endroits**,
|
||
aucun gaté, tous invisibles à `check_artifacts` (le `.py` source n'est pas un
|
||
`out/*.json`) :
|
||
1. `ecf_dgii_gen.py` docstring module (l.24) ;
|
||
2. docstring `_validate` (l.110) ;
|
||
3. message de succès CLI `validate` « … schéma + 12 invariants verts. » (l.277) ;
|
||
4. aide du sous-parseur `validate` (l.289) ;
|
||
5. README bloc de commande « schéma + 12 invariants » (l.65) ;
|
||
6. README titre de section « ## Les 12 invariants » (l.69) ;
|
||
7. README prose de score « 12 invariants de cross-cohérence » (l.105).
|
||
|
||
Ajouter un contrôle `# 13 ·` à `_validate` sans toucher les 7 chaînes ⇒ le README
|
||
ET les self-reports du `.py` mentent en silence pendant que la CI applique 13
|
||
contrôles. Aucune suite `tests/` (qui teste des FONCTIONS de validation, pas la
|
||
prose) n'attrape ce « vert trompeur ».
|
||
|
||
**Particularité du registre fiscal** (≠ SEO/Chat OTOIA au ledger propre 1..N) :
|
||
l'**invariant 1 est la validation de SCHÉMA** (`validate(bundle, schema)`, **non
|
||
marquée** `# 1 ·`), les marqueurs couvrent **2..N**, et l'invariant **12 est
|
||
splitté** en `# 12a ·` (unicité évènements) / `# 12b ·` (comptes du manifeste) —
|
||
deux volets d'un même invariant. Le gate reconstruit donc le ledger = `{1:schéma}`
|
||
∪ marqueurs-top-level, en collapsant 12a/12b, tout en **mordant un vrai doublon**
|
||
d'un numéro NON suffixé.
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Fiscal · e-CF DGII · IDENTITÉ
|
||
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
|
||
(`# N ·` de `_validate`) et exige :
|
||
- **(a)** ledger CONTIGU 1..N (schéma #1 présent + marqueurs 2..N sans lacune ;
|
||
doublon nu = défaut mordu ; 12a/12b comptés 1×) ;
|
||
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
|
||
- **(c)** l'annonce du titre README == |ledger| ;
|
||
- **(d)** toute mention « N invariants » du README == |ledger| ;
|
||
- **(e)** l'ÉNUMÉRATION numérotée du README (« 1. … 2. … N. ») elle-même contiguë
|
||
1..N (item ni sauté ni dupliqué).
|
||
|
||
**Vérif** — 5 checks verts sur l'arbre propre (ledger 1..12 = schéma #1 + 11
|
||
marqueurs). **8 morsures** adversariales : M1 self-report `.py` 12→13 (b) · M2
|
||
titre README 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout marqueur `# 13 ·`
|
||
⇒ ledger→13 pendant que la prose reste 12 (b+c+d, preuve que l'autorité est le
|
||
CODE, pas la prose) · M5 suppression `# 7 ·` ⇒ gap non-contigu (a) · M6 doublon nu
|
||
`# 5 ·` ⇒ détection de vrai doublon · M7 énumération README 7.→8. ⇒ deux 8/pas de
|
||
7 (e) · M8 suppression `validate(bundle, schema)` ⇒ invariant 1 introuvable. Gates
|
||
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_readme_claims` · `check_regression` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Mobile app_config : gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` (8ᵉ instance)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||
chiffres »). La classe « ledger d'invariants » (recompute le nombre d'invariants CI
|
||
depuis le registre-source numéroté de la fonction de validation, jamais une liste à
|
||
la main) était déjà appliquée à **7 générateurs** : Chat OTOIA · SEO · Fiscal e-CF ·
|
||
Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM Commissions. Le **8ᵉ**
|
||
générateur portant le même nombre magique restait NON couvert :
|
||
`mobile/app_config/app_config_gen.py`.
|
||
|
||
Le compte **« 12 invariants »** y est transcrit **à la main à QUATRE endroits**,
|
||
aucun gaté, tous invisibles à `check_artifacts` (le `.py`/`.md`/test n'est pas un
|
||
`out/*.json`) :
|
||
1. `app_config_gen.py` message de succès CLI « … schéma + 12 invariants verts. »
|
||
(l.228) ;
|
||
2. `README.md` bloc de commande « schéma + 12 invariants » (l.48) ;
|
||
3. `README.md` prose de récap « Schéma + 12\ninvariants verts » (l.67-68 — le « 12 »
|
||
et le mot séparés par un saut de ligne) ;
|
||
4. `tests/test_mobile_app_config.py` docstring « … + les 12 invariants du CLI »
|
||
(l.4).
|
||
|
||
Ajouter un contrôle `# Invariant 13 ·` à `_validate_bundle` sans toucher les 4
|
||
chaînes ⇒ le README, le self-report CLI ET la docstring de test mentent en silence
|
||
pendant que la CI applique 13 contrôles. Aucune suite `tests/` (qui EXÉCUTE la
|
||
validation mais ne compte jamais la prose) ne l'attrape.
|
||
|
||
**Particularités vs les 7 blocs précédents** (documentées dans l'en-tête du gate) :
|
||
- le registre-source utilise le style **`# Invariant N ·`** (le mot « Invariant »
|
||
précède le numéro) — les blocs ancrés sur le style nu `# N ·` NE le voient PAS ;
|
||
ancrage sur `#\s*Invariant\s+(\d+)…` ;
|
||
- la fonction s'appelle **`_validate_bundle`** (pas `_validate`) — l'ancre `def
|
||
_validate\b` des autres blocs échoue (`\b` échoue avant `_bundle`) ;
|
||
- ce README n'a NI titre « ## Les N invariants » NI énumération numérotée → on gate
|
||
**(a)(b)(b')(d)**, en AJOUTANT la surface **fichier-de-tests (b')**, absente des 7
|
||
blocs précédents (leurs tests ne portaient pas le chiffre).
|
||
|
||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Mobile · app_config · IDENTITÉ
|
||
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
|
||
(`# Invariant N ·` de `_validate_bundle`) et exige :
|
||
- **(a)** ledger CONTIGU 1..N (schéma compté à part ; lacune OU doublon nu = défaut
|
||
mordu) ;
|
||
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
|
||
- **(b')** toute mention « N invariants » du fichier de tests == |ledger| ;
|
||
- **(d)** toute mention « N invariants » du README == |ledger|.
|
||
|
||
**Vérif** — 4 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs). **8
|
||
morsures** adversariales : M1 self-report CLI 12→13 (b) · M2 README bloc-commande
|
||
12→13 (d) · M3 README récap (newline-split) 12→13 (d, preuve que `\s+` traverse le
|
||
saut de ligne) · M4 docstring test 12→13 (b', la surface neuve) · M5 ajout marqueur
|
||
`# Invariant 13 ·` ⇒ ledger→13 pendant que les 3 surfaces prose restent 12 (b+b'+d,
|
||
preuve que l'autorité est le CODE, pas la prose) · M6 suppression `# Invariant 7 ·`
|
||
⇒ gap non-contigu (a) + désync prose · M7 doublon nu `# Invariant 5 ·` ⇒ détection
|
||
de vrai doublon · M8 renommage `_validate_bundle` ⇒ registre introuvable. Gates
|
||
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||
`check_ci_integrity` · `check_readme_claims` · `check_regression` tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Doc QA : documentation du 2ᵉ étage QA (batterie de gates statiques `ci/*.sh`) dans `03_agents/qa/AGENT.md`
|
||
|
||
**Démarche — vérifier AVANT de gater (mémoire `verify-uncovered-before-gating`).**
|
||
La série récente empile des gates anti-dérive ; avant d'en ajouter un 9ᵉ, j'ai
|
||
d'abord cherché une **vraie dérive présente** plutôt qu'un gate spéculatif :
|
||
|
||
1. Scan ledger-vs-prose sur les 12 générateurs à ledger d'invariants. **Faux
|
||
signal** relevé : `fiscal/ecf_dgii` semblait afficher `ledger_markers=10` vs
|
||
`prose=12`. Investigation : le ledger n'est **pas** nu 1..12 — l'invariant #1
|
||
(validation de schéma, `validate(bundle, schema)`) n'est **pas** marqué `# 1 ·`
|
||
par design, et #12 est scindé en `12a`/`12b`. Le gate fiscal (`check_readme_claims.sh`
|
||
l.1908) **synthétise** déjà `ledger = marqueurs{2..12} ∪ {1}` et vérifie
|
||
self-report / titre README / mentions / énumération = 12. **Aucune dérive** ;
|
||
mon compteur grossier ne voyait ni le schéma-#1 ni `12a/12b`.
|
||
2. Sondé les comptes de `03_agents/{crm,qa}/AGENT.md` (« Total CRM 81 (25+31+25) »,
|
||
« 17 contrôles / 5 dimensions », « 8 livrables + 7 métriques ») → **tous gatés**
|
||
(l.681, 871, 2176, 2624, 3671…). Coverage **très dense**, conforme à la mémoire.
|
||
|
||
**Conclusion** : pas de gate à ajouter (ç'aurait été redondant · CLAUDE.md #5).
|
||
|
||
**Trou documentaire réel comblé.** Le titre + le scope de `qa/AGENT.md` promettent
|
||
des « **gates de méta-niveau** », mais le corps ne tabulait que les **4 modules
|
||
Python** d'audit (`audit_5d` · `audit_4big` · `regression` · `acceptance`) — il
|
||
**omettait entièrement** la batterie de **gates statiques `ci/*.sh`**, qui sont
|
||
pourtant l'application méta-niveau littérale (moitié de l'enforcement QA). Un
|
||
lecteur croyait donc QA = 4 modules.
|
||
|
||
**Ajout** — section « Deuxième étage QA : la batterie de gates statiques (`ci/*.sh`) »
|
||
listant les 7 gates courants par **nom + rôle** (`check_ci_integrity`,
|
||
`check_readme_claims`, `check_artifacts`, `check_regression`, `check_docs`,
|
||
`guard_constraints`, `validate_json`). **Anti-dérive by design** : le texte ancre
|
||
l'autorité du périmètre sur `check_ci_integrity` (qui **découvre** l'ensemble via
|
||
`git ls-files 'ci/*.sh'` et refuse tout gate décâblé/orphelin) et présente la liste
|
||
comme *l'état courant*, PAS une constante figée → aucun compte agrégé driftable
|
||
introduit.
|
||
|
||
**Sûreté** — prose sans lien Markdown neuf (check_docs), sans URL git interdite ni
|
||
terme CRM/paiement banni nu ; les lignes citant les prohibitions portent un
|
||
marqueur (`proscrites`/`interdits`/`bannie`) donc `guard_constraints` les laisse
|
||
passer. **Vérif** : 7 gates re-verts (guard_constraints · check_docs ·
|
||
check_readme_claims · check_ci_integrity · check_artifacts · check_regression ·
|
||
validate_json tous exit 0).
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Angle mort du gate « comptes de tests par suite » : la cellule `rbac` (fiche pointant une SPEC, pas un README)
|
||
|
||
**Démarche — vérifier AVANT de conclure « couvert » (mémoire `verify-uncovered-before-gating`).**
|
||
La série récente ayant épuisé les surfaces driftables évidentes, j'ai audité la
|
||
**complétude d'un gate existant** plutôt que d'en empiler un neuf (CLAUDE.md #5).
|
||
|
||
**Constat.** Le gate `check_readme_claims.sh` §« Fiches agents · comptes de tests
|
||
PAR SUITE (colonne « Tests ») » recompute chaque cellule `| N |` des tables de
|
||
livrables des `03_agents/*/AGENT.md` depuis l'autorité `regression_plan.json`
|
||
(`plan.suites[path].test_methods`). Son `row_re` exigeait pourtant que le lien de
|
||
la ligne pointe `…/README.md`. **Or une ligne y échappe** : la fiche
|
||
`erpnext_backend` (l.28) pointe le module `rbac/` via son **point d'entrée
|
||
`RBAC_50_ROLES_SPEC.md`** (le contrat 50-rôles, pas un README) — donc son
|
||
`row_re` ne mordait pas, et sa cellule « Tests | 10 | » restait **non vérifiée**.
|
||
|
||
**Mutation-test (preuve du trou).** Cellule `rbac` `10 → 99` sur la fiche →
|
||
`check_readme_claims` restait **VERT** (exit 0), aucune ligne `rbac` évaluée. C'est
|
||
exactement la classe « vert trompeur » que cette section du gate existe pour tuer
|
||
(un régénérateur de fiche pourrait y écrire un faux compte sans que la CI bronche).
|
||
La somme prose « RBAC 60 tests (10+11+12+11+16) » est **order-indépendante** → ne
|
||
contraint pas la cellule-table `rbac` individuelle. Vrai angle mort.
|
||
|
||
**Correctif (extension du gate, pas gate N+1).** `row_re` accepte désormais tout
|
||
`*.md` d'entrée du dossier module (`/[\w.-]+\.md` au lieu de `/README\.md`), pas
|
||
seulement README — le garde `path not in auth` (déjà présent) protège de tout
|
||
élargissement fortuit : seules les lignes pointant une **suite gatée connue** sont
|
||
évaluées, les autres restent ignorées. Une seule ligne entre ainsi dans le
|
||
périmètre : `rbac`. Commentaire mis à jour pour documenter le cas SPEC.
|
||
|
||
**Vérif.** (1) Arbre propre VERT + la ligne `rbac` est désormais **VUE**
|
||
(`AGENT.md:28 · rbac — Tests 10 == source (10)`). (2) Mutation `10→99` désormais
|
||
**MORDUE** (`Tests 99 MAIS source dit 10 → régénérer la fiche`, exit 1). (3)
|
||
Restauration → exit 0. **7 gates re-verts** : check_ci_integrity · check_artifacts ·
|
||
check_docs · check_readme_claims · check_regression · guard_constraints ·
|
||
validate_json tous exit 0.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Reprise du canal `daily_reports/` (lapsus depuis 2026-07-30) + sweep launch-readiness sourcée
|
||
|
||
**Constat.** Le canal **canonique** des rapports quotidiens
|
||
(`05_deliverables_mvp/daily_reports/YYYY-MM-DD.md` · fixé roadmap §Escalations,
|
||
référencé par les **13 fiches** `03_agents/*/AGENT.md`) n'avait **plus d'instantané
|
||
consolidé depuis le `2026-07-30`**. Or ~2 douzaines de sessions buffer ont continué
|
||
depuis — leur trace fine vit dans `05_activity_log/` (journal **technique par
|
||
session**), mais **aucun snapshot MVP sourcé** n'existait pour la phase Sprint 8.
|
||
Trou de reporting réel au niveau **stakeholder/direction**.
|
||
|
||
**Décision (workflow #5 — pas de gate redondant).** La série récente ayant épuisé
|
||
les surfaces driftables évidentes en durcissant des gates, et la phase Sprint 8
|
||
attendant une **vérification de régression exhaustive** (pas un module neuf), j'ai
|
||
(a) mené une **sweep de vérification launch-readiness** et (b) **repris le canal
|
||
`daily_reports/`** plutôt que d'empiler un gate N+1.
|
||
|
||
**Réalisé.**
|
||
- Créé `05_deliverables_mvp/daily_reports/2026-08-02.md` : instantané MVP **100 %
|
||
sourcé** (chaque chiffre re-vérifié contre l'artefact commité · anti-invention #6),
|
||
+ **note de convention** réconciliant les 2 canaux de journalisation
|
||
(`05_activity_log/` = technique/session · `daily_reports/` = synthèse/direction —
|
||
pas des doublons ; un lecteur d'`AGENT.md` était mal aiguillé vers un canal figé au
|
||
30/07).
|
||
- **Audit doc-vs-réalité de 2 fiches** (interdit « documenter du code sans vérifier
|
||
son existence courante ») : `crm/AGENT.md` table livrables **exacte** (25/31/25 = 81,
|
||
recomptés depuis `regression_plan.json`) ; `bim/AGENT.md` — son seul point de contact
|
||
commité `faisabilite/generator/genlib/renderer.py` **existe** et rend bien le Volet 4
|
||
Ingénierie en `{{…}}`. Aucune sur-promesse.
|
||
|
||
**État courant sourcé** (recalculé indépendamment, concordant) : **22/22** modules
|
||
qualité 4Big à 100/100 (`PASS`) · régression **22 suites · 564/564** tests
|
||
(`PASS`) · recette **15/15** promesses `in_repo` (`true`) · **13 agents** · **7 gates
|
||
CI** verts + reproductibilité `out/` byte-identique.
|
||
|
||
**Vérif.** Les **7 gates re-verts** (exit 0) avec le nouveau fichier :
|
||
check_docs (liens du rapport résolus) · guard_constraints (aucun terme interdit
|
||
introduit) · check_readme_claims · check_ci_integrity · validate_json ·
|
||
check_artifacts · check_regression. **Sûreté** : rapport dans le canal canonique
|
||
(roadmap), zéro chiffre inventé, tous liens Markdown vers cibles existantes.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Audit d'exactitude doc-vs-réalité des 11 fiches `AGENT.md` restantes — 0 dérive réelle · 2 « faux positifs » d'audit levés · 1 clarification de lisibilité (faisabilite)
|
||
|
||
**Nature** — Poursuite de la tâche explicitement recommandée par le daily report
|
||
`2026-08-02` (« Poursuivre l'audit d'exactitude des 11 fiches AGENT.md restantes,
|
||
mêmes contrôles que crm/bim — repérer toute sur-promesse d'artefact ou compte
|
||
périmé »). CLAUDE.md interdit « documenter du code sans vérifier son existence
|
||
courante ». **Pas un gate de plus** (workflow #5) : vérification, pas empilage.
|
||
|
||
**Méthode** — 4 agents `Explore` en parallèle sur les 11 fiches (devops ·
|
||
erpnext_backend · faisabilite · frontend_console · ifc_speckle · mobile ·
|
||
onapi_legal · publiciste · qa · rendu · seo). Pour chaque fiche : existence de
|
||
CHAQUE chemin in-repo cité · recompute de CHAQUE compte (tests via `grep -c
|
||
'def test_'` / `regression_plan.json` · invariants via marqueurs numérotés) ·
|
||
présence de CHAQUE artefact `out/*.json` · toute sur-promesse d'un livrable
|
||
in-repo inexistant.
|
||
|
||
**Résultat brut des agents** — 9 fiches « FULLY ACCURATE » (comptes de tests
|
||
recoupés : deploy_runbook 29 · e-CF 39 · confotur 44 · publiciste 23 · audit_5d 37
|
||
· audit_4big 34 · regression 26 · acceptance 37 · seo 36 · portails 19 · chat 31 ;
|
||
SEO 258 mots-clés / 10 nœuds schema.org / 10 pages hreflang ; Expo SDK 54). **2
|
||
fiches signalées en « dérive »** — toutes deux **VÉRIFIÉES FAUX POSITIFS** après
|
||
contrôle manuel (mémoire `verify-uncovered-before-gating` : prouver avant d'agir) :
|
||
|
||
1. **onapi_legal** (agent : 3 chemins « CRITICAL cassés » l.40/49/99 :
|
||
`../rbac/rbac_50_roles.json`, `../crm/workflow_vente/[out/]`). **Faux positif** :
|
||
ces chemins backtick sont **relatifs à l'arbre des livrables** (`05_deliverables_mvp/legal/`,
|
||
sujet de la fiche §24), PAS à l'emplacement de la fiche. Ils **MIROITENT le code**
|
||
du générateur : `rbac_scan.py` fait `os.path.join(_DELIVERABLES, "rbac",
|
||
"rbac_50_roles.json")` et `builder.py` publie `workflow_source:
|
||
"crm/workflow_vente/workflow_vente_spec.json"`. Les 3 cibles existent bien depuis
|
||
`05_deliverables_mvp/legal/` (vérifié `ls`). L'agent avait supposé à tort une
|
||
résolution fiche-relative. Convention documentée dans la mémoire
|
||
`backtick-path-escapes-check-docs` (les code-spans backtick sont volontairement
|
||
NON link-checkés). **Aucune correction — la fiche est exacte.**
|
||
|
||
2. **faisabilite** (agent : `data_room/_TEMPLATE_…/`, `_META/version.json`,
|
||
`otoia/capabilities/knowledge/faisabilite_template_v{X}.md` « HIGH · sources
|
||
inexistantes »). **Faux positif** : ces chemins sont **explicitement VPS-runtime /
|
||
« Composants à créer »** — préfixe absolu `/opt/oto/…`, placeholder de version
|
||
`{X}`, section §95 « Composants à créer ». La fiche les réconcilie déjà (§15-17
|
||
« composants runtime OTOIA/VPS … restent hors périmètre worker » ; §27-29 « aucune
|
||
faisabilité concrète PXX n'est commitée ici … le rendu réel … s'exécute côté
|
||
OTOIA/VPS #8 »). Hors-repo ASSUMÉ, pas une sur-promesse. **Table §Livrables
|
||
exacte** : `TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md` (existe) · generator 16 tests ·
|
||
bancable 22 tests. **Aucune sur-promesse in-repo.**
|
||
|
||
**Seule action — clarification de LISIBILITÉ (faisabilite), pas un correctif de
|
||
dérive.** Le point qui a induit l'agent en erreur est une vraie ambiguïté pour un
|
||
lecteur humain : le bloc « Sources canoniques » (§31-34) listait 3 chemins VPS
|
||
`{X}` sans signaler que la **contrepartie commitée/testée/gatée** du template
|
||
canonique est l'in-repo `TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md` (pourtant déjà en
|
||
tête de la table §Livrables). Ajout d'une **note additive** (style de réconciliation
|
||
déjà employé §15-17) : bandeau « Chemins VPS runtime — hors périmètre worker (#8),
|
||
non commités in-repo » + lien Markdown vers le template committé « référence de
|
||
vérité pour le worker ». **Zéro chiffre, zéro constante, zéro fait inventé** ;
|
||
purement désambiguïsant.
|
||
|
||
**Sûreté / vérif** — Avant édition : `grep` confirme qu'AUCUN gate ne parse la fiche
|
||
faisabilite ni la section « Sources canoniques » (édition prose libre). Après :
|
||
**7 gates re-verts (exit 0)** — `check_ci_integrity` · `check_artifacts` ·
|
||
`check_docs` (le nouveau lien Markdown résout vers une cible existante) ·
|
||
`check_readme_claims` · `check_regression` · `guard_constraints` (aucun terme
|
||
interdit introduit) · `validate_json`. **Bilan audit** : les **13 fiches**
|
||
`AGENT.md` sont désormais TOUTES auditées exactes (crm/bim la session précédente +
|
||
les 11 ici) ; les 2 « dérives » signalées étaient des artefacts de convention
|
||
(backtick deliverables-relative · chemins VPS/futurs assumés), pas des dérives.
|
||
|
||
---
|
||
|
||
## Session 20260802_120544 · Clôture de journée du canal `daily_reports/`
|
||
|
||
**Contexte** — Reprise en phase **Sprint 8 · buffer** (tous sprints 1→7 livrés
|
||
côté repo). Relu CLAUDE.md, la roadmap, MEMORY.md et le journal du jour. Constat :
|
||
la file de travail substantielle est **saturée** — 7 gates verts, 22/22 qualité,
|
||
564/564 régression, 15/15 recette, **13/13 fiches AGENT.md auditées exactes**
|
||
(campagne close au commit `2bc3afb`, session `113542`).
|
||
|
||
**Décision** (workflow #5 · pas d'empilage de gate redondant ; #6 · anti-invention)
|
||
— Aucune tâche roadmap ouverte ni blind-spot prouvé. Plutôt qu'un gate N+1 ou un
|
||
faux « module », **réconcilier le reporting stakeholder** : le snapshot canonique du
|
||
jour `05_deliverables_mvp/daily_reports/2026-08-02.md` (écrit à 11:11) était devenu
|
||
**vérifiablement périmé** — sa propre « Prochaine tâche #2 » (auditer les 11 fiches
|
||
restantes) et sa réserve `−4` (« audit limité à 2/13 fiches ») ont été **closes à
|
||
11:35** par le commit `2bc3afb`.
|
||
|
||
**Réalisé**
|
||
- **Recompute indépendant** (`python3` sur les artefacts commités) de TOUS les
|
||
agrégats de tête, concordant : qualité **22 modules PASS** (`pass_score` 95) ·
|
||
régression **22 suites / 564 ran / 564 passés / 0 échec** · recette **matrice de
|
||
15 items, verdict `true`** · **13** répertoires `03_agents/*/`.
|
||
- **Addendum de clôture ADDITIF** au snapshot `2026-08-02.md` (corps 110539 laissé
|
||
intact comme record historique) : acte la clôture 13/13 fiches, **lève la réserve
|
||
−4** caduque, réaffiche l'état re-vérifié (inchangé/vert), révise la « prochaine
|
||
tâche » (file d'audit vide → veille buffer). Zéro chiffre inventé — chaque valeur
|
||
re-dérivée d'un artefact.
|
||
- **Choix anti-doublon** (interdit #5 · « JAMAIS accumuler doublons ») : addendum au
|
||
fichier-jour canonique plutôt qu'un énième fichier per-session quasi-identique.
|
||
|
||
**Vérifications** — **7 gates CI re-joués → exit 0** (`check_ci_integrity` ·
|
||
`check_artifacts` byte-identique · `check_docs` liens résolus · `check_readme_claims`
|
||
· `check_regression` · `guard_constraints` aucun terme interdit · `validate_json`).
|
||
Aucune commande touchant au VPS.
|
||
|
||
**Non fait (hors périmètre #8)** — déploiement serveur réel, runner Gitea, Playwright
|
||
serveur : VPS `153.75.250.214`, plan ordonné `devops/deploy_runbook`.
|
||
|
||
---
|
||
|
||
## Session 20260802_123546 · Dérive « 12 agents » sur les docs de mandat de tête (hors README)
|
||
|
||
**Contexte** — Reprise en **Sprint 8 · buffer**. Relu CLAUDE.md, roadmap (0 item
|
||
ouvert), MEMORY.md, journal du jour. Re-vérification indépendante multi-angle de la
|
||
santé (recompute `python3` sur artefacts commités) : **7 gates → exit 0** · qualité
|
||
**22/22 @ 100/100** (verdict PASS · scores tous à 100, pas seulement le seuil) ·
|
||
régression **22 suites / 564 tests / 0 échec**, **13/13 modules** présents dans la
|
||
matrice · recette **15/15 `in_repo`** · constantes canoniques (3 %/8.5 %/52 %) toutes
|
||
ancrées · README (13 agents ×2, 22/22, 15 promesses) exact vs artefacts.
|
||
|
||
**Blind-spot RÉEL trouvé** (surface distincte de l'audit fiches `03_agents/*` clos au
|
||
`2bc3afb`) — audit des **docs de mandat de TÊTE**, non couverts par le gate de claims
|
||
(scopé README.md uniquement). `PORTAIL_BANCABLES_4BIG.md` vérifié exact (commande +
|
||
sorties `50_financier_bancable/{fr,en,es}.md` concordent avec `bancable_gen.py`). Mais
|
||
**deux docs affichaient encore « 12 agents »** :
|
||
- `AGENTS_EXISTING_ASSETS.md:1` (titre) · `02_master_prompt/MASTER_PROMPT_DTP_v2.md:28`.
|
||
|
||
Réalité re-dérivée : **13 agents** — 13 répertoires `03_agents/`, README canonique
|
||
« 13 agents » (×2, déjà gaté), le 13e (**publiciste**) **LIVRÉ+GATÉ** (fiche +
|
||
générateur `05_deliverables_mvp/publiciste/` **100/100** + suite de régression
|
||
`publiciste`). Les « 12 » précédaient l'ajout du publiciste → dérive doc-vs-réalité.
|
||
|
||
**Réalisé**
|
||
- **Correction du compte** 12 → 13 dans les deux docs (fait objectif, `03_agents/`
|
||
= source unique · #6 anti-invention).
|
||
- **Clarification publiciste** (`AGENTS_EXISTING_ASSETS.md` §13) : marqueur « (à créer) »
|
||
levé, **note « Statut mandat : LIVRÉ »** pointant fiche + générateur gaté, tout en
|
||
**préservant** la liste « À développer » comme **runtime VPS** hors périmètre worker
|
||
(#8) — respecte la convention « VPS-runtime `à créer` ≠ dérive » (memory
|
||
`fiche-accuracy-audit-closed`). Deux liens Markdown ajoutés → résolus (`check_docs`).
|
||
- **Extension du gate** `check_readme_claims.sh` (surface prouvée NON couverte par
|
||
mutation-test) : nouvelle boucle qui **réutilise le `n_agents` déjà dérivé** de
|
||
`git ls-files 03_agents/*/AGENT.md` (pas de nouvelle machinerie · #5 pas de doublon)
|
||
et exige que les DEUX docs de tête citent exactement ce compte. Classe
|
||
`doc-numeric-claims-gate`.
|
||
|
||
**Vérifications** — nouveau check **mutation-testé** (12 réinjecté → gate ROUGE
|
||
« dit 12 MAIS 03_agents/ en compte 13 », restauré → exit 0). **7 gates re-joués →
|
||
exit 0**. Aucune commande touchant au VPS.
|
||
|
||
**Non fait (hors périmètre #8)** — runtime VPS publiciste (`publiciste.py`, systemd/
|
||
inotify, WhatsApp), déploiement serveur : hors périmètre worker.
|
||
|
||
---
|
||
|
||
## Sprint 8 · buffer · Compte d'agents — ancrage des 3 surfaces vives de `GAP_ANALYSIS_SPRINT1.md` (dont la coche de couverture DoD) sur la source unique `n_agents`
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »).
|
||
Le commit précédent (cb8d931) a ancré « 13 agents » à `git ls-files 03_agents/*/AGENT.md`
|
||
(source unique `n_agents`) sur les 2 docs de mandat de tête (AGENTS_EXISTING_ASSETS titre +
|
||
MASTER_PROMPT §28), en plus du README ×2. Un balayage de complétude a exhumé une **3ᵉ
|
||
surface entièrement HORS gate** : le **livrable Sprint 1** `05_deliverables_mvp/
|
||
GAP_ANALYSIS_SPRINT1.md` (Definition of Done) retranscrit ce même compte à **trois
|
||
endroits vifs** — l.12 périmètre d'audit (« audit des **13 agents DTP** »), l.53 constat
|
||
global (« **12 des 13 agents** partent d'un socle existant »), et surtout l.214 la **coche
|
||
de couverture DoD** (« Couverture **13 agents** + OTOIA core ✅ »). Cette coche AFFIRME une
|
||
complétude COURANTE : elle DOIT suivre le nombre réel de fiches.
|
||
|
||
**Le « vert trompeur » — PROUVÉ ungate avant ajout** (mémoire
|
||
`verify-uncovered-before-gating`) : `GAP_ANALYSIS` n'est référencé par AUCUN `ci/*.sh`
|
||
(`grep` = 0) ; muter l.214 « 13 »→« 14 » laissait `check_readme_claims` **exit 0**. Or ce
|
||
NOMBRE a une **dérive prouvée** : le publiciste fut ajouté comme 13ᵉ agent (12→13), forçant
|
||
la correction de cb8d931. Si un 14ᵉ agent était mandaté, la coche DoD mentirait en silence.
|
||
`check_artifacts` est aveugle (la SPEC n'est pas un `out/*.json`) ; aucune suite `tests/` ne
|
||
touche l'ancre doc↔disque.
|
||
|
||
**Gate ajouté — EXTENSION de la boucle head-docs existante** (`ci/check_readme_claims.sh`,
|
||
bloc « Comptes d'agents dans les docs de mandat de tête »), pas un bloc neuf : 3 tuples
|
||
`(GAP, motif, label)` réutilisant le `n_agents` DÉJÀ dérivé (zéro nouvelle machinerie ·
|
||
zéro re-dérivation · #5). On n'ancre QUE le **total** — le « 12 » analytique de l.53
|
||
(agents à socle existant) N'EST PAS gaté (motif `\d+ des (\d+) agents` capture le 2ᵉ nombre).
|
||
Surface INTROUVABLE = échec (anti-évaporation #6). `ci/README.md` mis à jour : le décompte
|
||
d'agents est désormais documenté comme ancré à **7 surfaces vives**.
|
||
|
||
**Vérif** — 3 checks verts sur l'arbre propre (les 3 == 13). **6 morsures** adversariales,
|
||
reverts par `cp` ciblé (jamais `git checkout` large — cf. incident consigné) : **M1** DoD
|
||
13→14 (mord) · **M2** périmètre 13→99 (mord) · **M3** constat total 13→12 (mord) · **M4**
|
||
ligne DoD supprimée ⇒ INTROUVABLE · **M5 no-false-positive** : muter le « 12 » analytique
|
||
(12→11), total intact ⇒ gate **exit 0** (preuve qu'on ne gate que le total) · **M6
|
||
SILENT-GREEN** : dérive de la SOURCE (`git rm --cached` d'une fiche ⇒ `n_agents`=12), docs
|
||
figés à 13 ⇒ les **5 surfaces head-docs+GAP mordent** « dit 13 MAIS 03_agents/ en compte 12 »
|
||
(preuve que l'autorité est le git-index, pas la cohérence interne). Arbre restauré. **7 gates
|
||
re-joués → exit 0** : check_ci_integrity · check_artifacts · check_docs · check_readme_claims ·
|
||
check_regression · guard_constraints · validate_json. Aucune commande touchant au VPS.
|
||
|
||
## Sprint 8 · buffer · `roles_couverts` (« 44 ») — enquête anti-dérive → **non-surface prouvée** + documentation du garde-fou mono-source (évite un gate N+1 redondant #5)
|
||
|
||
**Contexte** — Série anti-dérive Sprint 8. Un balayage des comptes numériques a
|
||
exhumé un candidat de **cohérence croisée** : le total « **44 rôles couverts par les
|
||
5 portails** » apparaît, identique, dans **trois** manifestes byte-gatés
|
||
(`frontend/portails`, `frontend/chat_otoia`, `mobile/app_config` ·
|
||
`counts.roles_couverts=44`). Chaque « 44 » vif n'est ancré qu'à SON propre manifeste
|
||
(mobile README · `demo/scenarios/out/run_sheet.md`). Aucun `ci/*.sh` ne compare les
|
||
trois entre eux. Hypothèse initiale : surface non couverte, analogue au cross-check
|
||
RBAC « 50 » (`rbac/apply_plan/rbac_apply_plan.py:149` exige
|
||
`fixtures==userperm==roleprofile`).
|
||
|
||
**Enquête — l'analogie RBAC-50 ne tient PAS** (mémoire *fix-vs-gate-transitively-
|
||
protected-constant*). Le « 50 » EST cross-gaté car ses trois rendus **re-dérivent**
|
||
chacun leur décompte avec leur **propre** logique. Ici, au contraire, les trois
|
||
modules **importent la même** fonction de partition : `mobilelib/deps.py:77` et
|
||
`chatlib/deps.py:84` font tous deux `roles_by_portail = portails_builder.roles_by_portail`
|
||
(unique définition `frontend/portails/wslib/builder.py:36`), appliquée à l'**unique**
|
||
contrat `rbac/rbac_50_roles.json` (une seule copie de chaque dans `git ls-files`).
|
||
`roles_couverts` est donc **transitivement mono-sourcé** (code partagé + donnée
|
||
partagée) : les trois totaux **ne peuvent diverger** que si cette fonction ou ce
|
||
contrat change — auquel cas les trois changent ensemble et `check_artifacts`
|
||
(reproductibilité byte-à-byte) vérifie chaque rebuild. Un gate « 44==44==44 » serait
|
||
**redondant (#5)** et masquerait la vraie garantie (le partage de code), pas une
|
||
surface à protéger.
|
||
|
||
**Livraison — DOCUMENTATION du garde-fou, pas un gate.** Ajout d'un bloc sourcé
|
||
« **Non-surface documentée — `roles_couverts` N'EST PAS cross-gaté, à dessein** » dans
|
||
`ci/README.md` (entre le bloc mobile Expo et le bloc CONFOTUR) : cite les 3 imports
|
||
partagés + le contrat unique + contraste explicite avec le cross-check RBAC-50, pour
|
||
qu'une session future ne rajoute PAS le gate redondant. Chemins en backticks (non
|
||
link-checkés, cf. mémoire *backtick-path-escapes-check-docs*).
|
||
|
||
**Vérif** — 4 citations de lignes vérifiées exactes (`deps.py:77`, `deps.py:84`,
|
||
`wslib/builder.py:36`, `rbac_apply_plan.py:149`). **7 gates re-joués → exit 0** :
|
||
guard_constraints · validate_json · check_docs · check_artifacts · check_regression ·
|
||
check_ci_integrity · check_readme_claims. Aucune commande touchant au VPS.
|
||
|
||
## Sprint 8 · buffer · Reporting stakeholder (canal 2) — rattrapage du snapshot `daily_reports/2026-08-02` sur les 2 sessions `run_ci.sh` (commits `49123ec`/`dcb463e`)
|
||
|
||
**Contexte** — Série buffer Sprint 8. Le canal 2 (instantané stakeholder
|
||
`daily_reports/`, mémoire *two-logging-channels*) avait été rattrapé à 14:35 (addendum
|
||
`143555`) jusqu'au commit `1c0cb8e`. Depuis, **2 sessions buffer** ont introduit l'outil
|
||
DevOps `run_ci.sh` (`49123ec` @15:06 · `dcb463e` @15:36) sans mise à jour du canal 2 :
|
||
l'instantané avait de nouveau **glissé de 2 sessions**, alors que sa trace fine existait
|
||
déjà au canal 1 (ce fichier).
|
||
|
||
**Livraison — 3ᵉ addendum ADDITIF** dans `05_deliverables_mvp/daily_reports/2026-08-02.md`
|
||
(corps + 2 addenda intacts). Consolide les 2 sessions : (1) `49123ec` — `run_ci.sh`,
|
||
miroir LOCAL du gate dérivé de `gate.needs` (30 jobs = 7 gates `ci/*.sh` + 23 suites
|
||
module · PAS un gate #5 · root ∉ `ci/*.sh`) ; (2) `dcb463e` — retrait du décompte figé
|
||
30/23/7 du commentaire, désormais DÉRIVÉ à l'exécution. Aucun nouveau module (contrainte
|
||
respectée) · aucun gate ajouté (#5) · le seul livrable net est l'outil DevOps déjà gaté
|
||
par construction.
|
||
|
||
**Anti-invention #6** — chaque chiffre du tableau d'état re-dérivé par recompute `python3`
|
||
indépendant sur les artefacts commités : **22/22** qualité (scores distincts = {100} ·
|
||
verdict `PASS`) · **22 suites / 564 ran / 564 passés / 0 échec / 0 erreur / 0 skip**
|
||
(`regression_run.json` · `PASS`) · **15/15** promesses `in_repo` (`acceptance_matrix.json`
|
||
· verdict `true`) · **13** fiches (`03_agents/`) · `gate.needs` = **30**. Zéro donnée
|
||
nouvelle introduite ; l'état launch-readiness est **inchangé et vert**.
|
||
|
||
**Vérif** — `run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (miroir complet du gate) ·
|
||
`check_docs.sh` → conforme (les liens relatifs du nouvel addendum — `../../run_ci.sh`,
|
||
`../../.gitea/workflows/ci.yml`, `../qa/...` — mirrorent le style des addenda existants et
|
||
passent le link-check). Arbre `git status --porcelain` vide avant l'édition. Aucune
|
||
commande touchant au VPS.
|
||
|
||
## Session 20260802_163601 · Sprint 8 · buffer · Fiche `devops/AGENT.md` : documentation du livrable `run_ci.sh` (angle mort de complétude)
|
||
- **Constat** : `run_ci.sh` (miroir local du gate, ajouté aux commits `49123ec`/`dcb463e`) est déjà indexé dans la table Navigation du `README.md` racine, mais **absent de la fiche de son agent propriétaire** (`03_agents/devops/AGENT.md`) — la table « Livrables DevOps réellement produits » n'en listait que 2 (ci.yml+ci/, deploy_runbook). Gap de **complétude doc-vs-réalité**, pas une dérive de chiffre.
|
||
- **Action** : ajout d'une 3e ligne à la table + sous-section dédiée décrivant `run_ci.sh` **avec exactitude** : outillage local (confort dev), **PAS un gate** (CLAUDE.md #5 · aucun `ci/*.sh`, hors `gate.needs`, racine ∉ `ci/` donc hors invariant INV-B), liste de checks **dérivée** de `gate.needs` (zéro liste en dur → zéro dérive), décompte imprimé à l'exécution (#6). Aligné sur la mémoire `local-ci-runner`.
|
||
- **Zéro nouveau module · zéro gate ajouté** (#5). Seul changement = prose de fiche.
|
||
- **Vérif** : `check_docs.sh` valide le nouveau lien `../../run_ci.sh` (HARD) → vert · `guard_constraints.sh` vert · `run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP**.
|
||
|
||
## Session `20260802_170601` · Sprint 8 · buffer · DevOps — `run_ci.sh` : `-h|--help` + en-tête `--static` honnête (UX du lanceur, PAS un gate #5)
|
||
|
||
**Contexte** — Série buffer Sprint 8, tout vert (`run_ci.sh` → 30 PASS). L'outil
|
||
`run_ci.sh` (miroir local du gate, mémoire `local-ci-runner`) était fonctionnellement
|
||
complet mais avait **2 vraies aspérités d'UX**, reproduites avant correction :
|
||
1. `run_ci.sh --help` (et `-h`) échouait en « option inconnue » + exit 2, alors qu'un
|
||
bloc d'usage riche dormait dans l'en-tête du script, jamais surfacé — la convention
|
||
`--help` la plus universelle manquait.
|
||
2. En mode `--static`, l'en-tête annonçait toujours « **30 jobs = 7 gates + 23 suites** »
|
||
alors que seuls les 7 gates s'exécutent → le récap final « **7 PASS** » **contredisait**
|
||
l'en-tête (vert honnête mais message trompeur).
|
||
|
||
**Action** (édition de l'outil `run_ci.sh` uniquement + accord doc) :
|
||
- Ajout d'une fonction `usage()` (heredoc) et branchement `-h|--help` **AVANT** la
|
||
résolution git → l'aide marche même hors checkout (vérifié : exit 0 depuis `/tmp`),
|
||
alors qu'un vrai run hors git reste exit 3. Codes de sortie documentés (0/1/2/3).
|
||
- En-tête d'exécution rendu **mode-conscient** : en `--static`, affiche
|
||
« 30 jobs · mode --static → 7 gates ci/*.sh seuls (23 suites ignorées) » — plus de
|
||
contradiction avec le récap. Le mode plein est inchangé.
|
||
- Message d'option inconnue et bloc d'usage `ci/README.md` mis à jour (ajout `--help`).
|
||
- **Toutes les valeurs restent DÉRIVÉES** de `gate.needs` (`n_jobs/n_gates/n_suites`) —
|
||
aucune liste ni compte figé introduit (#6). **Zéro nouveau module · zéro gate ajouté**
|
||
(#5) : `run_ci.sh` demeure un lanceur (racine ∉ `ci/*.sh`, hors `gate.needs`, INV-B).
|
||
|
||
**Vérif** — `--help`/`-h` → exit 0 (dans + hors git) · `--bogus` → exit 2 · en-tête
|
||
`--static` désormais « 7 gates seuls » cohérent avec « 7 PASS » · `--list` inchangé
|
||
(30 jobs) · `check_docs` · `check_readme_claims` · `guard_constraints` verts ·
|
||
`run_ci.sh` (plein) → **30 PASS · 0 FAIL · 0 SKIP**. Arbre propre avant édition.
|
||
Aucune commande touchant au VPS.
|