Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 KiB
Activity Log · 2026-08-06 · Claude Code DTP Worker
Session 20260806_211659 — VÉRIF · axe NEUF « fidélité NUMÉRIQUE côté JS JSON.parse : entiers > 2^53 (corruption SILENCIEUSE) + magnitudes hors plage double (→ Infinity) » → CLEAN 0/114 · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire) — le 3e sommet de la trilogie « fidélité JSON.parse navigateur ». Le sweep de ce matin (20260806_181623) couvre les sentinelles NaN/Infinity non-strictes : elles lèvent une SyntaxError bruyante dans le JSON.parse de la Console Helios. Restait le sibling SILENCIEUX, invisible à toute la chaîne Python : (a) un entier abs > Number.MAX_SAFE_INTEGER (2^53−1 ≈ 9.007e15) — Python conserve la précision arbitraire et ne prévient jamais, mais JS le mappe au double IEEE-754 le plus proche = valeur silencieusement fausse, sans erreur ; (b) une magnitude > Number.MAX_VALUE (~1.8e308) → JS parse en Infinity, silencieux. Un id/montant-en-cents/numéro sérialisé en nombre JSON franchissant 2^53 passe tous les gates (validate_json vert car Python tolère, byte-repro identique) tout en corrompant l'affichage navigateur. Distinct de la FORME scalaire (type int/float) et des sentinelles NaN (valeurs illégales) : ici c'est la plage sûre du Number JS, jamais éprouvée.
Détecteur (stdlib pur, rejouable). Reparse chaque .json avec json.loads(text, parse_int=hook, parse_float=hook) : flag si abs(int) > 2^53−1 ou math.isinf(float(litéral)). Les numériques-chaîne ("999…", légitimes, non affectés par JSON.parse) sont épargnés.
Dents prouvées (8/8 + bout-en-bout). Épargnés : 18880000, 2^53−1 exact (MAX_SAFE), 18296296385 (numéro type-téléphone), "99999999999999999999" (chaîne), 0.085 (flottant). MORDENT : 2^53, 2^53+1, 1e400→Infinity. Bout-en-bout sur …/faisabilite/bancable/fixtures/brief_bancable.json : baseline = 0 non-sûr ; injection _probe = 2^53+7 → correctement flaguée int>2^53 (9007199254740999).
Résultat. Sur 114 fichiers JSON suivis : 0 nombre non-sûr. Le plus grand entier tracké = 18 880 000 (brief_bancable.json) — ~8 ordres de grandeur sous la limite. CLEAN — tous les artefacts sont consommables sans perte de précision par le Number JS.
Conformité. 0 édition de production (sweep read-only). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP. Fichiers : ce journal + 1 entrée mémoire (js-number-fidelity-sweep). Sweep rejouable — NON promu en gate (#5, occurrence-zéro ; note résiduelle : validate_json reste int-safe-tolérant par design Python, à durcir seulement si un générateur commence à émettre de grands ids/montants numériques).
Session 20260806_181623 — VÉRIF · axe NEUF « conformité JSON STRICTE (ECMA-404 / JS JSON.parse) : aucun NaN/Infinity non-strict » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). Tous les balayages JSON précédents valident le parse via Python — validate_json.sh fait json.load(...), et Python ACCEPTE par défaut les constantes non-standard NaN / Infinity / -Infinity (elles retournent float('nan')/inf). Or ces tokens sont illégaux en ECMA-404 : le JSON.parse de la Console Helios (frontend JS qui consomme les out/*.json) lève une SyntaxError dessus. Donc un artefact contenant un NaN (ex. champ dérivé div-par-zéro sérialisé avec allow_nan=True, défaut de json.dumps) passe tous les gates (validate_json vert, byte-repro identique) tout en cassant le rendu navigateur — faux-vert direct, invisible à toute la chaîne Python. Distinct de artifact-internal-set-semantics (valeurs) et de la « FORME scalaire int/float/str » (qui checke le TYPE, pas les valeurs-sentinelles non-strictes) : ici c'est la conformité au sous-ensemble JS-parseable, jamais éprouvée.
Détecteur (stdlib pur, rejouable). Re-parse chaque .json suivi avec json.loads(text, parse_constant=<callback qui lève>) — le callback parse_constant n'est invoqué QUE pour NaN/Infinity/-Infinity en position de valeur (pas les chaînes "NaN", légales) → réplique exacte de la sémantique de rejet de JSON.parse.
Dents prouvées (synthétique 5/5 + bout-en-bout). {"x":1.5} épargné ; {"x":NaN}, {"x":Infinity}, {"x":-Infinity} MORDENT ; {"x":"NaN"} (chaîne) épargné — 5/5 discriminés. Bout-en-bout sur un fichier réel (…/commissions/bareme.schema.json) : baseline = strict ; injection {"_probe":NaN} → correctement flaguée non-strict.
Résultat. Sur 114 fichiers JSON suivis : 0 constante numérique non-stricte. CLEAN — tous les artefacts sont consommables tels quels par JSON.parse côté navigateur. Autres divergences Python↔JS déjà couvertes ailleurs : BOM (runtime-encoding-integrity-sweep = 0 BOM, or JSON.parse rejette le BOM), clés dupliquées (manifest-count-field…/dup-keys = 0, or JS garde la dernière). Le delta neuf ici = les sentinelles NaN/Infinity.
Conformité. 0 édition de production (sweep read-only). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP. Fichiers : ce journal + 1 entrée mémoire. Sweep rejouable (stdlib pur) — NON promu en gate (#5, occurrence-zéro ; note résiduelle : validate_json reste tolérant NaN par design Python, à durcir seulement si un générateur commence à émettre des dérivés flottants risqués).
Session 20260806_174619 — VÉRIF · axe NEUF « intégrité des ancres-fragment Markdown ](#slug) : résolvent-elles vers un titre réel ? » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Les 7+ derniers commits sont des sweeps vérification-only à rendement décroissant ; daily_reports n'a pas de vrai snapshot stakeholder à ajouter. Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). Toutes les classes de liens gatées/balayées portent sur la cible-FICHIER d'un lien Markdown (check_docs §1 = « la cible existe ? », link-label-target-mismatch = « le label nomme-t-il la cible ? », backtick-path-escapes-check-docs = phantom en code-span). Aucune ne valide la partie fragment #slug : check_docs.sh la RETIRE explicitement (link="${link%%#*}") et ignore les ancres pures (\#*) continue). Donc un lien [label](#titre) — ou [label](fichier.md#titre) — dont le titre a été renommé/mal-orthographié passe au vert : la cible-fichier existe, seul le saut d'ancre est mort (aveugle au rendu, silencieux). Sœur-FRAGMENT de la classe cible-fichier.
Détecteur (stdlib, /tmp, read-only). Algorithme de slug GitHub/Gitea (minuscule · ponctuation retirée · accents conservés · espaces→tirets · doublons suffixés -1) ; code-spans neutralisés d'abord (comme check_docs, sinon faux-positifs) ; toute ancre ](#…)/](*.md#…) résolue contre l'ensemble des slugs-titres du fichier-cible (fichier absent = hors axe, c'est le job de check_docs).
Résultat → CLEAN. Sur les 379 .md trackés, 1 seul lien-ancre réel non-code-span : README.md:20 [DIRECTIVE_*.md](#directives-michel-input-specs-datés) → résout exactement vers le titre ## Directives Michel (input-specs datés) (slug avec é accentué préservé). 0 ancre pendante. Faux-positifs correctement écartés = les 2 `](#frag)` en code-span dans ce même log (prose méta décrivant ce candidat, occurrence-zéro). Détecteur teeth-prouvé bout-en-bout : l'ancre valide résout (True), un mutant-typo …-date (sans s) NE résout PAS (False = mordrait).
Périmètre / décision. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé (#6) · 0 commande VPS (#8) · aucun gate ajouté (#5 — axe CLEAN, 1 seule occurrence réelle, détecteur rejouable, comme les précédents sweeps occurrence-zéro). ./run_ci.sh 33 PASS · 0 FAIL · 0 SKIP inchangé. Mémoire : markdown-anchor-fragment-integrity.
Session 20260806_171614 — VÉRIF · axe NEUF « intégrité référentielle artefact → FICHIER (les chaînes-chemin des out/*.json résolvent-elles sur disque ?) » → CLEAN 0 pendante · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non
éditables). Le canal daily_reports est en retard de 3 sessions vérification-only (0 édition prod) —
y ajouter des lignes « CLEAN, 0 édition » serait du churn stakeholder, pas un vrai snapshot. Objectif :
un axe teeth encore jamais balayé qui puisse réellement mordre un défaut.
Axe NEUF (absent de la mémoire). Toutes les classes référentielles existantes résolvent des slugs
(role-portail-referential-integrity, project-entity-referential-integrity) ou re-dérivent des
comptes (manifest-count-field-payload-rederivation) ou comparent des valeurs entre artefacts
(cross-artifact-numeric-agreement). Aucune ne traite une valeur-chaîne d'un out/*.json comme une
RÉFÉRENCE DE FICHIER et ne vérifie qu'elle résout sur disque. Or ces artefacts en portent 124 :
generated_from/source_directive/rbac_source/workflow_source/doctype_source,
audited_artifacts[] (audit_5d), evidence (schémas d'audit_4big), roadmap_ref, acceptance
artifacts[].path, apply_plan doc/produces[], MANIFEST artifacts[]/generator. Aucun gate ne
parse une valeur JSON comme chemin (check_docs ne voit que les liens Markdown ; check_artifacts ne
voit que le byte-repro qui rejoue une chaîne pendante à l'identique). Un generated_from pointant un spec
renommé, un audited_artifacts vers un fichier déplacé, un evidence schéma inexistant =
faux-vert invisible, référence pendante qui contredit #6 « Zéro invention ».
Détecteur (stdlib pur, rejouable). Pour chaque valeur-chaîne matchant un motif nom-fichier
(.json/.md/.py/.sh/…) : tentative de résolution contre bases par ordre de spécificité — out/ · dir
module (SRC) · dir famille (grand-parent, ex. crm/) · racine deliverables · racine dépôt · rbac/ ;
plus un fallback bare-basename cross-module (un spec/schéma référencé par simple basename résout s'il
existe dans un module frère). Classes conceptuelles/runtime exclues by-design (n'ont pas à exister
comme fichiers du dépôt) : capabilities = noms de capacités OTOIA VPS (aec.py/knowledge.py/
prompt_engine.py/chat.py — cohérent avec chat.py absent du repo, cf. mémoire) · storage_layout
PIE = layout de stockage VPS (master.json/manifest.json) · demo evidence[].file = chemins
out/… relatifs au module-cible (pas à demo/scenarios).
Résultat → CLEAN. Sur les 57 out/*.json : 48 refs path-relatives + 27 bare-basename cross-module → toutes résolvent vers un fichier trackés ; 49 refs conceptuelles/runtime exclues (24 capabilities VPS + 23 demo-evidence target-relatives + 2 PIE storage) ; 0 référence pendante.
Dents prouvées bout-en-bout. baseline commissions/MANIFEST.json generated_from='bareme_spec.json'
→ résout (non-pendant) ; 4 mutants injectés (bareme_spec_RENAMED.json, DIRECTIVE_TYPO.md,
ghost.schema.json, crm/nonexist/x_spec.json) → les 4 MORDENT (pendants). Détecteur non-inert.
Limite conservatrice documentée : une pendante par bare-basename coïncidant avec le nom d'un autre
fichier tracké serait manquée (accepté — occurrence-zéro).
Périmètre / décision. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé (#6) ·
0 commande VPS (#8) · aucun gate ajouté (#5 — axe CLEAN, occurrence-zéro, détecteur rejouable
stdlib pur hors réseau). ./run_ci.sh 33 PASS · 0 FAIL · 0 SKIP inchangé (aucune édition de code).
Fichiers touchés : ce journal + 1 entrée mémoire (artifact-file-reference-integrity).
Session 20260806_164612 — VÉRIF · axe NEUF « intégrité de la FORME scalaire JSON (type int-vs-float-vs-str), pas la valeur » des 57 out/*.json → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Les 6+ derniers commits sont des sweeps vérification-only à rendement décroissant. Objectif : un axe objectif encore jamais balayé.
Axe NEUF (absent de la mémoire). Tous les axes numériques existants portent sur la valeur d'un nombre (accord inter/intra-artefact, re-dérivation, arithmétique dérivée) ou sur la forme-date. Aucun ne vérifie la FORME scalaire JSON elle-même : un compte sérialisé 3.0 (float) ou "3" (string) au lieu de 3 (int) se lit à l'identique mais casse type:integer / une comparaison stricte == 3 / une colonne DB typée. C'est aveugle au byte-repro (build==committed rejoue le type tel quel) et aveugle aux oracles-schémas (tous déclarent type:"number", qui accepte int ET float → un sequence_id float passe au vert). Sœur-FORME de cross-artifact-numeric-agreement (qui, lui, teste la valeur) et de l'axe forme-date.
Détecteur — 3 sous-checks, teeth-prouvés (/tmp, stdlib pur, hors réseau, rejouable) :
- (A) cohérence de type intra-fichier par chemin structurel — un chemin répété (ex.
[].sequence_id) dont les occurrences divergent de type scalaire = flag. Le regroupement par chemin structurel complet (pas le nom-feuille) évite les faux-positifs de collision de nom inter-schéma. - (B) clés à sémantique-compte (
nb_*/*_total/*_count/passed/failed/ran…) doivent être des int JSON, jamais float ni string-numérique. - (C) aucun littéral
NaN/Infinity(JSON strict invalide). - Dents : basculer un seul
sequence_idint-parmi-des-floats → MORD ({int,float}) ;nb_portails=5.0etroles_total="44"→ MORD ; les comptes réels deregression_run.jsonépargnés (0 faux-positif). Détecteurs non-inertes.
Résultat → CLEAN. B = 0, C = 0. A ne flague qu'1 chemin = run_sheet.json … evidence.[].valeur (str/int/bool) = la colonne clé-valeur hétérogène par design (même piège valeur générique déjà en mémoire), pas un bug ; les 56 autres fichiers sont parfaitement cohérents en type par chemin. Les divergences de type inter-FICHIERS (role/passed/verdict/is_submittable/a_confirmer/roadmap_line) = collisions de nom de clé per-module-local, le piège générique documenté (chaque schéma distinct).
Deux formes float/int DÉLIBÉRÉES — NON-défauts, à NE PAS « corriger » : (1) workspace.json sequence_id = float(1.0..5.0) via un cast explicite float(...) dans wslib/frappe.py:162 — fidélité au champ Float sequence_id du Workspace Frappe ; (2) is_submittable = int 0/1 dans les doctype_*.json — convention booléen 0/1 des DocType Frappe. Les deux sont by-design + épinglés byte-repro. Investigués avant conclusion (#5/#6) — pas de correction.
Périmètre / décision. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé (#6) · 0 commande VPS (#8) · aucun gate ajouté (#5 — axe CLEAN + formes Frappe isolées, comme les précédents CLEAN). ./run_ci.sh 33 PASS · 0 FAIL · 0 SKIP inchangé. Détecteur rejouable. Mémoire : json-scalar-type-integrity.
Session 20260806_161610 — VÉRIF · axe NEUF « intégrité arithmétique des champs DÉRIVÉS des artefacts committés » (moyennes/taux/sommes-pondérées/écarts/verdicts-seuil + nombres dans la prose) → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non
éditables). Objectif : un axe teeth encore jamais balayé. Candidat ancres intra-doc ](#frag)
ré-écarté (occurrence-zéro, déjà noté). Retenu : la valeur SÉMANTIQUE d'un nombre DÉRIVÉ.
L'axe (distinct de tout ce qui existe). Les balayages numériques passés couvrent la cardinalité
(manifest-count-field-payload-rederivation : nb_*/_total = len(payload)), l'accord inter-artefacts
d'un singleton (cross-artifact-numeric-agreement), et les doublons d'ensemble (artifact-internal-set-semantics).
Aucun ne recalcule une DÉRIVATION arithmétique — un nombre stocké = fonction d'autres nombres stockés
(moyenne, taux %, somme pondérée, écart, verdict-seuil). C'est aveugle au byte-repro d'une façon subtile :
check_artifacts (et le test test_build_egale_commite du module financement) comparent la sortie du
générateur au golden committé — donc ils attrapent la dérive (code changé sans régénérer) mais PAS
un bug de formule régénéré en même temps (code + golden bougent ensemble → toujours vert). Un
overall_percent faux montré au banquier (roadmap « P07 pitch banquier ») serait un faux-vert vs #6.
Cet axe recalcule depuis les entrées brutes, indépendamment du générateur.
Faux-piste corrigée en cours de route (honnêteté). J'avais d'abord supposé overall_percent non-testé
(grep : 0 assert). Mutation-test de finlib/gate.py (mean /len→/3.0, doc pct *100→*90, f-string
paid/required inversé, chacun in-place puis git checkout) → chaque mutant FAIL via
test_build_egale_commite (rebuild == golden byte-à-byte). Donc l'arithmétique est protégée-dérive.
La vraie lacune résiduelle = le bug-de-formule-avec-régén, que seul un recalcul premier-principe ferme.
Recalcul premier-principe → CLEAN partout :
- financement
gate_status_example.json:overall_percent= moyenne(4 %) = 39.1 ✓ ; 3 % par condition (paid/required,validated/total,signed/total, arrondis_round1) ✓ ;remaining=required−paid= 14000 ✓ ; les 5 nombres embarqués dans la prosereasons(29500/43500, reste14000,67.8%,5/13,2/4) miroir des champs structurés ✓. - audit_4big
quality_report.json: pour les 24 modulesapplicable_weight= Σ(poids checks),earned_weight= Σ(poids passés),score= round(earned/applicable×100),verdict= PASS ssi score≥95 ✓ ;totals(pass/fail/min/max/pass_rate_pct= round(pass/modules×100)) ✓. - financement
banques.json: 6× ordre LTV0≤ltv_min≤ltv_max≤100✓. - commissions : bornes de taux
[0,100]✓.
Dents prouvées bout-en-bout. Détecteurs de recalcul mordent sur mutants injectés dans une copie
de l'artefact : overall_percent 39.1→52.1 MORD, prose paid 29500→99999 MORD, audit_4big
score→77 MORD ; l'artefact réel passe. Détecteurs stdlib pur (json), sans réseau, rejouables.
Décision. NON promu en gate (#5) : les nombres dérivés sont déjà protégés-dérive par
build==committed/check_artifacts ; cet axe ferme la classe résiduelle (formule-avec-régén) par un
sweep rejouable, occurrence-zéro. 0 édition de production (mutations gate.py toutes restaurées
par git checkout — arbre propre), 0 commande VPS (#8). run_ci inchangé 33/0/0.
Session 20260806_151604 — VÉRIF · axe NEUF « intégrité référentielle projet-code ↔ projet-nom canonique dans les artefacts » → CLEAN 128/128 · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit de Michel
(non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). role-portail-referential-integrity-sweep prouve que tout slug
de rôle/portail des out/*.json résout vers rbac_50_roles.json. Le pendant jamais balayé sur la
dimension PROJET/ENTITÉ : tout code-projet (P0[0-9]) et nom-projet référencés dans les
artefacts résolvent-ils vers le canon CLAUDE.md §Projets, et quand un objet apparie un code et un
nom, l'appariement est-il le bon ? Distinct de claude-md-constant-anchor-gate (qui gate des copies
de la liste §Projets) : ici on chasse la référence pendante / le mauvais appariement (une invention
P10, ou P02 « Nakua » — P02 = Coral del Sur) — un faux-vert invisible à tous les gates et qui
contredirait directement la contrainte #6 « Zéro invention ».
Détecteur (stdlib pur, rejouable).
- Canon :
{P01 Structure, P02 Coral del Sur, P03 Nakua, P04 Xamaná Cantiles, P05 Las Colinas Najayo Arriba, P06 Coco Real, P07 Aqua Terra Las Terrenas, P08 Fasano Espirilla, P09 1069 Crisfer}. - Invariant fort (appariement objet) : pour chaque objet JSON dont les valeurs-chaînes contiennent
exactement un code ET exactement un nom canonique, le couple
(code, nom)doit être canonique. Le « exactement 1+1 » exclut proprement les blobs de prose (voir faux-positifs). - Chaîne-agrégat : l'unique objet multi-codes (
dossier_vente.options= les 9 paires inline"P0X <Nom>") vérifié ligne à ligne = 9/9 canoniques (déjà gaté §Projets). - Pendantes : aucun token
P[01][0-9]hors{P01..P09}. - Entités : les 8 de
§Entités(WAF · WA SRL · AC Arias Cuevas · Consortium ECR DR · Helios RD · WAG · Ploutos · 9060 QC) résolvent toutes (WA SRL 26× · Helios RD 23× · AC Arias Cuevas 21× · WAG 7× · 9060 QC 6× · Ploutos 5× · Consortium ECR DR 5× · WAF 4×) — 0 entité inventée.
Dents prouvées (mutants sur copie temp, prod intouchée). baseline pie_manifest.json = 0 mismatch ;
mutant P02 ← Nakua → MORD [('P02','Nakua')] ; mutant P02 → P10 → pendante flaguée
['P10']. 3/3 discriminés.
Résultat. Sur les 57 out/*.json : 128 objets 1:1 code+nom → 0 mismatch · 1 agrégat (les 9
paires inline) 9/9 canonique · 0 code pendant · 8/8 entités résolvent. Faux-positifs écartés (par
construction du détecteur objet) : 5 adjacences inline en prose ("P07 Aqua Terra — Pitch banquier", "P07 Aqua Terra faisabilité…" dans run_sheet/acceptance_matrix/MANIFEST) utilisent
la forme courte « Aqua Terra » (abréviation de « Aqua Terra Las Terrenas ») ou deux codes dans
une phrase — pas un mauvais appariement, prose légitime que l'invariant « exactement 1 nom complet »
exclut correctement.
Conclusion. CLEAN 128/128 (+ agrégat 9/9 + 0 pendant + 8/8 entités). Axe teeth-prouvé, dimension
projet/entité jamais balayée (sœur de la dimension rôle/portail). 0 édition de production (#8),
./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP. Fichiers : ce journal + 1 entrée mémoire
(project-entity-referential-integrity-sweep). Sweep rejouable (stdlib pur, sans réseau) — NON
promu en gate (#5 : appariement partiellement déjà couvert §Projets, occurrence-zéro sur la pendance).
Session 20260806_144555 — VÉRIF · axe NEUF « forme de normalisation Unicode (NFC vs NFD) des sources committées » → CLEAN 387/387 · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit de Michel
(non éditables). Objectif : un axe encore jamais balayé.
Axe NEUF (absent de la mémoire). Le sweep runtime-encoding-integrity-sweep a couvert le niveau
octet — BOM · UTF-8 invalide · caractères de contrôle · CRLF · compile .py. Il n'a jamais couvert
la forme de normalisation Unicode. Or ce repo est intégralement francophone (369/387 fichiers
non-ASCII, pleins d'accents) : un é décomposé (NFD = e + U+0301 combinant) se lit à
l'identique d'un é composé (NFC, un seul point de code), mais c'est une séquence d'octets
différente. Conséquences d'un fichier NFD glissé dans le repo :
- byte-repro aveugle — le générateur lit ces octets et les ré-écrit tels quels → artefact identique.
- faux-vert de gate — un gate qui
grepun littéral NFC (ex.faisabilité,Bancable) ou un test quiassert "…é…" in outputrate silencieusement le contenu NFD → la vérif passe au vert alors qu'elle ne teste rien.
Détecteur (stdlib pur). Pour chaque fichier texte committé (.py .json .md .sh .yml .yaml .txt .ts .tsx .js .jsx .toml .cfg .ini, hors .git/__pycache__/node_modules/.expo/venv) : décoder UTF-8,
comparer unicodedata.normalize("NFC", texte) au texte brut ; toute divergence = contenu non-NFC.
Teeth (bout-en-bout, pas juste abstrait). Deux fichiers temporaires de contenu visuellement
identique — clean.md (NFC) et decomposed.md (NFD de la même phrase) : le détecteur flague
decomposed.md (True) et épargne clean.md (False) ; et grep 'faisabilité' (littéral NFC)
matche clean.md mais rate decomposed.md → le faux-vert est démontré, pas supposé.
Résultat. 387 fichiers examinés · 369 non-ASCII (accentués) · 0 non-NFC · 0 échec de décodage. Le repo est intégralement en forme NFC → CLEAN 387/387. Aucun gate/test n'est exposé au faux-vert d'octets-divergents-lus-pareil.
Périmètre. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé · 0 commande
VPS (#8). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP. Fichiers touchés : ce journal +
1 entrée mémoire (unicode-nfc-normalization-sweep). Sweep rejouable tel quel (stdlib
unicodedata, sans réseau) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_141554 — VÉRIF · axe NEUF « accord numérique INTER-artefacts d'un concept-singleton partagé » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit de Michel
(non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). Les axes numériques existants sont soit intra-artefact
([[manifest-count-field-payload-rederivation]] : chaque MANIFEST.nb_* re-calculé depuis SON payload
voisin), soit code/prose ↔ artefact ([[doc-numeric-claims-gate]], [[prose-facts-vs-numeric-drift]]).
Angle orthogonal jamais couvert : artefact ↔ artefact — quand un même concept-singleton plateforme
apparaît comme scalaire dans deux out/*.json indépendants, les deux valeurs s'accordent-elles ?
Une divergence silencieuse (un manifest dit 50, un autre 44) ship verte car aucun gate ne compare deux
artefacts entre eux.
Détecteur (teeth-prouvé). Balayage des 57 out/*.json : collecte des clés-scalaires int en
position GLOBALE (chemin sans indice de liste [i] = concept singleton, ≠ position PER-ROW dans un
[i] = compte-de-ligne légitimement variable). Une clé globale partagée par >1 fichier avec valeurs
divergentes = candidat-conflation. Preuve de dents : mutation cible_rbac_roles 50→49 dans un seul
manifest en mémoire → le détecteur signale aussitôt la divergence (BITES).
Résultat empirique → CLEAN. Les concepts-singletons plateforme s'accordent tous cross-fichier :
cible_rbac_roles=50 (4 fichiers), ci_modules_count/modules/suites=24 (3), portails=5,
doctypes_uniques=45, custom_docperm=116, user_permission_templates=28, role_profiles=6,
min_score=100, pass_score=95 — zéro divergence.
TRAP documenté (comme les KEY-traps de [[manifest-count-field-payload-rederivation]]). L'heuristique
naïve « même nom de clé = même concept » sur-signale : fields/data_fields/permissions/roles/
sections/pass divergent entre modules (dossier_vente 30 fields vs confotur 18 ; audit_4big
pass=24 vs audit_5d pass=13) — mais chacun décrit son propre DocType/suite = per-module-local, PAS
un singleton plateforme. Le seul nom SURCHARGÉ portant deux sens cross-famille est roles_couverts
(50 dans rbac/roleprofile_gen vs 44 dans frontend/portails, frontend/chat_otoia,
mobile/app_config) — et c'est by-design, les DEUX re-dérivés du canonique rbac_50_roles.json :
- 50 = tous les rôles reçoivent un role-profile (
len(roles)=50) ; - 44 = rôles surfacés dans les 5 portails business (ventes 12 · construction 10 · compta 8 ·
direction 9 · achat 5) = 50 − 6 rôles
plateforme(devops,rbac-admin,qa,otoia,mobile,bi) sans portail business. Cohérent avec[[role-portail-referential-integrity-sweep]],[[roles-couverts-transitively-single-sourced]],[[roadmap-7-dashboards-not-drift]](les 6plateforme).
Décision. Aucune édition de production (l'axe est CLEAN) · aucun gate ajouté (#5 —
occurrence-zéro d'une vraie conflation ; détecteur rejouable, non-déterministe-safe) · aucune commande
VPS (#8). run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP. Canal stakeholder daily_reports/2026-08-06.md
porté au HEAD (currency 3710253→04f4c21 + cette session).
Session 20260806_134554 — VÉRIF · axe NEUF « l'artefact COMMITÉ sur disque conforme à son schéma ? » (fichier réel vs oracle, pas l'objet en-mémoire) → CLEAN 57/57 · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit appartenant à
Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). Les 7 axes-schéma existants (non-vacuité, closedness, wiring, $ref,
enum/const, cohérence interne, draft/keyword) examinent tous le schéma. La mémoire note un angle
jamais fermé : « jsonschema oracles validate in-memory objects not flat out/ (no flat pair = 0-err,
by-design) ». Question NEUVE et orthogonale : le fichier out/*.json réellement commité et livré
est-il conforme à son schéma — ou une régression de forme peut-elle ship verte parce que l'oracle ne
valide qu'un objet reconstruit en mémoire de forme différente ? Détecteur (jsonschema 3.2.0) : pour
chacun des 57 out/*.json, valider le fichier sur disque contre le schéma du module — soit comme
objet-entier, soit comme valeur d'une propriété top-level du bundle validé.
Résultat empirique. 5 fichiers valident WHOLE (pie_manifest, quality_report, audit_report,
regression_plan, + MANIFEST-scénario) ; la majorité valident comme propriété de bundle (le générateur
valide le bundle entier en mémoire puis écrit bundle["<prop>"] dans un fichier split). 4 « no-fit »
investigués un par un — tous by-design & couverts transitivement :
crm/workflow_vente/out/workflow.json: le générateur écrit[bundle["workflow"]](l.192) — wrap liste 1-élément = convention fixtures Frappe ; l'objet interne EST validé (propworkflow) et le wrapper est byte-repro-épinglé (check_artifacts).qa/acceptance/out/acceptance_matrix.json= bundle{matrix,verdict}moinsmanifest(extrait vers MANIFEST.json) ; sous-parties validées en mémoire + byte-repro.demo/scenarios/out/run_sheet.json: vue rendue distincte (scenario.schema.jsongarde en fait MANIFEST.json) ; sortiebuilddéterministe byte-repro'd + structure assertée parcheck_invariants(litrun_sheet["scenarios"],sc["id"]…).qa/regression/out/regression_run.json: snapshot d'EXÉCUTION (sous-ensemble du schéma-plan) — seul artefact hors byte-repro de check_artifacts, MAIS forme épinglée autrement :ci/check_regression.shré-exécuterunet byte-compare au fichier commité (l.53-70). Aucune dérive de forme ne passe.
Chaîne de garde fermée. Tout payload commité livré a sa forme gardée par une chaîne traçable :
schéma-whole · schéma-prop + byte-repro · wrap/split byte-repro d'un objet schéma-validé ·
ré-run byte-compare. 0 fichier livré à forme non-gardée. Le trou « in-memory vs commité » noté en
mémoire est comblé par byte-repro / ré-exécution, vérifié sur les 57.
Dents prouvées. Sur un fichier qui matche une prop (workflow_state.json→workflow_state) : mutation
clé-parasite (props en additionalProperties:false) → no-fit ; mutation type-scalaire → no-fit
(2/2 MORDENT). Le détecteur discrimine une divergence de forme réelle.
Conclusion. CLEAN 57/57. Axe teeth-prouvé, rejouable (/tmp jsonschema pur, hors réseau,
hors prod · #8). 0 édition de production, 0 artefact touché, ./run_ci.sh inchangé = 33 PASS ·
0 FAIL · 0 SKIP. NON promu en gate (#5, occurrence-zéro ; la chaîne byte-repro/ré-run garde déjà
la forme). Fichiers touchés : ce journal + 1 entrée mémoire (committed-artifact-conforms-to-schema).
Session 20260806_131551 — VÉRIF · axe NEUF « cohérence draft/keyword des 26 oracles-schémas » (déclaration $schema vs idiomes cross-draft) → CLEAN 26/26 · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 dans le repo ; prod VPS /
builds EAS / dépôts ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages
produit appartenant à Michel (non éditables). Objectif anti-churn : un 7ᵉ axe teeth des
oracles-schémas encore jamais balayé.
Axe NEUF (absent de la mémoire). Les 6 axes-schémas déjà en mémoire vérifient : (1) non-vacuité ·
(2) fermeture/complétude · (3) câblage à un test · (4) résolution des $ref · (5) enum/const vs skip
python -S · (6) satisfiabilité interne (required-vacuité + auto-contradiction). Aucun ne vérifie
la cohérence entre le $schema déclaré et les mots-clés effectivement employés. C'est un faux-vert
réel : un validateur JSON-Schema interprète chaque schéma selon le draft déclaré, et un mot-clé
d'un autre draft change de sémantique ou est silencieusement ignoré — l'oracle valide alors autre
chose que ce qu'il croit.
Détecteur maison (stdlib pur, récursif). Signale, sous une déclaration draft-07 :
exclusiveMinimum/exclusiveMaximum booléens (idiome draft-04 → mal-typé et ignoré en draft-07) ·
id au lieu de $id (draft-04) · required booléen (draft-03) · $defs comme porteur de
définitions (mot-clé draft-2019, alors que draft-07 utilise definitions) · les mots-clés
post-draft-07 (unevaluatedProperties/Items, $dynamicRef, $anchor, prefixItems,
dependentRequired, …) silencieusement ignorés par un validateur draft-07.
Résultat. 26/26 déclarent draft-07 (aucun $schema manquant) · 13/13 porteurs de
définitions utilisent definitions (jamais $defs) · 0 idiome booléen draft-04 · 0 mot-clé
post-draft-07. → CLEAN 26/26.
Dents prouvées. Copies-mutant en mémoire de rbac.schema.json (réel = 0 hazard) : exclusiveMinimum:true,
id:"http://…#", required:true, $defs:{…}, unevaluatedProperties:false → détecteur MORD 5/5.
Les 26 schémas réels restent CLEAN → détecteur non-inerte.
Conclusion. CLEAN 26/26 sur un axe de cohérence draft/keyword jamais couvert par les 6 axes
existants. 0 édition de production (#8), ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP.
Fichiers touchés : ce journal + 1 entrée mémoire (schema-oracle-draft-keyword-coherence). Sweep
rejouable tel quel (stdlib pur, sans réseau ni jsonschema) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_121542 — VÉRIF · axe NEUF « cohérence interne des 26 oracles-schémas » (required-vacuité + auto-contradiction const/enum/default) → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 dans le repo ; prod VPS /
builds EAS / dépôts ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages
produit appartenant à Michel (non éditables). Objectif anti-churn : un axe teeth des oracles-schémas
encore jamais balayé.
Axe NEUF (absent de la mémoire). Les 5 axes-schémas déjà en mémoire vérifient : (1) non-vacuité
(schema-oracle-teeth-audit) · (2) fermeture/complétude (schema-oracle-closedness-not-defect) ·
(3) câblage à un test (schema-oracle-wiring-axis) · (4) résolution des $ref
(schema-oracle-ref-integrity) · (5) enum/const vs skip python -S (schema-oracle-enum-const-vs-S-skip).
Aucun ne vérifie la cohérence LOGIQUE INTERNE d'un nœud : un schéma peut être clos, non-vacuous en
surface, $ref-sain ET pourtant contenir un nœud impossible à satisfaire (donc faux-vert : le test
qui valide un objet construit en mémoire ne le remarque jamais si l'objet n'exerce pas ce nœud).
Deux détecteurs maison (stdlib pur, marchent partout, sans réseau ni jsonschema).
- required-vacuité — un nœud objet avec
additionalProperties:falseET une clérequiredabsente deproperties/patternPropertiesest inatteignable (aucun objet ne peut valider). Marche récursif sur tous les mots-clés porteurs de sous-schéma (properties,items,$defs,allOf,anyOf,oneOf,if/then/else,additionalProperties-objet, …).additionalProperties-objet ou absent (défauttrue) ⇒ non signalé (la clé peut se satisfaire horsproperties) = conservateur. → 1405 nœuds balayés · 0 vacuité. - auto-contradiction —
const, chaque membreenum, etdefaultsont confrontés à leurs contraintes sœurs (type, bornes numériquesminimum/maximum/exclusive*,pattern,min/maxLength) ; plus les plages absurdes (minimum>maximum,minItems>maxItems). Unconstqui viole son propretype, undefaulthorspattern, un membre d'enum sous leminimum= données invalides latentes / nœud mort. → 317 constraint-checks · 0 contradiction.
Dents prouvées. Copies-mutant dans /tmp : required:["ghost"]+additionalProperties:false →
détecteur 1 MORD ; const:"notint" sous type:integer, membre enum 5 sous minimum:10,
default:"abc" hors pattern:^[0-9]+$, minimum:100/maximum:1 → détecteur 2 MORD (4/4). Les vrais
26 schémas restent CLEAN sur les deux → ce n'est pas un détecteur inerte.
Conclusion. CLEAN 26/26 sur deux sous-axes de cohérence interne jamais couverts. Aucun faux-vert
d'oracle de ce type. 0 édition de production (#8), ./run_ci.sh inchangé = 33 PASS · 0 FAIL ·
0 SKIP. Fichiers touchés : ce journal + 1 entrée mémoire (schema-oracle-internal-consistency). Sweep
rejouable tel quel (stdlib pur) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_101528 — VÉRIF · axe NEUF « fermeture & complétude des 26 oracles-schémas » → 1 anomalie investiguée (financement) = NON-DÉFAUT (choix explicite + couverture transitive byte-repro) · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (repo Sprints 1→7 ; prod VPS / builds
EAS / dépôts ONAPI hors périmètre worker · #8). Registre OPEN_DECISIONS_REGISTER.md = 3 arbitrages
produit appartenant à Michel (D-01 PIE contrats · D-02/V10 financement audit IA · D-03 chat.py),
non éditables par le worker. Objectif anti-churn : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire) → « fermeture (additionalProperties:false) & complétude des
properties » des 26 oracles. Distinct de schema-oracle-teeth-audit (qui prouvait la
non-vacuité : required + ≥2 mots-clés de contrainte). Ici la question est complémentaire :
un schéma non-vacuous mais ouvert (additionalProperties ≠ false) OU dont les properties
n'énumèrent qu'un sous-ensemble des clés réellement produites laisse passer des clés
fantômes/renommées/droppées — l'oracle serait alors partiellement aveugle sur la forme.
Méthode. (1) Balayage des 26 schémas : à chaque nœud-objet, additionalProperties is False ? →
23/26 entièrement fermés (convention dominante). (2) Pour les modules reconstructibles, diff
clés du bundle réel ⊆ properties déclarées. Confirmation authoritative pour financement via
build_bundle(spec, RoleResolver.from_path()) (le vrai bundle, pas une reconstruction).
Résultat — 1 seule anomalie réelle : crm/financement_bancaire/financement.schema.json.
- Top porte
"additionalProperties": trueécrit explicitement (les 3 pairs CRM —commissions/dossier_vente/workflow_vente— sont 100 % fermés). - Les
propertiesn'énumèrent qu'un sous-ensemble : 8 nœuds émettent des clés non déclarées (manifest.titre/source_version/source_directive/rbac_source,counts.documents_requis_*,banques[].note,sections[].resume,gate_spec.endpoint/submit_endpoint/banniere.couleurs,gate_status_example.projet/unite/banque_choisie). Le validateur maison (publiciste/lib/validator.py) honore pourtantadditionalProperties(support confirmé).
Verdict : NON-DÉFAUT — souplesse délibérée + transitivement couverte. (a) Le schéma valide
serré les champs structurels (ids pattern ^[a-z0-9_]+$, enum catégories/applies_to, counts
minimum/maximum, tableaux minItems/maxItems 4) et laisse libres les seuls champs
descriptifs (note, resume, titre, endpoint, couleurs) — même motif que seo
(schema_org.@graph[] JSON-LD intrinsèquement variable + manifest descriptif ouverts par design ;
brief.schema.json ouvert = schéma d'ENTRÉE). (b) Ces 8 champs sont épinglés au byte près par
test_build_egale_commite (l'artefact out/ commité == build frais) : toute dérive de nom/présence
sur la spec fixe est déjà RED via check_artifacts, indépendamment de la souplesse de l'oracle.
→ Fermer/compléter le schéma serait un churn sur un fichier au choix explicite (#5), à risque de
faux-rejet mal typé, sans dents nettes gagnées (spec figée + byte-repro).
Faux positif écarté (mobile). mobile/app_config/mobile.schema.json top = additionalProperties: false (fermé) ; les 4 « under-spec » signalés par le proxy = artefacts de ma reconstruction plate
(objets descriptifs imbriqués store_listing/app_config que le vrai bundle nidifie autrement).
Balayage additionalProperties confirmait déjà open_with_props=0 → mobile entièrement fermé.
Portée / anti-churn. 0 fichier de production édité, 0 schéma modifié, 0 artefact
reconstruit, 0 gate ajouté (#5), 0 chiffre saisi à la main (#6 — 26/23/8 recomputés
par balayage), 0 commande VPS (#8). Balayage lecture-seule, occurrence-zéro de défaut, rejouable
→ pas un gate, sibling de schema-oracle-teeth-audit / verify-non-defects-reconciliation-traps.
./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP (inchangé, aucune édition de code). Seules éditions :
ce journal + mémoire schema-oracle-closedness-not-defect (+ pointeur MEMORY.md).
Session 20260806_094524 — VÉRIF indépendante · NOUVEL axe teeth « tests silencieusement fantômes (shadowing) » → CLEAN 25/25 · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 côté repo ; prod VPS /
builds EAS / dépôts ONAPI hors périmètre worker · #8). Les ~15 derniers commits sont des sweeps
vérification-only à rendement décroissant. Objectif : chasser un vrai défaut ungaté sur un axe
teeth encore jamais balayé (anti-churn).
Axe NEUF (absent de la mémoire) → famille « tests silencieusement fantômes ». Distinct de
module-suite-teeth-mutation (mute la LOGIQUE générateur) et schema-oracle-teeth-audit (audite
l'ORACLE) : ici la cible est le harnais de test lui-même — 3 cas où une méthode de test est
définie mais unittest discover ne l'exécute jamais, sans qu'aucun gate ne détecte la perte :
def test_xdupliqué dans une classe — Python garde la dernière liaison ; la 1ʳᵉ ne tourne jamais.class TestFoodupliqué dans un fichier — la 2ᵉ classe masque la 1ʳᵉ ; les tests de la 1ʳᵉ ne tournent jamais.def test_*au niveau module (hors TestCase) —discoverne le lance pas, mais un compteur textuel le tallye.
Pourquoi c'est un vrai trou (aveugle aux autres sweeps). Un shadow fait passer le test par
absence ; et check_readme_claims (classe doc-numeric-claims-gate) compte les def test_
textuellement → un shadow ferait SUR-compter le README vs le compte runtime réel (dérive
inverse de l'habituelle). byte-repro / forward-compat / mutation-de-suite sont tous aveugles.
Détecteur (AST) — teeth-prouvé. Parse de chaque git ls-files '*test*.py' : par ClassDef,
Counter des méthodes test* > 1 ; par fichier, Counter des noms de ClassDef > 1 ; corps de
module, def test_* = niveau-module. Sonde synthétique 3-cas → les 3 MORDENT (méthode-dup,
classe-dup, niveau-module). Balayage réel → CLEAN 25/25 fichiers de test (0 méthode-dup,
0 classe-dup, 0 test niveau-module).
Verdict. Repo CLEAN sur le nouvel axe. Aucun défaut de production. Ces dents sont
réellement gardées par aucun gate (un test shadow serait détecté ici et nulle part ailleurs), mais
l'occurrence-zéro ne justifie pas un gate permanent (#5) : consigné comme sweep rejouable
(mémoire test-shadowing-sweep), sibling de runtime-encoding-integrity-sweep / hash-seed-….
Portée / anti-churn. Production éditée : néant. Édité : ce journal + 1 entrée mémoire. 0 artefact reconstruit, 0 gate ajouté (#5), 0 chiffre inventé (#6 — 25/25 recomputés par balayage), 0 commande VPS (#8). Structure du gate inchangée (33 checks).
Session 20260806_091519 — FIX doc · NOUVEL axe objectif « cohérence du compte de colonnes des tables Markdown » → 1 vrai défaut (ligne à colonne orpheline dans le TEMPLATE canonique de faisabilité)
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 côté repo ; prod
VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8). Les ~12 derniers commits sont des
sweeps vérification-only à rendement décroissant. Objectif : chasser un vrai défaut ungaté sur
un axe objectif encore jamais balayé (anti-churn), sinon améliorer une doc (fallback).
Axe NEUF (absent de la mémoire) → défaut réel. Cohérence du compte de colonnes des tables
Markdown : une ligne de corps dont le nombre de cellules | diffère de l'en-tête rend
désalignée (cellule orpheline). Distinct des sweeps encodage/liens/backtick déjà en mémoire.
Balayage de tous les .md (git ls-files '*.md', détection en-tête↔séparateur↔corps, | échappés
ignorés) → 3 hits, dont 2 faux positifs (fichiers-snapshots historiques
2026-08-05.md avec un | littéral dans un code-span `"|"` — immuables, non touchés) et
1 vrai défaut.
Défaut (réel, haute confiance).
05_deliverables_mvp/faisabilite/TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md §5.2 « Financier
bancable » — table à en-tête 4 colonnes (| # | Champ | Oblig. | Valeur canonique / placeholder |).
Les lignes 4.4→4.12 ont toutes 4 cellules (valeur+placeholder fusionnés dans la 4ᵉ colonne,
ex. 4.4 `{{cout_construction}}` USD). La ligne 4.13 en portait 5 : elle scindait
CONFOTUR · Fideicomiso (voir volet juridique) et `{{regime_fiscal}}` en deux cellules
→ colonne orpheline visible pour cette seule ligne, dans un template CANONIQUE que le
générateur 4 volets, le Publiciste et la régénération batch S4 (§8) doivent reproduire.
Fix. Fusion des deux fragments de 4.13 dans l'unique 4ᵉ colonne avec ·
(CONFOTUR · Fideicomiso (voir volet juridique) · `` {{regime_fiscal}}``), idiome des lignes sœurs (placeholder inline dans la cellule « Valeur »).regime_fiscalreste un champ légitime consommé pargenlib/renderer.py:195+banclib/finance.py`.
Sûreté vs gate. ci/check_readme_claims.sh:5765 lit ce template mais n'ancre que les lignes
#9 (l.144-146 **N %** (fixe · contrainte #9)) et #10 (l.149-151) ; la ligne 4.13 ne porte
ni #9 ni #10 ni pourcentage en gras → aucune regex du gate ne la touche.
Vérifié : les 5 surfaces d'ancrage du template restent vertes post-fix.
Vérif. Re-balayage tables → ligne 4.13 CLEAN (restent les 2 FP historiques) ·
ci/check_readme_claims.sh exit=0 · ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP.
Portée / anti-churn. Édité : le template (1 ligne) + ce journal + 1 entrée mémoire. 0 artefact
reconstruit (le template n'est pas un out/*.json), 0 gate ajouté (#5 — origine isolée, une
seule occurrence réelle repo-wide ; sweep rejouable, pas un contrat à figer, cf. link-label-target -mismatch), 0 chiffre inventé (#6), 0 commande VPS (#8). Structure du gate inchangée (33 checks).
Session 20260806_054454 — FIX doc · lien Markdown label↔cible incohérent (label = chemin de fichier, href pointant ailleurs) → 3 corrigés
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée/gatée. Les 6+ derniers commits sont des sweeps vérification-only
(0 édition) — rendement décroissant, du churn. Objectif : trouver un défaut réel actionnable
sur un axe non couvert par la mémoire, sinon améliorer une doc (fallback).
Axe éliminé (CLEAN, non re-flaggé). Chasse aux chemins-fantômes dans les code-spans backtick
(classe backtick-path-escapes-check-docs — les backticks neutralisent le link-check). Résolution
contre racine dépôt + racine deliverables + sous-arbres modules, en excluant les snapshots
historiques (05_activity_log/, daily_reports/), les globs */AGENT.md et les chemins
runtime/VPS (otoia/, out/, vente.otov7.com). Les 2 candidats survivants
(crm/commissions/README.md banclib/finance.py · onapi_legal/AGENT.md cflib/*.py) sont des
faux positifs : le premier est un label de lien dont la vraie cible
../../faisabilite/bancable/banclib/finance.py existe ; le second résout sous
legal/confotur/cflib/. → dimension CLEAN, mémoire confirmée.
Axe NEUF (absent de la mémoire) → défaut réel. Liens Markdown de la forme [label](cible)
où le label (code-span) est un chemin de fichier existant mais dont le basename diffère
de celui de la cible href. check_docs prouve que la cible résout, jamais que le label
la désigne : un lecteur qui Cmd-clique ci/guard_constraints.sh atterrit sur ci/README.md
(mauvais fichier). Balayage git ls-files '*.md' → 3 occurrences, toutes dans README.md :
- L54
[ci/guard_constraints.sh](ci/README.md)et[ci/check_docs.sh](ci/README.md) - L75
[ci/guard_constraints.sh](ci/README.md)
Les scripts nommés existent et sont git-trackés (ce sont des gates) ⇒ FIX = pointer chaque
label vers le fichier qu'il nomme (ci/guard_constraints.sh / ci/check_docs.sh). Résultat :
label == cible, navigation véridique, check_docs toujours vert (les cibles existent).
Vérif. Cibles existent ✓ · re-balayage mismatch → 0 · ci/check_docs.sh PASS ·
./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP.
Portée / anti-churn. Édité : README.md (3 href) + ce journal. 0 artefact reconstruit,
0 gate ajouté (#5 — origine isolée, pas une classe récurrente, cf. confotur 8-négatifs),
0 chiffre inventé (#6), 0 VPS (#8). Structure du gate inchangée (33 checks).
Session 20260806_051453 — VÉRIF indépendante · NOUVELLE famille objective « portabilité runtime / intégrité d'encodage » (3 axes neufs) → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre.
Roadmap 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 côté
repo ; le reste — déploiement prod, builds EAS, dépôts ONAPI — est hors périmètre worker,
VPS 153.75.250.214 · CLAUDE.md #8). Les 3 derniers commits sont vérification/currency-only
(12ec9d0 MANIFEST+dup-keys, 4bc9819 slugs, b274958 currency). Le canal stakeholder est
déjà courant : la dernière prod est 48276ee (FIX financement), attesté par le rapport
daily_reports/2026-08-06.md §034434 ; les 3 commits suivants sont non-opposables (0 édition
de prod). Re-porter la currency ou refaire un sweep déjà couvert serait du churn.
Pré-vérif — aucun travail incomplet. Grep TODO|FIXME|XXX|stub|NotImplemented|à compléter
sur tout .py de prod (hors tests/) → 0 marqueur réel ; tous les hits « placeholder »
sont le pattern #6 zéro-invention intentionnel ({{…}} documenté). Le code de prod est
complet. Le registre OPEN_DECISIONS_REGISTER.md (D-01→D-05) reste des arbitrages produit
Michel / dépendances hors dépôt, non-actionnables worker (ne pas re-surfacer, #5).
Axes ungatés cartographiés avant de chasser. check_docs gate tous les liens md relatifs
(git ls-files '*.md', code-spans neutralisés, ancres strippées) → liens couverts ; l'unique
ancre intra-doc (README.md:20 → ## Directives Michel (input-specs datés)) résout — 1 seule
occurrence, pas de gate (#5). Reste non couvert par aucun gate : l'intégrité d'octets
des fichiers texte (encodage, BOM, fins de ligne) et la compilabilité des .py non testés.
Le CI se soucie déjà de l'encodage runtime (PYTHONUTF8=1, cf. en-tête ci.yml) mais ne
vérifie rien côté fichiers commités. D'où cette famille neuve, absente de la mémoire
(hash-seed / locale-TZ / forward-compat testent le rebuild, pas les octets sources).
Axe 1 — intégrité d'encodage des fichiers texte commités. Parse binaire des 379 fichiers
texte commités (.json/.md/.py/.sh/.yml/.yaml/.txt/.cfg/.toml) :
- BOM UTF-8 (
\xef\xbb\xbfen tête, cassejson.loadstrict / rend""+clé) → 0. - UTF-8 invalide (
bytes.decode('utf-8')lève) → 0. - Caractères de contrôle parasites (
< 0x20hors\t\n\r, +U+2028/2029, + surrogatesD800–DFFF) → 0. - JSON non-parseable (
json.loadssur les 114.json) → 0.
Axe 2 — fins de ligne (portabilité cross-plateforme). Un CR dans un fichier commité
introduit une dérive d'octets invisible (diff byte / check_artifacts d'un runner à autocrlf).
Balayage \r sur tous les fichiers texte → 0 CRLF (LF pur partout).
Axe 3 — compilabilité de tout .py commité (dents réelles). python3 -m py_compile sur les
144 .py versionnés. Contrairement aux 25 suites unittest (qui n'importent que les modules
qu'elles exercent) et à forward-compat (-W error sur les suites), ceci prouve que chaque
fichier — y compris un helper *lib/ jamais importé par un test — est syntaxiquement sain : une
erreur de syntaxe latente dans un chemin non couvert passerait verte aujourd'hui. → 0 erreur.
Verdict. Repo CLEAN sur les 3 axes de la famille « portabilité runtime / intégrité
d'encodage ». Aucun défaut de production. Ces axes sont réellement gardés par aucun gate
(teeth réelles : un BOM, un octet non-UTF-8, un CRLF ou une syntaxe cassée serait détecté ici et
nulle part ailleurs), mais l'occurrence-zéro observée ne justifie pas un gate permanent (#5) :
consigné comme sweep rejouable (mémoire runtime-encoding-integrity-sweep), pas comme job CI.
Portée / anti-churn. Production éditée : néant. Seul ce journal touché. 0 artefact
reconstruit, 0 gate ajouté (#5), 0 chiffre inventé (#6 — 379/114/144 tous recomputés
par balayage), 0 commande VPS (#8). Structure du gate inchangée : run_ci.sh = 33 PASS
= 8 statiques + 25 suites, aucun jalon n'ajoute de job.
Session 20260806_044449 — VÉRIF indépendante · NOUVEL axe objectif « re-dérivation des champs-compte MANIFEST depuis le payload réel » + clés JSON dupliquées → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre.
Roadmap 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée. Les 3 derniers
commits sont vérification-only (4bc9819 intégrité slugs, b274958 currency, 243523c multi-axes).
Pour éviter le churn, chasse à un vrai défaut ungaté sur deux axes objectifs réellement neufs,
non couverts par les sweeps passés (mémoire : hash-seed, locale/TZ, forward-compat, intégrité
référentielle rôle/portail = résolution de slugs, bijection inverse, docstring↔code, liens md).
Axe 1 — clés JSON dupliquées (classe neuve). json.load garde silencieusement la dernière
valeur sur clé dupliquée ; la byte-repro est aveugle à ce défaut sémantique. Parse strict des
114 fichiers JSON commités avec object_pairs_hook levant sur doublon → 0 clé dupliquée,
0 erreur de parse. CLEAN.
Axe 2 — re-dérivation des champs-compte MANIFEST depuis le payload réel (classe neuve).
Distincte de « artifact internal set-semantics » (un dup ponctuel, 48276ee) et de « intégrité
référentielle slugs » (4bc9819, qui teste la résolution de chaque slug, pas la
re-dérivation d'un compte). Ici : chaque nb_*/_total d'un MANIFEST recomputé depuis la
liste réelle du fichier-payload voisin. Un bug générateur (comme le dup financement) serait
silencieux à la byte-repro mais tomberait ici. Résultats :
- frontend/portails —
nb_liens/nb_cartes/nb_rolespar workspace (5×3) + agrégats (liens_total=53,raccourcis_total=20) recomputés deworkspace.json→ 0 écart. ⚠️ Faux positif initial écarté :nb_liens≠len(links)car le tableau ERPNextlinksmélange lignestype=='Card Break'(= cartes) ettype=='Link'(= vrais liens) ;nb_liens=Σ(type==Link),nb_cartes=Σ(type==Card Break) — par design, écart initial (15−4=11…) = exactement le nb de cartes ; confirmé sur le champtype.link_countde chaque Card Break = nb de Links sous la carte. - frontend/chat_otoia —
nb_roles/nb_knowledge_doctypes(5×2) vschat_mount.json(roles_allowed/knowledge_scope) → 0 écart. - mobile/app_config —
nb_roles(5) vsrole_navigation.json(roles_allowed) → 0 écart. - rbac/roleprofile_gen —
nb_roles(6, dont Plateforme=6) vsrole_profile.json(roles, matché parrole_profile) → 0 écart. - qa/audit_5d —
controls_total=17 vs Σ(dimensions[].controls_total)=4+3+3+4+3 → 0 écart (les dimensions ne portent pas de liste de contrôles ; le sous-compte est l'autorité, gaté par la suite du module).
Verdict. Repo CLEAN sur les deux axes neufs. Aucun défaut de production. Le seul
« mismatch » apparent (nb_liens) était une convention ERPNext Card-Break/Link que mon len()
naïf lisait mal — re-dérivé correctement = concordance totale.
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit,
0 gate ajouté (#5 — ces re-dérivations sont couvertes en substance par les suites de module ;
je ne fige pas un énième gate pour un axe qui sort CLEAN), 0 chiffre saisi à la main (#6 —
tout recomputé depuis les payloads et run_ci.sh), aucune commande VPS (#8). Seule édition :
ce journal 05_activity_log/.
Session 20260806_041444 — VÉRIF indépendante · NOUVEL axe objectif « intégrité référentielle des slugs de rôles & portails inter-artefacts » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre.
Roadmap 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (8 gates statiques
- 25 suites, 24 modules dans ≥1 fiche). Les commits récents (
48276eeduproles_rbac_utilises,b274958currency) ayant épuisé les axes docstring↔code / numériques / currency, et l'anti-churn proscrivant une énième couche de currency pour des commits sans changement d'état, j'ai chassé un vrai défaut ungaté sur un axe OBJECTIF encore jamais balayé : la cohérence référentielle des identifiants de rôle et de portail cités à travers tous les artefactsout/*.jsonvs l'ensemble canoniquerbac/rbac_50_roles.json(même famille que le fix48276ee, mais côté existence des slugs et non unicité).
Axe 0 — sanity docstring↔code (modules non couverts au dernier sweep). Explore sceptique sur
commissions/dossier_vente/acceptance/audit_5d/regression/deploy_runbook/ecf/portails/faisabilite/
publiciste/rbac(apply,userperm,roleprofile) : CLEAN. Seul « défaut » remonté = le
validate [-o OUT] de deploy_runbook, non-défaut connu (bijection compat docstring↔argparse
self-consistante — mémoire docstring-vs-code-drift), écarté.
Axe 1 — rôles. Extraction de 980 valeurs-feuilles sous toute clé matchant /role/i dans
les ~50 artefacts 05_deliverables_mvp/**/out/*.json, filtrées aux tokens slug-like kebab
(le format des 50 ids canoniques, ex. ventes-conseiller). 0 slug non résolu vs les 50 ids.
→ intégrité totale des références de rôle.
Axe 2 — portails. 66 valeurs-feuilles sous toute clé matchant /portail|portal/i. Les 6
tokens hors portails_business (5) sont tous des non-défauts vérifiés :
sell·project·buying·accounting·organization= valeurs du sous-champicondes objets portail demobile/app_config(noms d'icônes ERPNext, pas le champportail— lequel ne porte que les 5 métier canoniques) ;plateforme= le 6ᵉ portail technique légitime (roleprofile_genreporteportails_metier=5 /portails_techniques=1).rbac_50_roles.jsondistribue bien les 50 rôles sur 6 portails (ventes12·construction10·direction9·compta8·plateforme6·achat5) ;portails_businessn'énumère délibérément que les 5 métier. Aucunportailréel hors des 6 attendus.
Verdict. Repo CLEAN sur le nouvel axe (rôles + portails référentiellement intègres) ; les axes docstring↔code et numériques stakeholder restent concordants. Aucun défaut de production.
Anti-gate (#5). Pas de nouveau gate : beaucoup de ces slugs sont déjà transitivement
mono-sourcés (mémoire roles-couverts-transitively-single-sourced) et l'axe est une vérification
rejouable (à l'image de hash-seed / locale-TZ / forward-compat), pas un contrat à figer. Le
one-liner Python du sweep est consigné ici pour rejeu.
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit,
0 gate ajouté (#5), 0 chiffre saisi à la main (#6 — 980/66/50/6 recomputés des
artefacts + rbac_50_roles.json), aucune commande VPS (#8). ./run_ci.sh = 33 PASS · 0 FAIL
· 0 SKIP (inchangé, aucune édition de code). Seule édition : ce journal + une entrée mémoire.
Session 20260806_034434 — CURRENCY canal stakeholder a8342e9→HEAD 48276ee + re-vérif indépendante de la dernière prod (48276ee)
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre
propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (spec
CHOISIR_MON_UNITE = frontend VPS hors-périmètre #8). Aucune tâche fonctionnelle
in-repo. Constat : le daily report 2026-08-06.md (rédigé session 011419, attestait
HEAD a8342e9) ne consignait pas deux jalons de production 08-06 postérieurs —
354a687 (fix help-text argparse seo) et surtout 48276ee (fix générateur
financement_bancaire + MANIFEST reconstruit). Mémoire two-logging-channels : garder
le canal stakeholder courant, chaque chiffre sourcé d'artefact.
Re-vérification indépendante de la dernière prod 48276ee (avant de consigner).
- MANIFEST courant
roles_rbac_utilises= 3 objets distincts (compta-tresorier, ventes-conseiller, ventes-directeur), 0 doublon → fix effectif et correct. - Balayage surfaces sœurs (README/finlib/tests) : aucune ne cite l'ancien compte
« 4 » ni une liste 4-entrées ; commentaire
builder.py:89-95décrit correctement le dédoublonnage → 0 dérive résiduelle (motif « twin fix sweep ALL surfaces », propre). - Champ appropriément ungaté (#5) : depuis l'état propre,
check_artifactsMORD une régression future (régénéré-avec-doublon ≠ committé-3-distinct → RED) → pas de gate N+1.
Action. Ajout d'une section de currency à daily_reports/2026-08-06.md : tableau
des 2 jalons de prod postérieurs (nature + opposabilité au merge), re-vérif de 48276ee,
état courant re-dérivé au HEAD 48276ee (8 statiques + 25 suites = 33 PASS · régression
624/607/17-skip = PASS). Chiffres recomputés de git ls-files/run_ci.sh/ci.yml/
regression_run.json, jamais saisis à la main.
Verdict. Dernière prod (48276ee) correcte + complète, 0 dérive résiduelle.
Canal stakeholder à jour au HEAD. Aucun défaut de production.
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit,
0 gate ajouté (#5), 0 chiffre inventé (#6), aucune commande VPS (#8). Seules
éditions : daily_reports/2026-08-06.md + ce journal 05_activity_log/.
Session 20260806_031432 — FIX générateur (NOUVELLE sous-classe : liste per-slot vs ensemble distinct) · crm/financement_bancaire : MANIFEST.roles_rbac_utilises répétait ventes-conseiller (dup ungaté)
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée/gatée. Les 5 derniers commits étant FIX-help / currency / vérif-only,
chasse adversariale à un vrai défaut ungaté sur un axe frais (anti-churn) : la
cohérence sémantique interne des artefacts commités (un champ « ensemble » contenant un
doublon) — axe non balayé par les 7 axes de 024430.
Défaut trouvé (réel, haute confiance).
05_deliverables_mvp/crm/financement_bancaire/out/MANIFEST.json · roles_rbac_utilises portait
4 entrées pour 3 rôles distincts : ventes-conseiller / OTO Ventes Conseiller répété
(positions 1 et 4). Reproductible depuis le générateur (donc pas un artefact stale — byte-repro
vert) : finlib/builder.py:89 faisait [roles_resolved[k] for k in sorted(roles_resolved)],
itérant les 4 slots logiques de spec["roles"] — or deux slots (conseiller_wag ET
validation_dossier) pointent le même rôle ventes-conseiller → doublon.
Preuve que c'est un bug et non un per-slot voulu.
- La sortie drope la clé de slot (
cle) : chaque entrée ne porte querole_id+erpnext_role_name. Un doublon ne transporte donc aucune information distinctive — si le per-slot était voulu, la clé de slot serait sérialisée. Smoking gun. - Sémantique documentée : README
MANIFEST.json → … rôles RBAC **utilisés** » (README.md:54) +ci/README.md:1100« l'**ensemble distinct**(role_id, nom)` réellement payé ». - Convention codebase : les 3 modules frères produisent tous un ensemble distinct
(
confotur3/3 ·commissions4/4 ·workflow_vente7/7,n == distinct).financementétait le seul outlier (4/3).
Fix (finlib/builder.py:89, aligné idiome commissions). Ensemble distinct trié par
role_id : [{"role_id": rid, "erpnext_role_name": resolver.erpnext_name(rid)} for rid in sorted({rr["role_id"] for rr in roles_resolved.values()})] + commentaire expliquant le
dédoublonnage multi-slots. Artefact régénéré (build) → roles_rbac_utilises = 3/3 distinct
(compta-tresorier, ventes-conseiller, ventes-directeur).
Pourquoi 0 gate ajouté (#5). Le champ roles_rbac_utilises de financement n'est lu par
aucun gate (les blocs check_readme_claims qui le lisent visent confotur/commissions/
workflow_vente, en SET — donc dedup) ni aucun consommateur externe (grep = 0). Le champ
counts n'inclut pas de compte de rôles pour ce module (contrairement aux frères) → aucune
prose/oracle à réaligner. Le fix corrige la source ; un gate distinctness serait redondant
avec la convention déjà tenue par les frères et non-régressante ici (verify-before-gate).
Non-régression. ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP après (byte-repro
check_artifacts vert avec le nouvel artefact ; les 35 tests crm-financement-bancaire
verts — aucun ne figeait roles_rbac_utilises, l.204 teste role_validation, non affecté).
2 fichiers édités (finlib/builder.py + out/MANIFEST.json régénéré), 0 chiffre inventé
(#6 — 4/3 recomputés), aucune commande VPS (#8).
Session 20260806_024430 — VÉRIF indépendante multi-axes (7 axes) → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée/gatée côté dépôt (les restes roadmap = livrables VPS/live hors
périmètre worker · #8/instructions). Les 5 derniers commits étant FIX-help / currency / prose,
chasse adversariale ciblée à un vrai défaut ungaté sur des axes frais (anti-churn : ne PAS
réempiler une énième couche help-text/currency/prose déjà balayée par 021424/014423/011419).
Chaque axe est soit corrigé, soit prouvé accurate/gaté — aucune édition manufacturée (#5/#6).
Les 7 axes balayés (tous CLEAN) :
-
README « Entrée CLI » ↔ argparse (23 modules) — sous-commandes / flags documentés dans les README vs
add_parser(...)/add_argument(...)réels. Aucun flag/sous-commande fantôme ni divergence de nom (--outvs--output, etc.). Distinct de l'axe help-TEXT (déjà clos021424). -
Sémantique du CLI
validate—cmd_validatere-construit en mémoire (_build) puis valide ; il ne relit pas l'artefactout/commité. By-design, PAS un défaut de dents : la dérive byte d'unout/corrompu est couverte parcheck_artifacts(diff byte-à-byte). Noté pour qu'une session future ne le « flag » pas comme teeth manquantes (verify-before-flag). -
Ledger 15 invariants SEO —
seo_gen.pynumérote bien 1…15 (# N ·, l.77-215) ; les 3 surfaces prose (validatesuccès l.285, helpvalidatel.297, README## Garanties (15 invariants)) disent 15, self-consistent. Le « =9 » de la mémoireinvariant-ledger-count= 9 MODULES en dialecte A, PAS 9 invariants/module (relecture — non-contradiction). -
Root
README.md« 24/24 modules · 24 suites » — correctement sourcé :24 modules= périmètreaudit_4big/out/quality_report.json;24 suites=regression_plan.jsonqui s'auto-exclut (self_module: qa/regression, ISA 315 · séparation des pouvoirs) → 24 = 25 gates-de-testrun_ci− 1 (le harnais régression lui-même). Piège de réconciliation 24-vs-25 : NON-défaut.totals.suites=24 · test_methods=624, cohérent avecregression_run.json. -
Prose
notes[]embarquée dans les artefacts JSON (regression_plan.json, 5 notes) — surface libre re-générée+byte-gatée ; contenu accurate (dérivation.gitea/workflows/ci.ymlviaq4lib/registry.parse_ci, self-exclusion, anti-invention #6). CLEAN. -
Complétude de
gate.needs— le job d'agrégatgate(seul verrou de merge) liste bien les 33 checks (8 statiques + 25 suites) ;e2e-baselinecorrectement exclu (manuelworkflow_dispatch). Déjà GATÉ parci/check_ci_integrity.shINV-A (gate.needs == {tous jobs} − {gate} − {manuels}) → une dérive de câblage casserait le gate. -
Commentaires inline
#cross-fait/compteur (dans*_gen.py+*lib/, PAS docstrings/README déjà audités) — 14 commentaires porteurs de compte vérifiés vs la réalité qu'ils décrivent (50 rôles, 3 langues, 5 portails, 4 capacités OTOIA, 12 groupes Master Data PIE, 5 dimensions audit_5d, ≥2 modules démo, axe5=20 pts, 4 conditions financement…). Tous synchronisés. CLEAN.
Non-régression. 0 fichier de production touché (git status vide avant/après). run_ci.sh
33 PASS · 0 FAIL · 0 SKIP inchangé. 0 artefact reconstruit, 0 gate ajouté (#5 — tout défendable
est déjà gaté), 0 chiffre inventé (#6 — tout recomputé depuis artefacts/code), 0 VPS (#8).
Session 20260806_021424 — FIX argparse-help↔code (classe docstring-vs-code-drift, sous-classe help-text du fix a8342e9) · seo/seo_gen.py : le help build nommait des fichiers de sortie INEXISTANTS
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre.
Les 5 derniers commits étant currency/vérif-only, chasse adversariale à un vrai défaut ungaté,
en poursuivant la sous-classe ouverte par le fix a8342e9 (session 004414) : le texte help
d'une sous-commande argparse énumérant ses fichiers de sortie. Audit exhaustif des 22
générateurs (*_gen.py avec sous-commande build/generate) : annoncé-dans-le-help ↔
réellement-écrit-par-le-code.
Résultat de l'audit. 21/22 CLEAN — mais 1 vrai défaut trouvé, haute confiance :
05_deliverables_mvp/seo/seo_gen.py:293. Le help build disait
"genere seo_keywords/schema_org/hreflang + MANIFEST", or les fichiers RÉELLEMENT écrits
(seo_gen.py:259-262) sont seo_keywords.json, seo_schema_org.json, seo_hreflang.json,
MANIFEST.json. Le help droppait le préfixe seo_ sur 2 des 3 fichiers (schema_org,
hreflang) ET l'extension .json sur tous : il nommait donc des fichiers qui n'existent pas
(schema_org ≠ seo_schema_org.json). Ce n'est pas un simple raccourci toléré — exactement la
classe du fix a8342e9 (le help ment sur la surface de sortie que l'agent lit pour lancer son
propre livrable).
Preuve que c'était bien la SEULE surface fautive (fix en une passe). Le docstring module
(seo_gen.py:6-9) ET le README (README.md:15-18) nomment déjà correctement les 4 fichiers
exacts (out/seo_schema_org.json, out/seo_hreflang.json, …) — c'est le help l'outlier, pas
l'inverse. Les modules frères (commissions, audit_5d, app_config post-a8342e9) énumèrent
des noms de fichiers complets ; seo était le seul en raccourci imprécis.
Fix (seo_gen.py:293). Help → "genere seo_keywords.json / seo_schema_org.json / seo_hreflang.json / MANIFEST.json" — exact, aligné sur le docstring/README du module ET la
convention des frères. Style respecté : ASCII sans-accent (genere, comme le reste du fichier).
Pourquoi 0 gate ajouté (#5). Le help n'est ni sérialisé dans un artefact ni parsé par un gate
(le gate check_readme_claims « Entrée CLI » ne lit que le NOM add_parser("build") via AST, pas
le help=). Un gate parseur du help serait fragile : les 22 help strings n'ont PAS de
convention machine-parsable commune (seo en raccourci, faisabilite décrit un répertoire
data_room/PXX/, séparateurs / vs + mêlés). Mauvais compromis robustesse/valeur → la classe
reste couverte par l'audit périodique (ici 22/22 balayés), pas par un gate.
Non-régression. Sortie build byte-identical aux 4 artefacts out/ commités (help = code
pur, non sérialisé → 0 rebuild). python3 -S seo_gen.py build/validate OK. run_ci.sh 33 PASS ·
0 FAIL · 0 SKIP. 0 prod (#8), 0 artefact reconstruit, 0 chiffre inventé (#6 — tout recomputé).
Session 20260806_014423 — FIX prose-numérique canal stakeholder · le compte « bijection README↔réel » de la session 011419 sous-comptait (14 → 16) · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée/gatée. Les 5 derniers commits étant currency/vérif-only, chasse
adversariale ciblée à un vrai défaut ungaté — pas une énième couche de currency (anti-churn).
Premier axe (comment↔code des ci/*.sh, classe du fix 8903a4b) → un candidat check_regression.sh:17
écarté comme faux positif : le commentaire « (ran/passed/failures) » décrit ce que l'artefact
porte (le JSON contient bien failures:0), pas la ligne d'affichage l.79 ; le contrat réel du
gate est le diff -q byte-à-byte du fichier entier (l.71), qui couvre failures. Non-défaut.
Défaut trouvé (réel, haute confiance) — classe prose-facts-vs-numeric-drift. L'« axe de
vérification objectif » introduit par la session 011419 (bijection compte de tests annoncé dans
chaque README ↔ nombre réel de def test_*) affirmait « concordance totale sur les 14 READMEs
annonceurs » — figure reprise à l'identique dans DEUX surfaces : le canal stakeholder
daily_reports/2026-08-06.md (l.48-54) et ce journal (entrée 011419). Or l'énumération de
011419 liste effectivement 14 modules mais en omet 2 qui annoncent pourtant un compte dans
leur README, tous deux concordants avec le réel : frontend/portails 19 (19 def test_) et
demo/scenarios 39 (39 def test_). Le vrai total est 16 annonceurs / 9 sans annonce,
corroboré par l'en-tête faisant autorité de ci/check_readme_claims.sh (l.96 : « 27 claims
sur 16 modules »). Le « 11 sans annonce » du canal était le complément erroné (25 − 14) → 9.
Fix (sweep des 2 surfaces en une passe, cf. prose-facts-vs-numeric-drift).
daily_reports/2026-08-06.md:14 → 16, ajout des 2 entrées manquantes à l'énumération,11 → 9sans annonce, + ancrage explicite à l'en-tête « 16 modules » du gate.- ce journal :
14 → 16dans l'entrée011419(fait présent sur l'état courant du repo) avec commentaire de traçabilité ; nouvelle entrée (celle-ci) documentant la correction. Chiffres re-dérivés, non saisis :grep -rlE '[0-9]+ tests' README.md= 16 annonceurs ·def test_par suite = concordance 16/16 ·find -name tests= 25 suites → complément 9 · en-tête gate = 16.
Non-régression. Le compte est un fait de prose ungaté (canal stakeholder + journal, classe
two-logging-channels : chiffre à sourcer d'artefact, ici recomputé). Aucun oracle/gate ne fige
cette phrase (vérifié) : édition sûre. ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP avant/après.
Portée. 0 fichier de production édité (2 docs : 1 deliverable stakeholder + ce journal),
0 artefact reconstruit, 0 gate ajouté (#5 — correction de fait, pas un contrôle ; la surface
de compte-de-tests des READMEs est déjà gatée par check_readme_claims.sh), 0 chiffre inventé
(#6 — 14→16/9/25 recomputés), aucune commande VPS (#8).
Session 20260806_011419 — CURRENCY canal stakeholder porté au HEAD a8342e9 + axe de vérif objectif ajouté (bijection compte-de-tests README↔réel) · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée+gatée. Le canal stakeholder daily_reports/ s'arrêtait au
2026-08-05 (HEAD 8903a4b) alors que deux jalons du 2026-08-06 avaient depuis été
livrés — dont un vrai fix de production (a8342e9, help argparse mobile/app_config omettant
store_listing.json) non encore consigné côté stakeholder. Contrairement aux currency
purement vérification-only (motif anti-churn adopté en 234409/231404), il y a ici une vraie
évolution d'état à reporter → currency légitime du canal lapsé (two-logging-channels).
Ce qui a été fait. Création de 05_deliverables_mvp/daily_reports/2026-08-06.md :
- tableau des 2 jalons 08-06 (
fe61207VÉRIF-only ·a8342e9FIX 1-ligne help-text) avec nature + opposabilité au merge ; - état courant re-dérivé d'artefacts commités : 33 PASS (8 statiques
git ls-files ci/*.shhorslib.sh+ 25 suites) · régression624 exécutés / 607 passés / 17 skippés / 0 échec = PASS(qa/regression/out/regression_run.jsontotals, 17 skips par design) ; - axe de vérification objectif ajouté ce jour : bijection
N testsannoncé dans chaque README ↔ nombre réel dedef test_*→ concordance totale sur les 16 READMEs porteurs d'une annonce (seo 36 · ecf 39 · confotur 44 · dossier_vente 31 …), 0 dérive.
Vérif indépendante préalable (lecture-seule). docstring↔code re-vérifié sur modules non
balayés la veille : publiciste (docstring/help/writes cohérents = projets_master.json +
index.html) · fiscal/ecf_dgii (« 12 invariants » = schéma(1)+2→12 numérotés, self-consistant,
pas un défaut). Couverture CI de publiciste confirmée dans gate.needs. Structure
ci/*.sh↔gate.needs intacte (8 statiques).
Non-régression. Le rapport est une doc stakeholder pure : ./run_ci.sh = 33 PASS · 0 FAIL
· 0 SKIP avant et après.
Portée. 0 fichier de production édité, 0 artefact reconstruit, 0 gate ajouté
(#5 — currency documentaire, pas un contrôle), 0 chiffre inventé (#6 — 8/33/25/624/607/17
recomputés de git ls-files · run_ci.sh · .gitea/workflows/ci.yml · regression_run.json),
aucune commande VPS (#8). Seules éditions : le rapport de currency + ce journal.
Session 20260806_004414 — FIX docstring↔code (sous-classe help-text argparse) · mobile/app_config_gen.py : le help de la sous-commande build omettait store_listing.json
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, roadmap
intégralement livrée/gatée. Plutôt qu'une énième passe vérification-only (les 5 derniers
commits l'étaient), chasse adversariale ciblée à un vrai défaut ungaté sur la classe
RECURRING docstring↔code, scopée aux modules non balayés la veille (session 001412
avait couvert seo/financement/demo/pie/rbac/audit_4big/chat/workspaces/workflow_vente/confotur).
Défaut trouvé (réel, haute confiance). 05_deliverables_mvp/mobile/app_config/app_config_gen.py:279 —
le help= de sub.add_parser("build", …) énumérait 4 sorties
(app_config.json / eas_build.json / role_navigation.json / MANIFEST.json) alors que
cmd_build() en écrit 5 (l.240-244) : store_listing.json (l.243) était omis du help.
La docstring (l.15, « écrit les 5 fichiers ci-dessus ») et le README (l.56/68) listent bien les 5.
Nouvelle sous-classe help-text argparse du motif docstring↔code — distincte des sorties
open(...,'w') vs docstring déjà balayées : le help CLI est une 2ᵉ surface prose dans le code.
Fix. Ajout de store_listing.json dans le help=, à sa position canonique
(…role_navigation → store_listing → MANIFEST), ordre identique docstring/README/code.
Aucun gate/test ne fige cette chaîne (grep -rn = 0 hit hors le fichier) → édition sûre,
pas d'oracle à réaligner.
Non-régression. Le help n'affecte aucune sortie : artefacts byte-identiques
(ci/check_artifacts.sh vert dans le merge). --help rend désormais les 5 fichiers.
./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP avant et après.
Portée. 1 fichier de production édité (1 ligne, help argparse), 0 artefact reconstruit (sortie inchangée), 0 gate ajouté (#5 — la chaîne n'était figée par personne, on corrige la source pas de nouveau gate), 0 chiffre inventé (#6), aucune commande VPS (#8). Reste de l'audit (audit_5d/acceptance/regression/deploy_runbook/commissions/dossier_vente/ ecf/portails/faisabilite) = CLEAN, comptes d'invariants et noms de sorties concordants.
Session 20260806_001412 — VÉRIF indépendante multi-axes (docstring↔code · numériques du canal stakeholder · liens in-repo) → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre.
La roadmap 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md est intégralement livrée/gatée (8 gates
statiques + 25 suites, 22 modules dans ≥1 fiche). Le canal de currency
daily_reports/2026-08-05.md attestait au HEAD 8903a4b ; depuis, deux commits
vérification-only (21f55a9 currency, c196e41 VÉRIF adversariale, tous deux « 0 prod »).
Conformément à la décision anti-churn déjà prise en 234409 (« plutôt que répéter le motif de
currency »), pas de énième couche de currency pour deux commits sans changement d'état
stakeholder. À la place : chasse indépendante à un vrai défaut ungaté sur trois surfaces,
plus re-dérivation des chiffres du canal stakeholder pour prouver qu'aucun n'a dérivé.
Axe 1 — docstring↔code (classe RECURRING). Balayage Explore skeptique des générateurs de
production + helpers, sortie-par-sortie : noms de fichiers open(...,'w') vs docstring/README,
flags argparse vs docstring, comptes d'invariants du ledger numéroté vs docstring. 13 modules
audités CLEAN (seo 4/4 fichiers · financement_bancaire 7/7 · demo_scenario 3/3 · pie 2/2 ·
rbac_fixtures 3/3 · audit_4big 2/2 · chat_otoia 3/3 · workspaces 2/2 · workflow_vente 4/4 ·
confotur 2/2 …). Comptes d'invariants concordants (seo 15 · audit_5d 15 · pie 10 · rbac_apply 7 ·
workflow_vente 9 · confotur 14 · dossier_vente 12 · commissions 10 · financement 12). Non-défaut
connu ré-écarté : validate [-o OUT] = bijection compat 8-vs-13 self-consistante, pas un défaut.
Axe 2 — numériques du canal stakeholder re-dérivés au HEAD. Aucune dérive :
- SEO 258 mots-clés recomputés de
seo/out/seo_keywords.json→ fr=87 · en=87 · es=84 (concorde). - Gates statiques :
git ls-files 'ci/*.sh'horslib.sh= 8 (concorde). - Suite de merge :
run_ci.sh= 33 PASS (= 8 statiques + 25 suites, concorde).
Axe 3 — liens markdown in-repo (surface objective). Extraction de tous les [texte](cible)
repo-wide non-http/#/mailto : 294 liens, 291 résolvent. Les 3 « cassés »
sont des faux positifs — des exemples de syntaxe de lien cités en prose dans des journaux
historiques, non des liens réels : 2026-08-02.md:392 ([texte](cible) documentant le
comportement de check_docs.sh) et 2026-07-30.md:1432/1477/1479 (l'exemple pédagogique
…/README.md à ellipse que le journal lui-même décrit comme « déclaré cassé par la regex »).
Le vrai gate check_docs.sh neutralise les code-spans à backticks et ne les compte pas ;
ma regex ad-hoc, plus grossière, a simplement reproduit le faux positif que le journal explique.
Classe pedagogical-hypothetical = KEEP (motif prose-facts). 0 lien réel cassé.
Verdict. Repo CLEAN sur les trois axes + numériques stakeholder intacts. Aucun défaut de
production. Session de vérification, à l'image de d6a8d73/c196e41.
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit,
0 gate ajouté (#5), 0 chiffre saisi à la main (#6 — 8/33/258/87/87/84/294/291
recomputés de git ls-files · run_ci.sh · l'artefact · le sweep de liens), aucune commande
VPS (#8). Seule édition : ce journal 05_activity_log/.
Session 20260806_061455 — CURRENCY canal stakeholder porté 48276ee → HEAD e786c12 (prose-facts drift) · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée/gatée (Sprints 1→7 côté repo ; déploiement prod / builds EAS /
dépôts ONAPI = hors périmètre worker, VPS 153.75.250.214, CLAUDE.md #8). Les ~10 derniers
commits sont majoritairement des sweeps vérification-only à rendement décroissant. Objectif :
éviter le churn (re-sweep déjà couvert) et corriger un fait de prose réellement dérivé.
Axes ré-audités CLEAN (non re-flaggés). (1) liens md avec ancre #… → 1 seule
occurrence (README.md:20), résout, déjà en mémoire. (2) Robustesse shell des gates : set -e
absent partout mais intentionnel (les gates accumulent les échecs puis exit en fin ;
lib.sh::cd_repo_root couvre le « vert trompeur » hors-git). (3) Mode git + shebang des 9
ci/*.sh : tous 100755 #!/usr/bin/env bash sauf lib.sh 100644 (sourcé, correct) ; invoqués
bash ci/… dans ci.yml. → cohérents, 0 défaut.
Défaut réel trouvé (classe prose-facts-vs-numeric-drift). Le canal stakeholder
05_deliverables_mvp/daily_reports/2026-08-06.md était ancré au HEAD 48276ee et affirmait
« dernière prod = 48276ee, tout le reste 0-édition ». Or 5 commits ont atterri depuis, et
l'un — e786c12 — édite README.md (3 href, fix label↔cible). La prose « dernière prod »
dérivait donc vs le graphe de commits réel : git log 48276ee..HEAD + filtrage des fichiers
hors 05_activity_log/ & daily_reports/ → 4 commits journal-only + 1 édition dépôt réelle
(e786c12 → README.md). → FIX = porter la currency à HEAD e786c12, tabuler l'opposabilité
au merge des 5 commits, ré-attester l'état courant.
Chiffres re-dérivés (#6, aucun saisi à la main). 8 gates statiques (git ls-files 'ci/*.sh'
hors lib.sh) · 25 suites (*-tests de gate.needs) · 25 modules testés (find test_*.py) ·
624/607/17 (regression_run.json::totals) · 33 PASS (run_ci.sh). Tous inchangés vs
48276ee — e786c12 ne touche que des href → aucune sortie/compte modifié, check_artifacts
byte-identique.
Vérif. ci/check_docs.sh PASS · ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP.
Portée / anti-churn. Édité : daily_reports/2026-08-06.md (section currency) + ce journal.
0 artefact reconstruit, 0 gate ajouté (#5 — dérive prose isolée, pas une classe
récurrente à gater), 0 chiffre inventé (#6), 0 commande VPS (#8). Structure du gate
inchangée (33 checks).
Session 20260806_064502 — Hypothèse « copie-constante à single-sourcer » RÉFUTÉE par le gate · +1 commentaire anti-footgun · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap intégralement livrée/gatée. Les ~10 derniers commits sont des sweeps vérif-only à
rendement décroissant. Objectif : traquer une vraie duplication de constante mandat à
single-sourcer (classe fix-vs-gate-transitively-protected-constant : DELETE la copie, pas
gate N+1), pas re-sweeper du déjà-couvert.
Piste investiguée (constantes canoniques #9/#10). Les 6 marqueurs 3 % / 8.5 % / 52 % / USD + DOP / Cardnet / Letter US existent en ≥4 endroits : (a) CLAUDE.md #9/#10 = source ·
(b) genlib/model.py CANONICAL (émis dans chaque rendu) · (c) genlib/renderer.py
_CANONICAL_MARKERS (oracle de check_canonical) · (d) tests/test_generator.py la liste
for marker in [...]. J'ai d'abord lu la 1ʳᵉ moitié du gate check_readme_claims qui n'ancrait
explicitement que (a→b) + la prose README → hypothèse : (c) et (d) sont des copies non
gatées → les dériver de model.CANONICAL (source unique auto-suivante).
RÉFUTATION — le gate a mordu (bien joué la discipline run_ci). Édité (c)+(d) →
./run_ci.sh = 32 PASS · 1 FAIL (check-readme-claims). La 2ᵉ moitié du même gate
(bloc ligne ~5475) ast.literal_eval statiquement les DEUX listes (regex
_CANONICAL_MARKERS\s*=\s*(\[.*?\]) et for marker in (\[...\])) et les set-diffe vs les 6
valeurs re-dérivées de CLAUDE.md. Donc (c)/(d) étaient déjà gatés. Pire : la dérivation
casse la raison d'être même de l'oracle — check_canonical() prouve que le rendu (bâti
DEPUIS model.CANONICAL) émet bien chaque valeur ; le dériver de CANONICAL rend le contrôle
tautologique (« md-bâti-depuis-X contient X » = vert vacux). Le commentaire du gate le dit
noir sur blanc : « si cette liste DÉRIVE de CANONICAL … le contrôle NE VÉRIFIE PLUS RIEN ».
Conclusion — EXCEPTION à fix-vs-gate-transitively-protected-constant. Cette règle vise
les copies passives (prose, config). Un oracle d'enforcement est actif : sa
duplication EST le test ; l'ancrage correct = garder le littéral + gate statique vs la source
(exactement l'état existant). Revert intégral des 2 éditions (test byte-identique restauré).
Livrable retenu. Un seul changement survit : +5 lignes de commentaire anti-footgun à
renderer.py:275, remontant au site de code la raison (indépendance de l'oracle) autrement
enfouie dans le script CI — pour qu'un mainteneur (ou moi, prochaine session) ne refasse pas la
« DRY-ification » qui décapite l'oracle. Sortie inchangée (commentaire pur), check_artifacts
byte-identique. Mémoire enforcement-oracle-must-stay-independent créée.
Vérif. ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP.
Portée / anti-churn. Édité : renderer.py (1 commentaire) + ce journal + mémoire. 0 artefact
reconstruit, 0 gate ajouté (#5), 0 chiffre inventé (#6 — les 6 marqueurs déjà présents,
re-dérivés du gate), 0 commande VPS (#8). Structure du gate inchangée (33 checks).
Session 20260806_071503 — anti-footgun in-fiche : documenter l'omission MANIFEST volontaire de la colonne « Sortie » · 0 artefact reconstruit
État au démarrage. ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
intégralement livrée + gatée. HEAD ddcafc5 (commentaire anti-footgun renderer.py, output
inchangé). Les ~15 derniers commits sont des sweeps vérif-only / currency à rendement décroissant
(la session précédente le notait). Objectif : éviter d'ajouter un énième « CLEAN sweep » et
trouver une amélioration durable réelle.
Chasse au défaut (Explore adversarial). Dispatché un agent lecture-seule sur 3 surfaces
(fiches 03_agents/*/AGENT.md ↔ modules réels · READMEs ↔ *_gen.py · liens label↔cible).
Retour : 1 candidat — frontend_console/AGENT.md:29 « colonne Sortie du chat_otoia omet
MANIFEST.json » alors que chat_otoia_gen.py:237 écrit bien les 3 fichiers.
Réfutation via mémoire — FAUX POSITIF connu. C'est exactement fiche-sortie-cell-omits- manifest-by-design : la colonne « Sortie » liste le payload métier seul ; le MANIFEST est
omis dans les DEUX rangs (portails + chat) = convention éditoriale, PAS une dérive ; les
docstrings/READMEs énumèrent bien les 3 (byte-gatés). « Corriger » violerait #5/#6 et risquerait
de casser le gate (la cellule est regex-parsée par check_readme_claims.sh). 0 édition de ce
faux positif.
Signal actionnable. Le fait qu'un Explore re-flague la MÊME cellule cette session (déjà
flaguée session 234409) = preuve que la convention est un footgun non documenté : elle brûle
des cycles d'audit et risque un « fix » erroné (ajout MANIFEST à la cellule gate-parsée → churn
#5, voire RED). Exactement le motif de la session précédente (commentaire anti-footgun remonté au
site dans renderer.py).
Livrable — note anti-footgun in-fiche. Ajouté au frontend_console/AGENT.md (juste après la
table + les puces Portails/Chat) un blockquote > **Note de lecture — colonne « Sortie ».**
expliquant que la cellule liste le payload seul, que chaque générateur écrit en plus un
out/MANIFEST.json volontairement omis par convention, que l'énumération exhaustive vit dans
docstrings/READMEs byte-gatés, et « ne PAS l'ajouter » (cellule regex-parsée). Rédigé pour
éviter les regex ancrées du gate (aucun motif N cartes/M liens/K rôles, aucun
**N tests** · job CI).
Vérif. check_readme_claims.sh exit 0 · check_docs.sh exit 0 · ./run_ci.sh =
33 PASS · 0 FAIL · 0 SKIP. Mémoire fiche-sortie-cell-omits-manifest-by-design mise à jour
(récurrence + mitigation in-fiche consignées).
Portée / anti-churn. Édité : frontend_console/AGENT.md (1 blockquote de prose) + ce journal
- mémoire. 0 artefact reconstruit, 0 gate ajouté (#5), 0 chiffre saisi à la main (#6 — la note ne contient aucun compteur), 0 commande VPS (#8). Structure du gate inchangée (33 checks).
Session 20260806_074504 — VÉRIF · NOUVEL axe méta « bijection de câblage CI » (fichiers ci/*.sh ↔ jobs ci.yml ↔ miroir run_ci) → CLEAN + auto-protégé · 0 édition de production
État au démarrage. ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, HEAD 4c741a7,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 côté repo ; prod/EAS/
ONAPI hors périmètre worker, VPS 153.75.250.214 · CLAUDE.md #8). Les ~15 derniers commits
sont des sweeps vérif-only / currency à rendement décroissant.
Currency écartée (serait du churn). Depuis la dernière currency daily_reports (e786c12),
2 commits seulement : ddcafc5 (commentaire renderer.py) + 4c741a7 (blockquote
frontend_console/AGENT.md) — 0 changement de sortie/compte (check_artifacts byte-identique).
Pas de vraie évolution d'état stakeholder → re-porter la currency serait du churn (two-logging-channels
= currency légitime seulement sur changement d'état réel). 0 édition du canal.
Axe NEUF chassé (absent de la mémoire) — bijection de câblage CI. Distinct de
gate-enumeration-completeness (compte de gates dans la prose) et ci-gate-verification-method
(archive vs runner) : la cohérence des trois couches filesystem ci/*.sh ↔ jobs .gitea/workflows/ci.yml
↔ miroir local run_ci.sh.
- 8/8 gates statiques invoqués par un job
ci.yml(check_artifacts / check_ci_integrity / check_docs / check_mobile_workflow / check_readme_claims / check_regression / guard_constraints / validate_json) → aucun gate orphelin/mort. run_ci.shne peut pas dériver de la CI par construction — il ne code en dur aucun nom de script (grep = 0 réf sur 7/8 ; l'unique hitcheck_ci_integrityest un commentaire). Il dérive la liste degate.needsdansci.ymlet rejoue chaquerun:/working-directory:déclaré, en imprimant le décompte jobs/gates/suites à l'exécution. Un gate ajouté/retiré s'y répercute automatiquement ; un « vert local mais rouge au push » est impossible. L'en-têterun_ci.shle documente noir sur blanc, etrun_cilui-même n'est pas un gate (#5,local-ci-runner).- Prose « 7/7 · 8 gates statiques verts » (
ci/README.md·check_mobile_workflow.sh:13·check_readme_claims.sh:9202) = descriptions pédagogiques de dents de mutation (ce qu'un retrait de ligne laisserait vert), pas des comptes d'état présent → KEEP (prose-facts-vs-numeric-drift).
Une sous-classe réellement ungatée (occurrence-zéro ⇒ sweep, pas gate, #5). check_ci_integrity
INV-A valide gate.needs == jobs − gate − manuels à partir de la liste de jobs de ci.yml, PAS de
l'ensemble filesystem ci/*.sh. Un script commité sur disque mais jamais câblé dans un job
serait un gate mort silencieux — non détecté. CLEAN aujourd'hui (8/8 câblés) ; verdict identique à
runtime-encoding-integrity-sweep (dents réelles, occurrence-zéro ⇒ rejouable, on ne fige pas un gate).
Verdict. Repo CLEAN + auto-protégé sur l'axe de câblage CI. Aucun défaut de production.
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit, 0 gate ajouté
(#5), 0 chiffre saisi à la main (#6 — 8/8 recomputé par balayage), 0 commande VPS (#8).
./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP (inchangé). Seules éditions : ce journal + mémoire
ci-wiring-bijection-sweep (+ pointeur MEMORY.md).
Session 20260806_081509 — VÉRIF récurrence du bug set-semantics (48276ee) sur TOUS les artefacts → CLEAN, détecteur à dents prouvées
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
intégralement livrée/gatée, 0 marqueur TODO/FIXME en production (recompté). La currency du canal
stakeholder est déjà à jour (le dernier commit def4706 l'a explicitement écartée : les commits depuis
e786c12 sont doc/commentaire, 0 sortie changée) → un re-port serait du churn. Plutôt qu'un énième
sweep déjà catalogué en mémoire, chasse à un défaut RÉEL : le dernier bug générateur commité
(48276ee) était un champ « ensemble » portant un doublon silencieux (roles_rbac_utilises bâti en
itérant des slots logiques ; 2 slots → même rôle → 4 entrées/3 distincts). Question ouverte : ce
bug se répète-t-il ailleurs ? Un seul champ avait été audité au moment du fix.
Méthode — généralisation du bug à l'univers complet des artefacts. Scan des 57 out/*.json
commités : pour chaque tableau, deux formes du bug —
- SCALAR-DUP : tableau de scalaires (la forme EXACTE du bug
48276ee, ex.["a","b","a"]) → 0 hit / 57 artefacts. - OBJ-DUP : tableau d'objets avec une clé d'identité dupliquée (
id/role_id/slug/doctype/permlevel…) → 33 hits, tous BY-DESIGN après revue :doctyperépété sur les lignes de fixtures Frappe (chaque ligne porte le même DocType) — attendu ;permlevel: 0répété sur plusieurs lignes de permission — attendu ;role_idrépété dansevenements/emission_events/depot_events— ce sont des journaux d'événements, pas des ensembles : un workflow fait légitimement agir le même rôle sur plusieurs étapes.
Contrôle ciblé du champ-ensemble d'origine. Les 6 MANIFEST portant roles_rbac_utilises sont
tous distincts : commissions 4/4 · dossier_vente 7/7 · financement_bancaire 3/3 (le fix
48276ee tient) · workflow_vente 7/7 · ecf_dgii 1/1 · confotur 3/3. 0 doublon résiduel.
Dents prouvées (une CLEAN d'un détecteur sans dents ne vaut rien). Test de mutation en mémoire :
le détecteur SCALAR-DUP mord sur la forme plantée ["a","b","a"] (= la forme 48276ee) et ne
lève pas de faux positif sur ["a","b","c"]. Le CLEAN est donc opposable.
Verdict. Le bug set-semantics (artifact-internal-set-semantics) ne se reproduit nulle part
dans les artefacts commités ; distinction clé établie et outillée : champ-ensemble (doit être
distinct) vs journal-d'événements / lignes-de-fixtures (répétition légitime). Aucun défaut de
production. Sweep lecture-seule, occurrence-zéro, rejouable → pas un gate (#5, sibling de
role-portail-referential-integrity-sweep et manifest-count-field-payload-rederivation).
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit, 0 gate ajouté
(#5), 0 chiffre saisi à la main (#6 — 57/6/comptes distincts recomputés par balayage), 0
commande VPS (#8). ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP (inchangé). Seules éditions : ce
journal + mémoire artifact-internal-set-semantics (extension « no-recurrence sweep »).
Session 20260806_084514 — VÉRIF axe neuf « dents de l'ORACLE jsonschema » (26 *.schema.json) → CLEAN 26/26, détecteurs à dents prouvées
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre. Roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée (8 sprints, 22 modules gatés, métriques MVP
atteintes) ; 0 marqueur TODO/FIXME/NotImplemented en production (recompté — tous les hits
placeholder sont le motif de design #6 « jamais 0 inventé ») ; currency du canal stakeholder déjà
écartée par def4706/081509 (commits depuis e786c12 = doc/commentaire, 0 sortie changée). Plutôt
qu'un énième re-sweep déjà catalogué, chasse d'un défaut RÉEL sur un axe non encore audité.
Axe neuf : les dents de la couche ORACLE elle-même. ~20 modules embarquent un test_schema_oracle
qui fait jsonschema.validate(<build en mémoire>, schema). Il valide le build frais (qui conforme
toujours par construction) contre un *.schema.json commité. Si un schéma était vide
({} ou {"type":"object"} sans required/contrainte) ou malformé, cet oracle serait
faux-vert — acceptant tout, ne prouvant rien. Le byte-repro et les suites ne regardent jamais la
force du schéma → classe réellement ungatée. (NB l'oracle tourne sous run_ci en python3
nu, pas -S ; les 17 skips sont propres au harnais regression python -S — cf.
regression-baseline-17-skips-by-design.)
Méthode. Pour chacun des 26 *.schema.json : (1) jsonschema.check_schema() — est-il un
schéma Draft-N valide ? (2) heuristique de dents — déclare-t-il required OU (properties + ≥2
mots-clés de contrainte parmi required/enum/pattern/minItems/minimum/const/minLength/
additionalProperties) ? Résultat : 26/26 valides, 0 WEAK. Tous portent required+properties réels
(kw 3..103 ; le plus maigre faisabilite/generator/brief.schema.json req=1 props=10 kw=3 reste
non-vide).
Dents de l'AUDIT prouvées (une CLEAN d'un détecteur sans dents ne vaut rien) : le détecteur de
validité mord un schéma malformé (required:"str", properties.x:123) ; le détecteur de dents
mord {} et {"type":"object"} ; aucun faux positif sur rbac.schema.json réel.
Contrôles connexes (même session, tous CLEAN).
- Mapping
role_id → erpnext_role_name= fonction sur les 57 artefacts : 0 conflit (unrole_idne résout jamais deux libellés distincts — sinon ERPNext créerait deux rôles). Complèterole-portail-referential-integrity-sweep(qui résolvait les slugs, pas l'égalité des libellés). - Oracle jsonschema exécuté, pas skippé, sous
run_ci— vérifié surpie/manifest(test_schema_oracle ... ok),jsonschema 3.2.0importable enpython3nu.
Verdict. Couche oracle genuinely constraining ; aucun défaut de production. Balayage
lecture-seule, occurrence-zéro, rejouable → pas un gate (#5), sibling de
runtime-encoding-integrity-sweep / role-portail-referential-integrity-sweep.
Portée / anti-churn. 0 fichier de production édité, 0 artefact reconstruit, 0 gate ajouté
(#5), 0 chiffre saisi à la main (#6 — 26/57 recomputés par balayage), 0 commande VPS (#8).
./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP (inchangé). Seules éditions : ce journal + mémoire
schema-oracle-teeth-audit (+ pointeur MEMORY.md).
Session 20260806_104533 — FIX couverture · oracle orphelin brief.schema.json câblé à un test (le seul des 26 schémas non enforced) · +1 test · 4 artefacts régénérés · CI verte
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md livrée/gatée, OPEN_DECISIONS_REGISTER.md = 3 arbitrages
produit appartenant à Michel (non éditables worker). Plutôt qu'un énième sweep zéro-trouvaille,
recherche d'une lacune de couverture réelle et corrigeable.
Axe. Câblage des 26 oracles *.schema.json aux tests (distinct du teeth-audit qui prouvait
la non-vacuité des schémas, et de la closedness : ici « le schéma valide-t-il réellement
quelque chose ? »). Balayage grep -rl <basename>.schema.json --include='*.py' sur tout le dépôt :
25/26 schémas référencés par au moins un .py ; 1 orphelin →
faisabilite/generator/brief.schema.json (0 référence dans aucun .py).
Constat — orphelin authentique, pas by-design.
- Le README du module documente
brief.schema.jsoncomme « Contrat d'entrée » (avec lien) et ses deux siblings (version.schema.json,projets_master.schema.json) SONT test-enforced (test_version_json_validates_against_schema,test_full_pipeline_master_validates). - Le garde-fou runtime
faisabilite_gen.py::_validate_brief()ne vérifie QUE le codeprojet(P01..P09), jamais la forme complète du brief (masterplan/architecture/typologies…). - Donc un brief drifté hors contrat (type erroné, clé renommée/droppée) passait inaperçu : l'asymétrie vs les 2 siblings est le tell.
Correction (teeth, pas churn). Ajout de test_input_briefs_validate_against_brief_schema
dans tests/test_generator.py : valide brief_complete.json et brief_incomplete.json contre
brief.schema.json via le validateur maison Publiciste (zéro-pip → toujours exécuté, pas de
skip sous python -S, donc skipped reste 17). Vérifié empiriquement AVANT d'écrire le test : les
deux fixtures conforment sous le validateur maison ET l'oracle jsonschema (errs=[] / OK).
L'« incomplet » est conforme au sens schéma (null/champ absent admis, seul projet requis) ; il
n'est « incomplet » qu'au sens sémantique (prix→placeholders/rétrogradation · anti-invention #6).
Cascade gatée régénérée (générateurs, jamais à la main · #6) :
count_tests(faits disque) →regression_plan.json:faisabilite/generator16→17.regression_gen.py build+run→regression_run.json+MANIFEST.json: totaux 624→625 exécutés · 607→608 passés · 17 skippés inchangés · 24/24 vertes · PASS byte-repro.audit_4big_gen.py build→quality_report.json: evidence « 16 méthodes test_* » → 17 (24/24 ≥95).
Prose gatée réalignée (le gate check-readme-claims a mordu, comme prévu, sur 4 surfaces) :
README module (16 tests→17, + mention du contrat d'entrée), fiches
03_agents/faisabilite/AGENT.md:22 (col Tests 16→17), 03_agents/qa/AGENT.md (624/607→625/608),
03_agents/erpnext_backend/AGENT.md (624→625). Prose ungatée present-tense réalignée :
GAP_ANALYSIS_SPRINT1.md (16→17 tests). Canal stakeholder : nouveau bloc de session appendé à
daily_reports/2026-08-06.md (les 3 blocs de currency antérieurs = snapshots datés à leur HEAD,
non réécrits — leur 624/607 était juste à leur instant).
Portée / anti-churn. 0 gate ajouté (#5 — le gate check-readme-claims existant a suffi à
prouver la cohérence, il a mordu sur les 4 fiches/README stales) · 0 chiffre saisi à la main
(#6 — tous recomputés par les générateurs) · 0 commande VPS (#8). ./run_ci.sh =
33 PASS · 0 FAIL · 0 SKIP. Fichiers touchés : 1 test (+1 méthode) · 4 artefacts régénérés ·
5 surfaces prose · ce journal.
Session 20260806_111534 — VÉRIF · axe NEUF « intégrité référentielle interne des $ref des 26 oracles-schémas » → CLEAN 108/108 refs · détecteur teeth-prouvé · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre,
roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→8 ; prod VPS / builds EAS /
dépôts ONAPI hors périmètre worker · #8). Registre OPEN_DECISIONS_REGISTER.md = arbitrages produit
appartenant à Michel, non éditables. Objectif anti-churn : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). Les 3 axes schéma déjà fermés portaient sur : la non-vacuité
(schema-oracle-teeth-audit — required + mots-clés de contrainte), la fermeture/complétude
(schema-oracle-closedness — additionalProperties + énumération des properties), et le câblage
(schema-oracle-wiring — chaque *.schema.json enforced par ≥1 test). Question encore jamais posée :
tout $ref interne d'un oracle résout-il vers une définition existante ? Un $ref pendouillant
(#/$defs/Xxx inexistant) est une oracle-integrity failure à double détente :
- côté oracle
jsonschema(optionnel, skippé souspython -S/ absent du runner Gitea pip-less) : quand le$refest atteint pendant la validation,jsonschemalèveRefResolutionError→ le test passe de VERT à ERROR/ROUGE — mais seulement là oùjsonschemaest présent ; - côté validateur maison zéro-pip (toujours exécuté) : un validateur qui ignore un
$refinconnu passe silencieusement → tout le sous-arbre de contraintes derrière le$refn'est jamais enforced = faux-vert. (Un seul validateur maison honore réellement$ref:publiciste/lib/validator.py; les autres modules ne valident le schéma complet que via l'oraclejsonschemaoptionnel — d'où l'enjeu.)
Balayage (résolveur JSON-Pointer maison, stdlib pur). Collecte récursive de tous les $ref des
26 *.schema.json, résolution de chaque pointeur #/… contre le corps du schéma ($defs /
definitions, dé-échappement ~0/~1), plus contrôle du type de la cible (doit être un objet-schéma)
et de l'inverse (définitions orphelines jamais référencées = schéma mort, cosmétique) :
| Métrique | Résultat |
|---|---|
Schémas portant $ref |
13 / 26 |
Total $ref |
108 |
$ref pendouillants (interne non résolu) |
0 |
$ref externes involontaires (non-#) |
0 |
| Cibles non-objet-schéma | 0 |
Définitions orphelines ($defs/definitions non référencées) |
0 |
→ CLEAN 108/108 : chaque $ref résout vers un objet-schéma existant, et chaque définition déclarée
est effectivement référencée (aucune contrainte silencieusement contournée, aucun schéma mort).
Détecteur teeth-prouvé (harnais /tmp, lecture seule — 0 fichier repo muté).
- Mutation → morsure : injection de
_MUTANT_DOESNOTEXISTsur un$refdefixtures.schema.json→ le résolveur signale bien 1 pendouillant (#/definitions/role_MUTANT…). Preuve que le CLEAN 108/108 n'est pas un détecteur inerte. - Preuve de la double détente (pourquoi un pendouillant est un vrai faux-vert), au niveau mécanisme :
jsonschemalèveRefResolutionErrordès qu'un$refpendouillant atteint est validé (schéma jouet{"x":{"$ref":"#/$defs/Missing"}}vs{"x":1}) → test ROUGE ;- un validateur maison naïf (type/required seuls,
$refignoré) renvoie[]sur le même doc → sous-arbre non enforced = faux-vert. (Note : les oraclesjsonschemavalident des objets reconstruits en mémoire, pas lesout/*.jsonà plat — cf.verify-non-defects-reconciliation-traps; aucune paire schéma↔out/-plat ne valide à 0 erreur, ce qui est normal et non un défaut.)
Portée / anti-churn. CLEAN → 0 correction de production (aucun oracle défectueux). 0 gate
ajouté (#5 — occurrence-zéro, rejouable à la demande comme les autres balayages teeth :
schema-oracle-teeth-audit, test-shadowing-sweep, runtime-encoding-integrity-sweep). 0 chiffre
saisi en dur · 0 commande VPS (#8). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP.
Fichiers touchés : ce journal + 1 entrée mémoire (schema-oracle-ref-integrity). Le résolveur est
consigné ci-dessus, rejouable tel quel (stdlib pur, sans réseau).
Session 20260806_114542 — VÉRIF · axe NEUF « le skip python -S de l'oracle jsonschema masque-t-il un écart enum/const/schéma vivant ? » + reverse-check membres d'enum fantômes → CLEAN (1 phantom {WAG} = NON-DÉFAUT) · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit appartenant à
Michel (non éditables). Objectif anti-churn : 5ᵉ axe teeth schéma jamais balayé, complémentaire des
4 précédents (non-vacuité · fermeture/complétude · wiring · intégrité des $ref).
Le soupçon. Le gate exécute les suites sous python -S → l'import du tiers jsonschema
échoue → les 7 tests-oracle @skipUnless(_HAS_JSONSCHEMA) (Draft-07 complet) comptent parmi les
17 skips by-design (cf. baseline régression 624 ran / 607 passed / 17 skipped). Question : ce
neutralisation masque-t-elle un écart enum / const / type / required / $ref réellement présent
dans les artefacts committés ? Et un enum de schéma sur-énumère-t-il vs les données (membre
fantôme = contrat partiellement aveugle sur la forme) ?
Méthode & résultat — CLEAN.
- Ré-exécution des 7 suites porteuses d'oracle avec
python3nu (vrai jsonschema 3.2.0, donc lesskipUnlesss'exécutent réellement) → skipped=0, toutes OK : dossier_vente 31 · workflow_vente 25 · commissions 25 · seo 36 · ecf_dgii 39 · confotur 44 · apply_plan 16. L'oracle Draft-07 dé-skippé valide le bundle correctement assemblé de chaque module (assemblage propre au module, pas une reconstruction) → 0 divergence. - Validation jsonschema réelle de
rbac/rbac_50_roles.jsonvsrbac/rbac.schema.json→ 0 erreur (l'oracle « neutralisé » passe pour de vrai sur le contrat RBAC phare). - Reverse-check membres d'enum fantômes sur les enums de rôle : data ⊆ enum pour
famille(9=9) ·portail(6=6) ·scope_donnees(4=4) ;entite_principaleenum(9) ⊋ data(8), seul fantôme = {WAG}.used-not-in-enum = ∅partout (une valeur produite hors-enum casserait la validation → serait déjà attrapée). {WAG} = NON-DÉFAUT : WAG est une entité canonique CLAUDE.md §Entités (Helios RD sous WAG) ; l'enumest le domaine autorisé, pas l'ensemble observé — un enum-surset est correct (breadth de contrat), comme l'axe fermeture/financement. - Teeth — les détecteurs enum+const du validateur maison
publiciste/lib/validator.py:76-81MORDENT :portail='PHANTOM_PORTAIL'hors-enum → flaggé (message exact) ; constcible_rbac_roles50→999 → flaggé ; data propre → 0 erreur. Détecteur prouvé non-toothless.
Les 19 autres schémas voient leur enum/const enforced par le validateur maison (gère
enum/const/type/required/additionalProperties/pattern/$ref) contre le bundle in-memory == out/
committé (check_artifacts), wiring déjà prouvé par l'axe « wiring ». Conclusion : le skip -S de
jsonschema ne masque AUCUN écart enum/const/schéma.
Périmètre. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé · 0 commande
VPS (#8). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP. Fichiers touchés : ce journal +
1 entrée mémoire (schema-oracle-enum-const-vs-S-skip). Sweep rejouable tel quel (jsonschema 3.2.0
local ; stdlib pur sinon ; sans réseau) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_124544 — VÉRIF axe NEUF « atteignabilité transitive d'une assertion » (aucun test run-only faux-vert) → CLEAN 0/651 + 1 durcissement (implicite→explicite)
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (Sprints 1→7 ; prod VPS / builds EAS / dépôts
ONAPI hors périmètre worker · #8). Registre OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit
appartenant à Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). test-shadowing-sweep couvre les tests jamais exécutés (dup de
nom/classe). Distinct et complémentaire : un test exécuté mais qui n'atteint aucune assertion
(run-only) reste vert même si le code casse = faux-vert. Détecteur AST (stdlib pur) : une méthode
test* est édentée si elle ne contient NI assertion directe (self.assert*/self.fail/
assertRaises/pytest.raises), NI appel levant (jsonschema.validate, _validate(...),
check_output/call, raise), NI délégation à un helper de sa classe lui-même édenté-négatif
(self._expect_error/_breaks…), NI appel à une fonction nommée assert/expect/breaks/reject/check.
Résolution transitive du graphe d'appels intra-classe (sinon 40 faux positifs sur les batteries
d'injections négatives qui délèguent à _expect_error/_breaks).
Dents prouvées. Mutants : un test_bad qui ne fait que print(compute(3)) → MORD (True) ;
self.assertEqual(...) → non signalé ; délégation helper (par set toothed OU par nom) → non signalé
(3/3 discriminés).
Résultat & durcissement. Sur 651 méthodes de test : 1 seul candidat →
qa/acceptance/tests/test_acceptance.py::TestCLI.test_validate_exits_zero, qui appelait gen.main( ["validate"]) sans assertion explicite. Investigation : ce n'était PAS un faux-vert réel — il mord
implicitement via SystemExit (cmd_validate→_validate_or_die→sys.exit(1) si erreurs). MAIS
fragilité latente : un refactor remplaçant sys.exit(1) par un return 1 silencieux rendrait le
test édenté sans que rien ne rougisse. Durcissement (strictement plus fort, aucun inconvénient) :
self.assertEqual(gen.main(["validate"]), 0) — mord désormais AUSSI sur le chemin retour-non-zéro, et
rend les dents visibles (aligné sur le style test_build_writes_outputs voisin). main retourne 0
en succès (cmd_validate → return 0), assertion correcte.
Conclusion. CLEAN 0/651 après durcissement (le détecteur re-passé confirme 0 édenté). Axe
teeth-prouvé. Édition minimale : 1 test durci (pas de logique de production touchée · #8), suite
qa-acceptance OK, ./run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP. Fichiers : ce journal + le test
durci + 1 entrée mémoire (test-assertion-reachability-sweep). Sweep rejouable (AST stdlib pur) —
NON promu en gate (#5, occurrence-quasi-zéro après fix).
Session 20260806_154606 — VÉRIF · axe NEUF « intégrité temporelle des tokens datés » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP, arbre propre, roadmap
ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / EAS / ONAPI hors périmètre · #8),
OPEN_DECISIONS_REGISTER.md = 3 arbitrages produit de Michel (non éditables). Objectif : un axe teeth
encore jamais balayé (candidat ancres intra-doc ](#frag) écarté = occurrence-zéro).
Axe NEUF (absent de la mémoire). Toutes les dimensions déjà couvertes portent sur des valeurs
(comptes, slugs, schémas, encodage NFC/byte). Jamais balayé : la validité calendaire des tokens
datés eux-mêmes. Un 2026-02-30 / 2026-13-01 / 20260840_… se lit comme une date mais n'existe
pas = date inventée / typo — faux-vert direct contre #6 « Zéro invention », invisible à tous
les gates (aucun ne parse les dates). Distinct de two-logging-channels (qui borne les figures, pas
la forme des dates).
Détecteur (stdlib pur datetime.date, rejouable).
- ISO
YYYY-MM-DD: chaque token doit construire undatetime.datevalide (mois 1-12, jour valide pour le mois, années bissextiles gérées par la stdlib). - Compact
YYYYMMDD(préfixe des session-ids_HHMMSS) : bornes digit-only(?<!\d)…(?!\d)pour capturer réellement20260806_154606(le\bnaïf échoue car_est word-char = piège corrigé) sans faux-parser la portion heure154606(n'amorce pas « 20 »). - Cohérence journal : chaque
05_activity_log/YYYY-MM-DD.md— la date du nom de fichier doit égaler la 1re date ISO de son en-tête.
Dents prouvées (mutants, prod intouchée). Selftest synthétique : 2026-02-30/2026-13-01/
20260230_1200 MORDENT, 2026-08-06/20260806_1200 épargnés. Bout-en-bout sur fichier réel
(05_activity_log/2026-08-06.md) : baseline [] ; mutant 2026-08-46 → flagué ; mutant 2026-02-30
→ flagué. 5/5 discriminés.
Résultat. Sur 379 fichiers trackés (.md/.json/.py/.yml/.sh/.txt) : 289 tokens ISO +
session-ids compacts → 0 date-calendaire invalide · 0 mismatch nom-fichier↔en-tête sur les
journaux 05_activity_log/. 0 date inventée. Faux-positifs écartés par construction : la portion
HHMMSS des timestamps (bornes digit-only), les entiers 8-chiffres non-datés hors fenêtre 2024-2027.
Conformité. 0 édition de production (sweep read-only). ./run_ci.sh inchangé = 33 PASS · 0 FAIL ·
0 SKIP. Fichiers : ce journal + 1 entrée mémoire. Sweep rejouable (stdlib pur) — NON promu en
gate (#5, occurrence-zéro).
Session 20260806_184624 — VÉRIF · axe NEUF « portabilité des NOMS de fichiers / structure de l'arbre git (collision-casse · réservés Windows · chars illégaux · dot/espace terminal · symlinks) » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh --static au démarrage = 8 PASS · 0 FAIL · 0 SKIP (gate complet = 33 jobs), arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). TOUS les balayages précédents portent sur le contenu des fichiers (comptes, slugs, schémas, encodage byte/NFC, dates) ou les valeurs des artefacts. Jamais éprouvé : la portabilité de l'arbre lui-même — les noms de fichiers et la structure du dépôt. Un checkout se fait sur macOS (case-insensitive/case-preserving) et Windows (NTFS : noms réservés, chars interdits, dot/espace terminal ignorés) — la CI, elle, tourne sous Linux (case-sensitive, POSIX permissif). Donc un futur agent qui commit out/Data.json à côté de out/data.json, ou un fichier con.md/a:b.json, garde la CI verte sous Linux tout en cassant le git checkout sur Mac/Windows (collision silencieuse : un des deux fichiers écrase l'autre / erreur d'invalid-argument). Faux-vert de portabilité, invisible à toute la chaîne de gates (aucun ne regarde les noms). Distinct de runtime-encoding-integrity-sweep/unicode-nfc-normalization-sweep (qui balaient le contenu octet/forme-NFC des fichiers, jamais leurs chemins).
Détecteur (stdlib pur, rejouable). Sur git ls-files (409 fichiers · 340 composants de chemin uniques) :
- Collision-casse : chemin complet ET (dirname + basename lower) — deux entrées
tolower-égales mais littéralement distinctes. - Réservés Windows :
CON/PRN/AUX/NUL/COM1-9/LPT1-9(comparés sur le stem lower, avant la 1re extension — NTFS réserve aussinul.txt). - Chars illégaux NTFS :
< > : " \ | ? *. - Chars de contrôle : tout
ord(ch)<32dans un composant. - Dot/espace TERMINAL de composant (
c != c.rstrip(" .")) — Windows les strippe silencieusement → renommage fantôme. - Longueur : composant >255 octets, chemin >200 octets.
- Symlinks trackés (
git ls-files -smode120000) : cible relative ? dans le repo ? résout sur disque ?
Dents prouvées (mutants synthétiques, prod intouchée). 6 classes discriminées end-to-end : collision out/Data.json⟷out/data.json MORD ; con.md, nul.txt (réservés) MORDENT ; a:b.json, we|rd.md (illégaux) MORDENT ; ta\tb.md (ctrl) MORD ; folder., x.md. (dot terminal) + folder name.md (espace terminal) MORDENT ; l'échantillon réel + le cas légal name .md (espace interne, autorisé) épargnés. Un premier mutant trailing-space mal formé (name .md) a été correctement épargné par le détecteur → mutant corrigé en composant à espace terminal, pas le détecteur (validation croisée).
Résultat. Sur 409 fichiers trackés : 0 collision-casse (chemin + basename), 0 nom réservé Windows, 0 char illégal/contrôle, 0 dot/espace terminal, 0 composant/chemin surdimensionné. 1 symlink tracké — 04_roadmap/ROADMAP.md → ROADMAP_8_WEEKS_OR_LESS.md : cible relative, dans le repo, résout vers un fichier existant = portable et sûr (ni absolu, ni hors-repo, ni dangling). Arbre git portable.
Conformité. 0 édition de production (sweep read-only). ./run_ci.sh --static inchangé = 8 PASS · 0 FAIL · 0 SKIP. Fichiers : ce journal + 1 entrée mémoire (tree-filename-portability-sweep). Sweep rejouable (git ls-files + stdlib pur) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_191634 — VÉRIF · axe NEUF « découverte & COLLECTE des tests par unittest discover (fichier test*.py · localisation sous tests/ · classe héritant bien de TestCase) » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP (gate complet), arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). La mémoire couvre déjà test-shadowing (méthodes/classes au nom dupliqué, silencieusement écrasées) et test-assertion-reachability (corps de méthode n'atteignant aucune assertion). Jamais éprouvé : le mécanisme de COLLECTE lui-même de python3 -m unittest discover -s tests — étape en amont de tout ce que la mémoire couvre. Trois faux-verts possibles, tous invisibles à la chaîne de gates (aucun ne modélise la découverte) :
- (a) Nommage de fichier :
discovern'inclut que le motif par défauttest*.py. Un fichierfoo_test.py/tests_foo.pycontenant de vraisTestCaseest silencieusement non-découvert → 0 test exécuté, gate vert. - (b) Localisation : la CI passe
-s tests. Un fichiertest_*.pyécrit hors d'un dossiertests/(à la racine du module, par erreur d'emplacement) n'est jamais atteint. - (c) Collecte de classe :
discoverne collecte que les sous-classes (transitives) deunittest.TestCase. Une classe portant des méthodestest_*mais dont la chaîne d'héritage n'atteint pasTestCase(oubli de la base, ou base intermédiaire elle-même non-TestCase) est ignorée → ses tests ne tournent jamais, comptés « présents » par grep mais inertes.
Détecteurs (stdlib pur ast + git ls-files, rejouables).
- (a) : sur les 25 dossiers
tests/, chaque*.pynon-__init__doit matchertest*.py. - (b) :
git ls-files— aucuntest*.pytracké hors d'un segment/tests/. - (c) : parse AST de chaque fichier de test ; pour chaque
ClassDefportant ≥1 méthodetest*, résolution transitive intra-fichier de la chaîne de bases jusqu'à un identifiant se terminant parTestCase(couvreunittest.TestCase,TestCase,base.TestCaseimporté).
Dents prouvées (mutants synthétiques, prod intouchée). (a) : les 25 fichiers matchent test*.py. (c) : selftest AST — class TestX(unittest.TestCase) épargné (0 problème) ; class TestX: sans base MORD (1) ; class TestX(Base) avec Base non-TestCase (chaîne cassée) MORD (1). 3/3 discriminés.
Résultat. 25 fichiers de test, 113 classes au total. (a) 25/25 fichiers découvrables (test*.py). (b) 0 fichier test*.py hors tests/ (aucun test orphelin d'emplacement). (c) 0/113 classe test-porteuse sans TestCase dans sa chaîne — toutes les classes portant des test_* sont réellement collectées. 0 faux-vert de collecte. Le périmètre exécuté par la CI = le périmètre réellement écrit.
Conformité. 0 édition de production (sweep read-only, harnais en /tmp). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP. Fichiers : ce journal + 1 entrée mémoire (test-discovery-collection-sweep). Sweep rejouable (ast + git ls-files) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_194635 — VÉRIF · axe NEUF « indépendance à l'ORDRE d'exécution des tests (couplage inter-test par état de classe/module partagé) » → CLEAN · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP (gate complet), arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel. Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). La mémoire couvre déjà, sur les suites : test-shadowing (méthodes/classes au nom dupliqué), test-assertion-reachability (corps n'atteignant aucune assertion), test-discovery-collection (le mécanisme de COLLECTE : nommage test*.py, localisation tests/, héritage TestCase). Jamais éprouvé : l'indépendance à l'ORDRE d'exécution. unittest discover exécute les méthodes en ordre alphabétique croissant fixe (sortTestMethodsUsing par défaut). Un test qui lit un état laissé par un test alphabétiquement antérieur (attribut de classe muté, variable de module, singleton) passe dans l'ordre fixe de la CI mais est couplé : c'est un faux-vert d'isolation, invisible à toute la chaîne de gates (aucun ne varie l'ordre) et distinct des 3 axes sœurs (nom / atteignabilité / collecte). Classe de bug réelle et classique (« order-dependent / flaky tests »).
Détecteurs (stdlib pur unittest, rejouables, harnais /tmp — 0 fichier prod touché). Pour chacun des 25 dossiers-module (cwd = dir module, sys.path répliquant python3 -m unittest discover -s tests), 3 exécutions comparées :
- normal : ordre CI (référence).
- rev_method :
sortTestMethodsUsinginversé → renverse l'ordre des méthodes intra-classe (couplage inter-méthode d'une même classe). - rev_full : inversion des méthodes + renversement récursif de l'arbre
TestSuite(_tests[::-1]) → renverse aussi l'ordre des classes (couplage inter-classe : classe A alpha-première produit un état lu par classe Z). Purge des modules fraîchement importés entre chaque dir pour un ré-import propre. Critère :run/f/eidentiques aux 3 modes et 0 failure/error partout.
Dents prouvées (mutants synthétiques, prod intouchée). 3 discriminations end-to-end :
- Intra-classe :
test_a_setsremplitTestCoupled.state,test_z_readsl'assert → normal (4,0,0) cache, rev_method (4,1,0) MORD. Suite propre voisine épargnée. - Inter-classe :
SHAREDde module rempli parTestA_Producer, lu parTestZ_Consumer→ normal (2,0,0) cache, rev_method (2,0,0) MISS (méthodes seules ne renversent pas l'ordre des classes), rev_full (2,1,0) MORD. Prouve que rev_full apporte une puissance de détection réelle au-delà de rev_method — les deux axes étaient nécessaires.
Résultat. Sur 25 suites · 651 tests : 0 module order-dependent sous rev_method et sous rev_full. run/failures/errors strictement identiques aux 3 ordres, 0 failure partout. Aucun couplage inter-test caché (ni intra-classe ni inter-classe) : le vert de la CI ne dépend pas de son ordre alphabétique fixe. Chaque test est réellement isolé.
Conformité. 0 édition de production (sweep read-only, harnais /tmp). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP, arbre propre. Fichiers : ce journal + 1 entrée mémoire (test-order-independence-sweep). Sweep rejouable (unittest stdlib pur) — NON promu en gate (#5, occurrence-zéro).
Session 20260806_201644 — VÉRIF · axe NEUF « intégrité STATIQUE de la couche shell-script : parse-validité (bash -n) + fidélité du code de sortie des gates » → CLEAN 10/10 parse · 8/8 exit load-bearing · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP (gate complet), arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel (non éditables). Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). runtime-encoding-integrity-sweep a fait compile() sur les 144 .py (parse-validité côté Python) mais jamais bash -n sur les scripts shell — la couche ci/*.sh + run_ci.sh n'a jamais été éprouvée en parse-validité. Deux sous-axes complémentaires, tous deux ciblant la couche gate elle-même (méta-niveau, aveugle aux autres sweeps qui vérifient le CONTENU) :
- (A) parse-validité shell (
bash -n) — une erreur de syntaxe dans une branche non-atteinte d'un gate (ex. la branchereport()/bad()du chemin ROUGE, jamais exercée tant que le repo est CLEAN) passe silencieusement :bash -nla détecterait, mais le runtime ne l'atteint jamais sur le chemin vert. Le jour où un vrai défaut fait entrer l'exécution dans cette branche, le gate crashe (exit 2) au lieu de reporter proprement — ou pire, l'erreur masque le check. Sœur-SHELL du.py compile. - (B) fidélité du code de sortie — les 8 gates sont accumulateurs (
set -uo pipefailsansset -e:FAIL=0;report()poseFAIL=1;exit "$FAIL"final). Un gate qui incrémenterait son compteur mais feraitexit 0(ou lirait la mauvaise variable, ou captureraitrc=$?après un pipe masquant l'exit d'un python) = faux-vert de la CI elle-même — le défaut de plus haut impact possible (rend TOUTE la couverture du gate inerte). Distinct deguard-constraints-flags-usage(qui mutation-teste INPUT→rouge, empiriquement ; ici je prouve structurellement que la ligneexitlit le bon accumulateur).
Détecteurs (rejouables, harnais /tmp, 0 fichier prod touché).
- (A)
bash -nsur chacun des 10 scripts shell trackés (git ls-files '*.sh'). - (B) audit de la ligne
exitfinale de chaque gate ; vérification quecheck_readme_claims.shinvoque son heredoc python enpython3 - <<'PY'non-pipé (l.121) →rc=$?(l.9237) capture bien l'exit python, pas celui d'untee.
Résultat → CLEAN. (A) 10/10 scripts parse OK sous bash -n (ci/*.sh × 9 + run_ci.sh). (B) 8/8 gates terminent par exit "$FAIL" (ou exit "$rc" pour readme-claims, rc = exit du python non-pipé) ; lib.sh n'a aucun exit top-level (fichier sourcé, correct — cohérent avec sa propre doc « jamais exécuté en propre »).
Dents prouvées (bout-en-bout, /tmp, prod intouchée).
- (A) copie de
validate_json.sh+if [[ 1 -eq 1 ]; then …(un]manquant) →bash -nPARSE-FAIL (MORD) ; le vrai script épargné (OK). Détecteur non-inert. - (B) mini-repo git
/tmpavec unsub/broken.jsonmalformé : le vraivalidate_json.sh→ RED ✓ ; un mutant identique oùexit "$FAIL"est remplacé parexit 0→ fake-green ✓. Prouve que la ligneexitest load-bearing — le faux-vert que le sous-axe (B) chasse est un vrai risque, et le vrai gate y échappe.
Conformité. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé (#6 — 10/8 recomputés par balayage) · 0 commande VPS (#8) · aucun gate ajouté (#5 — axe CLEAN, occurrence-zéro, détecteurs rejouables ; transitivement, la fidélité de sortie est aussi empiriquement prouvée par le mutation-bite guard-constraints-flags-usage). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP, arbre propre. Fichiers : ce journal + 1 entrée mémoire (shell-script-static-integrity-sweep).
Session 20260806_204654 — VÉRIF · axe NEUF « cohérence shebang ↔ bit-exécutable ↔ interpréteur sur l'arbre tracké » → CLEAN 409 fichiers · 0 édition de production
Choix de tâche. ./run_ci.sh au démarrage = 33 PASS · 0 FAIL · 0 SKIP (gate complet), arbre propre, roadmap ROADMAP_8_WEEKS_OR_LESS.md intégralement livrée/gatée (prod VPS / builds EAS / dépôts ONAPI hors périmètre worker · #8), OPEN_DECISIONS_REGISTER.md = arbitrages produit de Michel. Objectif : un axe teeth encore jamais balayé.
Axe NEUF (absent de la mémoire). La session précédente a fait bash -n (parse-validité shell) + fidélité du code de sortie sur la couche gate ; runtime-encoding-integrity-sweep a fait BOM/CRLF/ctrl-char au niveau octet sur les sources. Jamais éprouvée : la relation ligne-shebang ↔ bit-exécutable git ↔ interpréteur — méta-axe distinct, aveugle à toute la chaîne de gates. Six faux-verts possibles :
- (1) exec sans shebang : un fichier
100755sans#!→ l'exécution directe échoue à trouver un interpréteur (fallbackshhasardeux). - (2) CRLF dans le shebang :
#!/usr/bin/env bash\r→ « bad interpreter » à l'exécution (invisible au parsebash -nqui tolère le CR). - (3) BOM avant
#!: le shebang est silencieusement désactivé (le noyau ne le reconnaît plus en tête). - (4) mismatch interpréteur↔extension : un
.shportantpython3, ou un.pyportantbash. - (5) shebang hors ligne 1 : une ligne blanche/commentaire avant
#!le neutralise. - (6) dérive mode-git ↔ bit-disque :
os.access(X_OK)≠100755(fichier exécutable sur disque mais commité non-exec, ou l'inverse).
Détecteur (stdlib pur, rejouable, /tmp — 0 fichier prod touché). git ls-files -s (mode + chemin) + lecture des 4 premiers Ko de chaque fichier ; classification des 6 codes ci-dessus par fichier.
Résultat → CLEAN sur 409 fichiers trackés. 9 exécutables (100755 = les 8 ci/*.sh + run_ci.sh, tous #!/usr/bin/env bash), 46 shebangs au total. Les 37 shebang-mais-non-exec sont par conception — les .py (générateurs/tests) se lancent via python3 fichier.py / -m (jamais ./fichier.py), et ci/lib.sh est sourcé (100644, garde un shebang doc-hint — cohérent avec son absence de exit top-level trouvée la session précédente). 0 exec-sans-shebang · 0 CRLF · 0 BOM · 0 mismatch-interpréteur · 0 dérive disque/git.
Dents prouvées (mutants synthétiques /tmp, prod intouchée). Harnais rejouant la logique de détection sur des fixtures (mode, chemin, octets, disk_exec) : 9/9 discriminations — 3 fichiers propres (gate .sh, module .py, lib sourcée) épargnés ; 6 classes de mutants (exec-sans-shebang, CRLF, BOM, .sh→python, .py→bash, dérive-mode) mordues. Détecteurs non-inertes.
Conformité. VÉRIF pure : 0 fichier de production modifié · 0 chiffre inventé (#6 — 409/9/46/37 recomputés par balayage git ls-files -s) · 0 commande VPS (#8) · aucun gate ajouté (#5 — axe CLEAN, occurrence-zéro, détecteur rejouable). ./run_ci.sh inchangé = 33 PASS · 0 FAIL · 0 SKIP, arbre propre. Fichiers : ce journal + 1 entrée mémoire (shebang-execbit-coherence-sweep).