Patrick Schurig ead662e044 feat(outils): etm-powersync-gen-protocol-doc, prose générée depuis le contrat
Tableaux de messages (champs, types, obligatoire, valeurs, sens), règles
sémantiques et codes de motif, entre balises BEGIN/END GENERATED de
OPTIMIZER_PROTOCOL.md. --check (code 1 si la prose ou la forme canonique du
contrat n'est pas à jour) branché en 5/5 de tools/ci-quality.sh. --enums-from
pose les listes fermées du contrat depuis le dump du code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:34:17 +02:00

140 lines
7.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 — CINQ 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)
# 4. Un verdict de test peut-il dépendre de l'HEURE ? (tools/check-simulation-clock.py)
# 5. La prose du protocole est-elle GÉNÉRÉE depuis son contrat ?
# (tools/etm-powersync-gen-protocol-doc --check : tableaux de messages à jour, contrat
# OpenRPC sous sa forme canonique). Ici, l'artefact généré EST commité — la prose est
# lue par des humains et des optimiseurs tiers — donc on régénère et on refuse un écart.
#
# ── 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/5 · 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/5 · 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/5 · 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/5 · 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
# --- 5. La prose du protocole est-elle générée depuis le contrat ? -----------
titre "5/5 · protocole optimiseur : prose générée depuis le contrat OpenRPC"
if sortie="$(python3 -I tools/etm-powersync-gen-protocol-doc --check 2>&1)"; then
ok "${sortie#✓ }"
else
ko "docs/OPTIMIZER_PROTOCOL.md ou le contrat OpenRPC n'est pas à jour"
printf '\n'; printf '%s\n' "$sortie" | head -20 | sed 's/^/ /'
reparer "python3 tools/etm-powersync-gen-protocol-doc (puis relire le diff et commiter)"
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"