Patrick Schurig 0d2b6728ae [upstream-fix] un verdict de test ne doit pas dépendre de l'heure
Six appels à `update(QDateTime::currentDateTime())` manquaient la garde
ENERGY_SIMULATION que leurs voisins ont (l.124, l.288) :
setPhasePowerLimit, setAcquisitionTolerance, et trois dans
onActionExecuted — qui partent sur toute action manuelle appliquée à une
borne.

Un cycle d'arbitrage complet partait donc à l'horloge MURALE au milieu
d'une suite qui injecte un temps simulé. Les deux horloges se mélangeaient
dans les verrous : l'armement paresseux (ECS-412) figeait m_lastSwitch sur
l'heure qu'il était, et un scénario daté en dur se déroulait « avant » son
propre armement — elapsed négatif, verrou jamais ouvert.

Ce n'est pas un défaut de production : il n'y a là-bas qu'une seule horloge.
C'est une faille de REPRODUCTIBILITÉ, et elle touchait tout test de
simulation réglant une limite de phase, une tolérance d'acquisition, ou
exécutant une action manuelle sur une borne.

PREUVE DU CORRECTIF : la garde rendue, testL4ShedsAPacAndSaysItForcedTheLock
repasse avec une DATE FIXE, trois fois sur trois — ce qu'il ne pouvait pas
faire. L'ancrage sur l'horloge réelle, posé la veille faute d'avoir la
cause, était un contournement ; il est retiré, et la date fixe revient parce
qu'elle rend les journaux reproductibles.

tools/check-simulation-clock.py, câblé en 4/4 de ci-quality.sh, et vérifié
mordant : garde retirée, il nomme la ligne exacte. Il a trouvé trois des six
déclencheurs dès son premier passage.

Pourquoi une vérification STATIQUE et pas un test : la suite ne peut pas
dire d'elle-même que son verdict dépend de l'heure, puisque c'est le verdict
qui ment. Le défaut n'a été trouvé que parce qu'il échouait cinq fois sur
cinq à code identique — une fenêtre de nuisance plus étroite ne basculerait
qu'une fois sur vingt, passerait pour une fragilité de temporisation, et
serait relancée jusqu'à ce qu'elle passe. C'est la famille de la semaine
sous une forme de plus : après l'instrument dont le silence ressemble à un
résultat, l'instrument dont le VERDICT ment selon l'heure.

Suite complète : simulation 54/54, charging 48/48 identique à la référence
ligne à ligne, loadmodel 21/21, spotmarket 32/32, doxygen 0 avertissement.
Aucun test ne dépendait de ces cycles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
2026-08-31 00:10:55 +02:00

125 lines
6.0 KiB
Bash
Executable File

#!/bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#
# Contrôle de qualité documentaire — TROIS vérifications, un seul endroit.
#
# ./tools/ci-quality.sh
#
# Elles se posent au même endroit parce qu'elles partagent la même sortie
# (`doc-generated/`) et la même commande de réparation. Les séparer ferait trois
# jobs qui regénèrent trois fois la même chose.
#
# 1. L'INDEX DES RÈGLES est-il VRAI ? (tools/gen-rules.py, code de retour)
# Casse sur : un identifiant cité et jamais défini ; un identifiant qui n'existe
# qu'en ligne de tableau ; un domicile ambigu ; un numéro retiré réattribué.
# 2. La DOC DOXYGEN est-elle complète ? (0 avertissement — DoD §5 d'AGENTS.md)
# 3. Le tout est-il REPRODUCTIBLE ? (deux passes donnent le même index)
#
# ── CE QU'ON NE VÉRIFIE PAS, ET POURQUOI ────────────────────────────────────
#
# « Régénérer et refuser si ça diffère de ce qui est commité » n'a rien à comparer :
# `doc-generated/` est dans .gitignore, décision du 2026-08-29 — versionner une page
# générée produirait un diff à chaque édition de spec et des conflits sur un fichier
# que personne ne relit. Le risque qu'un tel diff attrape — un artefact commité qui
# dérive de sa source — n'existe pas quand l'artefact n'est pas commité : il a été
# SUPPRIMÉ, pas déplacé. Ce qui reste à garantir est que l'index soit VRAI, et c'est
# la vérification 1. La 3 couvre le reste : un générateur non déterministe.
#
# ── ÉTAT : ÉCRIT, PAS EN SERVICE ────────────────────────────────────────────
#
# Aucun déclencheur automatique n'est posé. `ci/gitea-workflow.yml.disabled` porte le
# workflow prêt à l'emploi et la marche à suivre pour l'activer. La raison est datée :
# au 2026-08-29 la forge ne répond pas et plus de trente commits attendent en local.
# Un job activé à l'aveugle échouerait à son premier passage — au moment précis où
# tout le monde pousse — et le premier réflexe serait de le désactiver. Un garde-fou
# désactivé une fois ne se réactive jamais.
#
# Il PASSE sur l'état du dépôt au 2026-08-29 : index RC=0 (113 règles), doxygen 0
# avertissement. Vérifié avant d'être déclaré prêt, parce qu'un job qu'on n'a jamais
# vu passer n'est pas un job, c'est une intention.
set -u
RACINE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$RACINE" || exit 1
ROUGE=""; VERT=""; GRAS=""; FIN=""
if [ -t 1 ]; then ROUGE=$'\e[31m'; VERT=$'\e[32m'; GRAS=$'\e[1m'; FIN=$'\e[0m'; fi
echec=0
titre() { printf '\n%s── %s %s\n' "$GRAS" "$1" "$FIN"; }
ko() { printf '%s✗ %s%s\n' "$ROUGE" "$1" "$FIN"; echec=1; }
ok() { printf '%s✓ %s%s\n' "$VERT" "$1" "$FIN"; }
# Toute panne doit dire QUOI LANCER. Un job qui annonce « l'index est périmé » sans
# donner la commande se contourne en le désactivant — c'est le chemin de moindre
# effort, et il gagne toujours.
reparer() { printf ' → réparer : %s\n' "$1"; }
# --- 1. L'index des règles est-il vrai ? ------------------------------------
titre "1/4 · index des règles"
if python3 tools/gen-rules.py; then
ok "index complet — aucun orphelin, aucun domicile ambigu"
else
ko "l'index des règles est INCOMPLET (détail ci-dessus)"
reparer "définir la règle manquante dans specs/, ou la déclarer dans tools/rules-retired.txt"
reparer "puis : python3 tools/gen-rules.py"
fi
# --- 2. La doc est-elle complète ? ------------------------------------------
titre "2/4 · documentation Doxygen"
if ! command -v doxygen >/dev/null 2>&1 || ! command -v dot >/dev/null 2>&1; then
ko "doxygen ou graphviz absent — la vérification n'a PAS eu lieu"
reparer "apt install doxygen graphviz"
else
./tools/gen-doc.sh >/dev/null 2>&1
JOURNAL="$RACINE/doc-generated/warnings.txt"
# `wc -l` et non `grep -c` : grep -c AFFICHE 0 puis SORT EN 1 quand il ne trouve rien,
# si bien qu'un « || echo 0 » empile un second zéro et que le test entier échoue sur un
# dépôt SAIN. Une commande de mesure dont l'échec produit une lecture fausse — le motif
# de la semaine, appliqué à l'outil (AGENTS.md, « Lire le journal de la box »).
N=0
[ -f "$JOURNAL" ] && N="$(grep -i "warning" "$JOURNAL" 2>/dev/null | wc -l)"
if [ "$N" -eq 0 ]; then
ok "0 avertissement"
else
ko "$N avertissement(s) Doxygen — DoD §5 : toute méthode publique d'etm/ se documente"
printf '\n'; sed 's|^.*/energyplugin/| |' "$JOURNAL" | head -20
reparer "./tools/gen-doc.sh puis lire doc-generated/warnings.txt"
fi
fi
# --- 3. Le générateur est-il déterministe ? ---------------------------------
titre "3/4 · reproductibilité de l'index"
A="$(mktemp)"; B="$(mktemp)"
python3 tools/gen-rules.py >/dev/null 2>&1; cp doc-generated/RULES.md "$A"
python3 tools/gen-rules.py >/dev/null 2>&1; cp doc-generated/RULES.md "$B"
if diff -q "$A" "$B" >/dev/null; then
ok "deux passes, un seul résultat"
else
ko "l'index n'est PAS reproductible — deux passes ont divergé"
printf '\n'; diff "$A" "$B" | head -20
reparer "un ordre d'itération dépend d'un QHash ou du système de fichiers : trier"
fi
rm -f "$A" "$B"
# --- 4. Un verdict de test peut-il dépendre de l'HEURE ? ---------------------
titre "4/4 · horloge des déclencheurs"
if sortie="$(python3 tools/check-simulation-clock.py 2>&1)"; then
ok "${sortie#✓ }"
else
ko "un déclencheur d'arbitrage tourne à l'horloge murale sans garde"
printf '\n'; printf '%s\n' "$sortie" | sed '1d;s/^/ /'
reparer "entourer l'appel de #ifndef ENERGY_SIMULATION, comme ses voisins"
fi
printf '\n'
if [ "$echec" -eq 0 ]; then
printf '%s✓ qualité documentaire : tout passe.%s\n' "$VERT" "$FIN"
else
printf '%s✗ qualité documentaire : au moins une vérification a échoué.%s\n' "$ROUGE" "$FIN"
printf ' Tout relancer d'"'"'un coup : ./tools/ci-quality.sh\n'
fi
exit "$echec"