Files
oto-enterprise-os-dtp/05_activity_log/2026-07-31.md
T

18 KiB
Raw Blame History

Activity Log · 2026-07-31 · Claude Code DTP

Session 20260731_015954 · Buffer S8 · « Vert trompeur » : les 6 gates ci/*.sh passaient VERT hors d'un arbre git en ne contrôlant RIEN

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD). Roadmap fonctionnellement close (21/21 modules 100/100, 534 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un faux-vert réel (gate qui passe sans rien vérifier), même classe que les bugs regression_run.json / out/ RBAC des sessions précédentes.

Défaut trouvé — cd "$(git rev-parse --show-toplevel)" échoue en SILENCE hors d'un checkout git

  • Mécanisme : les 6 gates commençaient par cd "$(git rev-parse --show-toplevel)". HORS d'un arbre de travail git (tarball, git archive | tar -x, git absent du PATH, working-dir erroné), git rev-parse n'écrit rien sur stdout → la commande devient cd "", un no-op qui RETOURNE SUCCÈS (le script reste dans le cwd au lieu d'échouer). Les gates poursuivaient sur un git ls-files renvoyant une liste VIDEvalidate_json (« Aucun fichier .json suivi — rien à valider »), guard_constraints et check_docs sortaient exit 0 VERT en ayant contrôlé ZÉRO fichier (≈50 JSON non validés, contraintes CLAUDE.md non vérifiées). Un gate qui passe au vert sans rien enforcer est le pire « vert trompeur » — précisément ce que la série buffer traque.
  • Preuve au sol (avant fix) : copie de l'arbre sans .gitvalidate_json exit 0 « rien à valider » ; guard_constraints exit 0 « Contraintes respectées » ; check_docs exit 0. Ce piège avait déjà faussé une vérif « 6 gates verts sur git archive » d'une session antérieure (un git archive | tar -x n'a pas de .git, alors que le runner Gitea a un vrai .git via actions/checkout@v4 — donc la méthode « archive » n'est PAS équivalente au runner pour tout check qui interroge git).

Fix (2 solutions · CLAUDE.md #4 — retenu : helper partagé DRY + durcissement INV-B)

  • Correctif de fond : nouveau helper ci/lib.sh (sourcé, jamais exécuté seul) exposant cd_repo_root — se positionne à la racine, ou échoue bruyamment (exit 3, code distinct d'un échec de contrôle 1) avec un message clair si git rev-parse ne renvoie rien. Les 6 gates remplacent leur cd "$(git rev-parse …)" par source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" || exit 3 ; cd_repo_root. (Alt. écartée : inliner le garde de 4 lignes × 6 fichiers = doublon, contraire à CLAUDE.md #5 · préférence lib partagée.)
  • Durcissement du gate de câblage (INV-B · check_ci_integrity.sh) : INV-B exigeait que chaque ci/*.sh soit lancé par un job — ci/lib.sh (jamais dans un run:) aurait été flaggé « gate mort ». INV-B accepte désormais un ci/*.sh câblé de deux façons : lancé par un job du gate, OU sourcé par ≥1 gate script (détecté par grep d'une ligne source …/<base> / . …/<base>, # exclu). C'est un renforcement réel : le gate prouve maintenant aussi que les libs partagées sont effectivement sourcées (pas du code mort).

Preuves de morsure

  • Faux-vert corrigé : arbre copié sans .git → les 6 gates sortent désormais exit 3 avec « ERREUR gate : hors d'un arbre de travail git » (avant : validate_json/guard_constraints/check_docs = exit 0 vert). Idem sur git archive HEAD | tar -x après commit.
  • INV-B reconnaît la lib : ci/lib.sh — lib sourcée par [les 6 gates] · non exécutée en propre (OK)exit 0.
  • INV-B attrape une lib morte : ci/_dead.sh (sourcé par personne) ⇒ ✗ … lancé/sourcé par AUCUN job CI (gate mort)exit 1.

Non-régression : 6 gates verts dans l'arbre de travail (= ce que voit le runner via actions/checkout@v4, avec un vrai .git) ; matrice 534/21 · PASS inchangée ; qa/audit_4big out/ inchangé (les édits ci/ sont hors 05_deliverables_mvp → aucune dérive de consommateur) ; ci.yml YAML valide.

Anti-invention (#6) : rien inventé — garde purement défensif (bash/git stdlib), INV-B relit ci.yml + grepe les gates sur disque à chaque run.

Hors périmètre worker (VPS · #8) : néant (stdlib en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.


Session 20260731_012944 · Buffer S8 · Reproductibilité en CHECKOUT PROPRE — le hand-off out/ des 4 générateurs RBAC était .gitignoreé → gate rouge en CI, vert en local seulement

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD). Roadmap fonctionnellement close (21/21 modules 100/100, 534 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un écart green-local / red-en-CI réel (même classe que le bug regression_run.json corrigé plus tôt aujourd'hui) plutôt qu'un correctif cosmétique.

Défaut trouvé — 2 gates ROUGES en checkout propre (check-artifacts + check-regression)

  • Méthode : test au sol par git archive HEAD | tar -x (= exactement ce que voit le runner Gitea), et non bash ci/*.sh dans l'arbre de travail.
  • Constat : les 4 générateurs RBAC (rbac/fixtures_gen, userperm_gen, roleprofile_gen, apply_plan) .gitignoreaient leur out/ (« re-générable à la demande ») — alors qu'ils sont audités en archétype generator (critère HANDOFF = out/MANIFEST.json + ≥1 artefact JSON). En checkout propre, leur out/ est absent → l'audit 4Big (qa/audit_4big, dont le build relit le out/ de CHAQUE module) les note 80 < 95INV7 ⇒ build refusé. Cascade : check-artifacts rouge (rebuild audit_4big + demo échouent) et check-regression rouge. Ça ne « passait » qu'en local, grâce aux out/ non suivis laissés sur disque par des build manuels — exactement la classe du bug regression_run.json (green-local/red-CI).
  • Preuve au sol : git archive de HEADcheck_artifacts exit 1 (INV7 rbac-* note 80 < 95 · demo/scenarios build a échoué) et check_regression exit 1. Les 15 autres générateurs commettent bien leur out/ → seuls les 4 RBAC étaient l'exception.

Fix (2 solutions · CLAUDE.md #4 — retenu : aligner sur les 15 autres modules)

  • Correctif de fond : committer le out/ des 4 générateurs RBAC (retrait de out/ des 4 .gitignore + git add). Faisabilité prouvée : chaque build est byte-déterministe (2 builds successifs identiques ; tri stable, zéro horodatage). apply_plan lit ses frères en process (imports), pas via leur out/ — committer le out/ sert uniquement le contrat HANDOFF audité. L'audit note désormais les 4 modules 100/100 en checkout propre. (Alt. écartée : reclasser les 4 en archétype sans HANDOFF — diverge du reste des générateurs et laisse check_artifacts les ignorer, moins cohérent · #5.)
  • Durcissement du gate (empêche la récidive) : ci/check_artifacts.sh comparait le build frais au fichier de l'arbre de travail — donc un out/ non suivi (ignoré / jamais git add) le rendait vert en local alors qu'il serait absent en CI. Ajout d'une assertion git ls-files --error-unmatch : tout fichier produit par build DOIT être suivi par git → l'écart devient une erreur locale honnête, plus une surprise en CI.
  • Régénération des consommateurs (mémoire projet) : l'édition des 4 README RBAC (en-tête out/ « non commité » → « commité · byte-déterministe », +18 o chacun) a fait dériver qa/audit_4big/out/quality_report.json (evidence DOC = taille README) — régénéré (PASS · 21/21 · min 100). demo + regression run rejoués : inchangés (ils lisaient déjà l'état correct).

Preuve de morsure du durcissement : git rm --cached role.json (fichier laissé sur disque, simulant un out/ ignoré) ⇒ check_artifacts exit 1 (✗ … NON SUIVI par git → absent en CI propre) ; re-git addexit 0. Avant le durcissement, ce même état passait vert.

Vérifs : 6 gates verts sur git archive propre (guard/json/docs/artifacts/ regression/ci_integrity) ; suites harnais OK (fixtures/userperm/roleprofile/ apply_plan/audit_4big/regression/demo · 32/32) ; matrice 534/21 inchangée ; build de TOUS les consommateurs → zéro dérive résiduelle.

Anti-invention (#6) : rien inventé — out/ RBAC = sortie déterministe des générateurs ; quality_report recalculé depuis les faits du dépôt.

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo ; les fixtures Frappe côté VPS restent .gitignoreées, hors de ce correctif).

Auto-score 4Big : 96/100.


Session 20260731_005934 · Buffer S8 · Intégrité du câblage CI — le gate d'agrégat verrouille TOUS les jobs (gates statiques compris)

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD). Roadmap fonctionnellement close (21/21 modules à 100/100, 534 tests verts, 5 gates statiques tous verts au démarrage). Poursuite de la série anti-dérive (INV4, fraîcheur matrice, reproductibilité artefacts) : recherche d'un trou de câblage réel plutôt qu'un correctif cosmétique.

Défaut trouvé — les gates STATIQUES n'étaient gardés par aucun invariant d'appartenance au gate

  • Constat : le job gate (needs: [...]) est le seul verrou de merge — un check absent de son needs: ne bloque rien, même rouge. Les deux couvertures existantes (audit_4big/registry.coverage_report, qa/regression/discovery. coverage_report) prouvent l'appartenance au gate uniquement pour les jobs de module (ceux portant un working-directory: sous 05_deliverables_mvp). Les 5 gates statiques (constraints-guard/validate-json/check-docs/ check-artifacts/check-regression → scripts ci/*.sh) n'ont pas de working-directory : rien ne garantissait qu'ils alimentent le gate. Retirer l'un d'eux de needs: (ou une typo dans la liste, ou un ci/*.sh décâblé) rendait le gate vert en se court-circuitant — même classe de dérive silencieuse que INV4 (orphan_tests_dirs), côté câblage CI.
  • Fix : nouveau gate ci/check_ci_integrity.sh (bash/awk/git, stdlib pur, zéro réseau) + job CI check-ci-integrity, lui-même ajouté à gate.needs. Deux invariants, lus par FAITS dans ci.yml (jamais une liste à la main) :
    • INV-A : gate.needs == { tous les jobs définis } { gate } { jobs manuels if: … workflow_dispatch …, ex. e2e-baseline légitimement hors du gate push/PR }. Détecte MISSING (job oublié → ne bloque pas), DANGLING (référence fantôme : typo / job supprimé) et MANUAL-IN-GATE (job manuel dans needs → gate en attente perpétuelle sur push).
    • INV-B : chaque ci/*.sh du dépôt est lancé par exactement un job (run: bash ci/<script>), lui-même dans gate.needs — un gate statique non câblé (script mort) ou décâblé casse le check.
  • Preuve de morsure (5 modes, tous exit 1) :
    • MISSING : check-artifacts retiré de needs✗ ABSENT de gate.needs + INV-B sur check_artifacts.sh ;
    • DANGLING : seo-testsseo-testz dans needs✗ référence 'seo-testz' non définie + ✗ seo-tests ABSENT ;
    • MANUAL-IN-GATE : e2e-baseline ajouté à needs✗ job manuel … gate en attente ;
    • SCRIPT-MORT : ci/check_phantom.sh commité sans job ⇒ ✗ lancé par AUCUN job ;
    • DÉCÂBLÉ : couvert par le mode MISSING (job hors needs). État nominal restauré ⇒ exit 0 (gate.needs = 28 jobs = 30 définis 1 (gate) 1 manuel [e2e-baseline]).

Non-régression : le nouveau job est statique (aucun working-directory) → comme les 5 autres, il n'entre pas dans la matrice de régression ni le registre audit_4big (dérivés des seuls jobs de module). Totaux 534/21 inchangés. Suites harnais OK : qa/regression (21 suites · 534), qa/audit_4big (21/21 · min 100), qa/acceptance (bijectif=True). ci.yml YAML valide.

Anti-invention (#6) : rien inventé — le gate relit ci.yml et recalcule les ensembles à chaque run (aucun compteur figé).

Vérifs : 6 gates statiques verts (guard_constraints/validate_json/ check_docs/check_artifacts/check_regression/check_ci_integrity) ; doc ci/README.md mise à jour (tableau des jobs, section 2 détaillée, exécution locale, checklist « 6 gates »).

Hors périmètre worker (VPS · #8) : néant (stdlib en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.


Session 20260731_002924 · Buffer S8 · Intégrité de la matrice de régression (bug CI réel + invariant disque→CI)

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD) — roadmap fonctionnellement close (21/21 modules gated à 100/100, 534 tests verts, 5 gates statiques). Recherche d'une incohérence de reproductibilité réelle plutôt qu'un correctif doc cosmétique. Deux défauts réels trouvés et corrigés, tous deux dans qa/regression.

Défaut #1 (PRIORITAIRE · bug CI réel) — le gate check-regression était cassé en checkout propre

  • Symptôme : ci/check_regression.sh (l.52-53) exige que qa/regression/out/regression_run.json existe dans le dépôt, puis compare un run frais à ce fichier commité (baseline byte-identique).
  • Contradiction : ce même fichier était .gitignoreé (« non déterministe (temps machine) → jamais commitée ») → jamais présent dans un checkout propre. Le job Gitea check-regression ne fait que checkout + bash check_regression.sh, aucune étape ne génère le fichier. Conséquence : le gate échouait sur CHAQUE run CI propre (artefact absent du dépôt). Il ne « passait » qu'en local, par la présence d'un regression_run.json non suivi laissé par un run manuel. Le commentaire de ci.yml disait déjà « le regression_run.json commité » — intention claire du commit précédent, jamais concrétisée (fichier resté ignoré).
  • Vérif de faisabilité (#1 analyser) : l'artefact run est byte-déterministe — clés {ran,passed,failures,...} + path relatif, aucun horodatage/hôte/ durée. Deux run successifs byte-identiques (vérifié). Le committer est donc licite et complète l'intention du gate.
  • Fix : run est désormais commité (baseline du gate) →
    • .gitignore : retrait de out/regression_run.json + note expliquant qu'il est commité parce que déterministe ;
    • README.md : section run réécrite (byte-déterministe · commité · baseline check_regression · distinction d'avec check_artifacts qui ne rejoue que build) ;
    • out/regression_run.json : ajouté au suivi (21 suites · 534 tests · PASS).
  • Preuve de morsure : fichier retiré (simule checkout propre) ⇒ gate exit 1 (artefact absent) — l'état exact en CI ; restauré ⇒ exit 0 (run == commité).

Défaut #2 (invariant manquant) — dérive silencieuse disque→CI non gardée

  • Gap : discovery.coverage_report prouvait le sens CI→disque (tout module gated a un tests/) mais pas l'inverse : un module gagnant un tests/ sur disque sans job CI n'entrait jamais dans la matrice, ses tests ne tournaient jamais, et la couverture ne le voyait pas (elle n'itère que les suites dérivées du CI) — la matrice restait « PASS » en sous-comptant en silence. Même classe de dérive que « demo 18→21 », direction opposée.
  • Fix : nouvel invariant INV4
    • discovery.disk_test_modules() : marche disque pure listant tout module portant un tests/ (POSIX, trié, déterministe) ;
    • coverage_report : champ orphan_tests_dirs = disk ci_paths ; ok exige désormais not orphan_tests_dirs ;
    • regression.schema.json : orphan_tests_dirs ajouté (required · strict) ;
    • regression_gen.py : message INV4 surface les orphelins ;
    • tests/test_regression.py : +2 tests (sous-ensemble disque⊆CI sur le dépôt réel ; preuve de morsure monkeypatch d'un orphelin ⇒ ok=False).
  • Preuve de morsure end-to-end : mkdir zz_orphan/tests + buildbuild refusé INV4 … orphan_tests_dirs=['zz_orphan'] ; répertoire retiré ⇒ build OK.

Anti-invention (#6) : rien inventé — les deux gates régénèrent la vérité (run frais / couverture recomputée) au lieu de figer un compteur.

Régénération des consommateurs (mémoire projet) : l'édition des tests + du README de qa/regression a fait dériver qa/audit_4big/out/quality_report.json (evidence « README.md 3710→4236 octets » et « 24→26 méthodes test_* ») — régénéré, verdict PASS · 21/21 · min 100. Totaux matrice 534/21 inchangés : le harnais qa/regression est self-exclu (INV3) → +2 méthodes test_* n'affectent pas la matrice.

Vérifs : 5 gates locaux verts (guard_constraints/validate_json/check_docs/ check_artifacts/check_regression) ; ci.yml YAML valide ; tests harnais 26/26 OK (régression) + 34/34 OK (audit_4big) ; matrice 534/534 · 21/21 · PASS ; artefacts out/ reproductibles à l'identique.

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.