v3 : troisième relecture — une coquille n'inverse plus un sens ; retour à la version précédente

- Mot mal tapé = un seul glissement (une lettre ajoutée, retirée, ou deux
  voisines inversées), jamais au début du mot (préfixes : valide/invalide,
  limitée/illimitée), jamais une lettre remplacée (deux/dix, majorité/minorité),
  jamais sur un mot de négation ou de quantité, mots de 4 lettres au moins.
- Parenthèses, crochets, « / » et tout ce qui est entre accents graves comptent
  comme le sens (ceil(N^(1/5)) → ceil(N^1/5) n'est plus une coquille).
- Caractères invisibles (inversion du sens d'écriture, largeur nulle, BOM)
  refusés dans les textes, titres, traductions et motifs de signalement.
- Déploiement : la version en service est gardée (image « previous ») ; un
  témoin marque le déploiement en cours ; « retablir » relance la maquette au
  premier déploiement, la version précédente ensuite ; la liste des conteneurs
  retirés n'est plus vidée par un pipeline annulé.
- Tests : 59 Rust (dont 14 scénarios d'API, avec deux → dix sur E2 en vigueur
  et l'inversion bidi), e2e 12 / 12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-21 05:21:08 +02:00
co-authored by Claude Opus 5.5
parent ac9c904e7e
commit 9203fd2501
10 changed files with 331 additions and 47 deletions
+32 -17
View File
@@ -34,6 +34,8 @@ steps:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- BUILD=$(echo "$CI_COMMIT_SHA" | cut -c1-8)
# La version en service est gardée : « retablir » la relance si ce déploiement échoue.
- docker image inspect libredecision-app:latest >/dev/null 2>&1 && docker tag libredecision-app:latest libredecision-app:previous || true
- docker build -t libredecision-app:latest -f docker/app.Dockerfile --target production --build-arg LD_BUILD=$BUILD .
- echo "Image app construite ($BUILD)"
@@ -99,6 +101,8 @@ steps:
- /opt/libredecision-app:/opt/libredecision-app
commands:
- DOMAIN=decision.librodrome.org
# Témoin : à partir d'ici, un échec appelle « retablir ». Le healthcheck réussi l'efface.
- touch /opt/libredecision-app/deploying
- echo "APP_DOMAIN=$DOMAIN" > /opt/libredecision-app/.env
- cp docker/app-compose.yml /opt/libredecision-app/docker-compose.yml
# Tout conteneur, même arrêté, qui revendique encore la racine (la maquette avant son
@@ -106,11 +110,11 @@ steps:
# « restart: always » : sinon un redémarrage de Docker le ramènerait, et fabio lui
# donnerait /api/* (route la plus longue). Rien n'est supprimé ; la maquette est
# recréée par son propre workflow, sous son nouveau domaine.
# Ceux qui tournaient sont notés dans /opt/libredecision-app/retires : si la suite
# échoue, l'étape « retablir » les relance (et seulement eux).
# Ceux qui tournaient sont ajoutés à /opt/libredecision-app/retires (jamais vidé ici :
# un pipeline annulé ne perd pas sa liste) ; si la suite échoue, « retablir » les relance.
- |
RETIRES=/opt/libredecision-app/retires
: > "$RETIRES"
touch "$RETIRES"
for TAG in "urlprefix-$DOMAIN/*" "urlprefix-$DOMAIN/api/*"; do
for C in $(docker ps -aq --filter "label=SERVICE_3000_TAGS=$TAG") $(docker ps -aq --filter "label=SERVICE_8002_TAGS=$TAG"); do
NAME=$(docker inspect --format '{{.Name}}' "$C")
@@ -182,6 +186,7 @@ steps:
*'"ok":true'*"\"build\":\"$BUILD\""*|*"\"build\":\"$BUILD\""*'"ok":true'*)
echo "PASS: $BUILD repond sur $TARGET"
: > /opt/libredecision-app/retires
rm -f /opt/libredecision-app/deploying
exit 0 ;;
esac
i=$((i+1))
@@ -190,9 +195,10 @@ steps:
echo "FAIL: $BUILD ne repond pas apres 10 minutes"
exit 1
# Échec après le retrait de la racine : le produit s'arrête, ce qu'il avait retiré
# repart (restart: always rendu). Échec avant (construction, tests) : rien à faire,
# la liste est vide depuis le dernier déploiement réussi.
# Échec avant le déploiement (construction, tests, SBOM) : rien n'a bougé, rien à faire.
# Échec après, au premier déploiement : le produit s'arrête et ce qu'il avait retiré de la
# racine repart (restart: always rendu). Échec après, plus tard : la version précédente du
# produit (image « previous ») est relancée.
# NOTE: volumes + pas de from_secret : compatible
- name: retablir
image: docker:27-cli
@@ -205,17 +211,26 @@ steps:
- /opt/libredecision-app:/opt/libredecision-app
commands:
- |
RETIRES=/opt/libredecision-app/retires
if [ ! -s "$RETIRES" ]; then echo "Rien a retablir"; exit 0; fi
# Hors de « restart: always » aussi, sinon un redémarrage de Docker le ramènerait
# à côté de ce qui revient ; le prochain déploiement le recrée (--force-recreate).
docker update --restart=no lpk-libredecision-app >/dev/null 2>&1 || true
docker stop lpk-libredecision-app >/dev/null 2>&1 || true
for C in $(cat "$RETIRES"); do
echo "Retour de $(docker inspect --format '{{.Name}}' "$C" 2>/dev/null || echo "$C")"
docker update --restart=always "$C" >/dev/null && docker start "$C" >/dev/null || echo " echec pour $C"
done
: > "$RETIRES"
D=/opt/libredecision-app
if [ ! -e "$D/deploying" ]; then echo "Echec avant le deploiement : rien a retablir"; exit 0; fi
if [ -s "$D/retires" ]; then
# Hors de « restart: always » aussi, sinon un redémarrage de Docker le ramènerait
# à côté de ce qui revient ; le prochain déploiement le recrée (--force-recreate).
docker update --restart=no lpk-libredecision-app >/dev/null 2>&1 || true
docker stop lpk-libredecision-app >/dev/null 2>&1 || true
for C in $(cat "$D/retires"); do
echo "Retour de $(docker inspect --format '{{.Name}}' "$C" 2>/dev/null || echo "$C")"
docker update --restart=always "$C" >/dev/null && docker start "$C" >/dev/null || echo " echec pour $C"
done
: > "$D/retires"
elif docker image inspect libredecision-app:previous >/dev/null 2>&1; then
echo "Retour a la version precedente du produit"
docker tag libredecision-app:previous libredecision-app:latest
cd "$D" && docker compose up -d --force-recreate
else
echo "Aucune version precedente a relancer"
fi
rm -f "$D/deploying"
when:
- status: failure